test(client): expand frontend test suite to 69.1% coverage

Add and extend tests across 32 files (+10 595 lines) covering Admin
panels (AuditLog, Backup, DevNotifications, GitHub), Collab (Chat,
Notes, Panel, Polls), Planner (DayDetailPanel, DayPlanSidebar),
Settings (DisplaySettings, Integrations, MapSettings), Files
(FileManager, FilesPage), Map, Layout (DemoBanner,
InAppNotificationBell), shared pickers (CustomDateTimePicker,
CustomTimePicker), Vacay holidays, pages (Dashboard, Login), unit
stores (authStore, inAppNotificationStore), API (authUrl, client
integration), and i18n. Also updates sonar-project.properties and
MSW trip handlers to support the new cases.
This commit is contained in:
jubnl
2026-04-07 21:55:41 +02:00
parent 9390a2e9c6
commit fd48169219
32 changed files with 10595 additions and 15 deletions
+247
View File
@@ -193,4 +193,251 @@ describe('authStore', () => {
expect(state.user).toBeNull();
});
});
describe('FE-STORE-AUTH-010: completeMfaLogin success', () => {
it('sets user, isAuthenticated, and calls connect', async () => {
const user = buildUser();
server.use(
http.post('/api/auth/mfa/verify-login', () =>
HttpResponse.json({ user, token: 'mfa-session-tok' })
)
);
await useAuthStore.getState().completeMfaLogin('mfa-tok', '123456');
const state = useAuthStore.getState();
expect(state.user).toEqual(user);
expect(state.isAuthenticated).toBe(true);
expect(state.isLoading).toBe(false);
expect(connect).toHaveBeenCalledOnce();
});
});
describe('FE-STORE-AUTH-011: completeMfaLogin failure', () => {
it('sets error and remains unauthenticated', async () => {
server.use(
http.post('/api/auth/mfa/verify-login', () =>
HttpResponse.json({ error: 'Invalid code' }, { status: 401 })
)
);
await expect(
useAuthStore.getState().completeMfaLogin('mfa-tok', '000000')
).rejects.toThrow();
const state = useAuthStore.getState();
expect(state.error).toBeTruthy();
expect(state.isAuthenticated).toBe(false);
expect(state.isLoading).toBe(false);
});
});
describe('FE-STORE-AUTH-012: register failure', () => {
it('sets error on registration failure', async () => {
server.use(
http.post('/api/auth/register', () =>
HttpResponse.json({ error: 'Email taken' }, { status: 400 })
)
);
await expect(
useAuthStore.getState().register('u', 'e@e.com', 'pw')
).rejects.toThrow();
const state = useAuthStore.getState();
expect(state.error).toBe('Email taken');
expect(state.isAuthenticated).toBe(false);
});
});
describe('FE-STORE-AUTH-013: loadUser silent mode', () => {
it('does not toggle isLoading when silent: true', async () => {
const user = buildUser();
server.use(
http.get('/api/auth/me', () => HttpResponse.json({ user }))
);
useAuthStore.setState({ isLoading: false });
// isLoading should remain false immediately after calling (silent mode)
const loadPromise = useAuthStore.getState().loadUser({ silent: true });
expect(useAuthStore.getState().isLoading).toBe(false);
await loadPromise;
const state = useAuthStore.getState();
expect(state.isAuthenticated).toBe(true);
expect(state.isLoading).toBe(false);
});
});
describe('FE-STORE-AUTH-014: loadUser network error (non-401)', () => {
it('preserves auth state on network error', async () => {
server.use(
http.get('/api/auth/me', () =>
HttpResponse.json({ error: 'Server error' }, { status: 500 })
)
);
useAuthStore.setState({ user: buildUser(), isAuthenticated: true });
await useAuthStore.getState().loadUser();
const state = useAuthStore.getState();
expect(state.isAuthenticated).toBe(true);
expect(state.isLoading).toBe(false);
});
});
describe('FE-STORE-AUTH-015: updateMapsKey', () => {
it('updates user maps_api_key', async () => {
server.use(
http.put('/api/auth/me/maps-key', () =>
HttpResponse.json({ success: true })
)
);
useAuthStore.setState({ user: buildUser() });
await useAuthStore.getState().updateMapsKey('my-key');
expect(useAuthStore.getState().user?.maps_api_key).toBe('my-key');
});
});
describe('FE-STORE-AUTH-016: updateMapsKey with null clears key', () => {
it('sets maps_api_key to null', async () => {
server.use(
http.put('/api/auth/me/maps-key', () =>
HttpResponse.json({ success: true })
)
);
useAuthStore.setState({ user: buildUser({ maps_api_key: 'old-key' }) });
await useAuthStore.getState().updateMapsKey(null);
expect(useAuthStore.getState().user?.maps_api_key).toBeNull();
});
});
describe('FE-STORE-AUTH-017: updateApiKeys', () => {
it('updates user with returned data', async () => {
const updatedUser = buildUser({ username: 'apiuser' });
server.use(
http.put('/api/auth/me/api-keys', () =>
HttpResponse.json({ user: updatedUser })
)
);
useAuthStore.setState({ user: buildUser() });
await useAuthStore.getState().updateApiKeys({ some_api_key: 'val' });
expect(useAuthStore.getState().user).toEqual(updatedUser);
});
});
describe('FE-STORE-AUTH-018: updateProfile', () => {
it('updates user profile', async () => {
const updatedUser = buildUser({ username: 'updated' });
server.use(
http.put('/api/auth/me/settings', () =>
HttpResponse.json({ user: updatedUser })
)
);
useAuthStore.setState({ user: buildUser() });
await useAuthStore.getState().updateProfile({ username: 'updated' });
expect(useAuthStore.getState().user?.username).toBe('updated');
});
});
describe('FE-STORE-AUTH-019: setDemoMode(true)', () => {
it('sets demoMode and localStorage', () => {
useAuthStore.getState().setDemoMode(true);
expect(useAuthStore.getState().demoMode).toBe(true);
expect(localStorage.getItem('demo_mode')).toBe('true');
});
});
describe('FE-STORE-AUTH-020: setDemoMode(false)', () => {
it('clears demoMode and localStorage', () => {
localStorage.setItem('demo_mode', 'true');
useAuthStore.getState().setDemoMode(false);
expect(useAuthStore.getState().demoMode).toBe(false);
expect(localStorage.getItem('demo_mode')).toBeNull();
});
});
describe('FE-STORE-AUTH-021: demoLogin success', () => {
it('authenticates and sets demoMode', async () => {
const user = buildUser();
server.use(
http.post('/api/auth/demo-login', () =>
HttpResponse.json({ user, token: 'tok' })
)
);
await useAuthStore.getState().demoLogin();
const state = useAuthStore.getState();
expect(state.isAuthenticated).toBe(true);
expect(state.demoMode).toBe(true);
expect(state.isLoading).toBe(false);
expect(connect).toHaveBeenCalled();
});
});
describe('FE-STORE-AUTH-022: simple setters', () => {
it('updates devMode, hasMapsKey, serverTimezone, appRequireMfa, tripRemindersEnabled', () => {
const { setDevMode, setHasMapsKey, setServerTimezone, setAppRequireMfa, setTripRemindersEnabled } =
useAuthStore.getState();
setDevMode(true);
expect(useAuthStore.getState().devMode).toBe(true);
setHasMapsKey(true);
expect(useAuthStore.getState().hasMapsKey).toBe(true);
setServerTimezone('Europe/Berlin');
expect(useAuthStore.getState().serverTimezone).toBe('Europe/Berlin');
setAppRequireMfa(true);
expect(useAuthStore.getState().appRequireMfa).toBe(true);
setTripRemindersEnabled(true);
expect(useAuthStore.getState().tripRemindersEnabled).toBe(true);
});
});
describe('FE-STORE-AUTH-023: deleteAvatar', () => {
it('sets avatar_url to null', async () => {
server.use(
http.delete('/api/auth/avatar', () =>
HttpResponse.json({ success: true })
)
);
useAuthStore.setState({ user: buildUser({ avatar_url: '/uploads/avatar.png' }) });
await useAuthStore.getState().deleteAvatar();
expect(useAuthStore.getState().user?.avatar_url).toBeNull();
});
});
describe('FE-STORE-AUTH-UPLOAD: uploadAvatar', () => {
it('updates avatar_url from response', async () => {
server.use(
http.post('/api/auth/avatar', () =>
HttpResponse.json({ avatar_url: '/uploads/avatar-new.png' })
)
);
useAuthStore.setState({ user: buildUser() });
const file = new File(['x'], 'avatar.png', { type: 'image/png' });
const result = await useAuthStore.getState().uploadAvatar(file);
expect(result.avatar_url).toBe('/uploads/avatar-new.png');
expect(useAuthStore.getState().user?.avatar_url).toBe('/uploads/avatar-new.png');
});
});
});