mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
Add E2E tests for internationalization across multiple pages
- Implemented navigation.spec.ts to test language switching and translation of navigation elements. - Created sessions-page.spec.ts to verify translations on the sessions page, including headers, status badges, and date formatting. - Developed settings-page.spec.ts to ensure settings page content is translated and persists across sessions. - Added skills-page.spec.ts to validate translations for skill categories, action buttons, and empty states.
This commit is contained in:
124
ccw/frontend/src/stores/appStore.test.ts
Normal file
124
ccw/frontend/src/stores/appStore.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
// ========================================
|
||||
// App Store Tests - Locale
|
||||
// ========================================
|
||||
// Tests for locale state management
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { useAppStore, selectLocale } from './appStore';
|
||||
import type { Locale } from '../types/store';
|
||||
|
||||
// Mock i18n utilities
|
||||
vi.mock('../lib/i18n', () => ({
|
||||
getInitialLocale: () => 'en' as Locale,
|
||||
updateIntl: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('AppStore - Locale State', () => {
|
||||
beforeEach(() => {
|
||||
// Reset store state before each test
|
||||
useAppStore.setState({
|
||||
locale: 'en',
|
||||
});
|
||||
});
|
||||
|
||||
describe('initial state', () => {
|
||||
it('should have initial locale', () => {
|
||||
const locale = useAppStore.getState().locale;
|
||||
expect(locale).toBeDefined();
|
||||
expect(['en', 'zh']).toContain(locale);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLocale', () => {
|
||||
it('should update locale to Chinese', () => {
|
||||
const store = useAppStore.getState();
|
||||
|
||||
store.setLocale('zh');
|
||||
|
||||
expect(useAppStore.getState().locale).toBe('zh');
|
||||
});
|
||||
|
||||
it('should update locale to English', () => {
|
||||
useAppStore.setState({ locale: 'zh' });
|
||||
|
||||
const store = useAppStore.getState();
|
||||
store.setLocale('en');
|
||||
|
||||
expect(useAppStore.getState().locale).toBe('en');
|
||||
});
|
||||
|
||||
it('should call updateIntl when locale changes', async () => {
|
||||
const { updateIntl } = await import('../lib/i18n');
|
||||
const store = useAppStore.getState();
|
||||
|
||||
store.setLocale('zh');
|
||||
|
||||
expect(updateIntl).toHaveBeenCalledWith('zh');
|
||||
});
|
||||
});
|
||||
|
||||
describe('locale persistence', () => {
|
||||
it('should persist locale to localStorage', () => {
|
||||
const store = useAppStore.getState();
|
||||
|
||||
store.setLocale('zh');
|
||||
|
||||
const stored = localStorage.getItem('ccw-app-store');
|
||||
expect(stored).toBeDefined();
|
||||
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
expect(parsed.state.locale).toBe('zh');
|
||||
}
|
||||
});
|
||||
|
||||
it('should retrieve locale from localStorage on hydration', () => {
|
||||
// Set locale in localStorage
|
||||
localStorage.setItem('ccw-app-store', JSON.stringify({
|
||||
state: { locale: 'zh', theme: 'system', sidebarCollapsed: false },
|
||||
version: 0,
|
||||
}));
|
||||
|
||||
// Create new store instance to test hydration
|
||||
const store = useAppStore.getState();
|
||||
|
||||
// Note: This test verifies the persist middleware works
|
||||
// In actual implementation, zustand persist handles this
|
||||
expect(['en', 'zh']).toContain(store.locale);
|
||||
});
|
||||
});
|
||||
|
||||
describe('document lang attribute', () => {
|
||||
it('should update document.lang when locale changes', async () => {
|
||||
const store = useAppStore.getState();
|
||||
|
||||
store.setLocale('zh');
|
||||
|
||||
// updateIntl is called and should update document.lang
|
||||
const { updateIntl } = await import('../lib/i18n');
|
||||
expect(updateIntl).toHaveBeenCalledWith('zh');
|
||||
});
|
||||
});
|
||||
|
||||
describe('locale selector', () => {
|
||||
it('should return current locale via selector', () => {
|
||||
useAppStore.setState({ locale: 'zh' });
|
||||
const locale = selectLocale(useAppStore.getState());
|
||||
|
||||
expect(locale).toBe('zh');
|
||||
});
|
||||
});
|
||||
|
||||
describe('locale validation', () => {
|
||||
it('should only accept valid locale values', () => {
|
||||
const store = useAppStore.getState();
|
||||
|
||||
// Test that only 'en' and 'zh' are valid
|
||||
const validLocales: Locale[] = ['en', 'zh'];
|
||||
|
||||
validLocales.forEach((locale) => {
|
||||
expect(() => store.setLocale(locale)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist, devtools } from 'zustand/middleware';
|
||||
import type { AppStore, Theme, ViewMode, SessionFilter, LiteTaskType } from '../types/store';
|
||||
import type { AppStore, Theme, Locale, ViewMode, SessionFilter, LiteTaskType } from '../types/store';
|
||||
import { getInitialLocale, updateIntl } from '../lib/i18n';
|
||||
|
||||
// Helper to resolve system theme
|
||||
const getSystemTheme = (): 'light' | 'dark' => {
|
||||
@@ -27,6 +28,9 @@ const initialState = {
|
||||
theme: 'system' as Theme,
|
||||
resolvedTheme: 'light' as 'light' | 'dark',
|
||||
|
||||
// Locale
|
||||
locale: getInitialLocale() as Locale,
|
||||
|
||||
// Sidebar
|
||||
sidebarOpen: true,
|
||||
sidebarCollapsed: false,
|
||||
@@ -69,6 +73,13 @@ export const useAppStore = create<AppStore>()(
|
||||
get().setTheme(newTheme);
|
||||
},
|
||||
|
||||
// ========== Locale Actions ==========
|
||||
|
||||
setLocale: (locale: Locale) => {
|
||||
set({ locale }, false, 'setLocale');
|
||||
updateIntl(locale);
|
||||
},
|
||||
|
||||
// ========== Sidebar Actions ==========
|
||||
|
||||
setSidebarOpen: (open: boolean) => {
|
||||
@@ -117,9 +128,10 @@ export const useAppStore = create<AppStore>()(
|
||||
}),
|
||||
{
|
||||
name: 'ccw-app-store',
|
||||
// Only persist theme preference
|
||||
// Only persist theme and locale preferences
|
||||
partialize: (state) => ({
|
||||
theme: state.theme,
|
||||
locale: state.locale,
|
||||
sidebarCollapsed: state.sidebarCollapsed,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
@@ -133,6 +145,10 @@ export const useAppStore = create<AppStore>()(
|
||||
document.documentElement.setAttribute('data-theme', resolved);
|
||||
}
|
||||
}
|
||||
// Apply locale on rehydration
|
||||
if (state) {
|
||||
updateIntl(state.locale);
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
@@ -158,6 +174,7 @@ if (typeof window !== 'undefined') {
|
||||
// Selectors for common access patterns
|
||||
export const selectTheme = (state: AppStore) => state.theme;
|
||||
export const selectResolvedTheme = (state: AppStore) => state.resolvedTheme;
|
||||
export const selectLocale = (state: AppStore) => state.locale;
|
||||
export const selectSidebarOpen = (state: AppStore) => state.sidebarOpen;
|
||||
export const selectCurrentView = (state: AppStore) => state.currentView;
|
||||
export const selectIsLoading = (state: AppStore) => state.isLoading;
|
||||
|
||||
Reference in New Issue
Block a user