diff --git a/frontend/src/pages/products/__tests__/index.spec.ts b/frontend/src/pages/products/__tests__/index.spec.ts index 1136758..c62e22e 100644 --- a/frontend/src/pages/products/__tests__/index.spec.ts +++ b/frontend/src/pages/products/__tests__/index.spec.ts @@ -49,7 +49,7 @@ const mockProducts: ProductRecord[] = [ company_id: 2, platform_id: 1, store_id: 2, - status_id: 0, + status_id: 3, platform_item_id: 'TB002', platform_model_id: null, name: '测试商品B - 这是一个非常长的商品名称用于测试省略号效果', @@ -340,7 +340,8 @@ describe('ProductsPage', () => { const tags = wrapper.findAll('.ant-tag') const tagTexts = tags.map((t) => t.text()) - expect(tagTexts).toContain('上架') + // mockProducts: status_id=1 → 草稿, status_id=3 → 下架 + expect(tagTexts).toContain('草稿') expect(tagTexts).toContain('下架') }) @@ -451,6 +452,106 @@ describe('ProductsPage', () => { expect(drawerHtml).toContain('tb_001') }) + describe('status mapping coverage', () => { + const statusExpectations: Array<{ id: number; text: string; color: string }> = [ + { id: 1, text: '草稿', color: 'default' }, + { id: 2, text: '在售', color: 'green' }, + { id: 3, text: '下架', color: 'orange' }, + { id: 4, text: '封禁', color: 'red' }, + { id: 5, text: '审核中', color: 'blue' }, + { id: 6, text: '卖家删除', color: 'volcano' }, + { id: 7, text: '平台删除', color: 'volcano' }, + ] + + it.each(statusExpectations)( + 'renders status_id=$id as "$text"', + async ({ id, text }) => { + const { api } = await import('@/utils/request') + const singleProduct = { ...mockProducts[0], id: 100 + id, status_id: id } + vi.mocked(api.get).mockImplementation((url: string) => { + if (url === '/api/v1/products') + return Promise.resolve({ items: [singleProduct], total: 1, page: 1, per_page: 15 }) as never + if (url === '/api/v1/companies') return Promise.resolve(mockLookupCompanies) as never + if (url === '/api/v1/platforms') return Promise.resolve(mockLookupPlatforms) as never + if (url === '/api/v1/stores') return Promise.resolve(mockLookupStores) as never + return Promise.resolve([]) as never + }) + + const { default: ProductsPage } = await import('../index.vue') + wrapper = mount(ProductsPage, { + attachTo: document.body, + global: { + stubs: { + SearchOutlined: { template: '' }, + ReloadOutlined: { template: '' }, + EyeOutlined: { template: '' }, + CascadeFilter: { template: '
' }, + }, + }, + }) + await flushPromises() + await nextTick() + + const tags = wrapper.findAll('.ant-tag') + const tagTexts = tags.map((t) => t.text()) + expect(tagTexts).toContain(text) + }, + ) + + it('renders fallback for unknown status_id', async () => { + const { api } = await import('@/utils/request') + const unknownProduct = { ...mockProducts[0], id: 999, status_id: 99 } + vi.mocked(api.get).mockImplementation((url: string) => { + if (url === '/api/v1/products') + return Promise.resolve({ items: [unknownProduct], total: 1, page: 1, per_page: 15 }) as never + if (url === '/api/v1/companies') return Promise.resolve(mockLookupCompanies) as never + if (url === '/api/v1/platforms') return Promise.resolve(mockLookupPlatforms) as never + if (url === '/api/v1/stores') return Promise.resolve(mockLookupStores) as never + return Promise.resolve([]) as never + }) + + const { default: ProductsPage } = await import('../index.vue') + wrapper = mount(ProductsPage, { + attachTo: document.body, + global: { + stubs: { + SearchOutlined: { template: '' }, + ReloadOutlined: { template: '' }, + EyeOutlined: { template: '' }, + CascadeFilter: { template: '
' }, + }, + }, + }) + await flushPromises() + await nextTick() + + const tags = wrapper.findAll('.ant-tag') + const tagTexts = tags.map((t) => t.text()) + expect(tagTexts).toContain('状态 99') + }) + }) + + it('renders all 7 status filter options', async () => { + await mountPage() + + // Ant Design Vue 渲染 select options 到 body portal,需先打开下拉 + const selectEl = document.body.querySelector('.ant-select-selector') + selectEl?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })) + await flushPromises() + await nextTick() + + const optionEls = document.body.querySelectorAll('.ant-select-item-option') + const optionTexts = Array.from(optionEls).map((el) => el.textContent?.trim()) + expect(optionTexts).toContain('草稿') + expect(optionTexts).toContain('在售') + expect(optionTexts).toContain('下架') + expect(optionTexts).toContain('封禁') + expect(optionTexts).toContain('审核中') + expect(optionTexts).toContain('卖家删除') + expect(optionTexts).toContain('平台删除') + expect(optionTexts.length).toBe(7) + }) + it('shows placeholder when ext/raw is null', async () => { const { api } = await mountPage() vi.mocked(api.get) diff --git a/frontend/src/pages/products/index.vue b/frontend/src/pages/products/index.vue index 90abb45..dce4914 100644 --- a/frontend/src/pages/products/index.vue +++ b/frontend/src/pages/products/index.vue @@ -19,8 +19,13 @@ const rawDetail = ref(null) let detailRequestId = 0 const statusMap: Record = { - 1: { text: '上架', color: 'green' }, - 0: { text: '下架', color: 'red' }, + 1: { text: '草稿', color: 'default' }, + 2: { text: '在售', color: 'green' }, + 3: { text: '下架', color: 'orange' }, + 4: { text: '封禁', color: 'red' }, + 5: { text: '审核中', color: 'blue' }, + 6: { text: '卖家删除', color: 'volcano' }, + 7: { text: '平台删除', color: 'volcano' }, } const columns = [ @@ -133,8 +138,13 @@ async function handleViewDetail(record: { id: number }) { allow-clear style="width: 120px" > - 上架 - 下架 + 草稿 + 在售 + 下架 + 封禁 + 审核中 + 卖家删除 + 平台删除