Skip to content

Commit 877d2fa

Browse files
authored
Fix flicker in SSR when using dark mode (#36)
* Fix flicker in SSR when using dark mode * Move cssVariables option to base theme options
1 parent bc4b1aa commit 877d2fa

File tree

11 files changed

+108
-108
lines changed

11 files changed

+108
-108
lines changed

src/__test-utils__/helpers.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ThemeProvider } from "@mui/material/styles";
2+
import { DiamondTheme } from "../themes/DiamondTheme";
3+
import { render, RenderResult } from "@testing-library/react";
4+
import { ThemeProviderProps } from "@mui/material/styles/ThemeProvider";
5+
6+
export const renderWithProviders = (
7+
ui: React.ReactNode,
8+
themeOptions?: Omit<ThemeProviderProps, "theme">,
9+
): RenderResult => {
10+
const Wrapper = ({ children }: { children: React.ReactNode }) => {
11+
return (
12+
<ThemeProvider {...themeOptions} theme={DiamondTheme}>
13+
{children}
14+
</ThemeProvider>
15+
);
16+
};
17+
18+
return render(ui, { wrapper: Wrapper });
19+
};

src/components/Footer.test.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
1-
import { render, screen, waitFor } from "@testing-library/react";
1+
import { screen, waitFor } from "@testing-library/react";
22
import "@testing-library/jest-dom";
33

44
import dlsLogo from "../public/generic/logo-short.svg";
55
import { Footer, FooterLink, FooterLinks } from "./Footer";
66
import { ImageColorSchemeSwitch } from "./ImageColorSchemeSwitch";
7-
import { ThemeProvider } from "../themes/ThemeProvider";
7+
import { renderWithProviders } from "../__test-utils__/helpers";
88

99
jest.mock("./ImageColorSchemeSwitch");
1010
// @ts-expect-error: doesn't find mockImplementation outside of testing.
1111
ImageColorSchemeSwitch.mockImplementation(() => <img src="src" alt="alt" />);
1212

