Skip to content

Commit e1b0446

Browse files
Refactor loader.js: Async init, secure storage, and dynamic i18n support
1 parent f6911a9 commit e1b0446

File tree

2 files changed

+298
-99
lines changed

2 files changed

+298
-99
lines changed

js/__tests__/loader.test.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
// Mock dependencies
5+
const mockI18next = {
6+
use: jest.fn().mockReturnThis(),
7+
init: jest.fn(),
8+
on: jest.fn(),
9+
t: jest.fn((key) => "translated_" + key),
10+
changeLanguage: jest.fn()
11+
};
12+
13+
const mockHttpBackend = jest.fn();
14+
15+
// Mock requirejs
16+
global.requirejs = jest.fn((deps, callback) => {
17+
callback(mockI18next, mockHttpBackend);
18+
});
19+
global.requirejs.config = jest.fn();
20+
21+
describe("loader.js coverage", () => {
22+
let consoleLogSpy;
23+
let consoleErrorSpy;
24+
25+
beforeEach(() => {
26+
jest.clearAllMocks();
27+
document.body.innerHTML =
28+
'<div data-i18n="title">Title</div>' +
29+
'<div data-i18n="label">Label</div>' +
30+
'<div id="loading-media"></div>';
31+
32+
consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => { });
33+
consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
34+
35+
// Mock localStorage
36+
Object.defineProperty(window, "localStorage", {
37+
value: {
38+
getItem: jest.fn(),
39+
setItem: jest.fn(),
40+
languagePreference: "en"
41+
},
42+
writable: true
43+
});
44+
45+
// Mock navigator
46+
Object.defineProperty(window, "navigator", {
47+
value: {
48+
language: "en-US"
49+
},
50+
writable: true
51+
});
52+
53+
// Setup i18next mock implementation for init
54+
mockI18next.init.mockImplementation((config, callback) => {
55+
if (config.lng === "init_error") {
56+
callback("Init Failed", null);
57+
} else {
58+
callback(null, (key) => key);
59+
}
60+
});
61+
});
62+
63+
afterEach(() => {
64+
consoleLogSpy.mockRestore();
65+
consoleErrorSpy.mockRestore();
66+
jest.resetModules();
67+
});
68+
69+
async function loadScript(options = {}) {
70+
if (options.lang) {
71+
window.localStorage.getItem.mockReturnValue(options.lang);
72+
} else if (options.initError) {
73+
window.localStorage.getItem.mockReturnValue("init_error");
74+
} else {
75+
window.localStorage.getItem.mockReturnValue(null);
76+
}
77+
78+
require("../loader.js");
79+
80+
// Wait for multiple ticks to allow async main() to progress
81+
await new Promise(resolve => process.nextTick(resolve));
82+
await new Promise(resolve => process.nextTick(resolve));
83+
await new Promise(resolve => process.nextTick(resolve));
84+
};
85+
86+
test("Configures requirejs correctly", async () => {
87+
await loadScript();
88+
expect(global.requirejs.config).toHaveBeenCalledWith(expect.objectContaining({
89+
baseUrl: "lib",
90+
paths: expect.any(Object)
91+
}));
92+
});
93+
94+
test("Full success path: initializes i18n, updates DOM, and loads app", async () => {
95+
await loadScript();
96+
97+
expect(mockI18next.init).toHaveBeenCalledWith(
98+
expect.objectContaining({ lng: "en" }),
99+
expect.any(Function)
100+
);
101+
expect(window.i18next).toBe(mockI18next);
102+
103+
const title = document.querySelector('[data-i18n="title"]');
104+
const label = document.querySelector('[data-i18n="label"]');
105+
106+
expect(title.textContent).toBe("translated_title");
107+
expect(label.textContent).toBe("translated_label");
108+
109+
expect(global.requirejs).toHaveBeenCalledWith(
110+
["utils/utils", "activity/activity"]
111+
);
112+
});
113+
114+
test("Handles i18next initialization error", async () => {
115+
await loadScript({ initError: true });
116+
117+
expect(consoleErrorSpy).toHaveBeenCalledWith("i18next init failed:", "Init Failed");
118+
expect(window.i18next).toBe(mockI18next);
119+
});
120+
121+
test("Handles changeLanguage error", async () => {
122+
// This test might be obsolete if changeLanguage is not called on init,
123+
// but we can keep it if we test the listener or OnClick.
124+
// For now, let's skip it or mock changeLanguage call if we add it back.
125+
});
126+
127+
test("Handles DOMContentLoaded when document is loading", async () => {
128+
Object.defineProperty(document, "readyState", {
129+
value: "loading",
130+
configurable: true
131+
});
132+
133+
const addEventListenerSpy = jest.spyOn(document, "addEventListener");
134+
135+
await loadScript();
136+
137+
expect(addEventListenerSpy).toHaveBeenCalledWith("DOMContentLoaded", expect.any(Function));
138+
139+
const eventHandler = addEventListenerSpy.mock.calls.find(
140+
call => call[0] === "DOMContentLoaded"
141+
)[1];
142+
143+
// Manually trigger the handler
144+
eventHandler();
145+
146+
const title = document.querySelector('[data-i18n="title"]');
147+
expect(title.textContent).toBe("translated_title");
148+
});
149+
150+
test("Triggering languageChanged event updates content", async () => {
151+
await loadScript();
152+
153+
// Reset content to verify update
154+
document.querySelector('[data-i18n="title"]').textContent = "Old Title";
155+
156+
// Find the on('languageChanged') callback
157+
const onCallback = mockI18next.on.mock.calls.find(call => call[0] === 'languageChanged')[1];
158+
onCallback();
159+
160+
const title = document.querySelector('[data-i18n="title"]');
161+
expect(title.textContent).toBe("translated_title");
162+
});
163+
});

0 commit comments

Comments
 (0)