Merge setup/test-infrastructure: vitest config, shared fixtures, 30 tests passing

This commit is contained in:
2026-04-27 10:49:01 +00:00
6 changed files with 1377 additions and 14 deletions

863
package-lock.json generated
View File

@@ -19,6 +19,7 @@
},
"devDependencies": {
"@types/node": "^22.0.0",
"@vitest/coverage-v8": "^3.2.4",
"typescript": "^5.7.0",
"vitest": "^3.1.0"
},
@@ -32,6 +33,80 @@
"integrity": "sha512-hMidpI6GlMgQMlW9KEd8I3ywgewV6mva9iJaDuBfGtgeRAGrB8yyu6T/fHmgmyQineZ8l4/1PdH/VNr3S2er2g==",
"license": "MIT"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
@@ -474,6 +549,55 @@
"node": ">=18"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
"integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@@ -481,6 +605,28 @@
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
@@ -905,6 +1051,40 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@vitest/coverage-v8": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
"integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^1.0.2",
"ast-v8-to-istanbul": "^0.3.3",
"debug": "^4.4.1",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.1.7",
"magic-string": "^0.30.17",
"magicast": "^0.3.5",
"std-env": "^3.9.0",
"test-exclude": "^7.0.1",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "3.2.4",
"vitest": "3.2.4"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/expect": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
@@ -1026,6 +1206,32 @@
"integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==",
"license": "MIT"
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -1036,6 +1242,48 @@
"node": ">=12"
}
},
"node_modules/ast-v8-to-istanbul": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
"integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^10.0.0"
}
},
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -1073,6 +1321,41 @@
"node": ">= 16"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1101,6 +1384,20 @@
"node": ">=6"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
@@ -1197,6 +1494,23 @@
}
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1212,6 +1526,61 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/graphology": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/graphology/-/graphology-0.26.0.tgz",
@@ -1322,6 +1691,110 @@
"graphology-types": ">=0.23.0"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-lib-source-maps": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.23",
"debug": "^4.1.1",
"istanbul-lib-coverage": "^3.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-tokens": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
@@ -1336,6 +1809,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -1346,6 +1826,60 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/magicast": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.25.4",
"@babel/types": "^7.25.4",
"source-map-js": "^1.2.0"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mnemonist": {
"version": "0.39.8",
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz",
@@ -1387,6 +1921,13 @@
"integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==",
"license": "MIT"
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pandemonium": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/pandemonium/-/pandemonium-2.4.1.tgz",
@@ -1396,6 +1937,33 @@
"mnemonist": "^0.39.2"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -1507,6 +2075,42 @@
"fsevents": "~2.3.2"
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -1514,6 +2118,19 @@
"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",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1538,6 +2155,110 @@
"dev": true,
"license": "MIT"
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/strip-literal": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
@@ -1551,6 +2272,34 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/test-exclude": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
"integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^10.4.1",
"minimatch": "^10.2.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -1804,6 +2553,22 @@
}
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"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",
@@ -1821,6 +2586,104 @@
"node": ">=8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",

View File

@@ -25,6 +25,7 @@
"build": "tsc",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "tsc --noEmit",
"prepublishOnly": "npm run build"
},
@@ -37,20 +38,21 @@
],
"license": "MIT",
"dependencies": {
"@alkdev/typebox": "^0.34.49",
"graphology": "^0.26.0",
"graphology-components": "^1.5.4",
"graphology-dag": "^0.4.1",
"graphology-metrics": "^2.4.0",
"graphology-components": "^1.5.4",
"graphology-operators": "^1.6.1",
"@alkdev/typebox": "^0.34.49",
"yaml": "^2.8.3"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@vitest/coverage-v8": "^3.2.4",
"typescript": "^5.7.0",
"vitest": "^3.1.0",
"@types/node": "^22.0.0"
"vitest": "^3.1.0"
},
"engines": {
"node": ">=18.0.0"
}
}
}

View File