1313
describe("Footer", () => {
1414
test("Should render logo only", () => {
15-
render(<Footer logo={{ src: dlsLogo, alt: "t" }} />);
15+
renderWithProviders(<Footer logo={{ src: dlsLogo, alt: "t" }} />);
1616

1717
expect(screen.getByRole("img")).toBeInTheDocument();
1818
// No copyright text
1919
expect(screen.queryByRole("paragraph")).not.toBeInTheDocument();
2020
});
2121

2222
test("Should render logo via theme", () => {
23-
render(
24-
<ThemeProvider>
25-
<Footer logo="theme" />
26-
</ThemeProvider>,
27-
);
23+
renderWithProviders(<Footer logo="theme" />);
2824

2925
expect(screen.getByRole("img")).toBeInTheDocument();
3026
});
3127

3228
test("Should render copyright only", async () => {
3329
const copyrightText = "add text here";
3430
const currentYear = new Date().getFullYear();
35-
render(<Footer logo={null} copyright={copyrightText} />);
31+
renderWithProviders(<Footer logo={null} copyright={copyrightText} />);
3632

3733
await waitFor(() => {
3834
expect(screen.queryByRole("paragraph")).toBeInTheDocument();
@@ -47,7 +43,7 @@ describe("Footer", () => {
4743
test("Should render logo and copyright", async () => {
4844
const copyrightText = "add text here";
4945
const currentYear = new Date().getFullYear();
50-
render(
46+
renderWithProviders(
5147
<Footer logo={{ src: dlsLogo, alt: "" }} copyright={copyrightText} />,
5248
);
5349

@@ -65,7 +61,7 @@ describe("Footer", () => {
6561
const lineOneText = "Link one";
6662
const linkOneName = "link-one-href";
6763

68-
render(
64+
renderWithProviders(
6965
<Footer>
7066
<FooterLinks>
7167
<FooterLink href={linkOneName}>{lineOneText}</FooterLink>
@@ -87,7 +83,7 @@ describe("Footer", () => {
8783
const linkTwoText = "Link two";
8884
const linkOneName = "link-one-href";
8985
const linkTwoName = "link-two-href";
90-
render(
86+
renderWithProviders(
9187
<Footer>
9288
<FooterLinks>
9389
<FooterLink href={linkOneName}>{linkOneText}</FooterLink>

src/components/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const FooterLink = ({ children, ...props }: LinkProps) => {
4242
<Link
4343
sx={{
4444
"&:hover": {
45-
color: theme.palette.secondary.main,
45+
color: theme.vars.palette.secondary.main,
4646
borderBottom: "solid 4px",
4747
},
4848
textDecoration: "none",
@@ -74,7 +74,7 @@ const Footer = ({ logo, copyright, children, ...props }: FooterProps) => {
7474
bottom: 0,
7575
marginTop: "auto",
7676
minHeight: 50,
77-
backgroundColor: theme.palette.primary.light,
77+
backgroundColor: theme.vars.palette.primary.light,
7878
}}
7979
{...props}
8080
>
Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import "@testing-library/jest-dom";
2-
import { render } from "@testing-library/react";
32

4-
import { ImageColorSchemeSwitch, getLogoSrc } from "./ImageColorSchemeSwitch";
5-
6-
jest.mock("@mui/material", () => {
7-
return {
8-
useColorScheme: jest.fn().mockReturnValue({ mode: "dark" }),
9-
};
10-
});
3+
import { ImageColorSchemeSwitch } from "./ImageColorSchemeSwitch";
4+
import { renderWithProviders } from "../__test-utils__/helpers";
5+
import { screen } from "@testing-library/react";
116

127
describe("ImageColorSchemeSwitch", () => {
138
const testVals = {
@@ -22,17 +17,17 @@ describe("ImageColorSchemeSwitch", () => {
2217
width?: string;
2318
height?: string;
2419
}) {
25-
const { getByAltText } = render(
20+
const { getByTestId } = renderWithProviders(
2621
<ImageColorSchemeSwitch image={{ ...testVals, ...image }} />,
2722
);
2823

29-
const img = getByAltText(testVals.alt);
24+
const img = getByTestId("image-light");
3025
expect(img).toBeInTheDocument();
3126
return img;
3227
}
3328

3429
it("should render without errors", () => {
35-
render(<ImageColorSchemeSwitch image={{ ...testVals }} />);
30+
renderWithProviders(<ImageColorSchemeSwitch image={{ ...testVals }} />);
3631
});
3732

3833
it("should have src and alt by default", () => {
@@ -66,46 +61,12 @@ describe("ImageColorSchemeSwitch", () => {
6661
it("should have alternate src", () => {
6762
const srcDark = "src/dark";
6863

69-
const img = getRenderImg({
70-
srcDark,
71-
});
72-
73-
expect(img).toHaveAttribute("src", srcDark);
74-
});
75-
});
76-
77-
describe("getLogoSrc", () => {
78-
const srcLight = "src/light",
79-
srcDark = "src/dark";
80-
81-
it("should be null if no image", () => {
82-
// @ts-expect-error: invalid input
83-
expect(getLogoSrc(null, "")).toStrictEqual(undefined);
84-
// @ts-expect-error: invalid input, calm down ts
85-
expect(getLogoSrc()).toStrictEqual(undefined);
86-
});
87-
88-
it("should be srcLight if no srcDark", () => {
89-
expect(getLogoSrc({ src: srcLight, alt: "" }, "light")).toStrictEqual(
90-
srcLight,
64+
renderWithProviders(
65+
<ImageColorSchemeSwitch image={{ ...testVals, srcDark }} />,
66+
{ defaultMode: "dark" },
9167
);
92-
});
68+
const img = screen.getByTestId("image-dark");
9369

94-
it("should be srcLight if mode is dark but no srcDark", () => {
95-
expect(getLogoSrc({ src: srcLight, alt: "" }, "dark")).toStrictEqual(
96-
srcLight,
97-
);
98-
});
99-
100-
it("should be srcLight if srcDark but mode light", () => {
101-
expect(
102-
getLogoSrc({ src: srcLight, srcDark: srcDark, alt: "" }, "light"),
103-
).toStrictEqual(srcLight);
104-
});
105-
106-
it("should be srcDark if mode dark", () => {
107-
expect(
108-
getLogoSrc({ src: "src/light", srcDark: srcDark, alt: "" }, "dark"),
109-
).toStrictEqual(srcDark);
70+
expect(img).toHaveAttribute("src", srcDark);
11071
});
11172
});
Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useColorScheme } from "@mui/material";
1+
import { styled } from "@mui/material";
22

33
type ImageColorSchemeSwitchType = {
44
src: string;
@@ -12,28 +12,40 @@ interface ImageColorSchemeSwitchProps {
1212
image: ImageColorSchemeSwitchType;
1313
}
1414

15-
export function getLogoSrc(image: ImageColorSchemeSwitchType, mode: string) {
16-
if (!image) {
17-
return undefined;
18-
}
19-
20-
if (image.srcDark === undefined) {
21-
return image.src;
22-
}
23-
24-
return mode === "dark" ? image.srcDark : image.src;
25-
}
26-
27-
const ImageColorSchemeSwitch = ({ image }: ImageColorSchemeSwitchProps) => {
28-
const { mode } = useColorScheme();
29-
if (!mode) return <></>;
30-
31-
const src: string | undefined = getLogoSrc(image, mode);
32-
33-
return (
34-
<img src={src} alt={image.alt} width={image.width} height={image.height} />
35-
);
36-
};
15+
/** Styled component which is only displayed in dark mode */
16+
const ImageDark = styled("img")(({ theme }) => [
17+
{ display: "none" },
18+
theme.applyStyles("dark", {
19+
display: "block",
20+
}),
21+
]);
22+
23+
/** Styled component which is only displayed in light mode */
24+
const ImageLight = styled("img")(({ theme }) => [
25+
{ display: "block" },
26+
theme.applyStyles("dark", {
27+
display: "none",
28+
}),
29+
]);
30+
31+
const ImageColorSchemeSwitch = ({ image }: ImageColorSchemeSwitchProps) => (
32+
<>
33+
<ImageLight
34+
data-testid="image-light"
35+
src={image.src}
36+
alt={image.alt}
37+
width={image.width}
38+
height={image.height}
39+
/>
40+
<ImageDark
41+
data-testid="image-dark"
42+
src={image.srcDark}
43+
alt={image.alt}
44+
width={image.width}
45+
height={image.height}
46+
/>
47+
</>
48+
);
3749

3850
export { ImageColorSchemeSwitch };
3951
export type { ImageColorSchemeSwitchProps, ImageColorSchemeSwitchType };

src/components/Navbar.test.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
import { fireEvent, screen, render } from "@testing-library/react";
1+
import { fireEvent, screen } from "@testing-library/react";
22
import { Navbar, NavLinks, NavLink } from "./Navbar";
33
import "@testing-library/jest-dom";
4+
import { renderWithProviders } from "../__test-utils__/helpers";
45

56
describe("Navbar", () => {
67
it("should not display logo if null", () => {
78
global.innerWidth = 600;
8-
render(<Navbar logo={null} />);
9+
renderWithProviders(<Navbar logo={null} />);
910
expect(screen.queryByAltText("Home")).not.toBeInTheDocument();
1011
});
1112
});
1213

1314
describe("Navbar Links", () => {
1415
it("should display hamburger menu on narrow displays", () => {
1516
global.innerWidth = 600;
16-
render(
17+
renderWithProviders(
1718
<NavLinks>
1819
<NavLink>Proposals</NavLink>
1920
</NavLinks>,
@@ -25,7 +26,7 @@ describe("Navbar Links", () => {
2526

2627
it("should display menu items when hamburger menu is clicked", () => {
2728
global.innerWidth = 600;
28-
render(
29+
renderWithProviders(
2930
<NavLinks>
3031
<NavLink>Proposals</NavLink>
3132
</NavLinks>,
@@ -38,7 +39,7 @@ describe("Navbar Links", () => {
3839

3940
it("should render links properly", () => {
4041
global.innerWidth = 600;
41-
render(
42+
renderWithProviders(
4243
<NavLinks>
4344
<NavLink>Proposals</NavLink>
4445
</NavLinks>,

src/components/Navbar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const NavLinks = ({ children }: NavLinksProps) => {
9191
onClose={onClose}
9292
anchor="left"
9393
PaperProps={{
94-
sx: { backgroundColor: theme.palette.primary.main },
94+
sx: { backgroundColor: theme.vars.palette.primary.main },
9595
}}
9696
>
9797
<Box
@@ -101,7 +101,7 @@ const NavLinks = ({ children }: NavLinksProps) => {
101101
display: "flex",
102102
flexDirection: "column",
103103
alignItems: "center",
104-
backgroundColor: theme.palette.primary.main,
104+
backgroundColor: theme.vars.palette.primary.main,
105105
}}
106106
>
107107
{children}
@@ -126,7 +126,7 @@ const Navbar = ({ children, logo, ...props }: NavbarProps) => {
126126
<Paper
127127
sx={{
128128
display: "flex",
129-
backgroundColor: theme.palette.primary.main,
129+
backgroundColor: theme.vars.palette.primary.main,
130130
px: { xs: "1rem", md: "7.5vw" },
131131
height: 50,
132132
width: "100%",

0 commit comments

Comments
 (0)