fix: GeoJSON export (#1283)
Some checks are pending
Deploy static content to Pages / deploy (push) Waiting to run

* fix: use global vars instead of window.

* feat: add GitHub Actions workflow for unit tests

* fix: change mapCoordinates declaration from let to var for compatibility
This commit is contained in:
Marc Emmanuel 2026-01-26 18:34:35 +01:00 committed by GitHub
parent 29bc2832e0
commit e37fce1eed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 124 additions and 5 deletions

17
.github/workflows/unit-tests.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: Unit Tests
on:
pull_request:
branches: [ master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Run Unit tests
run: npm run test

View file

@ -187,7 +187,7 @@ const onZoom = debounce(function () {
}, 50); }, 50);
const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoom); const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoom);
let mapCoordinates = {}; // map coordinates on globe var mapCoordinates = {}; // map coordinates on globe
let populationRate = +byId("populationRateInput").value; let populationRate = +byId("populationRateInput").value;
let distanceScale = +byId("distanceScaleInput").value; let distanceScale = +byId("distanceScaleInput").value;
let urbanization = +byId("urbanizationInput").value; let urbanization = +byId("urbanizationInput").value;

View file

@ -0,0 +1,97 @@
import { expect, describe, it } from 'vitest'
import { getLongitude, getLatitude, getCoordinates } from './commonUtils'
describe('getLongitude', () => {
const mapCoordinates = { lonW: -10, lonT: 20 };
const graphWidth = 1000;
it('should calculate longitude at the left edge (x=0)', () => {
expect(getLongitude(0, mapCoordinates, graphWidth, 2)).toBe(-10);
});
it('should calculate longitude at the right edge (x=graphWidth)', () => {
expect(getLongitude(1000, mapCoordinates, graphWidth, 2)).toBe(10);
});
it('should calculate longitude at the center (x=graphWidth/2)', () => {
expect(getLongitude(500, mapCoordinates, graphWidth, 2)).toBe(0);
});
it('should respect decimal precision', () => {
// 333/1000 * 20 = 6.66, -10 + 6.66 = -3.34
expect(getLongitude(333, mapCoordinates, graphWidth, 4)).toBe(-3.34);
});
it('should handle different map coordinate ranges', () => {
const wideMap = { lonW: -180, lonT: 360 };
expect(getLongitude(500, wideMap, graphWidth, 2)).toBe(0);
expect(getLongitude(0, wideMap, graphWidth, 2)).toBe(-180);
expect(getLongitude(1000, wideMap, graphWidth, 2)).toBe(180);
});
});
describe('getLatitude', () => {
const mapCoordinates = { latN: 60, latT: 40 };
const graphHeight = 800;
it('should calculate latitude at the top edge (y=0)', () => {
expect(getLatitude(0, mapCoordinates, graphHeight, 2)).toBe(60);
});
it('should calculate latitude at the bottom edge (y=graphHeight)', () => {
expect(getLatitude(800, mapCoordinates, graphHeight, 2)).toBe(20);
});
it('should calculate latitude at the center (y=graphHeight/2)', () => {
expect(getLatitude(400, mapCoordinates, graphHeight, 2)).toBe(40);
});
it('should respect decimal precision', () => {
// 60 - (333/800 * 40) = 60 - 16.65 = 43.35
expect(getLatitude(333, mapCoordinates, graphHeight, 4)).toBe(43.35);
});
it('should handle equator-centered maps', () => {
const equatorMap = { latN: 45, latT: 90 };
expect(getLatitude(400, equatorMap, graphHeight, 2)).toBe(0);
});
});
describe('getCoordinates', () => {
const mapCoordinates = { lonW: -10, lonT: 20, latN: 60, latT: 40 };
const graphWidth = 1000;
const graphHeight = 800;
it('should return [longitude, latitude] tuple', () => {
const result = getCoordinates(500, 400, mapCoordinates, graphWidth, graphHeight, 2);
expect(result).toEqual([0, 40]);
});
it('should calculate coordinates at top-left corner', () => {
const result = getCoordinates(0, 0, mapCoordinates, graphWidth, graphHeight, 2);
expect(result).toEqual([-10, 60]);
});
it('should calculate coordinates at bottom-right corner', () => {
const result = getCoordinates(1000, 800, mapCoordinates, graphWidth, graphHeight, 2);
expect(result).toEqual([10, 20]);
});
it('should respect decimal precision for both coordinates', () => {
const result = getCoordinates(333, 333, mapCoordinates, graphWidth, graphHeight, 4);
expect(result[0]).toBe(-3.34); // longitude
expect(result[1]).toBe(43.35); // latitude
});
it('should use default precision of 2 decimals', () => {
const result = getCoordinates(333, 333, mapCoordinates, graphWidth, graphHeight);
expect(result[0]).toBe(-3.34);
expect(result[1]).toBe(43.35);
});
it('should handle global map coordinates', () => {
const globalMap = { lonW: -180, lonT: 360, latN: 90, latT: 180 };
const result = getCoordinates(500, 400, globalMap, graphWidth, graphHeight, 2);
expect(result).toEqual([0, 0]); // center of the world
});
});

