- Updated .dockerignore to streamline build context by ensuring unnecessary files are excluded. - Refined CSS styles in leaflet-overrides.css to enhance visual consistency and user experience for map tooltips and popups. - Improved map initialization and update handling in useMapApi and useMapUpdates composables for better performance and reliability.
188 lines
5.9 KiB
TypeScript
188 lines
5.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
import type L from 'leaflet'
|
|
import type { Map, LayerGroup } from 'leaflet'
|
|
import { createCharacter, type CharacterData, type CharacterMapViewRef } from '../Character'
|
|
|
|
const { leafletMock } = vi.hoisted(() => {
|
|
const markerMock = {
|
|
on: vi.fn().mockReturnThis(),
|
|
addTo: vi.fn().mockReturnThis(),
|
|
setLatLng: vi.fn().mockReturnThis(),
|
|
setIcon: vi.fn().mockReturnThis(),
|
|
bindTooltip: vi.fn().mockReturnThis(),
|
|
setTooltipContent: vi.fn().mockReturnThis(),
|
|
getLatLng: vi.fn().mockReturnValue({ lat: 0, lng: 0 }),
|
|
}
|
|
const Icon = vi.fn().mockImplementation(function (this: unknown) {
|
|
return {}
|
|
})
|
|
const L = {
|
|
marker: vi.fn(() => markerMock),
|
|
Icon,
|
|
point: vi.fn((x: number, y: number) => ({ x, y })),
|
|
}
|
|
return { leafletMock: L }
|
|
})
|
|
|
|
vi.mock('leaflet', () => ({
|
|
__esModule: true,
|
|
default: leafletMock,
|
|
marker: leafletMock.marker,
|
|
Icon: leafletMock.Icon,
|
|
}))
|
|
|
|
vi.mock('~/lib/LeafletCustomTypes', () => ({
|
|
HnHMaxZoom: 6,
|
|
TileSize: 100,
|
|
}))
|
|
|
|
function getL(): L {
|
|
return leafletMock as unknown as L
|
|
}
|
|
|
|
function makeCharData(overrides: Partial<CharacterData> = {}): CharacterData {
|
|
return {
|
|
name: 'Hero',
|
|
position: { x: 100, y: 200 },
|
|
type: 'player',
|
|
id: 1,
|
|
map: 1,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function makeMapViewRef(mapid = 1): CharacterMapViewRef {
|
|
return {
|
|
map: {
|
|
unproject: vi.fn(() => ({ lat: 0, lng: 0 })),
|
|
removeLayer: vi.fn(),
|
|
} as unknown as Map,
|
|
mapid,
|
|
markerLayer: {
|
|
removeLayer: vi.fn(),
|
|
addLayer: vi.fn(),
|
|
} as unknown as LayerGroup,
|
|
}
|
|
}
|
|
|
|
describe('createCharacter', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('creates character with correct properties', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
expect(char.id).toBe(1)
|
|
expect(char.name).toBe('Hero')
|
|
expect(char.position).toEqual({ x: 100, y: 200 })
|
|
expect(char.type).toBe('player')
|
|
expect(char.map).toBe(1)
|
|
expect(char.text).toBe('Hero')
|
|
expect(char.value).toBe(1)
|
|
})
|
|
|
|
it('starts with null leaflet marker', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
expect(char.leafletMarker).toBeNull()
|
|
})
|
|
|
|
it('add creates marker when character is on correct map', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
char.add(mapview)
|
|
expect(mapview.map.unproject).toHaveBeenCalled()
|
|
})
|
|
|
|
it('add creates marker without title and binds Leaflet tooltip', () => {
|
|
const char = createCharacter(makeCharData({ position: { x: 100, y: 200 } }), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
char.add(mapview)
|
|
expect(leafletMock.marker).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
expect.not.objectContaining({ title: expect.anything() })
|
|
)
|
|
const marker = char.leafletMarker as { bindTooltip: ReturnType<typeof vi.fn> }
|
|
expect(marker.bindTooltip).toHaveBeenCalledWith(
|
|
'Hero · 1, 2',
|
|
expect.objectContaining({ direction: 'top', permanent: false })
|
|
)
|
|
})
|
|
|
|
it('add does not create marker for different map', () => {
|
|
const char = createCharacter(makeCharData({ map: 2 }), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
char.add(mapview)
|
|
expect(mapview.map.unproject).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('update changes position and map', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
|
|
char.update(mapview, {
|
|
...makeCharData(),
|
|
position: { x: 300, y: 400 },
|
|
map: 2,
|
|
})
|
|
|
|
expect(char.position).toEqual({ x: 300, y: 400 })
|
|
expect(char.map).toBe(2)
|
|
})
|
|
|
|
it('remove on a character without leaflet marker does nothing', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
char.remove(mapview) // should not throw
|
|
expect(char.leafletMarker).toBeNull()
|
|
})
|
|
|
|
it('setClickCallback works', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
const cb = vi.fn()
|
|
char.setClickCallback(cb)
|
|
})
|
|
|
|
it('update with changed ownedByMe updates marker icon', () => {
|
|
const char = createCharacter(makeCharData({ ownedByMe: false }), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
char.add(mapview)
|
|
const marker = char.leafletMarker as { setIcon: ReturnType<typeof vi.fn> }
|
|
expect(marker.setIcon).not.toHaveBeenCalled()
|
|
char.update(mapview, makeCharData({ ownedByMe: true }))
|
|
expect(marker.setIcon).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('update with position change updates tooltip content when marker exists', () => {
|
|
const char = createCharacter(makeCharData(), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
char.add(mapview)
|
|
const marker = char.leafletMarker as { setTooltipContent: ReturnType<typeof vi.fn> }
|
|
marker.setTooltipContent.mockClear()
|
|
char.update(mapview, makeCharData({ position: { x: 350, y: 450 } }))
|
|
expect(marker.setTooltipContent).toHaveBeenCalledWith('Hero · 3, 4')
|
|
})
|
|
|
|
it('remove cancels active position animation', () => {
|
|
const cancelSpy = vi.spyOn(global, 'cancelAnimationFrame').mockImplementation(() => {})
|
|
let rafCallback: (() => void) | null = null
|
|
vi.spyOn(global, 'requestAnimationFrame').mockImplementation((cb: (() => void) | (FrameRequestCallback)) => {
|
|
rafCallback = typeof cb === 'function' ? cb : () => {}
|
|
return 1
|
|
})
|
|
const char = createCharacter(makeCharData(), getL())
|
|
const mapview = makeMapViewRef(1)
|
|
mapview.map.unproject = vi.fn(() => ({ lat: 1, lng: 1 }))
|
|
char.add(mapview)
|
|
const marker = char.leafletMarker as { getLatLng: ReturnType<typeof vi.fn> }
|
|
marker.getLatLng.mockReturnValue({ lat: 0, lng: 0 })
|
|
char.update(mapview, makeCharData({ position: { x: 200, y: 200 } }))
|
|
expect(rafCallback).not.toBeNull()
|
|
cancelSpy.mockClear()
|
|
char.remove(mapview)
|
|
expect(cancelSpy).toHaveBeenCalledWith(1)
|
|
cancelSpy.mockRestore()
|
|
vi.restoreAllMocks()
|
|
})
|
|
})
|