Spaces:
Running
Running
File size: 6,017 Bytes
3939ca3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import test from 'node:test';import assert from 'node:assert/strict';
import {COUNTRIES,IDS,normalizeRows,validYear,finite,latest,paired,pearson,growth,change,coverage,status,formatValue,escapeHTML,csvCell,toCSV,validateSession,timeline,compareCountries,crossCountryGap} from './core.js';
import {makeWorldBankURL,parseWorldBank} from './worldbank.js';import {createDemo} from './demo-data.js';
const A='SE.SEC.ENRR',B='IT.NET.USER.ZS';const rows=normalizeRows([
{iso3:'ARG',year:2020,indicator:A,value:100},{iso3:'ARG',year:2021,indicator:A,value:101},{iso3:'BOL',year:2020,indicator:A,value:80},
{iso3:'BOL',year:2021,indicator:A,value:82},{iso3:'ARG',year:2020,indicator:B,value:77},{iso3:'ARG',year:2021,indicator:B,value:80},
{iso3:'BOL',year:2020,indicator:B,value:45},{iso3:'BOL',year:2021,indicator:B,value:49}]);
const mk=(iso3,year,indicator,value)=>({iso3,year,indicator,value});
test('country registry is unique and contains Bolivia and Paraguay',()=>{assert.equal(COUNTRIES.BOL,'Bolivia');assert.equal(COUNTRIES.PRY,'Paraguay');assert.equal(new Set(Object.keys(COUNTRIES)).size,16);});
test('8 indicators supported',()=>assert.equal(IDS.length,8));
test('years are constrained',()=>{assert.equal(validYear(2025),true);assert.equal(validYear(1980),false);assert.equal(validYear(2030),false);});
test('finite excludes Infinity and NaN',()=>{assert.equal(finite(Infinity),false);assert.equal(finite(0),true)});
test('normalize filters unexpected countries',()=>assert.equal(normalizeRows([mk('FAK',2020,A,1)]).length,0));
test('normalize filters unknown indicators',()=>assert.equal(normalizeRows([mk('ARG',2020,'FAKE',1)]).length,0));
test('normalize filters out-of-range year',()=>assert.equal(normalizeRows([mk('ARG',1980,A,1)]).length,0));
test('normalize filters missing values',()=>assert.equal(normalizeRows([mk('ARG',2020,A,null)]).length,0));
test('normalize preserves valid zero',()=>assert.equal(normalizeRows([mk('ARG',2020,A,0)])[0].value,0));
test('normalize de-duplicates country-year-indicator',()=>assert.equal(normalizeRows([mk('ARG',2020,A,2),mk('ARG',2020,A,3)]).length,1));
test('normalize sorts years',()=>assert.equal(normalizeRows([mk('ARG',2024,A,2),mk('ARG',2014,A,3)])[0].year,2014));
test('latest returns newest observation',()=>assert.equal(latest(rows,'ARG',A,2020,2022).year,2021));
test('latest does not forward-fill',()=>assert.equal(latest(rows,'ARG',A,2022,2024),null));
test('pairing uses exactly matching year and country',()=>assert.equal(paired(rows,A,B,['ARG','BOL'],2020,2021).length,4));
test('pairing excludes unmatched years',()=>assert.equal(paired(rows,A,B,['ARG'],2022,2023).length,0));
test('pearson returns null for insufficient data',()=>assert.equal(pearson([{x:1,y:2}], 'x','y'),null));
test('pearson is +1 for perfect positive association',()=>assert.ok(Math.abs(pearson([{x:1,y:2},{x:2,y:4},{x:3,y:6}], 'x','y')-1)<1e-9));
test('pearson is -1 for negative association',()=>assert.ok(Math.abs(pearson([{x:1,y:3},{x:2,y:2},{x:3,y:1}], 'x','y')+1)<1e-9));
test('pearson returns null for constant variable',()=>assert.equal(pearson([{x:1,y:2},{x:1,y:3},{x:1,y:4}],'x','y'),null));
test('growth is percent change, not percentage point difference',()=>assert.ok(Math.abs(growth(50,55)-10)<1e-10));
test('growth avoids zero denominator',()=>assert.equal(growth(0,2),null));
test('change tracks first and last real years',()=>assert.deepEqual(change({year:2020,value:10},{year:2022,value:13}),{amount:3,from:2020,to:2022}));
test('coverage includes expected missing cells',()=>assert.deepEqual(coverage(rows,['ARG','BOL'],[A,B],2020,2022),{count:8,eligible:12,ratio:8/12}));
test('status computes average reporting lag',()=>assert.equal(status(rows,['ARG'],[A],2020,2023).meanLag,2));
test('format value attaches valid units',()=>assert.match(formatValue(10.1,A),/%/));
test('html is escaped',()=>assert.equal(escapeHTML('<script>&"'), '<script>&"'));
test('CSV escapes quotes and commas',()=>assert.equal(csvCell('a,"b"'),'"a,""b"""'));
test('CSV export includes metadata and no executable content',()=>{let data=toCSV(rows);assert.match(data,/observation_status/);assert.match(data,/Argentina/)});
test('session rejects wrong schema',()=>assert.throws(()=>validateSession({format:'no',rows}),/Invalid/));
test('session roundtrip loads supported rows',()=>assert.equal(validateSession({format:'edlab-session-v1',rows}).rows.length,8));
test('timeline groups countries',()=>assert.equal(timeline(rows,['ARG','BOL'],A,2020,2021).length,2));
test('rank sorts by latest value',()=>assert.equal(compareCountries(rows,['ARG','BOL'],A,2020,2021)[0].country,'ARG'));
test('cross country gap exact year',()=>assert.equal(crossCountryGap(rows,'ARG','BOL',A,2020,2021,2020).delta,20));
test('cross country gap missing year returns null',()=>assert.equal(crossCountryGap(rows,'ARG','BOL',A,2020,2023,2022),null));
test('API URL uses WDI, JSON and selected economies',()=>{const u=makeWorldBankURL(['ARG','BOL'],[A,B],2010,2023);assert.match(u,/source=2/);assert.match(u,/format=json/);assert.match(u,/ARG;BOL/);assert.match(u,/2010:2023/)});
test('API parser preserves valid observation and drops null',()=>{let p=[{pages:1},[{countryiso3code:'ARG',indicator:{id:A},date:'2020',value:93,footnote:[{value:'caution'}]},{countryiso3code:'BOL',indicator:{id:A},date:'2020',value:null}]];const result=parseWorldBank(p);assert.equal(result.length,1);assert.equal(result[0].value,93);assert.equal(result[0].footnote,'caution');});
test('API parser rejects HTML/unknown formats',()=>assert.throws(()=>parseWorldBank({error:'fail'}),/Unexpected/));
test('synthetic demo covers multiple economies and indicators',()=>{const d=createDemo();assert.ok(d.length>1000);assert.ok(d.every(r=>COUNTRIES[r.iso3]&&IDS.includes(r.indicator)));});
test('synthetic demo is deterministic across loads',()=>assert.deepEqual(createDemo(),createDemo()));
test('CSV neutralizes spreadsheet formula injection in string fields',()=>assert.ok(csvCell('=SUM(1,2)').includes("'=SUM")));
|