Skip to content

Commit ff44d08

Browse files
committed
Refactor languageSlice: improve type safety, update logic to avoid unnecessary i18n calls, add comprehensive tests, ensure full coverage and lint compliance
1 parent fd65d82 commit ff44d08

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

app/store/languageSlice.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { create } from 'zustand';
2+
import { immer } from 'zustand/middleware/immer';
3+
import { createLanguageSlice, type LanguageSlice } from './languageSlice';
4+
import type { Language } from '@/lib/domain/language';
5+
import { vi } from 'vitest';
6+
import type { TextGeneratorState } from './textGeneratorStore';
7+
8+
global.window = Object.create(window);
9+
const mockReload = vi.fn();
10+
Object.defineProperty(window, 'location', {
11+
value: { reload: mockReload, search: '?foo=bar' },
12+
writable: true,
13+
});
14+
15+
vi.mock('../i18n.client', () => ({
16+
__esModule: true,
17+
default: { changeLanguage: vi.fn() },
18+
}));
19+
import i18n from '../i18n.client';
20+
21+
const minimalTextGeneratorState: Omit<TextGeneratorState, keyof LanguageSlice> = {} as any;
22+
type Store = TextGeneratorState;
23+
const createStore = () =>
24+
create<Store>()(
25+
immer((...a) => ({ ...minimalTextGeneratorState, ...createLanguageSlice(...a) }))
26+
);
27+
28+
describe('languageSlice', () => {
29+
beforeEach(() => {
30+
vi.clearAllMocks();
31+
(i18n.changeLanguage as any).mockResolvedValue(undefined);
32+
});
33+
34+
it('initializes with default language and languages', () => {
35+
const store = createStore();
36+
expect(store.getState().language).toBe('en');
37+
expect(store.getState().languages).toBeDefined();
38+
});
39+
40+
it('setLanguage updates language and calls i18n', async () => {
41+
const store = createStore();
42+
await store.getState().setLanguage('fr' as Language, undefined, '/en/page');
43+
expect(store.getState().language).toBe('fr');
44+
expect(i18n.changeLanguage).toHaveBeenCalledWith('fr');
45+
});
46+
47+
it('setLanguage does not update if language is the same', async () => {
48+
const store = createStore();
49+
await store.getState().setLanguage('en' as Language, undefined, '/en/page');
50+
expect(i18n.changeLanguage).not.toHaveBeenCalled();
51+
});
52+
53+
it('reloads page if i18n.changeLanguage throws', async () => {
54+
(i18n.changeLanguage as any).mockRejectedValue(new Error('fail'));
55+
const store = createStore();
56+
await store.getState().setLanguage('fr' as Language, undefined, '/en/page');
57+
expect(mockReload).toHaveBeenCalled();
58+
});
59+
60+
it('router.push is called with new path if router is provided', async () => {
61+
const push = vi.fn();
62+
const router = { push };
63+
const store = createStore();
64+
await store.getState().setLanguage('fr' as Language, router, '/en/page');
65+
expect(push).toHaveBeenCalledWith('/fr/page?foo=bar');
66+
});
67+
});

app/store/languageSlice.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ export const createLanguageSlice: StateCreator<
3232
language: 'en',
3333
languages: UI_LANGUAGES,
3434
setLanguage: async (lang, router, pathname) => {
35+
let prevLang: Language | undefined;
3536
set((state) => {
37+
prevLang = state.language;
3638
if (state.language === lang) return;
3739
state.language = lang;
3840
});
41+
if (prevLang === lang) return;
3942
try {
4043
await i18n.changeLanguage(lang);
4144
} catch (e) {

0 commit comments

Comments
 (0)