import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import React from 'react';
/*
* The whole point of this suite: the Maps script never loads. No API key, no
* `google` global, no custom elements — exactly the blocked-network / exhausted
* -quota situation the fallback exists for.
*/
vi.doMock('../google-maps-loader', () => ({
useGoogleMapsLoader: () => ({
isLoaded: false,
loadError: undefined,
load: async () => {},
}),
}));
const MUNICIPALITIES = [
{ id: 'porto-alegre', position: { lat: -30.0346, lng: -51.2177 }, title: 'Porto Alegre' },
{ id: 'caxias-do-sul', position: { lat: -29.1685, lng: -51.1796 }, title: 'Caxias do Sul' },
{ id: 'pelotas', position: { lat: -31.7654, lng: -52.3376 }, title: 'Pelotas' },
];
const OUTLINE = {
type: 'FeatureCollection' as const,
features: [
{
type: 'Feature' as const,
properties: { nome: 'RS' },
geometry: {
type: 'Polygon',
coordinates: [
[
[-53.11, -27.08],
[-49.69, -27.08],
[-49.69, -33.75],
[-53.11, -33.75],
[-53.11, -27.08],
],
],
},
},
],
};
describe('Map without a basemap', () => {
let Map: any;
beforeEach(async () => {
vi.clearAllMocks();
const module = await import('./map');
Map = module.Map;
// jsdom reports zero-sized elements; the projection needs real pixels.
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
configurable: true,
value: 800,
});
Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
configurable: true,
value: 600,
});
});
afterEach(() => {
vi.resetModules();
});
it('plots the markers when there is no API key at all', () => {
render();
expect(screen.getByRole('button', { name: 'Porto Alegre' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Pelotas' })).toBeInTheDocument();
// No Google element was ever created.
expect(document.querySelector('gmp-map')).not.toBeInTheDocument();
});
it('says the basemap is missing rather than passing for a real map', () => {
render();
expect(screen.getByRole('status')).toHaveTextContent('Base cartográfica indisponível');
});
it('accepts a custom notice label', () => {
render();
expect(screen.getByRole('status')).toHaveTextContent('Mapa offline');
});
it('places markers at distinct positions derived from their coordinates', () => {
render();
const positions = screen.getAllByRole('button').map(button => {
const host = button.closest('div[style*="left"]') as HTMLElement;
return `${host.style.left}|${host.style.top}`;
});
expect(new Set(positions).size).toBe(MUNICIPALITIES.length);
});
it('puts the northern municipality above the southern one', () => {
render();
const topOf = (name: string) => {
const host = screen.getByRole('button', { name }).closest('div[style*="top"]') as HTMLElement;
return parseFloat(host.style.top);
};
// Caxias do Sul (-29.17) is north of Pelotas (-31.77).
expect(topOf('Caxias do Sul')).toBeLessThan(topOf('Pelotas'));
});
it('draws the reference outline when one is supplied', () => {
const { container } = render();
const paths = container.querySelectorAll('svg path');
expect(paths.length).toBe(1);
expect(paths[0].getAttribute('d')).toMatch(/^M[\d.]+ [\d.]+/);
expect(paths[0].getAttribute('d')).toMatch(/Z$/);
});
it('draws no outline when none is supplied', () => {
const { container } = render();
expect(container.querySelector('svg path')).not.toBeInTheDocument();
});
it('keeps selection and keyboard activation working', () => {
const onFeatureSelect = vi.fn();
render();
const target = screen.getByRole('button', { name: 'Pelotas' });
expect(target).toHaveAttribute('tabindex', '0');
fireEvent.keyDown(target, { key: 'Enter' });
expect(onFeatureSelect).toHaveBeenCalledWith(
'pelotas',
expect.objectContaining({ id: 'pelotas', kind: 'marker' })
);
});
it('reflects a controlled selection', () => {
render(