- web: playwright.config.ts + e2e/navigation.spec.ts (7 navigation tests, scaffolded) - web: exclude e2e/ from tsconfig (playwright not yet installed as dep) - mobile: notes-store.test.ts (7 tests: hydrate, openNote, saveDraft, updateNote) - mobile: workspace-store.test.ts (5 tests: hydrate, preserve/reset active, set/clear) - mobile: inbox-store.test.ts (5 tests: hydrate, approve, reject, unknown id guards) - mobile: auth-store.test.ts (6 tests: bootstrap, signIn, signOut, failure paths) - Total: 76 backend, 14 web, 23 mobile = 113 tests
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
|
|
const isAuthenticatedMock = vi.fn();
|
|
const getMeMock = vi.fn();
|
|
const loginMock = vi.fn();
|
|
const clearTokensMock = vi.fn();
|
|
|
|
vi.mock('../api/auth', () => ({
|
|
getAuthClient: () => ({
|
|
isAuthenticated: isAuthenticatedMock,
|
|
getMe: getMeMock,
|
|
login: loginMock,
|
|
clearTokens: clearTokensMock,
|
|
getAccessToken: vi.fn(() => null),
|
|
}),
|
|
}));
|
|
|
|
import { useAuthStore } from './auth-store';
|
|
|
|
function resetStore() {
|
|
useAuthStore.setState({
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
email: null,
|
|
});
|
|
}
|
|
|
|
describe('useAuthStore', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
resetStore();
|
|
});
|
|
|
|
it('bootstrap sets authenticated when token exists', async () => {
|
|
isAuthenticatedMock.mockReturnValue(true);
|
|
getMeMock.mockResolvedValueOnce({ email: 'test@example.com' });
|
|
await useAuthStore.getState().bootstrap();
|
|
expect(useAuthStore.getState().isAuthenticated).toBe(true);
|
|
expect(useAuthStore.getState().email).toBe('test@example.com');
|
|
});
|
|
|
|
it('bootstrap sets unauthenticated when no token', async () => {
|
|
isAuthenticatedMock.mockReturnValue(false);
|
|
await useAuthStore.getState().bootstrap();
|
|
expect(useAuthStore.getState().isAuthenticated).toBe(false);
|
|
expect(useAuthStore.getState().email).toBeNull();
|
|
});
|
|
|
|
it('bootstrap clears tokens on getMe failure', async () => {
|
|
isAuthenticatedMock.mockReturnValue(true);
|
|
getMeMock.mockRejectedValueOnce(new Error('expired'));
|
|
await useAuthStore.getState().bootstrap();
|
|
expect(clearTokensMock).toHaveBeenCalled();
|
|
expect(useAuthStore.getState().isAuthenticated).toBe(false);
|
|
});
|
|
|
|
it('signIn sets authenticated on success', async () => {
|
|
loginMock.mockResolvedValueOnce(undefined);
|
|
const ok = await useAuthStore.getState().signIn('test@example.com', 'pass');
|
|
expect(ok).toBe(true);
|
|
expect(useAuthStore.getState().isAuthenticated).toBe(true);
|
|
expect(useAuthStore.getState().email).toBe('test@example.com');
|
|
});
|
|
|
|
it('signIn returns false on failure', async () => {
|
|
loginMock.mockRejectedValueOnce(new Error('bad credentials'));
|
|
const ok = await useAuthStore.getState().signIn('bad@example.com', 'wrong');
|
|
expect(ok).toBe(false);
|
|
expect(useAuthStore.getState().isAuthenticated).toBe(false);
|
|
expect(clearTokensMock).toHaveBeenCalled();
|
|
});
|
|
|
|
it('signOut clears state', () => {
|
|
useAuthStore.setState({ isAuthenticated: true, email: 'test@example.com' });
|
|
useAuthStore.getState().signOut();
|
|
expect(useAuthStore.getState().isAuthenticated).toBe(false);
|
|
expect(useAuthStore.getState().email).toBeNull();
|
|
expect(clearTokensMock).toHaveBeenCalled();
|
|
});
|
|
});
|