Files
shopdb-flask/frontend/src/composables/mapConfig.spec.js

145 lines
5.2 KiB
JavaScript

// The invariant the whole map model rests on: a position without a level is not
// drawn on the default level.
//
// Nothing else catches this. Rewriting the composable left four components
// reading a property that no longer existed, which is `undefined` rather than a
// compile error - Vite built it happily and every marker would have been
// positioned at NaN percent. A build proves the code parses, not that a marker
// lands anywhere.
import { describe, it, expect, vi, beforeEach } from 'vitest'
const list = vi.fn()
vi.mock('../api', () => ({ mapLevelsApi: { list: (...args) => list(...args) } }))
const {
state, loadMapConfig, reloadMapConfig, blueprintUrlFor, dimensionsFor,
hasLevel, levelName, levelOptions, setCurrentLevel,
} = await import('./mapConfig')
// Two buildings, three levels, sized like the real before and after: the ground
// floor at 3300x2550 and a second floor at 3308x4000.
const PAYLOAD = {
data: {
data: {
buildings: [
{
buildingid: 1, buildingname: 'Main', levels: [
{ levelid: 1, levelname: 'Ground floor', sortorder: 0, mapwidth: 3300,
mapheight: 2550, blueprintlight: '/x/g-light.png',
blueprintdark: '/x/g-dark.png', isdefault: true },
{ levelid: 2, levelname: 'Second floor', sortorder: 1, mapwidth: 3308,
mapheight: 4000, blueprintlight: '/x/2-light.png',
blueprintdark: null, isdefault: false },
],
},
{
buildingid: 2, buildingname: 'Annex', levels: [
{ levelid: 3, levelname: 'Ground floor', sortorder: 0, mapwidth: 1200,
mapheight: 900, blueprintlight: '/x/a-light.png',
blueprintdark: '/x/a-dark.png', isdefault: false },
],
},
],
defaultlevelid: 1,
},
},
}
beforeEach(async () => {
list.mockReset()
list.mockResolvedValue(PAYLOAD)
state.loaded = false
state.currentlevelid = null
await reloadMapConfig()
})
describe('loading', () => {
it('indexes every level of every building and adopts the default', () => {
expect(Object.keys(state.levels)).toEqual(['1', '2', '3'])
expect(state.defaultlevelid).toBe(1)
expect(state.currentlevelid).toBe(1)
})
it('fetches once across concurrent callers', async () => {
list.mockClear()
state.loaded = false
await Promise.all([loadMapConfig(), loadMapConfig(), loadMapConfig()])
expect(list).toHaveBeenCalledTimes(1)
})
it('survives an unreachable server without hanging every map on the page', async () => {
list.mockReset()
list.mockRejectedValue(new Error('network'))
state.loaded = false
await reloadMapConfig()
expect(state.loaded).toBe(true)
})
})
describe('dimensions belong to the level, not the site', () => {
it('returns each level its own native size', () => {
expect(dimensionsFor(1)).toEqual({ width: 3300, height: 2550 })
expect(dimensionsFor(2)).toEqual({ width: 3308, height: 4000 })
expect(dimensionsFor(3)).toEqual({ width: 1200, height: 900 })
})
it('does not report one level size for another', () => {
// The bug this guards: a marker on level 2 measured against level 1's
// height renders at 2550/4000 of the way down - plausible, and wrong.
expect(dimensionsFor(2).height).not.toBe(dimensionsFor(1).height)
})
})
describe('a position without a level is not drawn', () => {
it('has no blueprint for a null level', () => {
expect(blueprintUrlFor('light', null)).toBeNull()
expect(hasLevel(null)).toBe(false)
})
it('has no blueprint for a level this instance does not know', () => {
expect(blueprintUrlFor('light', 99)).toBeNull()
expect(hasLevel(99)).toBe(false)
})
it('never substitutes the default level for a missing one', () => {
const groundfloor = blueprintUrlFor('light', 1)
expect(groundfloor).toContain('g-light.png')
// The failure mode: returning the default blueprint for an unknown level.
expect(blueprintUrlFor('light', null)).not.toBe(groundfloor)
expect(blueprintUrlFor('light', 99)).not.toBe(groundfloor)
})
})
describe('blueprints', () => {
it('serves the theme asked for', () => {
expect(blueprintUrlFor('light', 1)).toContain('g-light.png')
expect(blueprintUrlFor('dark', 1)).toContain('g-dark.png')
})
it('falls back to the other theme rather than showing nothing', () => {
// Level 2 has no dark blueprint. A hard-to-read floor plan beats no floor
// plan, and a site that uploaded one image should still work in both themes.
expect(blueprintUrlFor('dark', 2)).toContain('2-light.png')
})
})
describe('naming and selection', () => {
it('qualifies a level by building only when there is more than one', () => {
// Both buildings have a 'Ground floor', so the name alone is ambiguous.
expect(levelName(1)).toBe('Main / Ground floor')
expect(levelName(3)).toBe('Annex / Ground floor')
})
it('offers every level in building then level order', () => {
expect(levelOptions().map(option => option.levelid)).toEqual([1, 2, 3])
})
it('refuses to make an unknown level current', () => {
setCurrentLevel(99)
expect(state.currentlevelid).toBe(1)
setCurrentLevel(2)
expect(state.currentlevelid).toBe(2)
})
})