From e94c43ca6ef25088061d6bb55a06dfe8d62921b1 Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Thu, 4 Jun 2026 11:52:41 +0200 Subject: [PATCH] test: Added xlsx parsing tests --- package-lock.json | 1239 ++++++++++++++++++++++++++- package.json | 6 +- scrape/api/announcements.ts | 14 + scrape/parse/v3.ts | 9 +- tests/content/1.xlsx | Bin 0 -> 9937 bytes tests/{ => content}/teachermap.json | 0 tests/parseAbsence.test.ts | 353 ++++++++ tests/parseXlsx.test.ts | 118 +++ tests/test.ts | 352 -------- 9 files changed, 1724 insertions(+), 367 deletions(-) create mode 100644 tests/content/1.xlsx rename tests/{ => content}/teachermap.json (100%) create mode 100644 tests/parseAbsence.test.ts create mode 100644 tests/parseXlsx.test.ts delete mode 100644 tests/test.ts diff --git a/package-lock.json b/package-lock.json index 3450b65..613c697 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,8 @@ "@types/node-cron": "^3.0.11", "@types/xml2js": "^0.4.14", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.8" } }, "node_modules/@babel/code-frame": { @@ -64,10 +65,33 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1446,6 +1470,32 @@ } } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@next/env": { "version": "16.1.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", @@ -1580,6 +1630,16 @@ "node": ">= 10" } }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@puppeteer/browsers": { "version": "2.10.5", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", @@ -1601,6 +1661,295 @@ "node": ">=18" } }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1616,6 +1965,17 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -1627,6 +1987,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1647,6 +2018,20 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", @@ -1880,6 +2265,119 @@ "@types/node": "*" } }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2067,6 +2565,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -2435,6 +2943,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -2674,6 +3192,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -2883,8 +3408,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -3106,6 +3631,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3233,6 +3765,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3271,6 +3813,16 @@ "node": ">=8.3.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -4073,6 +4625,279 @@ "immediate": "~3.0.5" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4179,6 +5004,16 @@ "node": ">=12" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4285,9 +5120,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -4424,6 +5259,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4608,6 +5454,13 @@ "node": ">=16" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -4909,6 +5762,40 @@ "rimraf": "bin.js" } }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -5229,6 +6116,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5304,6 +6198,13 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -5313,6 +6214,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/streamx": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", @@ -5433,6 +6341,81 @@ "b4a": "^1.6.4" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tldts": { "version": "7.0.23", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", @@ -5689,6 +6672,229 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -5725,6 +6931,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index d94d828..9ff646d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "type": "module", "main": "server.js", "scripts": { - "test": "tsx tests/test.ts", + "test": "vitest run", + "test:watch": "vitest", "start": "concurrently \"NODE_ENV=development tsx server.ts\" \"NODE_ENV=development tsx cron-runner.ts\"", "build": "tsc && cd web && hugo --gc --minify && cd ../viewer && npm run build", "build-noweb": "tsc", @@ -46,6 +47,7 @@ "@types/node-cron": "^3.0.11", "@types/xml2js": "^0.4.14", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.8" } } diff --git a/scrape/api/announcements.ts b/scrape/api/announcements.ts index 9d35984..5e57001 100644 --- a/scrape/api/announcements.ts +++ b/scrape/api/announcements.ts @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2026 Jakub Žitník + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + const API_BASE_URL = process.env.ANNOUNCEMENT_API || "http://localhost:3000"; export type Announcement = { diff --git a/scrape/parse/v3.ts b/scrape/parse/v3.ts index 23f7766..69d1ef3 100644 --- a/scrape/parse/v3.ts +++ b/scrape/parse/v3.ts @@ -39,7 +39,7 @@ interface ResolvedDay { /** * Read theme colors from the workbook */ -async function getThemeColors(filePath: string): Promise { +export async function getThemeColors(filePath: string): Promise { const data = fs.readFileSync(filePath); const zip = await JSZip.loadAsync(data); @@ -164,10 +164,9 @@ export default async function parseV3(workbook: Workbook, downloadedFilePath: st // ──────────────────────────────────────────────────────────── // -function getUpcomingSheets(workbook: ExcelJS.Workbook): ResolvedDay[] { +export function getUpcomingSheets(workbook: ExcelJS.Workbook, today: Date = new Date()): ResolvedDay[] { const dateRegex = /^(pondělí|úterý|středa|čtvrtek|pátek|po|út|ut|st|čt|ct|pa|pá)\s+(\d{1,2})\.+\s*(\d{1,2})\.+\s*(\d{4}|\d{2})/i; - const today = new Date(); const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()); const result: ResolvedDay[] = []; @@ -190,7 +189,7 @@ function getUpcomingSheets(workbook: ExcelJS.Workbook): ResolvedDay[] { return result; } -function groupSheetsByDate(items: ResolvedDay[]) { +export function groupSheetsByDate(items: ResolvedDay[]) { const map: Record = {}; for (const item of items) { @@ -214,7 +213,7 @@ function groupSheetsByDate(items: ResolvedDay[]) { // ──────────────────────────────────────────────────────────── // -function extractDaySchedule(sheet: Worksheet, teacherMap: Record, themeColors: ThemeColors | null, flags: Flag[]) { +export function extractDaySchedule(sheet: Worksheet, teacherMap: Record, themeColors: ThemeColors | null, flags: Flag[]) { return { changes: extractClassChanges(sheet, themeColors, flags), absence: extractAbsence(sheet, teacherMap), diff --git a/tests/content/1.xlsx b/tests/content/1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3b0e755bee78146d87fa3dbb1534960b2ef8b127 GIT binary patch literal 9937 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 V3;WlzhVgVS>3uqp7teo?|366gBIf`A literal 0 HcmV?d00001 diff --git a/tests/teachermap.json b/tests/content/teachermap.json similarity index 100% rename from tests/teachermap.json rename to tests/content/teachermap.json diff --git a/tests/parseAbsence.test.ts b/tests/parseAbsence.test.ts new file mode 100644 index 0000000..894db17 --- /dev/null +++ b/tests/parseAbsence.test.ts @@ -0,0 +1,353 @@ +import { describe, it, expect } from "vitest"; +import fs from "fs"; +import parseAbsence from "../scrape/utils/parseAbsence.js"; + +const teachermap: Record = JSON.parse( + fs.readFileSync("./tests/content/teachermap.json", "utf8"), +); + +const cases: [string, any[]][] = [ + [ + "Me", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null, + }, + ], + ], + [ + "ad", + [ + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null, + }, + ], + ], + [ + "me ad", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null, + }, + ], + ], + [ + "me 3", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "single", + hours: 3, + }, + ], + ], + [ + "ad 1", + [ + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "single", + hours: 1, + }, + ], + ], + [ + "me 2-4", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "range", + hours: { from: 2, to: 4 }, + }, + ], + ], + [ + "ad 5,6", + [ + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 5, to: 6 }, + }, + ], + ], + [ + "me 7+", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "range", + hours: { from: 7, to: 10 }, + }, + ], + ], + [ + "me,ad 3-5", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "range", + hours: { from: 3, to: 5 }, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 3, to: 5 }, + }, + ], + ], + [ + "me;ad 4", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "single", + hours: 4, + }, + ], + ], + [ + "me;ad;bo 2-3", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null, + }, + { + teacher: "Ing. Anna Bodnárová", + teacherCode: "bo", + type: "range", + hours: { from: 2, to: 3 }, + }, + ], + ], + [ + "me 2 ad 4-5", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "single", + hours: 2, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 4, to: 5 }, + }, + ], + ], + [ + "3", + [ + { + type: "invalid", + teacher: null, + teacherCode: null, + hours: null, + original: "3", + }, + ], + ], + [ + "2-4", + [ + { + type: "invalid", + teacher: null, + teacherCode: null, + hours: null, + original: "2-4", + }, + ], + ], + [ + "me Xx 3", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null, + }, + { + teacher: null, + teacherCode: "xx", + type: "single", + hours: 3, + }, + ], + ], + [ + "me,ad;bo 1-2 ad 5+", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "wholeDay", + hours: null, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "wholeDay", + hours: null, + }, + { + teacher: "Ing. Anna Bodnárová", + teacherCode: "bo", + type: "range", + hours: { from: 1, to: 2 }, + }, + { + teacher: "Bc. Daniel Adámek", + teacherCode: "ad", + type: "range", + hours: { from: 5, to: 10 }, + }, + ], + ], + [ + "Me-exk", + [ + { + teacher: "Michaela Meitnerová", + teacherCode: "me", + type: "exkurze", + hours: null, + }, + ], + ], + [ + "za Vn zastupuje Jk", + [ + { + teacher: "Ing. Zdeněk Vondra", + teacherCode: "vn", + type: "zastoupen", + hours: null, + zastupuje: { + teacher: "David Janoušek", + teacherCode: "jk", + }, + }, + ], + ], + [ + "Vc 1. h", + [ + { + teacher: "Ing. Antonín Vobecký", + teacherCode: "vc", + type: "single", + hours: 1, + }, + ], + ], + [ + "Sv-5-exk", + [ + { + teacher: "Ing. Jan Šváb", + teacherCode: "sv", + type: "exkurze", + hours: { from: 5, to: 10 }, + }, + ], + ], + [ + "Ex-exk. 3+", + [ + { + teacher: "Ing. Jana Exnerová", + teacherCode: "ex", + type: "exkurze", + hours: { from: 3, to: 10 }, + }, + ], + ], + [ + "Ex-exk.3+", + [ + { + teacher: "Ing. Jana Exnerová", + teacherCode: "ex", + type: "exkurze", + hours: { from: 3, to: 10 }, + }, + ], + ], + [ + "Ex-exk. 3", + [ + { + teacher: "Ing. Jana Exnerová", + teacherCode: "ex", + type: "exkurze", + hours: 3, + }, + ], + ], + [ + "Su 4 - 6", + [ + { + teacher: "MUDr. Kristina Studénková", + teacherCode: "su", + type: "range", + hours: { from: 4, to: 6 }, + }, + ], + ], +]; + +function expectSameItems(actual: any[], expected: any[]) { + expect(actual).toHaveLength(expected.length); + for (const item of expected) { + expect(actual).toContainEqual(item); + } +} + +describe("parseAbsence", () => { + it.each(cases)("parseAbsence(%s) → %j", (input, expected) => { + expectSameItems(parseAbsence(input, teachermap), expected); + }); +}); diff --git a/tests/parseXlsx.test.ts b/tests/parseXlsx.test.ts new file mode 100644 index 0000000..e09720b --- /dev/null +++ b/tests/parseXlsx.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import fs from "fs"; +import ExcelJS from "exceljs"; +import { + getThemeColors, + getUpcomingSheets, + groupSheetsByDate, + extractDaySchedule, +} from "../scrape/parse/v3.js"; + +const XLSX_PATH = "tests/content/1.xlsx"; +const TEACHERMAP_PATH = "tests/content/teachermap.json"; + +let workbook: ExcelJS.Workbook; +let teachermap: Record; + +beforeAll(async () => { + workbook = new ExcelJS.Workbook(); + await workbook.xlsx.readFile(XLSX_PATH); + teachermap = JSON.parse(fs.readFileSync(TEACHERMAP_PATH, "utf8")); +}); + +describe("sheet filtering", () => { + it("includes sheets on or after the injected date", () => { + const injected = new Date(2026, 5, 2); // June 2, 2026 + const sheets = getUpcomingSheets(workbook, injected); + + expect(sheets.length).toBeGreaterThanOrEqual(1); + expect(sheets.map((s) => s.dateKey)).toContain("2026-06-02"); + }); + + it("excludes sheets before the injected date", () => { + const injected = new Date(2026, 5, 2); + const sheets = getUpcomingSheets(workbook, injected); + + for (const s of sheets) { + const [y, m, d] = s.dateKey.split("-").map(Number); + expect(new Date(y, m - 1, d).getTime()).toBeGreaterThanOrEqual( + injected.getTime(), + ); + } + }); +}); + +describe("full day parsing", () => { + let result: ReturnType; + let sheet: ExcelJS.Worksheet; + let themeColors: Awaited>; + + beforeAll(async () => { + const injected = new Date(2026, 5, 2); + const upcoming = getUpcomingSheets(workbook, injected); + const resolved = groupSheetsByDate(upcoming); + const day = resolved.find((d) => d.dateKey === "2026-06-02")!; + sheet = day.sheet; + themeColors = await getThemeColors(XLSX_PATH); + result = extractDaySchedule(sheet, teachermap, themeColors, []); + }); + + describe("changes", () => { + it("is a non-empty record of class → lessons", () => { + const keys = Object.keys(result.changes); + expect(keys.length).toBeGreaterThan(0); + + for (const [cls, lessons] of Object.entries(result.changes)) { + expect(cls).toMatch(/^[AEC][0-4][a-c]?$/); + expect(lessons).toHaveLength(10); + for (const lesson of lessons) { + if (lesson === null) continue; + expect(lesson).toHaveProperty("text"); + expect(typeof lesson.text).toBe("string"); + expect(lesson).toHaveProperty("backgroundColor"); + } + } + }); + + it("includes A1a with known lesson at hour 5", () => { + const a1a = result.changes["A1a"]; + expect(a1a).toBeDefined(); + const hour5 = a1a[4]; + expect(hour5).not.toBeNull(); + expect(hour5!.text).toContain("ZE"); + }); + }); + + describe("absence", () => { + it("is an array of parsed absence entries", () => { + expect(Array.isArray(result.absence)).toBe(true); + expect(result.absence.length).toBeGreaterThan(0); + + for (const entry of result.absence) { + expect(entry).toHaveProperty("teacher"); + expect(entry).toHaveProperty("teacherCode"); + expect(entry).toHaveProperty("type"); + expect(entry).toHaveProperty("hours"); + } + }); + + it("contains a known absence entry", () => { + const su = result.absence.find((a: any) => a.teacherCode === "su"); + expect(su).toBeDefined(); + expect(su!.teacher).toContain("Studénková"); + }); + }); + + describe("takesPlace", () => { + it("is a non-empty string", () => { + expect(typeof result.takesPlace).toBe("string"); + expect(result.takesPlace.length).toBeGreaterThan(0); + }); + }); + + describe("reservedRooms", () => { + it("is an array of 10 entries", () => { + expect(result.reservedRooms).toHaveLength(10); + }); + }); +}); diff --git a/tests/test.ts b/tests/test.ts deleted file mode 100644 index 2f223c4..0000000 --- a/tests/test.ts +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2025 Jakub Žitník - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -import fs from "fs"; -import parseAbsence from "../scrape/utils/parseAbsence.js"; - -const teachermap: Record = JSON.parse(fs.readFileSync("./tests/teachermap.json", "utf8")); -let passedAll = true; - -test("Me", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "wholeDay", - hours: null, - }, -]); - -test("ad", [ - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "wholeDay", - hours: null, - }, -]); - -test("me ad", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "wholeDay", - hours: null, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "wholeDay", - hours: null, - }, -]); - -test("me 3", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "single", - hours: 3, - }, -]); - -test("ad 1", [ - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "single", - hours: 1, - }, -]); - -test("me 2-4", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "range", - hours: { from: 2, to: 4 }, - }, -]); - -test("ad 5,6", [ - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "range", - hours: { from: 5, to: 6 }, - }, -]); - -test("me 7+", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "range", - hours: { from: 7, to: 10 }, - }, -]); - -test("me,ad 3-5", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "range", - hours: { from: 3, to: 5 }, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "range", - hours: { from: 3, to: 5 }, - }, -]); - -test("me;ad 4", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "wholeDay", - hours: null, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "single", - hours: 4, - }, -]); - -test("me;ad;bo 2-3", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "wholeDay", - hours: null, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "wholeDay", - hours: null, - }, - { - teacher: "Ing. Anna Bodnárová", - teacherCode: "bo", - type: "range", - hours: { from: 2, to: 3 }, - }, -]); - -test("me 2 ad 4-5", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "single", - hours: 2, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "range", - hours: { from: 4, to: 5 }, - }, -]); - -test("3", [ - { - type: "invalid", - teacher: null, - teacherCode: null, - hours: null, - original: "3", - }, -]); - -test("2-4", [ - { - type: "invalid", - teacher: null, - teacherCode: null, - hours: null, - original: "2-4", - }, -]); - -test("me Xx 3", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "wholeDay", - hours: null, - }, - { teacher: null, teacherCode: "xx", type: "single", hours: 3 }, -]); - -test("me,ad;bo 1-2 ad 5+", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "wholeDay", - hours: null, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "wholeDay", - hours: null, - }, - { - teacher: "Ing. Anna Bodnárová", - teacherCode: "bo", - type: "range", - hours: { from: 1, to: 2 }, - }, - { - teacher: "Bc. Daniel Adámek", - teacherCode: "ad", - type: "range", - hours: { from: 5, to: 10 }, - }, -]); - -test("Me-exk", [ - { - teacher: "Michaela Meitnerová", - teacherCode: "me", - type: "exkurze", - hours: null, - }, -]); - -test("za Vn zastupuje Jk", [ - { - teacher: "Ing. Zdeněk Vondra", - teacherCode: "vn", - type: "zastoupen", - hours: null, - zastupuje: { - teacher: "David Janoušek", - teacherCode: "jk", - }, - }, -]); - -test("Vc 1. h", [ - { - teacher: "Ing. Antonín Vobecký", - teacherCode: "vc", - type: "single", - hours: 1, - }, -]); - -test("Sv-5-exk", [ - { - teacher: "Ing. Jan Šváb", - teacherCode: "sv", - type: "exkurze", - hours: { from: 5, to: 10 }, - }, -]); - -test("Ex-exk. 3+", [ - { - teacher: "Ing. Jana Exnerová", - teacherCode: "ex", - type: "exkurze", - hours: { from: 3, to: 10 }, - }, -]); - -test("Ex-exk.3+", [ - { - teacher: "Ing. Jana Exnerová", - teacherCode: "ex", - type: "exkurze", - hours: { from: 3, to: 10 }, - }, -]); - -test("Ex-exk. 3", [ - { - teacher: "Ing. Jana Exnerová", - teacherCode: "ex", - type: "exkurze", - hours: 3, - }, -]); - -test("Su 4 - 6", [ - { - teacher: "MUDr. Kristina Studénková", - teacherCode: "su", - type: "range", - hours: {from: 4, to: 6}, - } -]); - -function test(input: string, expectedOutput: any[]) { - const res = parseAbsence(input, teachermap); - - if (!deepEqual(res, expectedOutput)) { - passedAll = false; - console.error("ERROR for input: " + input); - console.log(JSON.stringify(res, null, 2)); - console.log(JSON.stringify(expectedOutput, null, 2)); - console.log(); - } -} - -function deepEqual(a: any, b: any): boolean { - if (a === b) return true; - - if ( - typeof a !== "object" || - a === null || - typeof b !== "object" || - b === null - ) { - return false; - } - - // Handle arrays (ignore order) - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false; - - const used = new Array(b.length).fill(false); - - return a.every((itemA) => - b.some((itemB, i) => { - if (used[i]) return false; // don't reuse elements - if (deepEqual(itemA, itemB)) { - used[i] = true; // mark element as used - return true; - } - return false; - }), - ); - } - - if (Array.isArray(a) !== Array.isArray(b)) return false; - - const keysA = Object.keys(a); - const keysB = Object.keys(b); - - if (keysA.length !== keysB.length) return false; - - return keysA.every((key) => keysB.includes(key) && deepEqual(a[key], b[key])); -} - -if (passedAll) { - console.log("All tests passed"); -}