Skip to content

Commit

Permalink
v0.0.16 (pacocoursey#53)
Browse files Browse the repository at this point in the history
* Few small fixes

* v0.0.16-beta.0

* Beta version with fixes for default theme and empty value entries

* v0.0.16-beta.2

* Transpile ??

* Set color-scheme in inline script instead of effect

* v0.0.16-beta.3

* v0.0.16-beta.4

* Update index.tsx

* reorganize a bit, start working on next/script

* simplify some logic, base64 encode the inline script to work with next/script

* update gitignore

* move to src/

* v0.0.16-beta.7

* add tests

* update example packages

* add test for empty values in value mapping

* Make peer version of Next ensure script is correct

* support nonce

* fix localStorage setting, add test for it

* v0.0.16-beta.8

* v0.0.16-beta.10

* v0.0.16-beta.11

* revert props handling

* v0.0.16-beta.12

* use buffer to generate base64 on server side

* v0.0.16-beta.13

* update example version to fix build

* remove unused dep, prepare version to release
  • Loading branch information
pacocoursey authored Feb 20, 2022
1 parent 30ae932 commit 837cb8a
Show file tree
Hide file tree
Showing 10 changed files with 823 additions and 3,660 deletions.
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
{
"loose": true
}
]
],
"@babel/plugin-proposal-nullish-coalescing-operator"
],
"presets": [
[
Expand Down
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.DS_Store
node_modules
dist
.next
node_modules/
dist/
.next/
.yalc/
yalc.lock
197 changes: 169 additions & 28 deletions __tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { act, render, screen } from '@testing-library/react'
import { ThemeProvider, useTheme } from '../index'
import { ThemeProvider, useTheme } from '../src'
import React, { useEffect } from 'react'

let localStorageMock: { [key: string]: string } = {}

// HelperComponent to render the theme inside a paragraph-tag and setting a theme via the forceSetTheme prop
const HelperComponent = ({ forceSetTheme }: { forceSetTheme?: string }) => {
const { setTheme, theme, forcedTheme } = useTheme()
const { setTheme, theme, forcedTheme, resolvedTheme, systemTheme } = useTheme()

useEffect(() => {
if (forceSetTheme) {
Expand All @@ -18,17 +18,19 @@ const HelperComponent = ({ forceSetTheme }: { forceSetTheme?: string }) => {
<>
<p data-testid="theme">{theme}</p>
<p data-testid="forcedTheme">{forcedTheme}</p>
<p data-testid="resolvedTheme">{resolvedTheme}</p>
<p data-testid="systemTheme">{systemTheme}</p>
</>
)
}

beforeAll(() => {
function setDeviceTheme(theme: 'light' | 'dark') {
// Create a mock of the window.matchMedia function
// Based on: https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
matches: theme === 'dark' ? true : false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
Expand All @@ -38,22 +40,28 @@ beforeAll(() => {
dispatchEvent: jest.fn()
}))
})
}

beforeAll(() => {
// Create mocks of localStorage getItem and setItem functions
global.Storage.prototype.getItem = jest.fn(
(key: string) => localStorageMock[key]
)
global.Storage.prototype.getItem = jest.fn((key: string) => localStorageMock[key])
global.Storage.prototype.setItem = jest.fn((key: string, value: string) => {
localStorageMock[key] = value
})
})

beforeEach(() => {
// Reset global side-effects
setDeviceTheme('light')
document.documentElement.style.colorScheme = ''
document.documentElement.removeAttribute('data-theme')
document.documentElement.removeAttribute('class')

// Clear the localStorage-mock
localStorageMock = {}
})

describe('defaultTheme test-suite', () => {
describe('defaultTheme', () => {
test('should return system when no default-theme is set', () => {
render(
<ThemeProvider>
Expand Down Expand Up @@ -95,7 +103,35 @@ describe('defaultTheme test-suite', () => {
})
})

describe('custom storageKey test-suite', () => {
describe('storage', () => {
test('should not set localStorage with default value', () => {
act(() => {
render(
<ThemeProvider defaultTheme="dark">
<HelperComponent />
</ThemeProvider>
)
})

expect(global.Storage.prototype.setItem).toBeCalledTimes(0)
expect(global.Storage.prototype.getItem('theme')).toBeUndefined()
})

test('should set localStorage when switching themes', () => {
act(() => {
render(
<ThemeProvider>
<HelperComponent forceSetTheme="dark" />
</ThemeProvider>
)
})

expect(global.Storage.prototype.setItem).toBeCalledTimes(1)
expect(global.Storage.prototype.getItem('theme')).toBe('dark')
})
})

describe('custom storageKey', () => {
test("should save to localStorage with 'theme' key when using default settings", () => {
act(() => {
render(
Expand All @@ -106,10 +142,7 @@ describe('custom storageKey test-suite', () => {
})

expect(global.Storage.prototype.getItem).toHaveBeenCalledWith('theme')
expect(global.Storage.prototype.setItem).toHaveBeenCalledWith(
'theme',
'light'
)
expect(global.Storage.prototype.setItem).toHaveBeenCalledWith('theme', 'light')
})

test("should save to localStorage with 'custom' when setting prop 'storageKey' to 'customKey'", () => {
Expand All @@ -122,14 +155,11 @@ describe('custom storageKey test-suite', () => {
})

expect(global.Storage.prototype.getItem).toHaveBeenCalledWith('customKey')
expect(global.Storage.prototype.setItem).toHaveBeenCalledWith(
'customKey',
'light'
)
expect(global.Storage.prototype.setItem).toHaveBeenCalledWith('customKey', 'light')
})
})

describe('custom attribute test-suite', () => {
describe('custom attribute', () => {
test('should use data-theme attribute when using default', () => {
act(() => {
render(
Expand Down Expand Up @@ -167,7 +197,7 @@ describe('custom attribute test-suite', () => {
})
})

describe('custom value-mapping test-suite', () => {
describe('custom value-mapping', () => {
test('should use custom value mapping when using value={{pink:"my-pink-theme"}}', () => {
localStorageMock['theme'] = 'pink'

Expand All @@ -182,17 +212,36 @@ describe('custom value-mapping test-suite', () => {
)
})

expect(document.documentElement.getAttribute('data-theme')).toBe(
'my-pink-theme'
)
expect(global.Storage.prototype.setItem).toHaveBeenCalledWith(
'theme',
'pink'
)
expect(document.documentElement.getAttribute('data-theme')).toBe('my-pink-theme')
expect(global.Storage.prototype.setItem).toHaveBeenCalledWith('theme', 'pink')
})

test('should allow missing values (attribute)', () => {
act(() => {
render(
<ThemeProvider value={{ dark: 'dark-mode' }}>
<HelperComponent forceSetTheme="light" />
</ThemeProvider>
)
})

expect(document.documentElement.hasAttribute('data-theme')).toBeFalsy()
})

test('should allow missing values (class)', () => {
act(() => {
render(
<ThemeProvider attribute="class" value={{ dark: 'dark-mode' }}>
<HelperComponent forceSetTheme="light" />
</ThemeProvider>
)
})

expect(document.documentElement.classList.contains('light')).toBeFalsy()
})
})

describe('forcedTheme test-suite', () => {
describe('forcedTheme', () => {
test('should render saved theme when no forcedTheme is set', () => {
localStorageMock['theme'] = 'dark'

Expand All @@ -206,7 +255,7 @@ describe('forcedTheme test-suite', () => {
expect(screen.getByTestId('forcedTheme').textContent).toBe('')
})

test('should render light theme when forcedTheme is set a', () => {
test('should render light theme when forcedTheme is set to light', () => {
localStorageMock['theme'] = 'dark'

act(() => {
Expand All @@ -221,3 +270,95 @@ describe('forcedTheme test-suite', () => {
expect(screen.getByTestId('forcedTheme').textContent).toBe('light')
})
})

describe('system', () => {
test('resolved theme should be set', () => {
setDeviceTheme('dark')

act(() => {
render(
<ThemeProvider>
<HelperComponent />
</ThemeProvider>
)
})

expect(screen.getByTestId('theme').textContent).toBe('system')
expect(screen.getByTestId('forcedTheme').textContent).toBe('')
expect(screen.getByTestId('resolvedTheme').textContent).toBe('dark')
})

test('system theme should be set, even if theme is not system', () => {
setDeviceTheme('dark')

act(() => {
render(
<ThemeProvider defaultTheme="light">
<HelperComponent />
</ThemeProvider>
)
})

expect(screen.getByTestId('theme').textContent).toBe('light')
expect(screen.getByTestId('forcedTheme').textContent).toBe('')
expect(screen.getByTestId('resolvedTheme').textContent).toBe('light')
expect(screen.getByTestId('systemTheme').textContent).toBe('dark')
})

test('system theme should not be set if enableSystem is false', () => {
setDeviceTheme('dark')

act(() => {
render(
<ThemeProvider defaultTheme="light" enableSystem={false}>
<HelperComponent />
</ThemeProvider>
)
})

expect(screen.getByTestId('theme').textContent).toBe('light')
expect(screen.getByTestId('forcedTheme').textContent).toBe('')
expect(screen.getByTestId('resolvedTheme').textContent).toBe('light')
expect(screen.getByTestId('systemTheme').textContent).toBe('')
})
})

describe('color-scheme', () => {
test('does not set color-scheme when disabled', () => {
act(() => {
render(
<ThemeProvider enableColorScheme={false}>
<HelperComponent />
</ThemeProvider>
)
})

expect(document.documentElement.style.colorScheme).toBe('')
})

test('should set color-scheme light when light theme is active', () => {
act(() => {
render(
<ThemeProvider>
<HelperComponent />
</ThemeProvider>
)
})

expect(document.documentElement.getAttribute('data-theme')).toBe('light')
expect(document.documentElement.style.colorScheme).toBe('light')
})

test('should set color-scheme dark when dark theme is active', () => {
act(() => {
render(
<ThemeProvider defaultTheme="dark">
<HelperComponent forceSetTheme="dark" />
</ThemeProvider>
)
})

expect(document.documentElement.getAttribute('data-theme')).toBe('dark')
expect(document.documentElement.style.colorScheme).toBe('dark')
})
})
4 changes: 2 additions & 2 deletions examples/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "^11.0.1",
"next-themes": "^0.0.14",
"next": "^12.1.0",
"next-themes": "^0.0.16-beta.13",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
Expand Down
Loading

0 comments on commit 837cb8a

Please sign in to comment.