import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { materialsApi } from '../api/materials' import { useMaterialsStore } from './materials' vi.mock('../api/materials', () => ({ materialsApi: { getAll: vi.fn(), create: vi.fn(), update: vi.fn(), obsolete: vi.fn(), restore: vi.fn() } })) describe('materials store selection options', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() }) it('loads every page so a selector can find materials outside the first page', async () => { const firstPage = Array.from({ length: 100 }, (_, index) => ({ id: 120 - index, material_code: String(120 - index).padStart(3, '0'), name: `物料 ${120 - index}` })) materialsApi.getAll .mockResolvedValueOnce({ data: { items: firstPage, limit: 100, offset: 0, total: 101, has_more: true } }) .mockResolvedValueOnce({ data: { items: [{ id: 1, material_code: '001', name: '早期物料' }], limit: 100, offset: 100, total: 101, has_more: false } }) const store = useMaterialsStore() await store.fetchMaterialOptions() expect(store.materialOptions).toHaveLength(101) expect(store.materialOptions.at(-1)).toEqual({ id: 1, material_code: '001', name: '早期物料' }) }) it('loads obsolete materials for historical record selectors only', async () => { materialsApi.getAll.mockResolvedValue({ data: { items: [ { id: 1, material_code: 'A-001', name: '有效物料', status: 'active' }, { id: 2, material_code: 'B-002', name: '历史物料', status: 'obsolete' } ], total: 2 } }) const store = useMaterialsStore() await store.fetchHistoricalMaterialOptions() expect(materialsApi.getAll).toHaveBeenCalledWith({ status: 'all', limit: 100, offset: 0 }) expect(store.historicalMaterialOptions).toEqual([ { id: 1, material_code: 'A-001', name: '有效物料', status: 'active' }, { id: 2, material_code: 'B-002', name: '历史物料', status: 'obsolete' } ]) expect(store.materialOptions).toEqual([]) }) }) describe('materials store 的筛选保持', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() materialsApi.getAll.mockResolvedValue({ data: { items: [], total: 0 } }) materialsApi.create.mockResolvedValue({ data: {} }) materialsApi.update.mockResolvedValue({ data: {} }) materialsApi.obsolete.mockResolvedValue({ data: {} }) materialsApi.restore.mockResolvedValue({ data: {} }) }) // 编辑/作废/恢复都在原地重新拉取列表,那次拉取必须沿用搜索框里的关键字:否则列表 // 会悄悄退回全量,而搜索框里还显示着用户输入的内容——用户会把全量列表当成筛选结果, // 接着的作废操作就打在误认的那一条上。 it('编辑物料后刷新列表要沿用当前的搜索关键字', async () => { const store = useMaterialsStore() await store.fetchMaterials(2, '螺丝') materialsApi.getAll.mockClear() await store.updateMaterial(7, { name: '内六角螺丝' }) expect(materialsApi.getAll).toHaveBeenCalledWith({ search: '螺丝', status: 'all', limit: 20, offset: 20 }) }) // 作废和恢复除了列表还要刷两份选项,所以 getAll 会被调用多次,只看第一次(列表那次)。 it('作废物料后刷新列表要沿用当前的搜索关键字', async () => { const store = useMaterialsStore() await store.fetchMaterials(1, '螺丝') materialsApi.getAll.mockClear() await store.obsoleteMaterial(7) expect(materialsApi.getAll).toHaveBeenNthCalledWith(1, { search: '螺丝', status: 'all', limit: 20, offset: 0 }) }) it('恢复物料后刷新列表要沿用当前的搜索关键字', async () => { const store = useMaterialsStore() await store.fetchMaterials(1, '螺丝') materialsApi.getAll.mockClear() await store.restoreMaterial(7) expect(materialsApi.getAll).toHaveBeenNthCalledWith(1, { search: '螺丝', status: 'all', limit: 20, offset: 0 }) }) // 状态筛选目前还没有页面在用,但 store 的接口本来就有这一维,原地刷新时漏掉它会 // 埋下同一个坑:以后页面加上状态下拉,列表又会悄悄退回全量。 it('刷新时一并沿用当前的状态筛选', async () => { const store = useMaterialsStore() await store.fetchMaterials(1, '', 'obsolete') materialsApi.getAll.mockClear() await store.updateMaterial(7, { name: '内六角螺丝' }) expect(materialsApi.getAll).toHaveBeenCalledWith({ search: '', status: 'obsolete', limit: 20, offset: 0 }) }) // 这处刻意与上面相反:新建的物料不一定匹配当前关键字,沿用搜索就看不见它,用户会 // 以为没建成功。列表按 id 倒序,回到第 1 页正好能看见它。 it('新建物料后要清空筛选并回到第 1 页', async () => { const store = useMaterialsStore() await store.fetchMaterials(3, '螺丝', 'active') materialsApi.getAll.mockClear() await store.createMaterial({ name: '新物料' }) expect(materialsApi.getAll).toHaveBeenCalledWith({ search: '', status: 'all', limit: 20, offset: 0 }) }) // 首次加载没有筛选条件,别把上一次的筛选状态带到新页面上。 it('不带筛选条件地重新查询会清掉旧的筛选状态', async () => { const store = useMaterialsStore() await store.fetchMaterials(2, '螺丝', 'obsolete') materialsApi.getAll.mockClear() await store.fetchMaterials() expect(materialsApi.getAll).toHaveBeenCalledWith({ search: '', status: 'all', limit: 20, offset: 0 }) }) })