From e37fce1eed2e1add0b08ca00440d1d4ff46e97b1 Mon Sep 17 00:00:00 2001 From: Marc Emmanuel Date: Mon, 26 Jan 2026 18:34:35 +0100 Subject: [PATCH] fix: GeoJSON export (#1283) * 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 --- .github/workflows/unit-tests.yml | 17 ++++++ public/main.js | 2 +- src/utils/commonUtils.test.ts | 97 ++++++++++++++++++++++++++++++++ src/utils/commonUtils.ts | 5 ++ src/utils/index.ts | 8 +-- 5 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100644 src/utils/commonUtils.test.ts diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..7d02cf0f --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -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 \ No newline at end of file diff --git a/public/main.js b/public/main.js index e922c44e..c0ac9d11 100644 --- a/public/main.js +++ b/public/main.js @@ -187,7 +187,7 @@ const onZoom = debounce(function () { }, 50); 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 distanceScale = +byId("distanceScaleInput").value; let urbanization = +byId("urbanizationInput").value; diff --git a/src/utils/commonUtils.test.ts b/src/utils/commonUtils.test.ts new file mode 100644 index 00000000..6ca595d3 --- /dev/null +++ b/src/utils/commonUtils.test.ts @@ -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 + }); +}); diff --git a/src/utils/commonUtils.ts b/src/utils/commonUtils.ts index 24f7501c..f326c067 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -317,4 +317,9 @@ declare global { getLatitude: typeof getLatitude; 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; } \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 73581a38..e75d88cb 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -116,7 +116,7 @@ window.isLand = (i: number) => isLand(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"; -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.debounce = debounce; window.throttle = throttle; @@ -127,9 +127,9 @@ window.wiki = wiki; window.link = link; window.isCtrlClick = isCtrlClick; window.generateDate = generateDate; -window.getLongitude = (x: number, decimals?: number) => getLongitude(x, (window as any).mapCoordinates, (window as any).graphWidth, decimals); -window.getLatitude = (y: number, decimals?: number) => getLatitude(y, (window as any).mapCoordinates, (window as any).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.getLongitude = (x: number, decimals?: number) => getLongitude(x, mapCoordinates, graphWidth, decimals); +window.getLatitude = (y: number, decimals?: number) => getLatitude(y, mapCoordinates, graphHeight, decimals); +window.getCoordinates = (x: number, y: number, decimals?: number) => getCoordinates(x, y, mapCoordinates, graphWidth, graphHeight, decimals); // Initialize prompt when DOM is ready if (document.readyState === 'loading') {