Skip to content

Commit

Permalink
update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mayank1513 committed Dec 7, 2023
1 parent 034d76e commit 28f0f52
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 28 deletions.
109 changes: 105 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@
🤟 👉 [Unleash the Power of React Server Components](https://medium.com/javascript-in-plain-english/unleash-the-power-of-react-server-components-eb3fe7201231)

This project is inspired by next-themes. Next-themes is an awesome package, however, it requires wrapping everything in a provider. The provider has to be a client component as it uses hooks. And thus, it takes away all the benefits of Server Components.
This project was originally inspired by next-themes. Next-themes is an awesome package, however, it requires wrapping everything in a provider. The provider has to be a client component as it uses hooks. And thus, it takes away all the benefits of Server Components.

`nextjs-themes` removes this limitation and enables you to unleash the full power of React 18 Server Components. In addition, more features are coming up soon... Stay tuned!
`nextjs-themes` removes this limitation and enables you to unleash the full power of React 18 Server Components. In addition, it adds more features and control over how you theme your app. Stay tuned!

- ✅ Perfect dark mode in 2 lines of code
- ✅ Fully Treeshakable (`import from nextjs-themes/client/component`)
- ✅ Designed for excellence
- ✅ Full TypeScript Support
- ✅ Unleash the full power of React18 Server components
- ✅ Works with all build systems/tools/frameworks for React18
- ✅ Perfect dark mode in 2 lines of code
- ✅ System setting with prefers-color-scheme
- ✅ Themed browser UI with color-scheme
- ✅ Support for Next.js 13 & Next.js 14 `appDir`
- ✅ No flash on load (for all - SSG, SSR, ISG, Server Components)
- ✅ Sync theme across tabs and windows
- ✅ Disable flashing when changing themes
- ✅ Force pages to specific themes
- ✅ Class or data attribute selector
- ✅ Class and data attribute selector
- ✅ Manipulate theme via `useTheme` hook
- ✅ Doccumented with [Typedoc](https://react18-tools.github.io/nextjs-themes) ([Docs](https://react18-tools.github.io/nextjs-themes))

Expand Down Expand Up @@ -161,6 +162,36 @@ That's it, your Next.js app fully supports dark mode, including System preferenc
}
```

## Images

You can also show different images based on the current theme.

```jsx
import Image from "next/image";
import { getResolvedTheme } from "nextjs-themes/utils";

function ThemedImage() {
const resolvedTheme = getResolvedTheme();
let src;

switch (resolvedTheme) {
case "light":
src = "/light.png";
break;
case "dark":
src = "/dark.png";
break;
default:
src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
break;
}

return <Image src={src} width={400} height={400} />;
}

export default ThemedImage;
```

### useTheme

In case your components need to know the current theme and be able to change it. The `useTheme` hook provides theme information:
Expand Down Expand Up @@ -221,6 +252,60 @@ In a similar way, you can also force color scheme.

Forcing color scheme will apply your defaultDark or defaultLight theme, configurable via hooks.

### With Styled Components and any CSS-in-JS

Next Themes is completely CSS independent, it will work with any library. For example, with Styled Components you just need to `createGlobalStyle` in your custom App:

```js
// pages/_app.js
import { createGlobalStyle } from "styled-components";
import { ThemeSwitcher } from "nextjs-themes";

// Your themeing variables
const GlobalStyle = createGlobalStyle`
:root {
--fg: #000;
--bg: #fff;
}
[data-theme="dark"] {
--fg: #fff;
--bg: #000;
}
`;

function MyApp({ Component, pageProps }) {
return (
<>
<GlobalStyle />
<ThemeSwitcher forcedTheme={Component.theme} />
<Component {...pageProps} />
</>
);
}
```

### With Tailwind

In your `tailwind.config.js`, set the dark mode property to class:

```js
// tailwind.config.js
module.exports = {
darkMode: "class",
};
```

⚡🎉Boom! You are ready to use darkTheme in tailwind.

> Caution! Your class must be set to `"dark"`, which is the default value we have used for this library. Tailwind, as of now, requires that class name must be `"dark"` for dark-theme.
That's it! Now you can use dark-mode specific classes:

```tsx
<h1 className="text-black dark:text-white">
```

## Migrating from v1 to v2

#### Motivation:
Expand Down Expand Up @@ -249,6 +334,22 @@ Take care of the following while migrating to `v2`.

Want handson course for getting started with Turborepo? Check out [React and Next.js with TypeScript](https://www.udemy.com/course/react-and-next-js-with-typescript/?referralCode=7202184A1E57C3DCA8B2)

## FAQ

**Do I need to use CSS variables with this library?**

Nope. It's just a convenient way. You can hard code values for every class as follows.

```css
.my-class {
color: #555;
}

[data-theme="dark"] .my-class {
color: white;
}
```

## License

Licensed as MIT open source.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { act, cleanup, fireEvent, render, renderHook, screen } from "@testing-library/react";
import { ColorSwitch } from "./color-switch";
import { useTheme } from "../../store";

describe("color-switch", () => {
afterEach(cleanup);

test("color-scheme-toggle", ({ expect }) => {
const hook = renderHook(() => useTheme());
render(<ColorSwitch />);
const element = screen.getByTestId("color-switch");
act(() => fireEvent.click(element));
expect(hook.result.current.colorSchemePref).toBe("dark");
act(() => fireEvent.click(element));
expect(hook.result.current.colorSchemePref).toBe("light");
act(() => fireEvent.click(element));
expect(hook.result.current.colorSchemePref).toBe("system");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function ColorSwitch({ size = 25 }: ColorSwitchProps) {
return (
<div
className="nextjs-themes--color-switch"
data-testid="color-switch"
onClick={toggleColorScheme}
onKeyUp={e => e.key === "Enter" && toggleColorScheme()}
role="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,25 @@ import { act, cleanup, render, renderHook } from "@testing-library/react";
import { afterEach, describe, test } from "vitest";
import { useTheme } from "../../store";
import { ThemeSwitcher } from "./theme-switcher";
import { getResolvedColorScheme, getResolvedTheme } from "../../utils";

/** Test if proper data-theme is added to documentElement
* concurrency is not feasible because of global store conflicts
/**
* -> concurrency is not feasible because of global store conflicts
*/
describe("theme-switcher", () => {
afterEach(cleanup);

test("Test first time load based on media query", async ({ expect }) => {
window.media = "dark";
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
window.media = "light";
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("");
});

test("Test defaultDark and defaultLight themes", async ({ expect }) => {
const { result } = renderHook(() => useTheme());
act(() => result.current.setDarkTheme("dark1"));
act(() => result.current.setLightTheme("light1"));
window.media = "dark";
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("dark1");
expect(getResolvedTheme()).toBe("dark1");
window.media = "light";
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("light1");
expect(getResolvedTheme()).toBe("light1");
expect(getResolvedColorScheme()).toBe("system");
});

// colorScheme has higher preference
Expand All @@ -36,7 +29,7 @@ describe("theme-switcher", () => {
act(() => result.current.setColorSchemePref(""));
act(() => result.current.setTheme("blue"));
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("blue");
expect(getResolvedTheme()).toBe("blue");
});

test("test color scheme preference", async ({ expect }) => {
Expand All @@ -45,11 +38,11 @@ describe("theme-switcher", () => {
act(() => result.current.setLightTheme("yellow"));
act(() => result.current.setTheme("blue"));
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("yellow");
expect(getResolvedTheme()).toBe("yellow");
act(() => result.current.setDarkTheme("dark-blue"));
act(() => result.current.setColorSchemePref("dark"));
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("dark-blue");
expect(getResolvedTheme()).toBe("dark-blue");
});

test("test forcedTheme", async ({ expect }) => {
Expand All @@ -59,7 +52,7 @@ describe("theme-switcher", () => {
act(() => result.current.setColorSchemePref("light"));
act(() => result.current.setTheme("f1"));
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("forced1");
expect(getResolvedTheme()).toBe("forced1");
});

test("forced colorScheme only", async ({ expect }) => {
Expand All @@ -71,19 +64,19 @@ describe("theme-switcher", () => {
act(() => result.current.setLightTheme("yellow"));
act(() => result.current.setDarkTheme("black"));
await act(() => render(<ThemeSwitcher />));
expect(document.documentElement.getAttribute("data-theme")).toBe("");
expect(getResolvedTheme()).toBe("");
act(() => result.current.setForcedTheme(undefined));
expect(document.documentElement.getAttribute("data-theme")).toBe("black");
expect(getResolvedTheme()).toBe("black");
});

test("forced theme prop", async ({ expect }) => {
await act(() => render(<ThemeSwitcher forcedTheme="theme1" />));
expect(document.documentElement.getAttribute("data-theme")).toBe("theme1");
expect(getResolvedTheme()).toBe("theme1");
});

test("forced colorScheme prop", async ({ expect }) => {
// global state is continuing from previous testss
await act(() => render(<ThemeSwitcher forcedColorScheme="light" />));
expect(document.documentElement.getAttribute("data-theme")).toBe("");
expect(getResolvedTheme()).toBe("");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function sharedServerComponentRenderer(
className: "",
};
if (dataTheme !== undefined) {
dataProps["data-theme"] += dataTheme;
dataProps["data-theme"] = dataTheme;
dataProps.className = dataTheme;
}
if (dataColorScheme !== undefined) {
Expand Down
8 changes: 8 additions & 0 deletions packages/nextjs-themes/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ export function resolveThemeFromColorScheme(state: ThemeStoreType, isSystemDark:
return theme;
}
}

export function getResolvedTheme() {
return document.documentElement.getAttribute("data-theme");
}

export function getResolvedColorScheme() {
return document.documentElement.getAttribute("data-color-scheme");
}
4 changes: 2 additions & 2 deletions packages/nextjs-themes/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export default defineConfig({
setupFiles: ["vitest.setup.ts"],
coverage: {
reporter: ["text", "json", "clover", "html"],
exclude: ["__mocks__"],
exclude: ["__mocks__", "**/index.ts"],
include: ["src/**/*"],
},
threads: true,
},
});

0 comments on commit 28f0f52

Please sign in to comment.