整改点位搜索数据加载与分类逻辑
This commit is contained in:
194
tests/unit/GuideUseCase.spec.ts
Normal file
194
tests/unit/GuideUseCase.spec.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { MuseumFloor, MuseumPoi } from '@/domain/museum'
|
||||
import type { PoiCategoryId } from '@/domain/poiCategories'
|
||||
import type { GuideRepository } from '@/repositories/GuideRepository'
|
||||
import type { GuideModelRepository } from '@/repositories/GuideModelRepository'
|
||||
import { GuideUseCase } from '@/usecases/guideUseCase'
|
||||
|
||||
const floors: MuseumFloor[] = [
|
||||
{ id: 'L1', label: '1F', order: 1 },
|
||||
{ id: 'L2', label: '2F', order: 2 }
|
||||
]
|
||||
|
||||
const quickCategoryIds: readonly PoiCategoryId[] = [
|
||||
'exhibition-hall',
|
||||
'cinema',
|
||||
'ticket-office',
|
||||
'dining',
|
||||
'shopping',
|
||||
'service-center',
|
||||
'restroom',
|
||||
'nursing-room',
|
||||
'elevator',
|
||||
'escalator'
|
||||
]
|
||||
|
||||
const createPoi = (id: string, floorId: string, categoryId: string): MuseumPoi => ({
|
||||
id,
|
||||
name: id,
|
||||
floorId,
|
||||
floorLabel: floorId === 'L2' ? '2F' : '1F',
|
||||
primaryCategory: {
|
||||
id: categoryId,
|
||||
label: categoryId,
|
||||
iconType: categoryId
|
||||
},
|
||||
categories: [],
|
||||
accessible: true
|
||||
})
|
||||
|
||||
const destinationsByFloor: Record<string, MuseumPoi[]> = {
|
||||
L1: [createPoi('space-l1-hall', 'L1', 'exhibition_hall')],
|
||||
L2: [createPoi('space-l2-hall', 'L2', 'exhibition_hall')]
|
||||
}
|
||||
|
||||
const quickResultsByFloor: Record<string, Partial<Record<PoiCategoryId, MuseumPoi[]>>> = {
|
||||
L1: {
|
||||
restroom: [createPoi('facility-l1-restroom', 'L1', 'toilet')]
|
||||
},
|
||||
L2: {
|
||||
restroom: [createPoi('facility-l2-restroom', 'L2', 'toilet')]
|
||||
}
|
||||
}
|
||||
|
||||
const keywordResultsByFloor: Record<string, MuseumPoi[]> = {
|
||||
L1: [createPoi('facility-l1-shop', 'L1', 'shop')],
|
||||
L2: [createPoi('facility-l2-shop', 'L2', 'shop')]
|
||||
}
|
||||
|
||||
const availability = quickCategoryIds.map((id, index) => ({
|
||||
id,
|
||||
count: index === quickCategoryIds.indexOf('restroom') ? 1 : 0,
|
||||
disabled: id !== 'restroom'
|
||||
}))
|
||||
|
||||
const createRepository = () => ({
|
||||
getAssetBaseUrl: vi.fn(() => ''),
|
||||
getFloors: vi.fn().mockResolvedValue(floors),
|
||||
normalizeFloorId: vi.fn((floorId: string) => ({
|
||||
'1F': 'L1',
|
||||
'2F': 'L2'
|
||||
}[floorId] || floorId)),
|
||||
listPois: vi.fn().mockResolvedValue([]),
|
||||
listSpacePoints: vi.fn().mockResolvedValue([]),
|
||||
listDestinationPois: vi.fn((floorId?: string) => (
|
||||
Promise.resolve(destinationsByFloor[floorId || 'L1'] || [])
|
||||
)),
|
||||
listQuickFindPois: vi.fn((categoryId: PoiCategoryId, floorId?: string) => (
|
||||
Promise.resolve(quickResultsByFloor[floorId || 'L1']?.[categoryId] || [])
|
||||
)),
|
||||
getQuickFindCategoryAvailability: vi.fn().mockResolvedValue(availability),
|
||||
getPoiById: vi.fn().mockResolvedValue(null),
|
||||
searchPois: vi.fn((keyword?: string, floorId?: string) => (
|
||||
Promise.resolve(keyword ? keywordResultsByFloor[floorId || 'L1'] || [] : [])
|
||||
)),
|
||||
getLocationPreview: vi.fn().mockResolvedValue(null),
|
||||
getRouteReadiness: vi.fn().mockResolvedValue({})
|
||||
})
|
||||
|
||||
const expectMapListParity = (state: { results: MuseumPoi[], visiblePoiIds: string[] }) => {
|
||||
expect(state.visiblePoiIds).toEqual(state.results.map((poi) => poi.id))
|
||||
}
|
||||
|
||||
describe('GuideUseCase point search state', () => {
|
||||
it('loads initial destinations through listDestinationPois and never searchPois', async () => {
|
||||
const repository = createRepository()
|
||||
const useCase = new GuideUseCase(
|
||||
repository as unknown as GuideRepository,
|
||||
{} as GuideModelRepository
|
||||
)
|
||||
|
||||
await expect(useCase.getInitialSearchSpacePoints('L2'))
|
||||
.resolves.toEqual(destinationsByFloor.L2)
|
||||
|
||||
expect(repository.listDestinationPois).toHaveBeenCalledWith('L2')
|
||||
expect(repository.searchPois).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('creates the default state from current-floor destinations and relays all ten availability entries', async () => {
|
||||
const repository = createRepository()
|
||||
const useCase = new GuideUseCase(
|
||||
repository as unknown as GuideRepository,
|
||||
{} as GuideModelRepository
|
||||
)
|
||||
|
||||
const state = await useCase.createInitialPoiSearchState('L1')
|
||||
|
||||
expect(state).toMatchObject({
|
||||
mode: 'default',
|
||||
floorId: 'L1',
|
||||
floorLabel: '1F',
|
||||
keyword: '',
|
||||
categoryId: ''
|
||||
})
|
||||
expect(state.results).toEqual(destinationsByFloor.L1)
|
||||
expectMapListParity(state)
|
||||
expect(state.categories.map((item) => ({
|
||||
id: item.definition.id,
|
||||
count: item.count,
|
||||
disabled: item.disabled
|
||||
}))).toEqual(availability)
|
||||
expect(repository.getQuickFindCategoryAvailability).toHaveBeenCalledWith('L1')
|
||||
expect(repository.listDestinationPois).toHaveBeenCalledWith('L1')
|
||||
expect(repository.searchPois).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps category, keyword, clear, and floor-change states on canonical result IDs', async () => {
|
||||
const repository = createRepository()
|
||||
const useCase = new GuideUseCase(
|
||||
repository as unknown as GuideRepository,
|
||||
{} as GuideModelRepository
|
||||
)
|
||||
|
||||
const initial = await useCase.createInitialPoiSearchState('L1')
|
||||
const category = await useCase.selectPoiSearchCategory(initial, 'restroom')
|
||||
|
||||
expect(category).toMatchObject({
|
||||
mode: 'category',
|
||||
floorId: 'L1',
|
||||
categoryId: 'restroom'
|
||||
})
|
||||
expect(category.results).toEqual(quickResultsByFloor.L1.restroom)
|
||||
expectMapListParity(category)
|
||||
expect(repository.listQuickFindPois).toHaveBeenCalledWith('restroom', 'L1')
|
||||
expect(repository.searchPois).not.toHaveBeenCalled()
|
||||
|
||||
const keyword = await useCase.searchPoiKeyword(category, 'shop')
|
||||
|
||||
expect(keyword).toMatchObject({
|
||||
mode: 'keyword',
|
||||
floorId: 'L1',
|
||||
keyword: 'shop',
|
||||
categoryId: ''
|
||||
})
|
||||
expect(keyword.results).toEqual(keywordResultsByFloor.L1)
|
||||
expectMapListParity(keyword)
|
||||
expect(repository.searchPois).toHaveBeenLastCalledWith('shop', 'L1')
|
||||
|
||||
const switchedFloor = await useCase.changePoiSearchFloor(keyword, 'L2')
|
||||
|
||||
expect(switchedFloor).toMatchObject({
|
||||
mode: 'keyword',
|
||||
floorId: 'L2',
|
||||
floorLabel: '2F',
|
||||
keyword: 'shop',
|
||||
categoryId: ''
|
||||
})
|
||||
expect(switchedFloor.results).toEqual(keywordResultsByFloor.L2)
|
||||
expectMapListParity(switchedFloor)
|
||||
expect(repository.searchPois).toHaveBeenLastCalledWith('shop', 'L2')
|
||||
|
||||
const cleared = await useCase.clearPoiSearch(switchedFloor)
|
||||
|
||||
expect(cleared).toMatchObject({
|
||||
mode: 'default',
|
||||
floorId: 'L2',
|
||||
floorLabel: '2F',
|
||||
keyword: '',
|
||||
categoryId: ''
|
||||
})
|
||||
expect(cleared.results).toEqual(destinationsByFloor.L2)
|
||||
expectMapListParity(cleared)
|
||||
expect(repository.listDestinationPois).toHaveBeenLastCalledWith('L2')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user