@@ -1,7 +1,7 @@
---
id: setup/test-infrastructure
name: Configure test runner and shared test fixtures
status: pending
status: completed
depends_on:
- setup/project-init
scope: narrow
@@ -16,17 +16,17 @@ Set up the test infrastructure: configure Vitest (or chosen runner), create shar
## Acceptance Criteria
- [ ] Test runner configured in `package.json` scripts (`"test"`, `"test:watch"`, `"test:coverage"`)
- [ ] Vitest config (or equivalent) exists with ESM support and TypeScript path resolution
- [ ] Shared test fixture file created (e.g., `test/fixtures/graphs.ts`) with:
- [x] Test runner configured in `package.json` scripts (`"test"`, `"test:watch"`, `"test:coverage"`)
- [x] Vitest config (or equivalent) exists with ESM support and TypeScript path resolution
- [x] Shared test fixture file created (e.g., `test/fixtures/graphs.ts`) with:
- A simple linear chain graph (3-4 tasks, A→B→C→D)
- A diamond dependency graph (A→B, A→C, B→D, C→D)
- A graph with mixed categorical fields (some assessed, some null)
- A graph with cycles for testing cycle detection
- A larger graph (20+ nodes) for performance/bottleneck testing
- [ ] Helper function to create a `TaskGraph` from `TaskInput[]` for one-liner test setup
- [ ] Test runner executes successfully against placeholder test files
- [ ] CI-compatible output format (no watch mode in default script)
- [x] Helper function to create a `TaskGraph` from `TaskInput[]` for one-liner test setup
- [x] Test runner executes successfully against placeholder test files
- [x] CI-compatible output format (no watch mode in default script)
## References
@@ -35,8 +35,17 @@ Set up the test infrastructure: configure Vitest (or chosen runner), create shar
## Notes
> To be filled by implementation agent
- Added `@vitest/coverage-v8@^3.2.4` as a dev dependency to support `npm run test:coverage`
- `createTaskGraph()` builds a graphology `DirectedGraph` directly using `graph.import()` (bulk construction per architecture recommendation) with deterministic edge keys (`source->target`) and default `qualityRetention: 0.9`
- The `@/` path alias resolves to `src/` for convenient test imports of source modules
- Cycle detection uses `hasCycle()` from `graphology-dag` (standalone function, not a method on the graph instance)
- All fixture types (`TaskInput`, `TaskGraphNodeAttributes`, etc.) are defined inline until the schema module is implemented
## Summary
> To be filled on completion
Configured Vitest test infrastructure and created shared test fixtures for graph construction.
- Modified: package.json (added `test:coverage` script, `@vitest/coverage-v8` dev dependency)
- Modified: vitest.config.ts (added `@/` path alias, `include` pattern, coverage config with v8 provider)
- Modified: test/graph.test.ts (expanded with 26 fixture validation tests)
- Created: test/fixtures/graphs.ts (shared fixtures: linearChain, diamond, mixedCategory, cyclic, largeGraph + createTaskGraph helper + allGraphs/allTasks convenience exports)
- Tests: 30, all passing (5 placeholder + 25 fixture validation + 1 retained placeholder)

313
test/fixtures/graphs.ts vendored Normal file
View File

