import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import React from 'react';
vi.doMock('../google-maps-loader', () => ({
useGoogleMapsLoader: () => ({ isLoaded: true, loadError: undefined, load: async () => {} }),
}));
const POA = { lat: -30.03, lng: -51.22 };
describe('Map — field and forensics', () => {
let Map: any;
const polygonInstances: any[] = [];
const polylineInstances: any[] = [];
const mapInstance: any = {
addListener: vi.fn(() => ({ remove: vi.fn() })),
getZoom: vi.fn(() => 12),
getCenter: vi.fn(() => ({ lat: () => -30, lng: () => -51 })),
};
beforeEach(async () => {
vi.clearAllMocks();
polygonInstances.length = 0;
polylineInstances.length = 0;
Map = (await import('./map')).Map;
Object.defineProperty(HTMLElement.prototype, 'innerMap', {
configurable: true,
get() {
return this.tagName === 'GMP-MAP' ? mapInstance : undefined;
},
});
const shape = (bucket: any[]) =>
vi.fn(function (options: any) {
const instance = {
options,
setMap: vi.fn(),
setOptions: vi.fn(function (next: any) {
instance.options = { ...instance.options, ...next };
}),
addListener: vi.fn(() => ({ remove: vi.fn() })),
};
bucket.push(instance);
return instance;
});
global.google = {
maps: {
Map: vi.fn(function () {
return mapInstance;
}),
Polygon: shape(polygonInstances),
Polyline: shape(polylineInstances),
Circle: shape([]),
InfoWindow: vi.fn(function () {
return { open: vi.fn(), close: vi.fn(), setContent: vi.fn() };
}),
LatLng: vi.fn(function (lat: number, lng: number) {
return { lat, lng };
}),
LatLngBounds: vi.fn(function () {
return { extend: vi.fn() };
}),
Data: vi.fn(function () {
return {
setMap: vi.fn(),
addGeoJson: vi.fn(),
setStyle: vi.fn(),
forEach: vi.fn(),
remove: vi.fn(),
revertStyle: vi.fn(),
overrideStyle: vi.fn(),
addListener: vi.fn(() => ({ remove: vi.fn() })),
};
}),
drawing: {
DrawingManager: vi.fn(function () {
return {
setMap: vi.fn(),
setOptions: vi.fn(),
setDrawingMode: vi.fn(),
addListener: vi.fn(() => ({ remove: vi.fn() })),
};
}),
},
visualization: {
HeatmapLayer: vi.fn(function () {
return { setMap: vi.fn(), setOptions: vi.fn() };
}),
},
event: {
addListener: vi.fn(() => ({ remove: vi.fn() })),
addListenerOnce: vi.fn(() => ({ remove: vi.fn() })),
},
marker: { AdvancedMarkerElement: vi.fn() },
},
} as any;
});
afterEach(() => {
delete (HTMLElement.prototype as any).innerMap;
delete (global as any).google;
vi.resetModules();
});
/* --- C2 ------------------------------------------------------------------ */
it('draws an azimuth sector as a closed wedge', () => {
render(
);
expect(polygonInstances).toHaveLength(1);
const path = polygonInstances[0].options.paths;
// Vertex at the site on both ends, so it reads as a beam.
expect(path[0]).toEqual(POA);
expect(path[path.length - 1]).toEqual(POA);
});
/* --- C3 ------------------------------------------------------------------ */
describe('polylines', () => {
it('draws a line through arbitrary coordinates', () => {
render(
);
expect(polylineInstances).toHaveLength(1);
expect(polylineInstances[0].options.strokeWeight).toBe(4);
});
it('renders a dash as repeated symbols with an invisible stroke', () => {
render(
);
// Polyline has no dash property; the documented technique is icon repeat.
expect(polylineInstances[0].options.strokeOpacity).toBe(0);
expect(polylineInstances[0].options.icons[0].repeat).toBe('11px');
});
});
/* --- B6 ------------------------------------------------------------------ */
describe('annotations', () => {
it('renders user-authored pins with their author in the accessible name', () => {
render(
);
expect(screen.getByRole('button', { name: 'Ponto de coleta — Ana' })).toBeInTheDocument();
});
it('removes an annotation when its pin is activated', () => {
const onAnnotationRemove = vi.fn();
render(
);
fireEvent.click(screen.getByRole('button', { name: 'Ponto' }));
expect(onAnnotationRemove).toHaveBeenCalledWith('a1');
});
});
/* --- H2 ------------------------------------------------------------------ */
describe('device location', () => {
it('renders the device position', () => {
render();
expect(screen.getByRole('button', { name: 'Device location' })).toBeInTheDocument();
});
it('makes a simulated coordinate visible, not just logged', () => {
render();
expect(screen.getByRole('button', { name: 'Simulated device location' })).toBeInTheDocument();
expect(document.querySelector('[data-map-caption]')).toHaveTextContent('Simulated location');
});
});
/* --- G1 ------------------------------------------------------------------ */
describe('time window', () => {
const markers = [
{ id: 'a', position: POA, title: 'Às 10h', data: { t: 600 } },
{ id: 'b', position: { lat: -29, lng: -50 }, title: 'Às 14h', data: { t: 840 } },
{ id: 'c', position: { lat: -28, lng: -49 }, title: 'Às 20h', data: { t: 1200 } },
];
it('draws only the markers inside the window', () => {
render();
expect(screen.getByRole('button', { name: 'Às 10h' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Às 20h' })).not.toBeInTheDocument();
});
it('keeps records with no usable timestamp rather than vanishing them', () => {
render(
);
expect(screen.getByRole('button', { name: 'Sem carimbo' })).toBeInTheDocument();
});
it('counts only in-window records in a cluster badge', () => {
render(