Files
frontend-miniapp/tests/unit/GuideUseCase.spec.ts

195 lines
6.3 KiB
TypeScript

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')
})
})