import { render, screen, fireEvent } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; // Use doMock to avoid hoisting issues and potential caching conflicts vi.doMock('../google-maps-loader', () => ({ useGoogleMapsLoader: () => ({ isLoaded: true, loadError: undefined, load: async () => {}, }), })); describe('Map', () => { let Map: any; const mockMap = { setCenter: vi.fn(), setZoom: vi.fn(), getZoom: vi.fn(() => 10), fitBounds: vi.fn(), addListener: vi.fn(() => ({ remove: vi.fn() })), }; const mockInfoWindow = { open: vi.fn(), close: vi.fn(), setContent: vi.fn(), addListener: vi.fn(() => ({ remove: vi.fn() })), }; beforeEach(async () => { vi.clearAllMocks(); // Dynamically import Map after doMock const module = await import('./map'); Map = module.Map; // Mock Google Maps Globals global.google = { maps: { Map: vi.fn(function () { return mockMap; }), InfoWindow: vi.fn(function () { return mockInfoWindow; }), Circle: vi.fn(function () { return { setMap: vi.fn() }; }), Polygon: vi.fn(function () { return { setMap: vi.fn() }; }), LatLngBounds: vi.fn(function () { return { extend: vi.fn() }; }), event: { addListener: vi.fn(() => ({ remove: vi.fn() })), addListenerOnce: vi.fn(() => ({ remove: vi.fn() })), }, marker: { AdvancedMarkerElement: vi.fn(), }, }, } as any; }); afterEach(() => { delete (global as any).google; vi.resetModules(); }); it('renders correctly when loaded', () => { render(); const mapElement = document.querySelector('gmp-map'); expect(mapElement).toBeInTheDocument(); }); it('renders markers correctly', () => { const markers = [ { position: { lat: 10, lng: 20 }, title: 'Marker 1' }, { position: { lat: 30, lng: 40 }, title: 'Marker 2' }, ]; render(); const advancedMarkers = document.querySelectorAll('gmp-advanced-marker'); expect(advancedMarkers.length).toBe(2); expect(advancedMarkers[0]).toHaveAttribute('title', 'Marker 1'); }); /* --- Backwards compatibility ------------------------------------------- */ describe('backwards compatibility', () => { it('still renders the teardrop pin when no shape is given', () => { render(); const pin = screen.getByRole('button'); expect(pin).toHaveStyle({ borderRadius: '50% 50% 50% 0' }); expect(pin).toHaveTextContent('A'); }); it('honors the literal customColor prop', () => { render( ); expect(screen.getByRole('button')).toHaveStyle({ backgroundColor: '#ff0000' }); }); it('falls back to the demo map id when none is supplied', () => { render(); expect(document.querySelector('gmp-map')).toHaveAttribute('map-id', 'DEMO_MAP_ID'); }); it('uses a supplied mapId', () => { render(); expect(document.querySelector('gmp-map')).toHaveAttribute('map-id', 'my-cloud-style'); }); }); /* --- B1 proportional symbology ------------------------------------------ */ describe('proportional markers', () => { it('sizes a circle marker from its scale', () => { render( ); // sqrt(1) = 1 -> the top of the range -> 20px radius -> 40px box. expect(screen.getByRole('button')).toHaveStyle({ width: '40px', height: '40px' }); }); it('honors a fixed radiusPx', () => { render( ); expect(screen.getByRole('button')).toHaveStyle({ width: '12px', height: '12px' }); }); it('renders a square shape without a circular radius', () => { render( ); expect(screen.getByRole('button')).toHaveAttribute('data-shape', 'square'); }); }); /* --- B5 selection and events -------------------------------------------- */ describe('selection and events', () => { const markers = [ { id: 'porto-alegre', position: { lat: -30.03, lng: -51.23 }, title: 'Porto Alegre' }, { id: 'gravatai', position: { lat: -29.94, lng: -50.99 }, title: 'Gravataí' }, ]; it('exposes every marker as a labelled button', () => { render(); expect(screen.getByRole('button', { name: 'Porto Alegre' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Gravataí' })).toBeInTheDocument(); }); it('makes markers reachable by keyboard', () => { render(); expect(screen.getByRole('button', { name: 'Gravataí' })).toHaveAttribute('tabindex', '0'); }); it('reports the selected feature on click', () => { const onFeatureSelect = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'Gravataí' })); expect(onFeatureSelect).toHaveBeenCalledWith( 'gravatai', expect.objectContaining({ id: 'gravatai', kind: 'marker', index: 1 }) ); }); it('activates on Enter and on Space', () => { const onFeatureSelect = vi.fn(); render(); const target = screen.getByRole('button', { name: 'Porto Alegre' }); fireEvent.keyDown(target, { key: 'Enter' }); expect(onFeatureSelect).toHaveBeenCalledTimes(1); // The activation guard collapses repeats inside one tick, so advance time. vi.setSystemTime(Date.now() + 100); fireEvent.keyDown(target, { key: ' ' }); expect(onFeatureSelect).toHaveBeenCalledTimes(2); vi.useRealTimers(); }); it('reflects a controlled selectedId with aria-pressed', () => { render(
} />); expect(screen.getByText('Nenhum município no filtro')).toBeInTheDocument(); }); it('renders the loading skeleton when loading is forced', () => { const { container } = render(Sem permissão para estes dados
} /> ); expect(screen.getByText('Sem permissão para estes dados')).toBeInTheDocument(); expect(screen.queryByText('data error')).not.toBeInTheDocument(); }); it('lets the error state outrank loading and empty', () => { render(