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 074078f7..2268e518 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -389,4 +389,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; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 52fa3646..eb7a1ea8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -233,8 +233,8 @@ import { window.clipPoly = (points: [number, number][], secure?: number) => clipPoly( points, - (window as any).graphWidth, - (window as any).graphHeight, + graphWidth, + graphHeight, secure, ); window.getSegmentId = getSegmentId; @@ -250,24 +250,24 @@ window.generateDate = generateDate; window.getLongitude = (x: number, decimals?: number) => getLongitude( x, - (window as any).mapCoordinates, - (window as any).graphWidth, + mapCoordinates, + graphWidth, decimals, ); window.getLatitude = (y: number, decimals?: number) => getLatitude( y, - (window as any).mapCoordinates, - (window as any).graphHeight, + 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, + mapCoordinates, + graphWidth, + graphHeight, decimals, );