// FE-MAP-GHOST-GL-001 to 002: the assistant's suggestions on the GL map, as hand-positioned pins.
import { act, renderHook } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import type { AssistantGhost } from './mapViewContract';
import { useAssistantGhostMarkersGL } from './useAssistantGhostMarkersGL';
const GHOSTS: AssistantGhost[] = [
{ id: 'a', lat: 41.9, lng: 12.48, name: 'Pantheon' },
{ id: 'b', lat: 41.89, lng: 12.49, name: 'Forum' },
];
function setup(hoveredId: string | null = null) {
const container = document.createElement('div');
document.body.appendChild(container);
const map = { getContainer: () => container, on: vi.fn(), off: vi.fn() };
const attached: { el: HTMLElement; lng: number; lat: number; remove: ReturnType }[] = [];
const pinsRef = { current: [] as { remove: () => void }[] };
const onHover = vi.fn();
const onClick = vi.fn();
// Stable across renders, as the map view hands them over: only `hovered` changes.
const mapRef = { current: map };
const attachPin = (el: HTMLElement, lng: number, lat: number) => {
container.appendChild(el);
const pin = { el, lng, lat, remove: vi.fn(() => el.remove()) };
attached.push(pin);
return pin;
};
const hook = renderHook(
({ hovered }) =>
useAssistantGhostMarkersGL({
mapRef,
mapReady: true,
pinsRef,
attachPin,
ghosts: GHOSTS,
hoveredId: hovered,
onHover,
onClick,
}),
{ initialProps: { hovered: hoveredId } }
);
return { container, attached, pinsRef, onHover, hook };
}
describe('useAssistantGhostMarkersGL', () => {
it('FE-MAP-GHOST-GL-001: puts each ghost where it belongs, hands the pins to the reposition, and names it as text', () => {
const { container, attached, pinsRef, onHover } = setup();
expect(attached.map((p) => [p.lng, p.lat])).toEqual([
[12.48, 41.9],
[12.49, 41.89],
]);
expect(pinsRef.current).toHaveLength(2);
attached[1]?.el.dispatchEvent(new Event('pointerenter'));
expect(onHover).toHaveBeenCalledWith('b');
const label = container.querySelector('[data-testid="assistant-ghost-label"]');
expect(label?.textContent).toBe('Forum');
expect(label?.querySelector('b')).toBeNull();
});
it('FE-MAP-GHOST-GL-002: a hover marks its ghost without rebuilding a pin, and unmounting takes them all off', () => {
const { attached, pinsRef, hook } = setup();
act(() => hook.rerender({ hovered: 'a' }));
expect(attached).toHaveLength(2);
expect(attached[0]?.el.classList.contains('is-hot')).toBe(true);
expect(attached[1]?.el.classList.contains('is-hot')).toBe(false);
hook.unmount();
expect(attached.every((p) => p.remove.mock.calls.length === 1)).toBe(true);
expect(pinsRef.current).toEqual([]);
});
});