Skip to content

Commit 182fa20

Browse files
committed
feat: default learning language based on UI language
1 parent 2efcc8b commit 182fa20

File tree

2 files changed

+200
-39
lines changed

2 files changed

+200
-39
lines changed

app/components/TextGenerator/TextGeneratorContainer.test.tsx

Lines changed: 181 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,38 @@ vi.mock('./ProgressTracker', () => ({ default: () => <div>ProgressTracker</div>
1717
vi.mock('./Generator', () => ({ default: () => <div>Generator</div> }));
1818

1919
describe('TextGeneratorContainer', () => {
20+
const mockFetchProgress = vi.fn();
21+
const mockSetPassageLanguage = vi.fn();
22+
23+
const mockStoreState = {
24+
loading: false,
25+
quizData: null,
26+
showContent: false,
27+
isAnswered: false,
28+
fetchProgress: mockFetchProgress,
29+
passageLanguage: 'en',
30+
setPassageLanguage: mockSetPassageLanguage,
31+
// Add other state properties if needed by the component
32+
};
33+
2034
beforeEach(() => {
2135
vi.clearAllMocks();
2236
(useLanguage as any).mockReturnValue({ language: 'en' });
23-
(useTextGeneratorStore as any).mockReturnValue({
24-
loading: false,
25-
quizData: null,
26-
showContent: false,
27-
isAnswered: false,
28-
fetchProgress: vi.fn(),
29-
});
37+
38+
// Mock the hook return value
39+
(useTextGeneratorStore as any).mockReturnValue(mockStoreState);
40+
41+
// Mock the getState method on the mocked store module
42+
(useTextGeneratorStore as any).getState = vi.fn(() => mockStoreState);
3043
});
3144

3245
it('renders all static child components', () => {
3346
(useSession as any).mockReturnValue({ status: 'unauthenticated' });
47+
// Update mocks if specific state is needed for this test
48+
const specificState = { ...mockStoreState, loading: false };
49+
(useTextGeneratorStore as any).mockReturnValue(specificState);
50+
(useTextGeneratorStore as any).getState = vi.fn(() => specificState);
51+
3452
render(<TextGeneratorContainer />);
3553
expect(screen.getByText('LanguageSelector')).toBeInTheDocument();
3654
expect(screen.getByText('LoginPrompt')).toBeInTheDocument();
@@ -39,65 +57,191 @@ describe('TextGeneratorContainer', () => {
3957
});
4058

4159
it('shows QuizSkeleton when loading and no quizData', () => {
42-
(useTextGeneratorStore as any).mockReturnValue({
43-
loading: true,
44-
quizData: null,
45-
showContent: false,
46-
isAnswered: false,
47-
fetchProgress: vi.fn(),
48-
});
60+
const specificState = { ...mockStoreState, loading: true, quizData: null };
61+
(useTextGeneratorStore as any).mockReturnValue(specificState);
62+
(useTextGeneratorStore as any).getState = vi.fn(() => specificState);
4963
render(<TextGeneratorContainer />);
5064
expect(screen.getByText('QuizSkeleton')).toBeInTheDocument();
5165
});
5266

5367
it('shows generated content when quizData and showContent', () => {
54-
(useTextGeneratorStore as any).mockReturnValue({
68+
const specificState = {
69+
...mockStoreState,
5570
loading: false,
5671
quizData: { id: 1 },
5772
showContent: true,
58-
isAnswered: false,
59-
fetchProgress: vi.fn(),
60-
});
73+
};
74+
(useTextGeneratorStore as any).mockReturnValue(specificState);
75+
(useTextGeneratorStore as any).getState = vi.fn(() => specificState);
6176
render(<TextGeneratorContainer />);
6277
expect(screen.getByText('ReadingPassage')).toBeInTheDocument();
6378
expect(screen.getByText('QuizSection')).toBeInTheDocument();
6479
});
6580

6681
it('shows ProgressTracker when isAnswered', () => {
67-
(useTextGeneratorStore as any).mockReturnValue({
68-
loading: false,
69-
quizData: { id: 1 },
70-
showContent: false,
71-
isAnswered: true,
72-
fetchProgress: vi.fn(),
73-
});
82+
const specificState = { ...mockStoreState, isAnswered: true };
83+
(useTextGeneratorStore as any).mockReturnValue(specificState);
84+
(useTextGeneratorStore as any).getState = vi.fn(() => specificState);
7485
render(<TextGeneratorContainer />);
7586
expect(screen.getByText('ProgressTracker')).toBeInTheDocument();
7687
});
7788

7889
it('shows ProgressTracker when not content visible and not loading', () => {
79-
(useTextGeneratorStore as any).mockReturnValue({
90+
const specificState = {
91+
...mockStoreState,
8092
loading: false,
8193
quizData: null,
8294
showContent: false,
8395
isAnswered: false,
84-
fetchProgress: vi.fn(),
85-
});
96+
};
97+
(useTextGeneratorStore as any).mockReturnValue(specificState);
98+
(useTextGeneratorStore as any).getState = vi.fn(() => specificState);
8699
render(<TextGeneratorContainer />);
87100
expect(screen.getByText('ProgressTracker')).toBeInTheDocument();
88101
});
89102

90103
it('fetches user progress when authenticated', () => {
91-
const fetchProgress = vi.fn();
104+
const specificState = { ...mockStoreState, fetchProgress: mockFetchProgress }; // Ensure fetchProgress is the mock
92105
(useSession as any).mockReturnValue({ status: 'authenticated' });
93-
(useTextGeneratorStore as any).mockReturnValue({
94-
loading: false,
95-
quizData: null,
96-
showContent: false,
97-
isAnswered: false,
98-
fetchProgress,
99-
});
106+
(useTextGeneratorStore as any).mockReturnValue(specificState);
107+
(useTextGeneratorStore as any).getState = vi.fn(() => specificState);
100108
render(<TextGeneratorContainer />);
101-
expect(fetchProgress).toHaveBeenCalled();
109+
expect(mockFetchProgress).toHaveBeenCalled();
110+
});
111+
112+
describe('Default Passage Language Logic', () => {
113+
it('sets passageLanguage to "es" if UI language is "en" and current passageLanguage is not "es"', () => {
114+
(useLanguage as any).mockReturnValue({ language: 'en' });
115+
const initialPassageLang = 'de'; // Different from 'es'
116+
const specificInitialState = {
117+
...mockStoreState,
118+
passageLanguage: initialPassageLang,
119+
setPassageLanguage: mockSetPassageLanguage,
120+
};
121+
(useTextGeneratorStore as any).mockReturnValue(specificInitialState);
122+
(useTextGeneratorStore as any).getState = vi.fn(() => specificInitialState);
123+
124+
render(<TextGeneratorContainer />);
125+
expect(mockSetPassageLanguage).toHaveBeenCalledWith('es');
126+
expect(mockSetPassageLanguage).toHaveBeenCalledTimes(1);
127+
});
128+
129+
it('does not change passageLanguage if UI language is "en" and current passageLanguage is already "es"', () => {
130+
(useLanguage as any).mockReturnValue({ language: 'en' });
131+
const initialPassageLang = 'es'; // Already 'es'
132+
const specificInitialState = {
133+
...mockStoreState,
134+
passageLanguage: initialPassageLang,
135+
setPassageLanguage: mockSetPassageLanguage,
136+
};
137+
(useTextGeneratorStore as any).mockReturnValue(specificInitialState);
138+
(useTextGeneratorStore as any).getState = vi.fn(() => specificInitialState);
139+
140+
render(<TextGeneratorContainer />);
141+
// It might be called with 'es' if the initial state in mockStoreState was different before override,
142+
// but the crucial part is that it shouldn't be called to *change* it from 'es' due to the component's internal check.
143+
// Given our logic `if (currentPassageLanguage !== 'es')`, it should not be called.
144+
expect(mockSetPassageLanguage).not.toHaveBeenCalled();
145+
});
146+
147+
it('sets passageLanguage to "en" if UI language is not "en" and current passageLanguage is not "en"', () => {
148+
(useLanguage as any).mockReturnValue({ language: 'fr' }); // Non-'en' UI language
149+
const initialPassageLang = 'de'; // Different from 'en'
150+
const specificInitialState = {
151+
...mockStoreState,
152+
passageLanguage: initialPassageLang,
153+
setPassageLanguage: mockSetPassageLanguage,
154+
};
155+
156+
(useTextGeneratorStore as any).mockReturnValue(specificInitialState);
157+
(useTextGeneratorStore as any).getState = vi.fn(() => specificInitialState);
158+
159+
render(<TextGeneratorContainer />);
160+
expect(mockSetPassageLanguage).toHaveBeenCalledWith('en');
161+
expect(mockSetPassageLanguage).toHaveBeenCalledTimes(1);
162+
});
163+
164+
it('does not change passageLanguage if UI language is not "en" and current passageLanguage is already "en"', () => {
165+
(useLanguage as any).mockReturnValue({ language: 'fr' }); // Non-'en' UI language
166+
const initialPassageLang = 'en'; // Already 'en'
167+
const specificInitialState = {
168+
...mockStoreState,
169+
passageLanguage: initialPassageLang,
170+
setPassageLanguage: mockSetPassageLanguage,
171+
};
172+
(useTextGeneratorStore as any).mockReturnValue(specificInitialState);
173+
(useTextGeneratorStore as any).getState = vi.fn(() => specificInitialState);
174+
175+
render(<TextGeneratorContainer />);
176+
// Given our logic `if (currentPassageLanguage !== 'en')`, it should not be called.
177+
expect(mockSetPassageLanguage).not.toHaveBeenCalled();
178+
});
179+
180+
it('calls setPassageLanguage only once even with multiple relevant state changes in dependencies', () => {
181+
(useLanguage as any).mockReturnValue({ language: 'en' });
182+
const initialPassageLang = 'de';
183+
const specificInitialState = {
184+
...mockStoreState,
185+
passageLanguage: initialPassageLang,
186+
setPassageLanguage: mockSetPassageLanguage,
187+
fetchProgress: mockFetchProgress,
188+
};
189+
(useTextGeneratorStore as any).mockReturnValue(specificInitialState);
190+
(useTextGeneratorStore as any).getState = vi.fn(() => specificInitialState);
191+
192+
const { rerender } = render(<TextGeneratorContainer />);
193+
expect(mockSetPassageLanguage).toHaveBeenCalledWith('es');
194+
expect(mockSetPassageLanguage).toHaveBeenCalledTimes(1);
195+
196+
// Simulate a change in another dependency of the useEffect, like session status
197+
// This specific mock change won't directly cause TextGeneratorContainer's useEffect to re-evaluate setPassageLanguage
198+
// because the useRef defaultLanguageAppliedRef prevents it.
199+
// We are verifying that the internal ref logic works.
200+
(useSession as any).mockReturnValue({ status: 'authenticated' });
201+
// To trigger re-render and thus the effect with new `status` prop
202+
// we need to ensure a state that TextGeneratorContainer itself uses from store changes,
203+
// or a prop changes. Here, we re-render with potentially different session status.
204+
// However, the default language setting part of the effect is guarded by defaultLanguageAppliedRef.
205+
206+
// Let's mock a change that would re-run the effect if not for the ref guard.
207+
// For example, changing contextLanguage.
208+
(useLanguage as any).mockReturnValue({ language: 'fr' }); // Change UI language
209+
// Important: we need to update the store mocks to reflect that setPassageLanguage has already run and changed passageLanguage
210+
// and that the setPassageLanguage mock itself is the same instance for subsequent calls.
211+
// The critical part is the defaultLanguageAppliedRef.current check.
212+
213+
rerender(<TextGeneratorContainer />); // Re-render with potentially new contextLanguage
214+
215+
// Even if contextLanguage changes, the default setting should have already occurred and not repeat.
216+
// The setPassageLanguage('es') was from the first render.
217+
// If contextLanguage changed to 'fr', and assuming 'es' was set, new logic would be:
218+
// contextLanguage = 'fr', currentPassageLanguage = 'es' (set by previous run)
219+
// if ('fr' === 'en') -> false. else if ('es' !== 'en') -> true. setPassageLanguage('en')
220+
// This shows the default logic might run again if contextLanguage changes *after* initial setup
221+
// The current implementation applies default once on mount. If UI lang changes later, it might re-default.
222+
// The current tests are for the *initial* defaulting.
223+
// For this "once" test, let's simplify: ensure after first call, subsequent renders with same initial conditions don't recall.
224+
// The ref ensures that for the *same initial conditions*, it won't run again.
225+
226+
// Resetting for a cleaner "once" check with stable contextLanguage
227+
vi.clearAllMocks(); // Clear calls for this specific check
228+
(useLanguage as any).mockReturnValue({ language: 'en' });
229+
const rerenderState = {
230+
...mockStoreState,
231+
passageLanguage: 'de',
232+
setPassageLanguage: mockSetPassageLanguage,
233+
};
234+
(useTextGeneratorStore as any).mockReturnValue(rerenderState);
235+
(useTextGeneratorStore as any).getState = vi.fn(() => rerenderState);
236+
// First render
237+
const { rerender: rerender2 } = render(<TextGeneratorContainer />);
238+
expect(mockSetPassageLanguage).toHaveBeenCalledWith('es');
239+
expect(mockSetPassageLanguage).toHaveBeenCalledTimes(1);
240+
241+
// Subsequent render (e.g., due to unrelated parent re-render or minor state change not affecting default logic)
242+
// The component's internal ref should prevent calling setPassageLanguage again for defaulting.
243+
rerender2(<TextGeneratorContainer />);
244+
expect(mockSetPassageLanguage).toHaveBeenCalledTimes(1); // Still 1, not 2
245+
});
102246
});
103247
});

app/components/TextGenerator/TextGeneratorContainer.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useRef, useEffect } from 'react';
44
import { useLanguage } from '@/hooks/useLanguage';
55
import { useSession } from 'next-auth/react';
66
import useTextGeneratorStore from '@/store/textGeneratorStore';
7+
import { type LearningLanguage } from '@/lib/domain/language';
78
import LanguageSelector from './LanguageSelector';
89
import LoginPrompt from './LoginPrompt';
910
import ErrorDisplay from './ErrorDisplay';
@@ -16,10 +17,12 @@ import Generator from './Generator';
1617
const TextGeneratorContainer = () => {
1718
const { language: contextLanguage } = useLanguage();
1819
const { status } = useSession();
19-
const { loading, quizData, showContent, isAnswered, fetchProgress } = useTextGeneratorStore();
20+
const { loading, quizData, showContent, isAnswered, fetchProgress, setPassageLanguage } =
21+
useTextGeneratorStore();
2022

2123
const contentContainerRef = useRef<HTMLDivElement>(null);
2224
const generatedContentRef = useRef<HTMLDivElement>(null);
25+
const defaultLanguageAppliedRef = useRef(false);
2326

2427
const isContentVisible = !!(quizData && !loading && showContent);
2528

@@ -41,8 +44,22 @@ const TextGeneratorContainer = () => {
4144
void fetchProgress();
4245
}
4346

47+
if (!defaultLanguageAppliedRef.current) {
48+
const currentPassageLanguage = useTextGeneratorStore.getState().passageLanguage;
49+
if (contextLanguage === 'en') {
50+
if (currentPassageLanguage !== 'es') {
51+
setPassageLanguage('es' as LearningLanguage);
52+
}
53+
} else {
54+
if (currentPassageLanguage !== 'en') {
55+
setPassageLanguage('en' as LearningLanguage);
56+
}
57+
}
58+
defaultLanguageAppliedRef.current = true;
59+
}
60+
4461
useTextGeneratorStore.setState({ generatedQuestionLanguage: contextLanguage });
45-
}, [status, fetchProgress, contextLanguage]);
62+
}, [status, fetchProgress, contextLanguage, setPassageLanguage]);
4663

4764
return (
4865
<div

0 commit comments

Comments
 (0)