import { render, screen, fireEvent, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import type { MapControl } from './map-control'; vi.doMock('../google-maps-loader', () => ({ useGoogleMapsLoader: () => ({ isLoaded: true, loadError: undefined, load: async () => {} }), })); const MUNICIPALITIES = [ { id: 'poa', position: { lat: -30.03, lng: -51.22 }, title: 'Porto Alegre', data: { uf: 'RS' } }, { id: 'cxs', position: { lat: -29.17, lng: -51.18 }, title: 'Caxias do Sul', data: { uf: 'RS' } }, { id: 'flo', position: { lat: -27.6, lng: -48.55 }, title: 'Florianópolis', data: { uf: 'SC' } }, ]; describe('Map — territorial analysis', () => { let Map: any; const polygonInstances: any[] = []; const circleInstances: any[] = []; let heatmapOptions: any = null; const mapInstance = { addListener: vi.fn(() => ({ remove: vi.fn() })) }; beforeEach(async () => { vi.clearAllMocks(); polygonInstances.length = 0; circleInstances.length = 0; heatmapOptions = null; const module = await import('./map'); Map = module.Map; const shape = (bucket: any[]) => vi.fn(function (options: any) { const instance = { options, setMap: vi.fn(), setOptions: vi.fn(function (next: any) { instance.options = next; }), addListener: vi.fn(() => ({ remove: vi.fn() })), }; bucket.push(instance); return instance; }); /* * jsdom never upgrades the gmp-map custom element, so `innerMap` — the * google.maps.Map the geometry, choropleth and heatmap hooks attach to — * would never appear. Faking it is what makes those layers testable at all. */ Object.defineProperty(HTMLElement.prototype, 'innerMap', { configurable: true, get() { return this.tagName === 'GMP-MAP' ? mapInstance : undefined; }, }); global.google = { maps: { Map: vi.fn(function () { return mapInstance; }), InfoWindow: vi.fn(function () { return { open: vi.fn(), close: vi.fn(), setContent: vi.fn(), addListener: vi.fn(() => ({ remove: vi.fn() })), }; }), Circle: shape(circleInstances), Polygon: shape(polygonInstances), 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() })), }; }), visualization: { HeatmapLayer: vi.fn(function (options: any) { heatmapOptions = options; 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(); }); /* --- C1 / E2 ------------------------------------------------------------ */ describe('multiple geometries', () => { it('renders many polygons and circles at once', () => { render( ); expect(polygonInstances).toHaveLength(2); expect(circleInstances).toHaveLength(3); }); it('renders the singular polygon alongside the plural ones', () => { render( ); // Backwards compatibility: both must draw. expect(polygonInstances).toHaveLength(2); }); it('multiplies feature opacity by its layer', () => { render( ); expect(polygonInstances[0].options.fillOpacity).toBeCloseTo(0.25, 5); }); it('takes zIndex from the layer when the feature declares none', () => { render( ); expect(polygonInstances[0].options.zIndex).toBe(42); }); it('hides geometry belonging to a hidden layer', () => { render( ); expect(circleInstances[0].options.visible).toBe(false); }); }); /* --- E1 ----------------------------------------------------------------- */ it('hides markers belonging to a hidden layer', () => { render( ); expect(screen.queryByRole('button', { name: 'Porto Alegre' })).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Caxias do Sul' })).toBeInTheDocument(); }); /* --- D1 ----------------------------------------------------------------- */ describe('heatmap', () => { it('builds the layer from the supplied points', () => { render( ); expect(heatmapOptions.data).toHaveLength(2); expect(heatmapOptions.radius).toBe(28); }); it('prepends a transparent stop to the token gradient', () => { render( ); // Google reads index 0 as the color of "no density"; an opaque first stop // would flood the whole viewport. expect(heatmapOptions.gradient[0]).toBe('rgba(0, 0, 0, 0)'); }); }); /* --- B3 / J3 ------------------------------------------------------------ */ describe('clustering and the privacy floor', () => { it('collapses grouped records into one badged marker', () => { render(
} }]} /> ); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Porto Alegre' })); expect(screen.getByRole('dialog')).toHaveTextContent('Detalhes da unidade'); fireEvent.click(screen.getByRole('button', { name: 'Close' })); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); it('honours the declared width', () => { render(