Skip to content

Commit e5c94bc

Browse files
committed
Theme.tsx tests
1 parent cd964f2 commit e5c94bc

File tree

3 files changed

+136
-3
lines changed

3 files changed

+136
-3
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { render, fireEvent } from "@solidjs/testing-library";
2+
import { describe, it, expect, vi, beforeEach } from "vitest";
3+
import { Theme } from "../../../src/ts/components/layout/Theme";
4+
import * as ThemeSignal from "../../../src/ts/signals/theme";
5+
import { createSignal } from "solid-js";
6+
import { ThemeWithName } from "../../../src/ts/constants/themes";
7+
import * as Loader from "../../../src/ts/elements/loader";
8+
import * as Notifications from "../../../src/ts/elements/notifications";
9+
10+
vi.mock("../../../src/ts/constants/themes", () => ({
11+
themes: {
12+
dark: { hasCss: true },
13+
light: {},
14+
},
15+
}));
16+
17+
vi.mock("./FavIcon", () => ({
18+
FavIcon: () => <div id="favicon" />,
19+
}));
20+
21+
describe("Theme component", () => {
22+
const [themeSignal, setThemeSignal] = createSignal<ThemeWithName>({} as any);
23+
const themeSignalMock = vi.spyOn(ThemeSignal, "getTheme");
24+
const loaderShowMock = vi.spyOn(Loader, "show");
25+
const loaderHideMock = vi.spyOn(Loader, "hide");
26+
const notificationAddMock = vi.spyOn(Notifications, "add");
27+
28+
beforeEach(() => {
29+
vi.clearAllMocks();
30+
loaderShowMock.mockClear();
31+
loaderHideMock.mockClear();
32+
notificationAddMock.mockClear();
33+
themeSignalMock.mockImplementation(() => themeSignal());
34+
setThemeSignal({
35+
name: "dark",
36+
bg: "#000",
37+
main: "#fff",
38+
caret: "#fff",
39+
sub: "#aaa",
40+
subAlt: "#888",
41+
text: "#fff",
42+
error: "#f00",
43+
errorExtra: "#c00",
44+
colorfulError: "#f55",
45+
colorfulErrorExtra: "#c55",
46+
});
47+
});
48+
49+
it("injects CSS variables based on theme", () => {
50+
const { style } = renderComponent();
51+
52+
expect(style.innerHTML).toEqual(`
53+
:root {
54+
--bg-color: #000;
55+
--main-color: #fff;
56+
--caret-color: #fff;
57+
--sub-color: #aaa;
58+
--sub-alt-color: #888;
59+
--text-color: #fff;
60+
--error-color: #f00;
61+
--error-extra-color: #c00;
62+
--colorful-error-color: #f55;
63+
--colorful-error-extra-color: #c55;
64+
}`);
65+
});
66+
67+
it("updates CSS variables based on signal", () => {
68+
setThemeSignal({ name: "light", bg: "#f00" } as any);
69+
const { style } = renderComponent();
70+
71+
expect(style.innerHTML).toContain("--bg-color: #f00;");
72+
});
73+
74+
it("loads CSS file and shows loader when theme has CSS", () => {
75+
const { css } = renderComponent();
76+
77+
expect(css.getAttribute("href")).toBe("/themes/dark.css");
78+
expect(loaderShowMock).toHaveBeenCalledOnce();
79+
fireEvent.load(css);
80+
expect(loaderHideMock).toHaveBeenCalledOnce();
81+
});
82+
83+
it("removes CSS when theme has no CSS", async () => {
84+
themeSignalMock.mockImplementation(() => ({ name: "light" }) as any);
85+
const { css } = renderComponent();
86+
expect(css.getAttribute("href")).toBe("");
87+
});
88+
89+
it("removes CSS when theme is custom", async () => {
90+
themeSignalMock.mockImplementation(() => ({ name: "custom" }) as any);
91+
const { css } = renderComponent();
92+
expect(css.getAttribute("href")).toBe("");
93+
});
94+
95+
it("handles CSS load error", () => {
96+
const { css } = renderComponent();
97+
expect(loaderShowMock).toHaveBeenCalledOnce();
98+
fireEvent.error(css);
99+
expect(loaderHideMock).toHaveBeenCalledOnce();
100+
expect(notificationAddMock).toHaveBeenCalledWith("Failed to load theme", 0);
101+
});
102+
103+
it("renders favicon", () => {
104+
const { favIcon } = renderComponent();
105+
106+
expect(favIcon).toBeInTheDocument();
107+
expect(favIcon).toBeEmptyDOMElement(); //mocked
108+
});
109+
110+
function renderComponent(): {
111+
style: HTMLStyleElement;
112+
css: HTMLLinkElement;
113+
metaThemeColor: HTMLMetaElement;
114+
favIcon: HTMLElement;
115+
} {
116+
render(() => <Theme />);
117+
118+
//make sure content is rendered to the head, not the body
119+
const head = document.head;
120+
121+
return {
122+
// oxlint-disable-next-line typescript/no-non-null-assertion
123+
style: head.querySelector("style#theme")!,
124+
// oxlint-disable-next-line typescript/no-non-null-assertion
125+
css: head.querySelector("link#currentTheme")!,
126+
// oxlint-disable-next-line typescript/no-non-null-assertion
127+
metaThemeColor: head.querySelector("meta#metaThemeColor")!,
128+
// oxlint-disable-next-line typescript/no-non-null-assertion
129+
favIcon: head.querySelector("#favicon")!,
130+
};
131+
}
132+
});

frontend/src/ts/components/layout/Theme.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export function Theme(): JSXElement {
3838
const colors = getTheme();
3939
styleEl()?.setHtml(`
4040
:root {
41-
4241
--bg-color: ${colors.bg};
4342
--main-color: ${colors.main};
4443
--caret-color: ${colors.caret};
@@ -49,8 +48,7 @@ export function Theme(): JSXElement {
4948
--error-extra-color: ${colors.errorExtra};
5049
--colorful-error-color: ${colors.colorfulError};
5150
--colorful-error-extra-color: ${colors.colorfulErrorExtra};
52-
}
53-
`);
51+
}`);
5452
});
5553

5654
createEffect(() => {

frontend/vitest.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export const projects: UserWorkspaceConfig[] = [
3737
plugins,
3838
},
3939
{
40+
ssr: {
41+
noExternal: ["@solidjs/meta"],
42+
},
4043
test: {
4144
name: { label: "jsx", color: "green" },
4245
include: ["__tests__/**/*.spec.tsx"],

0 commit comments

Comments
 (0)