Skip to content

Commit aa43505

Browse files
committed
Add support for multiple instances of the element on a page
1 parent df7c446 commit aa43505

6 files changed

+102
-57
lines changed
27.6 KB
Loading

test/test4.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Theme switch test 4</title>
6+
<script src="../theme-switch.js"></script>
7+
<style>
8+
#container {
9+
display: flex;
10+
flex-direction: column;
11+
width: max-content;
12+
}
13+
14+
theme-switch { width: 512px; }
15+
</style>
16+
</head>
17+
<body>
18+
19+
<div id="container">
20+
<theme-switch id="theme-switch-1"></theme-switch>
21+
<theme-switch id="theme-switch-2"></theme-switch>
22+
</div>
23+
24+
</body>
25+
</html>

test/theme-switch.test.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ setSystemThemeTo("light");
5151
const main = require("../theme-switch");
5252
// Could have instead exported the functions in theme-switch.js
5353
const mainInternals = {
54-
toggleTheme: main.__get__("toggleTheme"),
5554
getSystemTheme: main.__get__("getSystemTheme"),
55+
themeSwitchClass: main.__get__("ThemeSwitchElement"),
5656
getUserThemeSelection: main.__get__("getUserThemeSelection")
5757
};
5858

@@ -99,8 +99,12 @@ test(`getUserThemeSelection should return "auto" when user had previously select
9999
* and https://stackoverflow.com/q/44173754/8583692
100100
* We had ho mock the implementation of some functions to prevent error.
101101
*
102-
* We cannot mock an internal function of a module with Jest.
103-
* Instead, we used babel rewire plugin
102+
* To mock instance method of a class, override the class prototype with new implementation of the method.
103+
* See https://github.com/speedskater/babel-plugin-rewire/issues/45
104+
* and https://stackoverflow.com/q/29151528
105+
*
106+
* To mock an internal function of a module (we cannot mock them with Jest),
107+
* we can use babel rewire plugin
104108
* (or rewire-test-env-only plugin so the minified file is not bloated
105109
* see https://www.npmjs.com/package/babel-plugin-rewire-test-env-only).
106110
* See https://stackoverflow.com/q/51269431/8583692
@@ -109,7 +113,7 @@ test(`getUserThemeSelection should return "auto" when user had previously select
109113
* and https://github.com/jhnns/rewire/issues/136#issuecomment-380829197
110114
* and https://www.npmtrends.com/babel-plugin-rewire-vs-mock-require-vs-proxyquire-vs-rewire
111115
*
112-
* If we wanted to mock a regular function, we could have used any of these approaches:
116+
* To mock a regular (global) function, we could have used any of these approaches:
113117
*
114118
* ```javascript
115119
* jest.mock("theme-switch");
@@ -123,23 +127,26 @@ test(`getUserThemeSelection should return "auto" when user had previously select
123127
* ```
124128
*/
125129
test(`When user theme is light, toggleTheme should update the theme to dark`, () => {
126-
main.__Rewire__("animateThemeButtonIconToDark", () => {});
130+
mainInternals.themeSwitchClass.prototype.animateThemeButtonIconToDark = () => {};
131+
const instance = new mainInternals.themeSwitchClass();
127132
localStorage.setItem("theme", "light");
128-
mainInternals.toggleTheme();
133+
instance.toggleTheme();
129134
expect(mainInternals.getUserThemeSelection()).toBe("dark");
130135
});
131136

132137
test(`When user theme is dark, toggleTheme should update the theme to auto`, () => {
133-
main.__Rewire__("animateThemeButtonIconToAuto", () => {});
138+
mainInternals.themeSwitchClass.prototype.animateThemeButtonIconToAuto = () => {};
139+
const instance = new mainInternals.themeSwitchClass();
134140
localStorage.setItem("theme", "dark");
135-
mainInternals.toggleTheme();
141+
instance.toggleTheme();
136142
expect(mainInternals.getUserThemeSelection()).toBe("auto");
137143
});
138144

139145
test(`When user theme is auto, toggleTheme should update the theme to light`, () => {
140-
main.__Rewire__("animateThemeButtonIconToLight", () => {});
146+
mainInternals.themeSwitchClass.prototype.animateThemeButtonIconToLight = () => {};
147+
const instance = new mainInternals.themeSwitchClass();
141148
localStorage.setItem("theme", "auto");
142-
mainInternals.toggleTheme();
149+
instance.toggleTheme();
143150
expect(mainInternals.getUserThemeSelection()).toBe("light");
144151
});
145152

@@ -225,6 +232,19 @@ describe("Screenshot tests", () => {
225232
await expect(action).rejects.toThrowError("Node is either not visible or not an HTMLElement");
226233
}, 100_000);
227234

235+
test(`When there are multiple instances of the element in page, clicking on of them should affect only that element`, async () => {
236+
const screenshot = await takeScreenshot(
237+
() => { localStorage.setItem("theme", "light"); },
238+
async (page) => {
239+
const element = await page.$("#theme-switch-1");
240+
element.click();
241+
},
242+
"test4.html",
243+
"#container"
244+
);
245+
expect(screenshot).toMatchReferenceSnapshot();
246+
}, 100_000);
247+
228248
afterAll(() => {fileSystem.rmSync(snapshotFileName);});
229249
});
230250

theme-switch.js

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,59 @@ const ICON_INITIAL_STATE_FOR_AUTO = [10, 0, 33, 0];
111111
const ICON_INITIAL_STATE_FOR_DARK = [10, 0, 20, 1];
112112
const ICON_INITIAL_STATE_FOR_LIGHT = [5, 1, 33, 1];
113113

114-
let shadowRoot;
115-
116114
class ThemeSwitchElement extends HTMLElement {
115+
shadowRoot;
116+
117117
constructor() {
118118
super();
119119

120120
// See https://stackoverflow.com/q/2305654/8583692
121-
shadowRoot = this.attachShadow({ mode: "open" });
122-
shadowRoot.innerHTML = generateIcon(...getInitialStateForIcon());
121+
this.shadowRoot = this.attachShadow({ mode: "open" });
122+
this.shadowRoot.innerHTML = generateIcon(...getInitialStateForIcon());
123123

124124
// Add the click listener to the top-most parent (the custom element itself)
125125
// so the padding etc. on the element be also clickable
126-
shadowRoot.host.addEventListener("click", toggleTheme);
126+
this.shadowRoot.host.addEventListener("click", this.toggleTheme);
127127

128128
// Create some CSS to apply to the shadow DOM
129129
// See https://css-tricks.com/styling-a-web-component/
130130
const style = document.createElement("style");
131131
style.textContent = generateStyle();
132-
shadowRoot.append(style);
132+
this.shadowRoot.append(style);
133+
}
134+
135+
// See https://stackoverflow.com/q/48316611
136+
toggleTheme() {
137+
let theme = getUserThemeSelection();
138+
if (theme === THEME_AUTO) {
139+
localStorage.setItem(THEME_KEY, THEME_LIGHT);
140+
this.animateThemeButtonIconToLight();
141+
} else if (theme === THEME_DARK) {
142+
localStorage.setItem(THEME_KEY, THEME_AUTO);
143+
this.animateThemeButtonIconToAuto();
144+
} else /* if (theme === THEME_LIGHT) */ {
145+
localStorage.setItem(THEME_KEY, THEME_DARK);
146+
this.animateThemeButtonIconToDark();
147+
}
148+
updateTheme();
149+
}
150+
151+
animateThemeButtonIconToLight() {
152+
this.shadowRoot.getElementById("letter-anim-hide").beginElement();
153+
this.shadowRoot.getElementById("core-anim-shrink").beginElement();
154+
this.shadowRoot.getElementById("rays-anim-rotate").beginElement();
155+
this.shadowRoot.getElementById("rays-anim-show").beginElement();
156+
}
157+
158+
animateThemeButtonIconToAuto() {
159+
this.shadowRoot.getElementById("eclipse-anim-go").beginElement();
160+
this.shadowRoot.getElementById("letter-anim-show").beginElement();
161+
}
162+
163+
animateThemeButtonIconToDark() {
164+
this.shadowRoot.getElementById("rays-anim-hide").beginElement();
165+
this.shadowRoot.getElementById("core-anim-enlarge").beginElement();
166+
this.shadowRoot.getElementById("eclipse-anim-come").beginElement();
133167
}
134168
}
135169

@@ -242,40 +276,6 @@ function getInitialStateForIcon() {
242276
}
243277
}
244278

245-
// See https://stackoverflow.com/q/48316611
246-
function toggleTheme() {
247-
let theme = getUserThemeSelection();
248-
if (theme === THEME_AUTO) {
249-
localStorage.setItem(THEME_KEY, THEME_LIGHT);
250-
animateThemeButtonIconToLight();
251-
} else if (theme === THEME_DARK) {
252-
localStorage.setItem(THEME_KEY, THEME_AUTO);
253-
animateThemeButtonIconToAuto();
254-
} else /* if (theme === THEME_LIGHT) */ {
255-
localStorage.setItem(THEME_KEY, THEME_DARK);
256-
animateThemeButtonIconToDark();
257-
}
258-
updateTheme();
259-
}
260-
261-
function animateThemeButtonIconToLight() {
262-
shadowRoot.getElementById("letter-anim-hide").beginElement();
263-
shadowRoot.getElementById("core-anim-shrink").beginElement();
264-
shadowRoot.getElementById("rays-anim-rotate").beginElement();
265-
shadowRoot.getElementById("rays-anim-show").beginElement();
266-
}
267-
268-
function animateThemeButtonIconToAuto() {
269-
shadowRoot.getElementById("eclipse-anim-go").beginElement();
270-
shadowRoot.getElementById("letter-anim-show").beginElement();
271-
}
272-
273-
function animateThemeButtonIconToDark() {
274-
shadowRoot.getElementById("rays-anim-hide").beginElement();
275-
shadowRoot.getElementById("core-anim-enlarge").beginElement();
276-
shadowRoot.getElementById("eclipse-anim-come").beginElement();
277-
}
278-
279279
// Export for tests run by npm (no longer needed; kept for future reference)
280280
// See https://stackoverflow.com/q/63752210/8583692
281281
// and https://stackoverflow.com/a/54680602/8583692

theme-switch.min.js

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)