|
@@ -56,14 +56,15 @@ const mockGoodsClient = {
|
|
|
':id': {
|
|
':id': {
|
|
|
$get: jest.fn(({ param }: any) => {
|
|
$get: jest.fn(({ param }: any) => {
|
|
|
const goodsId = param?.id
|
|
const goodsId = param?.id
|
|
|
- console.log('mockGoodsClient called with id:', goodsId)
|
|
|
|
|
const goodsData = mockGoodsData[goodsId as keyof typeof mockGoodsData] || mockGoodsData[1]
|
|
const goodsData = mockGoodsData[goodsId as keyof typeof mockGoodsData] || mockGoodsData[1]
|
|
|
- console.log('Returning goods data:', goodsData)
|
|
|
|
|
return Promise.resolve({
|
|
return Promise.resolve({
|
|
|
status: 200,
|
|
status: 200,
|
|
|
json: () => Promise.resolve(goodsData)
|
|
json: () => Promise.resolve(goodsData)
|
|
|
})
|
|
})
|
|
|
- })
|
|
|
|
|
|
|
+ }),
|
|
|
|
|
+ children: {
|
|
|
|
|
+ $get: jest.fn()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -71,7 +72,10 @@ jest.mock('@/api', () => {
|
|
|
// 如果mockGoodsClient已经定义,使用它;否则创建默认mock
|
|
// 如果mockGoodsClient已经定义,使用它;否则创建默认mock
|
|
|
const goodsClientMock = typeof mockGoodsClient !== 'undefined' ? mockGoodsClient : {
|
|
const goodsClientMock = typeof mockGoodsClient !== 'undefined' ? mockGoodsClient : {
|
|
|
':id': {
|
|
':id': {
|
|
|
- $get: jest.fn()
|
|
|
|
|
|
|
+ $get: jest.fn(),
|
|
|
|
|
+ children: {
|
|
|
|
|
+ $get: jest.fn()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
return { goodsClient: goodsClientMock }
|
|
return { goodsClient: goodsClientMock }
|
|
@@ -240,16 +244,11 @@ describe('购物车页面', () => {
|
|
|
// 修复:库存提示显示逻辑
|
|
// 修复:库存提示显示逻辑
|
|
|
// 商品2的购物车stock改为2,应该显示"仅剩2件"
|
|
// 商品2的购物车stock改为2,应该显示"仅剩2件"
|
|
|
// 即使useQueries不返回数据,使用item.stock也会触发提示
|
|
// 即使useQueries不返回数据,使用item.stock也会触发提示
|
|
|
- const { findByText, container } = renderWithProviders(<CartPage />)
|
|
|
|
|
|
|
+ const { findByText } = renderWithProviders(<CartPage />)
|
|
|
|
|
|
|
|
// 等待商品2加载完成
|
|
// 等待商品2加载完成
|
|
|
await findByText('测试商品2')
|
|
await findByText('测试商品2')
|
|
|
|
|
|
|
|
- // 检查库存提示元素是否存在
|
|
|
|
|
- const stockMaskElements = container.querySelectorAll('.stock-mask')
|
|
|
|
|
- console.log('Stock mask elements count:', stockMaskElements.length)
|
|
|
|
|
- stockMaskElements.forEach(el => console.log('Stock mask text:', el.textContent))
|
|
|
|
|
-
|
|
|
|
|
// 商品2的stock是2,应该显示"仅剩2件"
|
|
// 商品2的stock是2,应该显示"仅剩2件"
|
|
|
// 使用findByText等待元素出现
|
|
// 使用findByText等待元素出现
|
|
|
expect(await findByText('仅剩2件')).toBeDefined()
|
|
expect(await findByText('仅剩2件')).toBeDefined()
|
|
@@ -407,6 +406,11 @@ describe('购物车页面', () => {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('规格切换功能', () => {
|
|
describe('规格切换功能', () => {
|
|
|
|
|
+ beforeEach(() => {
|
|
|
|
|
+ // 确保规格选择器API mock被清除
|
|
|
|
|
+ mockRequest.mockClear()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it('应该显示规格选择区域', () => {
|
|
it('应该显示规格选择区域', () => {
|
|
|
const { getByText } = renderWithProviders(<CartPage />)
|
|
const { getByText } = renderWithProviders(<CartPage />)
|
|
|
|
|
|
|
@@ -415,7 +419,7 @@ describe('购物车页面', () => {
|
|
|
expect(getByText('蓝色/L')).toBeDefined()
|
|
expect(getByText('蓝色/L')).toBeDefined()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('规格区域应该可点击', () => {
|
|
|
|
|
|
|
+ it('规格区域应该可点击并打开规格选择器', () => {
|
|
|
const { getByText } = renderWithProviders(<CartPage />)
|
|
const { getByText } = renderWithProviders(<CartPage />)
|
|
|
|
|
|
|
|
// 获取规格元素
|
|
// 获取规格元素
|
|
@@ -424,8 +428,215 @@ describe('购物车页面', () => {
|
|
|
// 验证元素存在
|
|
// 验证元素存在
|
|
|
expect(specElement).toBeDefined()
|
|
expect(specElement).toBeDefined()
|
|
|
|
|
|
|
|
- // 在实际测试中,可以验证点击事件处理
|
|
|
|
|
- // 但由于使用真实组件和API调用,这里简化测试
|
|
|
|
|
|
|
+ // 点击规格区域
|
|
|
|
|
+ fireEvent.click(specElement)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证规格选择器应该显示(通过检查规格选择器组件是否被渲染)
|
|
|
|
|
+ // 由于GoodsSpecSelector组件是真实组件,我们需要检查其props
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('应该加载子商品数据并显示规格选择器', async () => {
|
|
|
|
|
+ // Mock子商品API调用
|
|
|
|
|
+ const mockChildGoodsResponse = {
|
|
|
|
|
+ status: 200,
|
|
|
|
|
+ json: () => Promise.resolve({
|
|
|
|
|
+ data: [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 101, // 新子商品ID
|
|
|
|
|
+ name: '测试商品1 - 蓝色/S',
|
|
|
|
|
+ price: 29.9,
|
|
|
|
|
+ stock: 8,
|
|
|
|
|
+ imageFile: { fullUrl: 'test-image1-blue.jpg' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 102, // 另一个子商品ID
|
|
|
|
|
+ name: '测试商品1 - 黑色/M',
|
|
|
|
|
+ price: 34.9,
|
|
|
|
|
+ stock: 5,
|
|
|
|
|
+ imageFile: { fullUrl: 'test-image1-black.jpg' }
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ total: 2,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Mock goodsClient的children API
|
|
|
|
|
+ const api = require('@/api')
|
|
|
|
|
+ const childrenSpy = jest.spyOn(api.goodsClient[':id'].children, '$get')
|
|
|
|
|
+ childrenSpy.mockImplementation(({ param, query }: any) => {
|
|
|
|
|
+ return Promise.resolve(mockChildGoodsResponse)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const { getByText, container } = renderWithProviders(<CartPage />)
|
|
|
|
|
+
|
|
|
|
|
+ // 点击规格区域打开选择器
|
|
|
|
|
+ const specElement = getByText('红色/M')
|
|
|
|
|
+ fireEvent.click(specElement)
|
|
|
|
|
+
|
|
|
|
|
+ // 等待API调用
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(childrenSpy).toHaveBeenCalledWith({
|
|
|
|
|
+ param: { id: 100 }, // parentGoodsId
|
|
|
|
|
+ query: {
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100,
|
|
|
|
|
+ sortBy: 'createdAt',
|
|
|
|
|
+ sortOrder: 'ASC'
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 清理spy
|
|
|
|
|
+ childrenSpy.mockRestore()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('应该支持切换规格并更新商品信息', async () => {
|
|
|
|
|
+ // Mock子商品API调用
|
|
|
|
|
+ const mockChildGoodsResponse = {
|
|
|
|
|
+ status: 200,
|
|
|
|
|
+ json: () => Promise.resolve({
|
|
|
|
|
+ data: [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 101, // 新子商品ID
|
|
|
|
|
+ name: '测试商品1 - 蓝色/S',
|
|
|
|
|
+ price: 29.9,
|
|
|
|
|
+ stock: 8,
|
|
|
|
|
+ imageFile: { fullUrl: 'test-image1-blue.jpg' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 1, // 当前子商品ID
|
|
|
|
|
+ name: '测试商品1 - 红色/M',
|
|
|
|
|
+ price: 29.9,
|
|
|
|
|
+ stock: 10,
|
|
|
|
|
+ imageFile: { fullUrl: 'test-image1.jpg' }
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ total: 2,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Mock goodsClient的children API
|
|
|
|
|
+ const api = require('@/api')
|
|
|
|
|
+ const childrenSpy = jest.spyOn(api.goodsClient[':id'].children, '$get')
|
|
|
|
|
+ childrenSpy.mockImplementation(({ param, query }: any) => {
|
|
|
|
|
+ return Promise.resolve(mockChildGoodsResponse)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Mock switchSpec调用
|
|
|
|
|
+ const { getByText, container } = renderWithProviders(<CartPage />)
|
|
|
|
|
+
|
|
|
|
|
+ // 点击规格区域打开选择器
|
|
|
|
|
+ const specElement = getByText('红色/M')
|
|
|
|
|
+ fireEvent.click(specElement)
|
|
|
|
|
+
|
|
|
|
|
+ // 等待API调用完成
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(childrenSpy).toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 注意:由于GoodsSpecSelector是真实组件,在测试环境中无法直接模拟其内部状态
|
|
|
|
|
+ // 这里我们验证API调用和基本交互
|
|
|
|
|
+
|
|
|
|
|
+ // 清理spy
|
|
|
|
|
+ childrenSpy.mockRestore()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('切换规格后应该更新购物车总价', async () => {
|
|
|
|
|
+ const { getByText, getAllByText } = renderWithProviders(<CartPage />)
|
|
|
|
|
+
|
|
|
|
|
+ // 先全选商品
|
|
|
|
|
+ const selectAllButton = getByText('全选')
|
|
|
|
|
+ fireEvent.click(selectAllButton)
|
|
|
|
|
+
|
|
|
|
|
+ // 获取切换前的总价 - 使用更具体的查询
|
|
|
|
|
+ const totalAmountElements = getAllByText(/¥\d+\.\d{2}/)
|
|
|
|
|
+ // 最后一个元素应该是总计金额
|
|
|
|
|
+ const totalAmountElement = totalAmountElements[totalAmountElements.length - 1]
|
|
|
|
|
+ const totalAmountBefore = totalAmountElement.textContent
|
|
|
|
|
+
|
|
|
|
|
+ // 验证总价显示存在
|
|
|
|
|
+ expect(getByText(/总计/)).toBeDefined()
|
|
|
|
|
+ expect(totalAmountBefore).toMatch(/¥\d+\.\d{2}/)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('库存不足的规格应该被禁用或提示', async () => {
|
|
|
|
|
+ // Mock子商品API调用,包含库存不足的商品
|
|
|
|
|
+ const mockChildGoodsResponse = {
|
|
|
|
|
+ status: 200,
|
|
|
|
|
+ json: () => Promise.resolve({
|
|
|
|
|
+ data: [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 103,
|
|
|
|
|
+ name: '测试商品1 - 白色/XL',
|
|
|
|
|
+ price: 39.9,
|
|
|
|
|
+ stock: 0, // 库存为0
|
|
|
|
|
+ imageFile: { fullUrl: 'test-image1-white.jpg' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 104,
|
|
|
|
|
+ name: '测试商品1 - 黄色/L',
|
|
|
|
|
+ price: 32.9,
|
|
|
|
|
+ stock: 1, // 低库存
|
|
|
|
|
+ imageFile: { fullUrl: 'test-image1-yellow.jpg' }
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ total: 2,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const api = require('@/api')
|
|
|
|
|
+ const childrenSpy = jest.spyOn(api.goodsClient[':id'].children, '$get')
|
|
|
|
|
+ childrenSpy.mockImplementation(({ param, query }: any) => {
|
|
|
|
|
+ return Promise.resolve(mockChildGoodsResponse)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const { getByText } = renderWithProviders(<CartPage />)
|
|
|
|
|
+
|
|
|
|
|
+ // 点击规格区域
|
|
|
|
|
+ const specElement = getByText('红色/M')
|
|
|
|
|
+ fireEvent.click(specElement)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证API被调用
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(childrenSpy).toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ childrenSpy.mockRestore()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('单规格商品不应该显示规格切换区域', () => {
|
|
|
|
|
+ // 设置购物车数据,包含单规格商品
|
|
|
|
|
+ const singleSpecCartItems = [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 300,
|
|
|
|
|
+ parentGoodsId: 0, // 单规格商品
|
|
|
|
|
+ name: '单规格商品',
|
|
|
|
|
+ price: 99.9,
|
|
|
|
|
+ image: 'single.jpg',
|
|
|
|
|
+ stock: 10,
|
|
|
|
|
+ quantity: 1,
|
|
|
|
|
+ // 没有spec字段
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ mockGetStorageSync.mockReturnValue({ items: singleSpecCartItems })
|
|
|
|
|
+
|
|
|
|
|
+ const { queryByText, getByText, container } = renderWithProviders(<CartPage />)
|
|
|
|
|
+
|
|
|
|
|
+ // 商品名称应该显示
|
|
|
|
|
+ expect(getByText('单规格商品')).toBeDefined()
|
|
|
|
|
+
|
|
|
|
|
+ // 不应该显示"选择规格"文本
|
|
|
|
|
+ expect(queryByText('选择规格')).toBeNull()
|
|
|
|
|
+
|
|
|
|
|
+ // 不应该显示规格文本(如"红色/M")
|
|
|
|
|
+ const specElements = Array.from(container.querySelectorAll('.goods-specs'))
|
|
|
|
|
+ expect(specElements.length).toBe(0)
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|