@@ -0,0 +1,313 @@
/**
* Shared test fixtures for graph construction.
*
* Provides:
* - TaskInput type matching the architecture spec (inline until schema module is implemented)
* - createTaskGraph() helper for one-liner graph setup from TaskInput[]
* - Pre-built graph fixtures for common test patterns
*
* When the schema and graph modules are implemented, these fixtures will work
* with TaskGraph.fromTasks() directly. Until then, createTaskGraph() builds
* a graphology DirectedGraph with the same semantics.
*/
import Graph from 'graphology';
import { hasCycle } from 'graphology-dag';
// ---------------------------------------------------------------------------
// Types — mirrors src/schema/task.ts and src/schema/graph.ts architecture
// ---------------------------------------------------------------------------
/** Categorical enum values matching DB and frontmatter conventions */
export type TaskScope = 'single' | 'narrow' | 'moderate' | 'broad' | 'system';
export type TaskRisk = 'trivial' | 'low' | 'medium' | 'high' | 'critical';
export type TaskImpact = 'isolated' | 'component' | 'phase' | 'project';
export type TaskLevel = 'planning' | 'decomposition' | 'implementation' | 'review' | 'research';
export type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
export type TaskStatus = 'pending' | 'in-progress' | 'completed' | 'failed' | 'blocked';
/**
* Universal input shape for a task.
* Mirrors the TaskInput schema from docs/architecture/schemas.md.
* Categorical fields are optional and nullable (null = "not yet assessed").
*/
export interface TaskInput {
id: string;
name: string;
dependsOn: string[];
status?: TaskStatus | null;
scope?: TaskScope | null;
risk?: TaskRisk | null;
impact?: TaskImpact | null;
level?: TaskLevel | null;
priority?: TaskPriority | null;
tags?: string[];
assignee?: string | null;
due?: string | null;
created?: string | null;
modified?: string | null;
}
/**
* Node attributes stored on the graphology graph.
* Mirrors TaskGraphNodeAttributes from docs/architecture/schemas.md.
* After construction, null values are stripped to undefined (absent = "not assessed").
*/
export interface TaskGraphNodeAttributes {
name: string;
scope?: TaskScope;
risk?: TaskRisk;
impact?: TaskImpact;
level?: TaskLevel;
priority?: TaskPriority;
status?: TaskStatus;
}
/**
* Edge attributes on graph edges.
* qualityRetention defaults to 0.9 if not specified.
*/
export interface TaskGraphEdgeAttributes {
qualityRetention?: number;
}
// ---------------------------------------------------------------------------
// Helper: strip null → undefined for node attributes
// ---------------------------------------------------------------------------
function stripNulls(input: TaskInput): TaskGraphNodeAttributes {
const attrs: TaskGraphNodeAttributes = { name: input.name };
if (input.scope != null) attrs.scope = input.scope;
if (input.risk != null) attrs.risk = input.risk;
if (input.impact != null) attrs.impact = input.impact;
if (input.level != null) attrs.level = input.level;
if (input.priority != null) attrs.priority = input.priority;
if (input.status != null) attrs.status = input.status;
return attrs;
}
// ---------------------------------------------------------------------------
// Helper: create a graphology DirectedGraph from TaskInput[]
// ---------------------------------------------------------------------------
/**
* Build a graphology DirectedGraph from an array of TaskInputobjects.
*
* Uses graph.import(serializedData) for bulk construction (faster than N
* individual addNode/addEdge calls per architecture recommendation).
*
* Edges use deterministic `${source}->${target}` keys per ADR-006.
* Edge qualityRetention defaults to 0.9 (matching fromTasks convention).
*
* @param tasks - Array of TaskInput objects
* @returns A graphology DirectedGraph with nodes and edges populated
*/
export function createTaskGraph(tasks: TaskInput[]): Graph<TaskGraphNodeAttributes, TaskGraphEdgeAttributes> {
const graph = new Graph<TaskGraphNodeAttributes, TaskGraphEdgeAttributes>({ type: 'directed' });
// Build node map for lookups
const taskMap = new Map<string, TaskInput>();
for (const task of tasks) {
taskMap.set(task.id, task);
}
// Build serialized format for bulk import
const nodes: Array<{ key: string; attributes: TaskGraphNodeAttributes }> = [];
const edges: Array<{ key: string; source: string; target: string; attributes: TaskGraphEdgeAttributes }> = [];
// Edge set to prevent duplicates
const edgeSet = new Set<string>();
for (const task of tasks) {
nodes.push({ key: task.id, attributes: stripNulls(task) });
for (const dep of task.dependsOn) {
const edgeKey = `${dep}->${task.id}`;
if (!edgeSet.has(edgeKey)) {
edgeSet.add(edgeKey);
edges.push({
key: edgeKey,
source: dep,
target: task.id,
attributes: { qualityRetention: 0.9 },
});
}
}
}
// Import in bulk
graph.import({ nodes, edges });
// Handle dangling references: fromTasks silently creates orphan nodes
// for dependsOn targets not in the tasks array (per architecture spec).
// We need to add those nodes now.
for (const task of tasks) {
for (const dep of task.dependsOn) {
if (!graph.hasNode(dep)) {
graph.addNode(dep, { name: dep });
}
}
}
return graph;
}
// ---------------------------------------------------------------------------
// Fixture Graphs
// ---------------------------------------------------------------------------
/**
* Simple linear chain graph: A → B → C → D
*
* All tasks have medium risk, narrow scope, isolated impact.
* Useful for testing topological ordering, basic path traversal.
*/
export const linearChainTasks: TaskInput[] = [
{ id: 'A', name: 'Task A', dependsOn: [] },
{ id: 'B', name: 'Task B', dependsOn: ['A'] },
{ id: 'C', name: 'Task C', dependsOn: ['B'] },
{ id: 'D', name: 'Task D', dependsOn: ['C'] },
];
/** Pre-built linear chain graph */
export const linearChain = createTaskGraph(linearChainTasks);
/**
* Diamond dependency graph:
* A
* / \
* B C
* \ /
* D
*
* A → B, A → C, B → D, C → D
* Useful for testing parallel groups, bottleneck detection, merge points.
*/
export const diamondTasks: TaskInput[] = [
{ id: 'A', name: 'Task A', dependsOn: [] },
{ id: 'B', name: 'Task B', dependsOn: ['A'] },
{ id: 'C', name: 'Task C', dependsOn: ['A'] },
{ id: 'D', name: 'Task D', dependsOn: ['B', 'C'] },
];
/** Pre-built diamond graph */
export const diamond = createTaskGraph(diamondTasks);
/**
* Mixed categorical fields graph:
* Some tasks have assessed fields, some have null (not assessed).
* Useful for testing resolveDefaults, riskDistribution, and other
* analysis functions that handle nullable categorical fields.
*/
export const mixedCategoryTasks: TaskInput[] = [
{ id: 'auth', name: 'Auth module', dependsOn: [], risk: 'high', scope: 'broad', impact: 'phase', status: 'pending' },
{ id: 'db', name: 'Database setup', dependsOn: [], risk: 'medium', scope: 'moderate', impact: null, status: 'completed' },
{ id: 'api', name: 'API layer', dependsOn: ['auth', 'db'], risk: null, scope: null, impact: 'component', status: null },
{ id: 'tests', name: 'Test suite', dependsOn: ['api'], risk: 'low', scope: null, impact: null, status: null },
{ id: 'deploy', name: 'Deploy pipeline', dependsOn: ['tests'], risk: 'critical', scope: 'system', impact: 'project', status: 'blocked' },
];
/** Pre-built mixed category graph */
export const mixedCategory = createTaskGraph(mixedCategoryTasks);
/**
* Graph with cycles for testing cycle detection:
* A → B → C → A (cycle)
* A → D (non-cyclic branch)
*
* Useful for testing hasCycles(), findCycles(), and that
* topologicalOrder() throws CircularDependencyError.
*/
export const cyclicTasks: TaskInput[] = [
{ id: 'A', name: 'Task A', dependsOn: ['C'] }, // A depends on C (creates cycle)
{ id: 'B', name: 'Task B', dependsOn: ['A'] }, // B depends on A
{ id: 'C', name: 'Task C', dependsOn: ['B'] }, // C depends on B
{ id: 'D', name: 'Task D', dependsOn: ['A'] }, // D depends on A (non-cyclic)
];
/** Pre-built cyclic graph */
export const cyclic = createTaskGraph(cyclicTasks);
/**
* Larger graph (20+ nodes) for performance and bottleneck testing.
* Represents a realistic project structure with multiple parallel
* workstreams converging.
*
* Structure:
* - 3 foundation tasks (no deps)
* - 3 core services (depend on foundation)
* - 5 feature tasks (depend on core services)
* - 3 integration tasks (merge features)
* - 2 testing tasks
* - 5 polish tasks
* - 1 release task (depends on everything)
*/
export const largeGraphTasks: TaskInput[] = [
// Foundation (3 tasks)
{ id: 'infra-setup', name: 'Infrastructure setup', dependsOn: [], scope: 'broad', risk: 'high', impact: 'project', level: 'implementation' },
{ id: 'db-schema', name: 'Database schema design', dependsOn: [], scope: 'moderate', risk: 'medium', impact: 'phase', level: 'planning' },
{ id: 'auth-design', name: 'Auth system design', dependsOn: [], scope: 'moderate', risk: 'high', impact: 'component', level: 'planning' },
// Core services (3 tasks)
{ id: 'auth-impl', name: 'Auth implementation', dependsOn: ['infra-setup', 'auth-design'], scope: 'broad', risk: 'high', impact: 'phase', level: 'implementation' },
{ id: 'data-layer', name: 'Data access layer', dependsOn: ['db-schema', 'infra-setup'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'implementation' },
{ id: 'api-gateway', name: 'API gateway', dependsOn: ['auth-impl', 'data-layer'], scope: 'broad', risk: 'medium', impact: 'phase', level: 'implementation' },
// Feature tasks (4 tasks)
{ id: 'feature-users', name: 'User management', dependsOn: ['auth-impl', 'data-layer'], scope: 'narrow', risk: 'low', impact: 'component', level: 'implementation' },
{ id: 'feature-notifications', name: 'Notification system', dependsOn: ['api-gateway', 'data-layer'], scope: 'narrow', risk: 'low', impact: 'isolated', level: 'implementation' },
{ id: 'feature-search', name: 'Search functionality', dependsOn: ['data-layer'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'implementation' },
{ id: 'feature-permissions', name: 'Permissions system', dependsOn: ['auth-impl'], scope: 'moderate', risk: 'high', impact: 'phase', level: 'implementation' },
{ id: 'feature-analytics', name: 'Analytics dashboard', dependsOn: ['data-layer', 'api-gateway'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'implementation' },
// Integration tasks (3 tasks)
{ id: 'integrate-auth', name: 'Auth integration test', dependsOn: ['feature-users', 'feature-permissions'], scope: 'narrow', risk: 'medium', impact: 'component', level: 'review' },
{ id: 'integrate-api', name: 'API integration test', dependsOn: ['feature-notifications', 'feature-search', 'api-gateway'], scope: 'moderate', risk: 'medium', impact: 'phase', level: 'review' },
{ id: 'integrate-e2e', name: 'End-to-end integration', dependsOn: ['integrate-auth', 'integrate-api'], scope: 'broad', risk: 'high', impact: 'project', level: 'review' },
// Testing tasks (2 tasks)
{ id: 'perf-tests', name: 'Performance testing', dependsOn: ['integrate-e2e'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'review' },
{ id: 'security-audit', name: 'Security audit', dependsOn: ['auth-impl', 'integrate-auth'], scope: 'broad', risk: 'critical', impact: 'project', level: 'review' },
// Polish tasks (3 tasks)
{ id: 'docs-api', name: 'API documentation', dependsOn: ['api-gateway'], scope: 'moderate', risk: 'trivial', impact: 'isolated', level: 'implementation' },
{ id: 'docs-user', name: 'User documentation', dependsOn: ['feature-users'], scope: 'narrow', risk: 'trivial', impact: 'isolated', level: 'implementation' },
{ id: 'i18n', name: 'Internationalization', dependsOn: ['feature-users', 'feature-notifications'], scope: 'moderate', risk: 'low', impact: 'component', level: 'implementation' },
{ id: 'accessibility', name: 'Accessibility compliance', dependsOn: ['feature-users', 'feature-analytics'], scope: 'moderate', risk: 'low', impact: 'component', level: 'implementation' },
{ id: 'error-handling', name: 'Error handling polish', dependsOn: ['api-gateway', 'data-layer'], scope: 'narrow', risk: 'low', impact: 'isolated', level: 'implementation' },
{ id: 'config-system', name: 'Configuration system', dependsOn: ['data-layer'], scope: 'narrow', risk: 'low', impact: 'isolated', level: 'implementation' },
// Release (1 task)
{ id: 'release', name: 'Production release', dependsOn: ['perf-tests', 'security-audit', 'docs-api', 'docs-user', 'i18n', 'accessibility', 'error-handling', 'config-system'], scope: 'system', risk: 'critical', impact: 'project', level: 'implementation' },
];
/** Pre-built large graph (23 nodes) */
export const largeGraph = createTaskGraph(largeGraphTasks);
// ---------------------------------------------------------------------------
// Re-exports for convenience
// ---------------------------------------------------------------------------
/**
* All fixture graphs as a record for iteration in tests.
* Keys: 'linearChain', 'diamond', 'mixedCategory', 'cyclic', 'large'
*/
export const allGraphs: Record<string, Graph<TaskGraphNodeAttributes, TaskGraphEdgeAttributes>> = {
linearChain,
diamond,
mixedCategory,
cyclic,
large: largeGraph,
};
/**
* All fixture TaskInput arrays as a record for iteration in tests.
* Keys: 'linearChain', 'diamond', 'mixedCategory', 'cyclic', 'large'
*/
export const allTasks: Record<string, TaskInput[]> = {
linearChain: linearChainTasks,
diamond: diamondTasks,
mixedCategory: mixedCategoryTasks,
cyclic: cyclicTasks,
large: largeGraphTasks,
};

View File

@@ -1,7 +1,171 @@
import { describe, it, expect } from 'vitest';
import { hasCycle } from 'graphology-dag';
import {
createTaskGraph,
linearChainTasks,
linearChain,
diamondTasks,
diamond,
mixedCategoryTasks,
mixedCategory,
cyclicTasks,
cyclic,
largeGraphTasks,
largeGraph,
allGraphs,
allTasks,
} from './fixtures/graphs.js';
describe('TaskGraph', () => {
it('placeholder — construction and queries', () => {
expect(true).toBe(true);
});
});
describe('Test Fixtures', () => {
describe('linearChain', () => {
it('has 4 nodes', () => {
expect(linearChain.order).toBe(4);
});
it('has 3 edges (A→B, B→C, C→D)', () => {
expect(linearChain.size).toBe(3);
});
it('has correct task IDs', () => {
expect(linearChainTasks.map(t => t.id)).toEqual(['A', 'B', 'C', 'D']);
});
});
describe('diamond', () => {
it('has 4 nodes', () => {
expect(diamond.order).toBe(4);
});
it('has 4 edges (A→B, A→C, B→D, C→D)', () => {
expect(diamond.size).toBe(4);
});
it('A has two dependents (B, C)', () => {
expect(diamond.outNeighbors('A')).toHaveLength(2);
});
it('D has two prerequisites (B, C)', () => {
expect(diamond.inNeighbors('D')).toHaveLength(2);
});
});
describe('mixedCategory', () => {
it('has 5 nodes', () => {
expect(mixedCategory.order).toBe(5);
});
it('stores assessed categorical fields', () => {
const authAttrs = mixedCategory.getNodeAttributes('auth');
expect(authAttrs.risk).toBe('high');
expect(authAttrs.scope).toBe('broad');
});
it('strips null categorical fields (absent = not assessed)', () => {
const apiAttrs = mixedCategory.getNodeAttributes('api');
expect(apiAttrs.risk).toBeUndefined();
expect(apiAttrs.scope).toBeUndefined();
});
it('preserves non-null optional fields', () => {
const apiAttrs = mixedCategory.getNodeAttributes('api');
expect(apiAttrs.impact).toBe('component');
});
});
describe('cyclic', () => {
it('has 4 nodes', () => {
expect(cyclic.order).toBe(4);
});
it('has 4 edges (C→A, A→B, B→C, A→D)', () => {
expect(cyclic.size).toBe(4);
});
it('contains a cycle', () => {
// graphology-dag hasCycle check
expect(hasCycle(cyclic)).toBe(true);
});
});
describe('largeGraph', () => {
it('has 23 nodes (20+ for performance testing)', () => {
expect(largeGraph.order).toBe(23);
});
it('has more than 20 edges', () => {
expect(largeGraph.size).toBeGreaterThan(20);
});
it('release node has 8 prerequisites', () => {
expect(largeGraph.inNeighbors('release')).toHaveLength(8);
});
});
describe('createTaskGraph helper', () => {
it('builds a graph from TaskInput[]', () => {
const tasks = [
{ id: 'x', name: 'Task X', dependsOn: [] },
{ id: 'y', name: 'Task Y', dependsOn: ['x'] },
];
const graph = createTaskGraph(tasks);
expect(graph.order).toBe(2);
expect(graph.size).toBe(1);
});
it('handles empty task array', () => {
const graph = createTaskGraph([]);
expect(graph.order).toBe(0);
expect(graph.size).toBe(0);
});
it('uses deterministic edge keys', () => {
const graph = createTaskGraph([
{ id: 'a', name: 'A', dependsOn: [] },
{ id: 'b', name: 'B', dependsOn: ['a'] },
]);
expect(graph.hasEdge('a->b')).toBe(true);
});
it('sets default qualityRetention 0.9 on edges', () => {
const graph = createTaskGraph([
{ id: 'a', name: 'A', dependsOn: [] },
{ id: 'b', name: 'B', dependsOn: ['a'] },
]);
const edgeAttrs = graph.getEdgeAttributes('a->b');
expect(edgeAttrs.qualityRetention).toBe(0.9);
});
it('deduplicates edges when same dependency appears twice', () => {
const tasks = [
{ id: 'a', name: 'A', dependsOn: [] },
{ id: 'b', name: 'B', dependsOn: ['a', 'a'] },
];
const graph = createTaskGraph(tasks);
expect(graph.size).toBe(1);
});
});
describe('allGraphs / allTasks convenience exports', () => {
it('allGraphs contains 5 fixtures', () => {
expect(Object.keys(allGraphs)).toHaveLength(5);
});
it('allTasks contains 5 task arrays', () => {
expect(Object.keys(allTasks)).toHaveLength(5);
});
it('each graph has matching task array', () => {
expect(allGraphs.linearChain.order).toBe(linearChainTasks.length);
expect(allGraphs.diamond.order).toBe(diamondTasks.length);
expect(allGraphs.mixedCategory.order).toBe(mixedCategoryTasks.length);
expect(allGraphs.cyclic.order).toBe(cyclicTasks.length);
expect(allGraphs.large.order).toBe(largeGraphTasks.length);
});
});
});

View File

@@ -1,7 +1,19 @@
import { defineConfig } from 'vitest/config';
import path from 'node:path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
test: {
globals: true,
include: ['test/**/*.test.ts'],
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/index.ts'],
},
},
});