Skip to content

Commit

Permalink
v0.3 (#261)
Browse files Browse the repository at this point in the history
* build setup improvements

* fix builds, update deps

* update dep

* update test workflow

* bump actions in e2e

* 0.3.0-beta.1
  • Loading branch information
pacocoursey authored Mar 13, 2024
1 parent 40e57a9 commit f5df966
Show file tree
Hide file tree
Showing 19 changed files with 1,375 additions and 4,351 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ jobs:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
with:
version: 8

- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'pnpm'
Expand All @@ -31,7 +31,7 @@ jobs:
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ jobs:
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
with:
version: 8

- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 20.x
cache: 'pnpm'

- run: pnpm install
Expand Down
2 changes: 1 addition & 1 deletion examples/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "next start"
},
"dependencies": {
"next": "12.2.2",
"next": "^13.4.19",
"next-themes": "workspace:*",
"react": "18.2.0",
"react-dom": "18.2.0"
Expand Down
2 changes: 1 addition & 1 deletion examples/tailwind/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "next start"
},
"dependencies": {
"next": "12.2.2",
"next": "^13.4.19",
"next-themes": "workspace:*",
"react": "18.2.0",
"react-dom": "18.2.0"
Expand Down
22 changes: 9 additions & 13 deletions examples/with-app-dir/package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
{
"name": "with-app-dir",
"version": "0.1.0",
"private": true,
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"start": "next start"
},
"dependencies": {
"@types/node": "20.5.7",
"@types/react": "^18.2.21",
"@types/react-dom": "18.2.7",
"autoprefixer": "10.4.15",
"next": "^13.4.19",
"next-themes": "workspace:*",
"postcss": "8.4.28",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.3.3",
"typescript": "^5.2.2"
"react-dom": "18.2.0"
},
"devDependencies": {
"autoprefixer": "10.4.15",
"postcss": "8.4.28",
"tailwindcss": "3.3.3"
}
}
}
4 changes: 2 additions & 2 deletions examples/with-app-dir/src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import { useTheme } from 'next-themes'

function ThemeToggle() {
const { theme, setTheme } = useTheme()
const { theme, resolvedTheme, setTheme } = useTheme()

return (
<button
className="mt-16 px-4 py-2 text-white dark:text-black bg-black dark:bg-white font-semibold rounded-md"
onClick={() => {
setTheme(theme === 'light' ? 'dark' : 'light')
setTheme(resolvedTheme === 'light' ? 'dark' : 'light')
}}
>
Change Theme
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
import { act, render, screen } from '@testing-library/react'
import { ThemeProvider, useTheme } from '../src'
import React, { useEffect } from 'react'
// @vitest-environment jsdom

let localStorageMock: { [key: string]: string } = {}
import * as React from 'react'
import { act, render, screen } from '@testing-library/react'
import { vi, beforeAll, beforeEach, afterEach, afterAll, describe, test, it, expect } from 'vitest'
import { cleanup } from '@testing-library/react'

import { ThemeProvider, useTheme } from '../src/index'

let originalLocalStorage: Storage
const localStorageMock: Storage = (() => {
let store: Record<string, string> = {}

return {
getItem: vi.fn((key: string): string => store[key] ?? null),
setItem: vi.fn((key: string, value: string): void => {
store[key] = value.toString()
}),
removeItem: vi.fn((key: string): void => {
delete store[key]
}),
clear: vi.fn((): void => {
store = {}
}),
key: vi.fn((index: number): string | null => ''),
length: Object.keys(store).length
}
})()

// 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, resolvedTheme, systemTheme } = useTheme()

useEffect(() => {
React.useEffect(() => {
if (forceSetTheme) {
setTheme(forceSetTheme)
}
Expand All @@ -29,36 +52,42 @@ function setDeviceTheme(theme: 'light' | 'dark') {
// 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 => ({
value: vi.fn().mockImplementation(query => ({
matches: theme === 'dark' ? true : false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
addListener: vi.fn(), // Deprecated
removeListener: vi.fn(), // Deprecated
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
}))
})
}

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

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

// Clear the localStorage-mock
localStorageMock = {}
localStorageMock.clear()
})

afterEach(() => {
cleanup()
})

afterAll(() => {
window.localStorage = originalLocalStorage
})

describe('defaultTheme', () => {
Expand Down Expand Up @@ -129,8 +158,8 @@ describe('storage', () => {
)
})

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

test('should set localStorage when switching themes', () => {
Expand All @@ -142,8 +171,8 @@ describe('storage', () => {
)
})

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

Expand All @@ -157,8 +186,8 @@ describe('custom storageKey', () => {
)
})

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

test("should save to localStorage with 'custom' when setting prop 'storageKey' to 'customKey'", () => {
Expand All @@ -170,8 +199,8 @@ describe('custom storageKey', () => {
)
})

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

Expand Down Expand Up @@ -215,7 +244,7 @@ describe('custom attribute', () => {

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

act(() => {
render(
Expand All @@ -229,7 +258,7 @@ describe('custom value-mapping', () => {
})

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

test('should allow missing values (attribute)', () => {
Expand Down Expand Up @@ -259,7 +288,7 @@ describe('custom value-mapping', () => {

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

render(
<ThemeProvider>
Expand All @@ -272,7 +301,7 @@ describe('forcedTheme', () => {
})

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

act(() => {
render(
Expand Down
File renamed without changes.
29 changes: 29 additions & 0 deletions next-themes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "next-themes",
"version": "0.3.0-beta.1",
"license": "MIT",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"prepublish": "pnpm build",
"build": "tsup src",
"dev": "tsup src --watch",
"test": "vitest run __tests__"
},
"peerDependencies": {
"react": "^16.8 || ^17 || ^18",
"react-dom": "^16.8 || ^17 || ^18"
},
"devDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"repository": {
"type": "git",
"url": "https://github.com/pacocoursey/next-themes.git"
}
}
Loading

0 comments on commit f5df966

Please sign in to comment.