diff --git a/src/store/authSlice.ts b/src/store/authSlice.ts index 8255f61..ce2b0b5 100644 --- a/src/store/authSlice.ts +++ b/src/store/authSlice.ts @@ -1,30 +1,39 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { storage } from '@/utils/storage' +// import type 避免运行时循环依赖:store → api/auth → http/client → store +import type { WxUserInfo } from '@/api/auth' interface State { token: string | null userId: string | null + userInfo: WxUserInfo | null // 登录返回的完整用户信息(spec D6) } const STORAGE_KEY = 'auth_token' -const initialState: State = { +// initialState 用惰性函数:保证 reducer(undefined) 时才读取 storage, +// 使测试里 beforeEach 清空 mock 后再初始化能拿到正确的干净状态 +const initialState = (): State => ({ token: storage.get(STORAGE_KEY), - userId: null -} + userId: null, + userInfo: null +}) const slice = createSlice({ name: 'auth', initialState, reducers: { - setAuth(state, action: PayloadAction<{ token: string; userId: string }>) { + // token 落盘持久化;userInfo 只入内存,冷启动由后续接口拉取,避免缓存过期数据(spec D6) + setAuth(state, action: PayloadAction<{ token: string; userInfo: WxUserInfo }>) { state.token = action.payload.token - state.userId = action.payload.userId + state.userId = action.payload.userInfo.userId + state.userInfo = action.payload.userInfo storage.set(STORAGE_KEY, action.payload.token) }, clearAuth(state) { state.token = null state.userId = null + state.userInfo = null storage.remove(STORAGE_KEY) } } diff --git a/tests/unit/store/authSlice.spec.ts b/tests/unit/store/authSlice.spec.ts new file mode 100644 index 0000000..bffb565 --- /dev/null +++ b/tests/unit/store/authSlice.spec.ts @@ -0,0 +1,57 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' + +// 内存 Map 模拟 Taro storage(同 tests/unit/family/family.spec.ts 模式) +const mem = new Map() +vi.mock('@/utils/storage', () => ({ + storage: { + get: (key: string): T | null => (mem.has(key) ? (mem.get(key) as T) : null), + set: (key: string, value: T): void => { mem.set(key, value) }, + remove: (key: string): void => { mem.delete(key) } + } +})) + +import reducer, { setAuth, clearAuth } from '@/store/authSlice' +import type { WxUserInfo } from '@/api/auth' + +const userInfo: WxUserInfo = { + userId: 'u-100', + userType: 1, + avatar: 'https://example.com/a.png', + nickName: '张大爷', + mobile: '13800000000', + gender: 1, + region: '深圳市南山区', + loginFrom: 'miniapp' +} + +beforeEach(() => { + mem.clear() +}) + +describe('authSlice', () => { + it('setAuth 写入 token/userId/userInfo,token 落盘', () => { + let state = reducer(undefined, { type: 'init' }) + state = reducer(state, setAuth({ token: 'tk-1', userInfo })) + expect(state.token).toBe('tk-1') + expect(state.userId).toBe('u-100') + expect(state.userInfo).toEqual(userInfo) + expect(mem.get('auth_token')).toBe('tk-1') + }) + + it('setAuth 后 clearAuth 全部清空并移除持久化 token', () => { + let state = reducer(undefined, { type: 'init' }) + state = reducer(state, setAuth({ token: 'tk-1', userInfo })) + state = reducer(state, clearAuth()) + expect(state.token).toBeNull() + expect(state.userId).toBeNull() + expect(state.userInfo).toBeNull() + expect(mem.has('auth_token')).toBe(false) + }) + + it('初始态从 storage 恢复 token', () => { + mem.set('auth_token', 'tk-saved') + const state = reducer(undefined, { type: 'init' }) + expect(state.token).toBe('tk-saved') + expect(state.userInfo).toBeNull() + }) +})