Skip to content

Commit 5b57e26

Browse files
committed
chore: add tests for offline mode
Check useIsOnline hook's events Ensure offlineEnabled correctly handled Ensure offline badge is displayed
1 parent e232474 commit 5b57e26

3 files changed

Lines changed: 155 additions & 0 deletions

File tree

src/components/SaveButton/SaveButton.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@ import configureStore from "redux-mock-store";
55
import { triggerSave } from "../../redux/EditorSlice";
66
import SaveButton from "./SaveButton";
77

8+
jest.mock("../../hooks/useIsOnline");
9+
10+
import useIsOnline from "../../hooks/useIsOnline";
11+
812
const logInHandler = jest.fn();
913

1014
describe("When project is loaded", () => {
1115
beforeAll(() => {
1216
document.addEventListener("editor-logIn", logInHandler);
1317
});
1418

19+
beforeEach(() => {
20+
useIsOnline.mockReturnValue(true);
21+
});
22+
1523
describe("With logged in user", () => {
1624
let store;
1725

@@ -204,6 +212,69 @@ describe("When project is loaded", () => {
204212
});
205213
});
206214

215+
describe("offline badge", () => {
216+
const offlineState = {
217+
editor: {
218+
loading: "success",
219+
webComponent: true,
220+
offlineEnabled: true,
221+
project: {},
222+
},
223+
auth: {},
224+
};
225+
226+
beforeEach(() => {
227+
useIsOnline.mockReturnValue(false);
228+
});
229+
230+
test("shows offline badge when offline and offlineEnabled is true", () => {
231+
const store = configureStore([])(offlineState);
232+
render(
233+
<Provider store={store}>
234+
<SaveButton />
235+
</Provider>,
236+
);
237+
expect(screen.queryByText("header.offline")).toBeInTheDocument();
238+
});
239+
240+
test("does not show offline badge when offlineEnabled is false", () => {
241+
const store = configureStore([])({
242+
...offlineState,
243+
editor: { ...offlineState.editor, offlineEnabled: false },
244+
});
245+
render(
246+
<Provider store={store}>
247+
<SaveButton />
248+
</Provider>,
249+
);
250+
expect(screen.queryByText("header.offline")).not.toBeInTheDocument();
251+
});
252+
253+
test("does not show offline badge when online, even if offlineEnabled is true", () => {
254+
useIsOnline.mockReturnValue(true);
255+
const store = configureStore([])(offlineState);
256+
render(
257+
<Provider store={store}>
258+
<SaveButton />
259+
</Provider>,
260+
);
261+
expect(screen.queryByText("header.offline")).not.toBeInTheDocument();
262+
});
263+
264+
test("does not show offline badge when user is logged in", () => {
265+
const store = configureStore([])({
266+
...offlineState,
267+
auth: { user: { profile: { user: "some-user" } } },
268+
});
269+
render(
270+
<Provider store={store}>
271+
<SaveButton />
272+
</Provider>,
273+
);
274+
expect(screen.queryByText("header.offline")).not.toBeInTheDocument();
275+
});
276+
});
277+
207278
afterAll(() => {
208279
document.removeEventListener("editor-logIn", logInHandler);
209280
});

src/hooks/useIsOnline.test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { act, renderHook } from "@testing-library/react";
2+
import useIsOnline from "./useIsOnline";
3+
4+
const dispatchWindowEvent = (type) => {
5+
act(() => {
6+
window.dispatchEvent(new Event(type));
7+
});
8+
};
9+
10+
describe("useIsOnline", () => {
11+
let swEventTarget;
12+
13+
beforeEach(() => {
14+
Object.defineProperty(navigator, "onLine", {
15+
configurable: true,
16+
get: () => true,
17+
});
18+
19+
// jsdom doesn't implement navigator.serviceWorker so provide a minimal stub
20+
swEventTarget = new EventTarget();
21+
Object.defineProperty(navigator, "serviceWorker", {
22+
configurable: true,
23+
value: swEventTarget,
24+
});
25+
});
26+
27+
const dispatchSWMessage = (data) => {
28+
act(() => {
29+
swEventTarget.dispatchEvent(new MessageEvent("message", { data }));
30+
});
31+
};
32+
33+
test("returns true when navigator.onLine is true", () => {
34+
const { result } = renderHook(() => useIsOnline());
35+
expect(result.current).toBe(true);
36+
});
37+
38+
test("returns false when navigator.onLine is false", () => {
39+
Object.defineProperty(navigator, "onLine", {
40+
configurable: true,
41+
get: () => false,
42+
});
43+
const { result } = renderHook(() => useIsOnline());
44+
expect(result.current).toBe(false);
45+
});
46+
47+
test("updates to false when the offline window event fires", () => {
48+
const { result } = renderHook(() => useIsOnline());
49+
expect(result.current).toBe(true);
50+
dispatchWindowEvent("offline");
51+
expect(result.current).toBe(false);
52+
});
53+
54+
test("updates to true when the online window event fires", () => {
55+
Object.defineProperty(navigator, "onLine", {
56+
configurable: true,
57+
get: () => false,
58+
});
59+
const { result } = renderHook(() => useIsOnline());
60+
expect(result.current).toBe(false);
61+
dispatchWindowEvent("online");
62+
expect(result.current).toBe(true);
63+
});
64+
65+
test("updates to false when the service worker broadcasts OFFLINE", () => {
66+
const { result } = renderHook(() => useIsOnline());
67+
expect(result.current).toBe(true);
68+
dispatchSWMessage({ type: "OFFLINE" });
69+
expect(result.current).toBe(false);
70+
});
71+
72+
test("ignores service worker messages with a different type", () => {
73+
const { result } = renderHook(() => useIsOnline());
74+
dispatchSWMessage({ type: "OTHER" });
75+
expect(result.current).toBe(true);
76+
});
77+
});

src/redux/EditorSlice.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import reducer, {
1111
setIsOutputOnly,
1212
setErrorDetails,
1313
setReadOnly,
14+
setOfflineEnabled,
1415
addProjectComponent,
1516
updateProjectComponent,
1617
setCascadeUpdate,
@@ -113,6 +114,12 @@ test("Action setReadOnly correctly sets readOnly", () => {
113114
expect(reducer(previousState, setReadOnly(true))).toEqual(expectedState);
114115
});
115116

117+
test("Action setOfflineEnabled correctly sets offlineEnabled", () => {
118+
const previousState = { offlineEnabled: false };
119+
const expectedState = { offlineEnabled: true };
120+
expect(reducer(previousState, setOfflineEnabled(true))).toEqual(expectedState);
121+
});
122+
116123
test("Action addProjectComponent adds component to project with correct content", () => {
117124
const previousState = {
118125
project: {

0 commit comments

Comments
 (0)