View file

@ -317,4 +317,9 @@ declare global {
getLatitude: typeof getLatitude; getLatitude: typeof getLatitude;
getCoordinates: typeof getCoordinates; getCoordinates: typeof getCoordinates;
} }
// Global variables defined in main.js
var mapCoordinates: { latT?: number; latN?: number; latS?: number; lonT?: number; lonW?: number; lonE?: number };
var graphWidth: number;
var graphHeight: number;
} }

View file

@ -116,7 +116,7 @@ window.isLand = (i: number) => isLand(i, (window as any).pack);
window.isWater = (i: number) => isWater(i, (window as any).pack); window.isWater = (i: number) => isWater(i, (window as any).pack);
import { clipPoly, getSegmentId, debounce, throttle, parseError, getBase64, openURL, wiki, link, isCtrlClick, generateDate, getLongitude, getLatitude, getCoordinates, initializePrompt } from "./commonUtils"; import { clipPoly, getSegmentId, debounce, throttle, parseError, getBase64, openURL, wiki, link, isCtrlClick, generateDate, getLongitude, getLatitude, getCoordinates, initializePrompt } from "./commonUtils";
window.clipPoly = (points: [number, number][], secure?: number) => clipPoly(points, (window as any).graphWidth, (window as any).graphHeight, secure); window.clipPoly = (points: [number, number][], secure?: number) => clipPoly(points, graphWidth, graphHeight, secure);
window.getSegmentId = getSegmentId; window.getSegmentId = getSegmentId;
window.debounce = debounce; window.debounce = debounce;
window.throttle = throttle; window.throttle = throttle;
@ -127,9 +127,9 @@ window.wiki = wiki;
window.link = link; window.link = link;
window.isCtrlClick = isCtrlClick; window.isCtrlClick = isCtrlClick;
window.generateDate = generateDate; window.generateDate = generateDate;
window.getLongitude = (x: number, decimals?: number) => getLongitude(x, (window as any).mapCoordinates, (window as any).graphWidth, decimals); window.getLongitude = (x: number, decimals?: number) => getLongitude(x, mapCoordinates, graphWidth, decimals);
window.getLatitude = (y: number, decimals?: number) => getLatitude(y, (window as any).mapCoordinates, (window as any).graphHeight, decimals); window.getLatitude = (y: number, decimals?: number) => getLatitude(y, mapCoordinates, graphHeight, decimals);
window.getCoordinates = (x: number, y: number, decimals?: number) => getCoordinates(x, y, (window as any).mapCoordinates, (window as any).graphWidth, (window as any).graphHeight, decimals); window.getCoordinates = (x: number, y: number, decimals?: number) => getCoordinates(x, y, mapCoordinates, graphWidth, graphHeight, decimals);
// Initialize prompt when DOM is ready // Initialize prompt when DOM is ready
if (document.readyState === 'loading') { if (document.readyState === 'loading') {