498 lines
23 KiB
TypeScript
498 lines
23 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
|
|
interface PoiCandidate {
|
|
poiId: string
|
|
floorId: string
|
|
name: string
|
|
primaryCategory: string
|
|
positionGltf: [number, number, number]
|
|
}
|
|
|
|
interface GuideDiagnostics {
|
|
isInitialModelReady: () => boolean
|
|
getFloors: () => Array<{ floorId: string; label: string }>
|
|
getReport: () => {
|
|
floorId: string
|
|
activeFocusPoiId: string
|
|
activeView: 'overview' | 'floor' | 'multi'
|
|
visibleVisualMarkerWithoutTextCount: number
|
|
}
|
|
resetToViewBaseline: (options: { view: 'floor'; floorId: string; reason: 'floor-reset' }) => Promise<unknown>
|
|
switchFloor: (floorId: string) => Promise<unknown>
|
|
showMultiFloor: () => Promise<unknown>
|
|
getFloorPois: (floorId: string) => Promise<PoiCandidate[]>
|
|
getVisiblePoiScreenPositions: () => Array<{
|
|
poiId: string
|
|
screen: { x: number; y: number }
|
|
}>
|
|
getPoiFocusState: (poiId?: string) => {
|
|
poiId: string
|
|
selectedPoiId: string
|
|
dataTier: 'fast' | 'full' | null
|
|
focusStartedDataTier: 'fast' | 'full' | null
|
|
markerCount: number
|
|
displayPositionCount: number
|
|
baseAffordanceCount: number
|
|
pulseAffordanceCount: number
|
|
glowAffordanceCount: number
|
|
modelHighlightRootCount: number
|
|
}
|
|
focusTargetPoi: (request: PoiCandidate & { requestId: string }) => Promise<unknown>
|
|
clearTargetFocus: () => void
|
|
}
|
|
|
|
const openGuide = async (page: Page) => {
|
|
await page.goto('/')
|
|
await page.waitForURL(/tab=guide/, { timeout: 30_000 })
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.isInitialModelReady() || false
|
|
)), { timeout: 180_000 }).toBe(true)
|
|
}
|
|
|
|
const resetToFloorWithLandmarkLabels = async (page: Page) => page.evaluate(async () => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
|
|
for (const floor of api.getFloors()) {
|
|
const pois = await api.getFloorPois(floor.floorId)
|
|
const hasLandmark = pois.some((poi) => poi.primaryCategory.startsWith('exhibition_hall'))
|
|
if (!hasLandmark) continue
|
|
|
|
await api.resetToViewBaseline({ view: 'floor', floorId: floor.floorId, reason: 'floor-reset' })
|
|
return floor.floorId
|
|
}
|
|
|
|
throw new Error('No floor contains landmark POIs')
|
|
})
|
|
|
|
const resetToB2WithDuplicatedBusinessName = async (page: Page) => page.evaluate(async () => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
|
|
const floor = api.getFloors().find((candidate) => candidate.label === 'B2')
|
|
if (floor) {
|
|
await api.resetToViewBaseline({ view: 'floor', floorId: floor.floorId, reason: 'floor-reset' })
|
|
return floor.floorId
|
|
}
|
|
|
|
throw new Error('B2 landmark floor is unavailable')
|
|
})
|
|
|
|
const resetToFloorByLabel = async (page: Page, label: string) => page.evaluate(async (floorLabel) => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
const floor = api.getFloors().find((candidate) => candidate.label === floorLabel)
|
|
if (!floor) throw new Error(`Floor ${floorLabel} is unavailable`)
|
|
|
|
await api.resetToViewBaseline({ view: 'floor', floorId: floor.floorId, reason: 'floor-reset' })
|
|
return floor.floorId
|
|
}, label)
|
|
|
|
test('uses the current backend name for the exterior Honghuatan Road label', async ({ page }) => {
|
|
await openGuide(page)
|
|
|
|
const roadLabel = page.locator(
|
|
'[data-poi-label-kind="ambient"][data-poi-id="overview-honghua-road"]:visible'
|
|
)
|
|
await expect(roadLabel).toBeVisible({ timeout: 30_000 })
|
|
await expect(roadLabel.locator('.three-poi-dom-label__title')).toHaveText('红花潭路')
|
|
await expect(roadLabel.locator('.three-poi-dom-label__icon')).toHaveAttribute('data-poi-icon', 'road')
|
|
})
|
|
|
|
for (const deviceScaleFactor of [1, 3]) {
|
|
test.describe(`POI DOM labels at DPR ${deviceScaleFactor}`, () => {
|
|
test.use({ viewport: { width: 390, height: 844 }, deviceScaleFactor })
|
|
|
|
test('renders measured native labels and clears focused labels without residue', async ({ page }, testInfo) => {
|
|
await openGuide(page)
|
|
const floorId = await resetToFloorWithLandmarkLabels(page)
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getPoiFocusState().dataTier || null
|
|
)), { timeout: 30_000 }).toBe('full')
|
|
// Exterior labels are scene-owned. Once an indoor floor commits, no
|
|
// exterior DOM label may survive an earlier asynchronous load.
|
|
await expect(page.locator('.three-poi-dom-label--overview')).toHaveCount(0)
|
|
const labels = page.locator('[data-poi-label-kind="ambient"]:visible')
|
|
await expect(labels.first()).toBeVisible({ timeout: 30_000 })
|
|
|
|
const markerPositions = new Map<string, { x: number; y: number }>()
|
|
;(await page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getVisiblePoiScreenPositions() || []
|
|
))).forEach((marker) => {
|
|
// A merged logical POI can have several physical markers, while its
|
|
// single DOM label is anchored to the first (canonical) marker.
|
|
if (!markerPositions.has(marker.poiId)) markerPositions.set(marker.poiId, marker.screen)
|
|
})
|
|
const labelCount = await labels.count()
|
|
await expect(labels.first().locator('.three-poi-dom-label__icon')).toBeVisible()
|
|
for (let index = 0; index < labelCount; index += 1) {
|
|
const label = labels.nth(index)
|
|
const poiId = await label.getAttribute('data-poi-id')
|
|
const bounds = await label.boundingBox()
|
|
const marker = poiId ? markerPositions.get(poiId) : null
|
|
if (!bounds || !marker) continue
|
|
|
|
// Only the one calibrated B2 pair may use a compact screen-only
|
|
// offset; all other labels keep their management-calibrated anchor.
|
|
expect(Math.abs(bounds.x + bounds.width / 2 - marker.x)).toBeLessThanOrEqual(44)
|
|
expect(Math.abs(bounds.y + bounds.height - marker.y)).toBeLessThanOrEqual(25)
|
|
await expect(page.locator(`[data-poi-leader-for="${poiId}"]`)).toHaveCount(0)
|
|
}
|
|
|
|
const hall = page.locator('[data-poi-label-kind="ambient"].three-poi-dom-label--landmark:visible').first()
|
|
await expect(hall).toBeVisible()
|
|
|
|
for (const locator of [hall]) {
|
|
await expect(locator).toHaveCSS('pointer-events', 'none')
|
|
const fontSize = await locator.evaluate((element) => Number.parseFloat(getComputedStyle(element).fontSize))
|
|
expect(fontSize).toBeGreaterThanOrEqual(12)
|
|
}
|
|
|
|
for (const locator of [hall]) {
|
|
const box = await locator.boundingBox()
|
|
expect(box).not.toBeNull()
|
|
expect(box!.x).toBeGreaterThanOrEqual(0)
|
|
expect(box!.y).toBeGreaterThanOrEqual(0)
|
|
expect(box!.x + box!.width).toBeLessThanOrEqual(390)
|
|
expect(box!.y + box!.height).toBeLessThanOrEqual(844)
|
|
}
|
|
|
|
const poiId = await hall.getAttribute('data-poi-id')
|
|
expect(poiId).toBeTruthy()
|
|
await page.evaluate(async ({ floorId: targetFloorId, targetPoiId }) => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
const poi = (await api.getFloorPois(targetFloorId)).find((candidate) => candidate.poiId === targetPoiId)
|
|
if (!poi) throw new Error(`missing POI ${targetPoiId}`)
|
|
await api.focusTargetPoi({ ...poi, requestId: `poi-dom-label-${targetPoiId}` })
|
|
}, { floorId, targetPoiId: poiId! })
|
|
|
|
const focus = page.locator(`[data-poi-label-kind="ambient"][data-poi-id="${poiId}"]`)
|
|
await expect(focus).toBeVisible({ timeout: 20_000 })
|
|
await expect(focus).toHaveCSS('pointer-events', 'none')
|
|
await expect(focus).toHaveClass(/three-poi-dom-label--selected/)
|
|
await expect(focus.locator('.three-poi-dom-label__title')).toHaveCSS('font-size', '12px')
|
|
await expect(focus.locator('.three-poi-dom-label__meta')).toHaveCount(0)
|
|
await page.screenshot({ path: testInfo.outputPath(`poi-dom-labels-dpr-${deviceScaleFactor}-focus.png`) })
|
|
|
|
await page.evaluate(() => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
api?.clearTargetFocus()
|
|
})
|
|
await expect(focus).not.toHaveClass(/three-poi-dom-label--selected/)
|
|
await page.screenshot({ path: testInfo.outputPath(`poi-dom-labels-dpr-${deviceScaleFactor}-ambient.png`) })
|
|
})
|
|
|
|
test('keeps the B2 landmark pair separate and merges duplicate business labels', async ({ page }, testInfo) => {
|
|
await openGuide(page)
|
|
await resetToB2WithDuplicatedBusinessName(page)
|
|
|
|
const vr = page.locator('[data-poi-label-kind="ambient"]', { hasText: 'XR虚拟现实影院' })
|
|
const youth = page.locator('[data-poi-label-kind="ambient"]', { hasText: '青少年探索中心' })
|
|
await expect(vr).toBeVisible({ timeout: 30_000 })
|
|
await expect(youth).toBeVisible({ timeout: 30_000 })
|
|
|
|
await expect.poll(async () => {
|
|
const vrBox = await vr.boundingBox()
|
|
const youthBox = await youth.boundingBox()
|
|
if (!vrBox || !youthBox) return null
|
|
return vrBox.x < youthBox.x + youthBox.width
|
|
&& vrBox.x + vrBox.width > youthBox.x
|
|
&& vrBox.y < youthBox.y + youthBox.height
|
|
&& vrBox.y + vrBox.height > youthBox.y
|
|
}, { timeout: 30_000 }).toBe(false)
|
|
|
|
await expect(page.locator('[data-poi-label-kind="ambient"]:visible', { hasText: '秦匠' })).toHaveCount(1)
|
|
|
|
const artisan = await page.evaluate(async () => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
const floorId = api.getReport().floorId
|
|
return (await api.getFloorPois(floorId)).find((poi) => poi.name === '秦匠') || null
|
|
})
|
|
expect(artisan).not.toBeNull()
|
|
await page.evaluate(async (request) => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api || !request) throw new Error('ThreeMap diagnostics unavailable')
|
|
await api.focusTargetPoi({ ...request, requestId: 'poi-dom-label-merged-business' })
|
|
}, artisan!)
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport().activeFocusPoiId || ''
|
|
))).toBe(artisan!.poiId)
|
|
|
|
await expect.poll(async () => page.evaluate((poiId) => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getPoiFocusState(poiId).dataTier || null
|
|
), artisan!.poiId), { timeout: 30_000 }).toBe('full')
|
|
|
|
const focusState = await page.evaluate((poiId) => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getPoiFocusState(poiId) || null
|
|
), artisan!.poiId)
|
|
expect(focusState).toMatchObject({
|
|
poiId: artisan!.poiId,
|
|
selectedPoiId: artisan!.poiId,
|
|
dataTier: 'full',
|
|
markerCount: 1,
|
|
displayPositionCount: 2,
|
|
baseAffordanceCount: 0,
|
|
pulseAffordanceCount: 0,
|
|
glowAffordanceCount: 0,
|
|
modelHighlightRootCount: 2
|
|
})
|
|
await page.screenshot({ path: testInfo.outputPath(`qinjiang-focus-dpr-${deviceScaleFactor}.png`) })
|
|
})
|
|
})
|
|
}
|
|
|
|
test('keeps floor ambient labels scene-owned across floor, multi, and floor transitions', async ({ page }) => {
|
|
test.setTimeout(240_000)
|
|
await openGuide(page)
|
|
const floorId = await resetToFloorWithLandmarkLabels(page)
|
|
const ambientLabels = page.locator('[data-poi-label-kind="ambient"]:visible')
|
|
await expect(ambientLabels.first()).toBeVisible({ timeout: 30_000 })
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport().visibleVisualMarkerWithoutTextCount ?? -1
|
|
))).toBe(0)
|
|
|
|
await page.evaluate(async () => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
await api.showMultiFloor()
|
|
})
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport().activeView || ''
|
|
)), { timeout: 120_000 }).toBe('multi')
|
|
await expect(ambientLabels).toHaveCount(0)
|
|
await page.waitForTimeout(1500)
|
|
await expect(ambientLabels).toHaveCount(0)
|
|
|
|
await page.evaluate(async (targetFloorId) => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
await api.resetToViewBaseline({ view: 'floor', floorId: targetFloorId, reason: 'floor-reset' })
|
|
}, floorId)
|
|
await expect(ambientLabels.first()).toBeVisible({ timeout: 30_000 })
|
|
const visibleFloorIds = await ambientLabels.evaluateAll((elements) => (
|
|
elements.map((element) => (element as HTMLElement).dataset.floorId || '')
|
|
))
|
|
expect(new Set(visibleFloorIds)).toEqual(new Set([floorId]))
|
|
})
|
|
|
|
test('shows the eleven fresh B2 default destinations after the map floor changes', async ({ page }) => {
|
|
test.setTimeout(240_000)
|
|
await openGuide(page)
|
|
const b2FloorId = await page.evaluate(async () => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
const floor = api.getFloors().find((candidate) => candidate.label === 'B2')
|
|
if (!floor) throw new Error('B2 is unavailable')
|
|
await api.switchFloor(floor.floorId)
|
|
return floor.floorId
|
|
})
|
|
await expect.poll(async () => page.evaluate(() => {
|
|
const report = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport()
|
|
return report ? `${report.activeView}:${report.floorId}` : ''
|
|
}), { timeout: 120_000 }).toBe(`floor:${b2FloorId}`)
|
|
|
|
await page.locator('.search-box').click()
|
|
await expect(page.locator('[data-testid="poi-result-list"] .result-row')).toHaveCount(11, {
|
|
timeout: 30_000
|
|
})
|
|
await expect(page.locator('.result-count')).toContainText('11 处地点')
|
|
})
|
|
|
|
test('merges the four numbered L1 ticket machines across labels, markers, and search', async ({ page }) => {
|
|
await openGuide(page)
|
|
const l1FloorId = await resetToFloorByLabel(page, '1F')
|
|
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getPoiFocusState('5682').dataTier || null
|
|
)), { timeout: 30_000 }).toBe('full')
|
|
|
|
await expect(page.locator('[data-poi-label-kind="ambient"][data-poi-id="5682"]:visible')).toHaveCount(1, {
|
|
timeout: 30_000
|
|
})
|
|
await expect(page.locator('[data-poi-label-kind="ambient"][data-poi-id="5591"]:visible')).toHaveCount(1)
|
|
await expect(page.locator(
|
|
'[data-poi-label-kind="ambient"][data-poi-id="352502409978861752"]:visible'
|
|
)).toHaveCount(1)
|
|
|
|
const l1Pois = await page.evaluate(async () => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
const floor = api.getFloors().find((candidate) => candidate.label === '1F')
|
|
if (!floor) throw new Error('1F is unavailable')
|
|
return api.getFloorPois(floor.floorId)
|
|
})
|
|
expect(l1Pois.find((poi) => poi.poiId === '5591')?.positionGltf).toEqual([
|
|
3.587204,
|
|
1.205524,
|
|
13.269173
|
|
])
|
|
|
|
const floorExits = [
|
|
['floor-exit-2077983437229297849', '展览入口'],
|
|
['floor-exit-2077983437229297850', '观众次入口'],
|
|
['floor-exit-2077983437229297851', '影剧院入口']
|
|
] as const
|
|
for (const [poiId, name] of floorExits) {
|
|
const exitPoi = l1Pois.find((poi) => poi.poiId === poiId)
|
|
expect(exitPoi).toMatchObject({ name, primaryCategory: 'navigation_anchor' })
|
|
}
|
|
const floorExitStates = await page.evaluate((poiIds) => {
|
|
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__
|
|
if (!api) throw new Error('ThreeMap diagnostics unavailable')
|
|
return poiIds.map((poiId) => api.getPoiFocusState(poiId))
|
|
}, floorExits.map(([poiId]) => poiId))
|
|
expect(floorExitStates).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ markerCount: 1, displayPositionCount: 1 }),
|
|
expect.objectContaining({ markerCount: 1, displayPositionCount: 1 }),
|
|
expect.objectContaining({ markerCount: 1, displayPositionCount: 1 })
|
|
]))
|
|
await expect(page.locator(
|
|
'[data-poi-label-kind="ambient"][data-poi-id="floor-exit-2077983437229297849"]:visible'
|
|
)).toHaveCount(1)
|
|
await expect(page.locator(
|
|
'[data-poi-label-kind="ambient"][data-poi-id="floor-exit-2077983437229297851"]:visible'
|
|
)).toHaveCount(1)
|
|
|
|
const markerState = await page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getPoiFocusState('5682') || null
|
|
))
|
|
expect(markerState).toMatchObject({
|
|
dataTier: 'full',
|
|
markerCount: 4,
|
|
displayPositionCount: 4
|
|
})
|
|
|
|
await page.getByTestId('poi-category-ticket-office').first().click()
|
|
await expect(page.getByTestId('poi-result-5682')).toHaveCount(1, { timeout: 30_000 })
|
|
await expect(page.getByTestId('poi-result-5683')).toHaveCount(0, { timeout: 30_000 })
|
|
await expect(page.getByTestId('poi-result-5684')).toHaveCount(0, { timeout: 30_000 })
|
|
await expect(page.getByTestId('poi-result-5685')).toHaveCount(0, { timeout: 30_000 })
|
|
await expect(page.getByTestId('poi-result-5591')).toHaveCount(1, { timeout: 30_000 })
|
|
await expect(page.getByTestId('poi-result-352502409978861752')).toHaveCount(1, { timeout: 30_000 })
|
|
await expect(page.getByTestId('poi-result-5682').locator('.result-name')).toHaveText('售票机')
|
|
await expect(page.getByTestId('poi-result-5682').locator('.result-meta')).toHaveText('1F · 售票处')
|
|
await expect(page.locator('.home-category-results-meta')).toHaveText('当前楼层 1F · 3 处地点')
|
|
await expect(page.locator('.floor-switcher')).toBeVisible()
|
|
await expect(page.locator('.clear-button')).toHaveCount(0)
|
|
await expect(page.getByTestId('poi-category-cancel')).toHaveCount(1)
|
|
|
|
await page.locator('canvas').hover()
|
|
await page.mouse.wheel(0, 3000)
|
|
await expect.poll(async () => page.evaluate(() => {
|
|
const report = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport()
|
|
return report ? `${report.activeView}:${report.floorId}` : ''
|
|
}), { timeout: 30_000 }).toBe(`floor:${l1FloorId}`)
|
|
await expect(page.locator('.floor-switcher')).toBeVisible()
|
|
await expect(page.locator('.home-category-results-meta')).toHaveText('当前楼层 1F · 3 处地点')
|
|
})
|
|
|
|
test('keeps the floor mode toggle light while committing multi and single views', async ({ page }) => {
|
|
await openGuide(page)
|
|
const l1FloorId = await resetToFloorByLabel(page, '1F')
|
|
const floorHeader = page.locator('.floor-header')
|
|
|
|
await page.getByTestId('poi-category-ticket-office').first().click()
|
|
await expect(page.getByTestId('poi-result-5682')).toBeVisible({ timeout: 30_000 })
|
|
await expect(floorHeader).toBeVisible({ timeout: 30_000 })
|
|
await expect(floorHeader.locator('.floor-header-label')).toHaveText('多层')
|
|
const singleLayout = await floorHeader.evaluate((element) => {
|
|
const icon = element.querySelector('.floor-header-icon')?.getBoundingClientRect()
|
|
const label = element.querySelector('.floor-header-label')?.getBoundingClientRect()
|
|
if (!icon || !label) return null
|
|
return {
|
|
iconBottom: icon.bottom,
|
|
labelTop: label.top,
|
|
iconCenter: icon.left + icon.width / 2,
|
|
labelCenter: label.left + label.width / 2
|
|
}
|
|
})
|
|
expect(singleLayout).not.toBeNull()
|
|
expect(singleLayout!.iconBottom).toBeLessThanOrEqual(singleLayout!.labelTop)
|
|
expect(Math.abs(singleLayout!.iconCenter - singleLayout!.labelCenter)).toBeLessThan(1)
|
|
await floorHeader.click()
|
|
|
|
await expect.poll(async () => page.evaluate(() => (
|
|
(window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport().activeView || ''
|
|
)), { timeout: 120_000 }).toBe('multi')
|
|
await expect(floorHeader).toHaveClass(/active/)
|
|
await expect(floorHeader.locator('.floor-header-label')).toHaveText('单层')
|
|
await expect(floorHeader).toHaveCSS('background-color', 'rgb(237, 241, 255)')
|
|
|
|
await page.getByTestId('poi-category-cancel').click()
|
|
await expect(page.locator('.home-category-results')).toHaveCount(0)
|
|
await expect(floorHeader).toBeVisible()
|
|
await floorHeader.click()
|
|
await expect.poll(async () => page.evaluate(() => {
|
|
const report = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: GuideDiagnostics })
|
|
.__GUIDE_3D_VISUAL_STABILITY__?.getReport()
|
|
return report ? `${report.activeView}:${report.floorId}` : ''
|
|
}), { timeout: 120_000 }).toBe(`floor:${l1FloorId}`)
|
|
await expect(floorHeader).not.toHaveClass(/active/)
|
|
await expect(floorHeader.locator('.floor-header-label')).toHaveText('多层')
|
|
await expect(floorHeader).toHaveCSS('background-color', 'rgb(255, 255, 255)')
|
|
})
|
|
|
|
test('uses one visual specification for landmark and service labels', async ({ page }) => {
|
|
await openGuide(page)
|
|
await resetToFloorByLabel(page, '1F')
|
|
|
|
await expect.poll(async () => page.locator(
|
|
'[data-poi-label-kind="ambient"]:visible'
|
|
).count(), { timeout: 30_000 }).toBeGreaterThan(3)
|
|
|
|
const metrics = await page.locator('[data-poi-label-kind="ambient"]:visible').evaluateAll((elements) => (
|
|
elements.map((element) => {
|
|
const icon = element.querySelector('.three-poi-dom-label__icon')
|
|
const style = window.getComputedStyle(element)
|
|
const iconStyle = icon ? window.getComputedStyle(icon) : null
|
|
return {
|
|
height: element.getBoundingClientRect().height,
|
|
fontSize: style.fontSize,
|
|
lineHeight: style.lineHeight,
|
|
iconWidth: iconStyle?.width || '',
|
|
iconHeight: iconStyle?.height || ''
|
|
}
|
|
})
|
|
))
|
|
|
|
expect(metrics).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ height: 22, fontSize: '12px', lineHeight: '15px', iconWidth: '16px', iconHeight: '16px' })
|
|
]))
|
|
for (const metric of metrics) {
|
|
expect(metric).toEqual({
|
|
height: 22,
|
|
fontSize: '12px',
|
|
lineHeight: '15px',
|
|
iconWidth: '16px',
|
|
iconHeight: '16px'
|
|
})
|
|
}
|
|
})
|