Files
TREK/client/src/components/Map/useAssistantGhostMarkersGL.test.ts
T
Maurice d24db65cb8 fix(assistant): green CI round, trip notes the assistant can keep, calmer change cards
- the assistant keeps facts for a trip (remember_for_trip) as a change card,
  applied into the user's trip notes; existing expenses can be changed
- change card fields as a few icon chips with the rest folded under Details,
  the day band marks only what changes, the week grid as compact tiles
- no map button on receipts of things that have no place on the map
- tests follow the composer's accessible name, the receipt's status icon and
  the GL map's container; the hanging stream helper honours an early abort
- Postgres probe: qualified columns in the upsert updates; entities restored
  to the generator's output; strict ratchet reads the SDK client as untyped
2026-10-22 21:30:26 +00:00

72 lines
2.8 KiB
TypeScript

// 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: '<b>Forum</b>' },
];
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<typeof vi.fn> }[] = [];
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('<b>Forum</b>');
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([]);
});
});