Added SwiperJS swipers on ProductCards and added preliminary support for nested categories using a tree of TreeNode structures. Added unit tests for TreeNode.
This commit is contained in:
parent
9225b3c727
commit
935340d90e
297
package-lock.json
generated
297
package-lock.json
generated
|
@ -24,7 +24,8 @@
|
||||||
"playwright": "*",
|
"playwright": "*",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"swiper": "^11.1.4"
|
"swiper": "^11.1.4",
|
||||||
|
"vitest": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apify/tsconfig": "^0.1.0",
|
"@apify/tsconfig": "^0.1.0",
|
||||||
|
@ -1857,6 +1858,81 @@
|
||||||
"vite": "^4.2.0 || ^5.0.0"
|
"vite": "^4.2.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/expect": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "2.0.3",
|
||||||
|
"@vitest/utils": "2.0.3",
|
||||||
|
"chai": "^5.1.1",
|
||||||
|
"tinyrainbow": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/pretty-format": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==",
|
||||||
|
"dependencies": {
|
||||||
|
"tinyrainbow": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/runner": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/utils": "2.0.3",
|
||||||
|
"pathe": "^1.1.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/snapshot": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "2.0.3",
|
||||||
|
"magic-string": "^0.30.10",
|
||||||
|
"pathe": "^1.1.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/spy": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==",
|
||||||
|
"dependencies": {
|
||||||
|
"tinyspy": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/utils": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "2.0.3",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"loupe": "^3.1.1",
|
||||||
|
"tinyrainbow": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vladfrangu/async_event_emitter": {
|
"node_modules/@vladfrangu/async_event_emitter": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.0.tgz",
|
||||||
|
@ -2139,6 +2215,14 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/assertion-error": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/astro": {
|
"node_modules/astro": {
|
||||||
"version": "4.11.5",
|
"version": "4.11.5",
|
||||||
"resolved": "https://registry.npmjs.org/astro/-/astro-4.11.5.tgz",
|
"resolved": "https://registry.npmjs.org/astro/-/astro-4.11.5.tgz",
|
||||||
|
@ -2429,6 +2513,14 @@
|
||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cac": {
|
||||||
|
"version": "6.7.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||||
|
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cacheable-lookup": {
|
"node_modules/cacheable-lookup": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
|
||||||
|
@ -2529,6 +2621,21 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chai": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==",
|
||||||
|
"dependencies": {
|
||||||
|
"assertion-error": "^2.0.1",
|
||||||
|
"check-error": "^2.1.1",
|
||||||
|
"deep-eql": "^5.0.1",
|
||||||
|
"loupe": "^3.1.0",
|
||||||
|
"pathval": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -2571,6 +2678,14 @@
|
||||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/check-error": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cheerio": {
|
"node_modules/cheerio": {
|
||||||
"version": "1.0.0-rc.12",
|
"version": "1.0.0-rc.12",
|
||||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
|
||||||
|
@ -3053,6 +3168,14 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deep-eql": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/defaults": {
|
"node_modules/defaults": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||||
|
@ -3680,6 +3803,14 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-func-name": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-stream": {
|
"node_modules/get-stream": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -4966,6 +5097,14 @@
|
||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/loupe": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==",
|
||||||
|
"dependencies": {
|
||||||
|
"get-func-name": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lowercase-keys": {
|
"node_modules/lowercase-keys": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
|
||||||
|
@ -6309,6 +6448,19 @@
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
|
||||||
|
},
|
||||||
|
"node_modules/pathval": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pause-stream": {
|
"node_modules/pause-stream": {
|
||||||
"version": "0.0.11",
|
"version": "0.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||||
|
@ -7237,6 +7389,11 @@
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/siginfo": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="
|
||||||
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
@ -7355,6 +7512,16 @@
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"license": "BSD-3-Clause"
|
"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=="
|
||||||
|
},
|
||||||
|
"node_modules/std-env": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg=="
|
||||||
|
},
|
||||||
"node_modules/stdin-discarder": {
|
"node_modules/stdin-discarder": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -7542,6 +7709,35 @@
|
||||||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tinybench": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw=="
|
||||||
|
},
|
||||||
|
"node_modules/tinypool": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyrainbow": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyspy": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tldts": {
|
"node_modules/tldts": {
|
||||||
"version": "6.1.30",
|
"version": "6.1.30",
|
||||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.30.tgz",
|
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.30.tgz",
|
||||||
|
@ -8060,6 +8256,27 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-node": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"cac": "^6.7.14",
|
||||||
|
"debug": "^4.3.5",
|
||||||
|
"pathe": "^1.1.2",
|
||||||
|
"tinyrainbow": "^1.2.0",
|
||||||
|
"vite": "^5.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vite-node": "vite-node.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite/node_modules/fsevents": {
|
"node_modules/vite/node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
@ -8086,6 +8303,69 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ampproject/remapping": "^2.3.0",
|
||||||
|
"@vitest/expect": "2.0.3",
|
||||||
|
"@vitest/pretty-format": "^2.0.3",
|
||||||
|
"@vitest/runner": "2.0.3",
|
||||||
|
"@vitest/snapshot": "2.0.3",
|
||||||
|
"@vitest/spy": "2.0.3",
|
||||||
|
"@vitest/utils": "2.0.3",
|
||||||
|
"chai": "^5.1.1",
|
||||||
|
"debug": "^4.3.5",
|
||||||
|
"execa": "^8.0.1",
|
||||||
|
"magic-string": "^0.30.10",
|
||||||
|
"pathe": "^1.1.2",
|
||||||
|
"std-env": "^3.7.0",
|
||||||
|
"tinybench": "^2.8.0",
|
||||||
|
"tinypool": "^1.0.0",
|
||||||
|
"tinyrainbow": "^1.2.0",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vite-node": "2.0.3",
|
||||||
|
"why-is-node-running": "^2.2.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vitest": "vitest.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@edge-runtime/vm": "*",
|
||||||
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
|
"@vitest/browser": "2.0.3",
|
||||||
|
"@vitest/ui": "2.0.3",
|
||||||
|
"happy-dom": "*",
|
||||||
|
"jsdom": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@edge-runtime/vm": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/ui": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"happy-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jsdom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/volar-service-css": {
|
"node_modules/volar-service-css": {
|
||||||
"version": "0.0.59",
|
"version": "0.0.59",
|
||||||
"resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.59.tgz",
|
"resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.59.tgz",
|
||||||
|
@ -8386,6 +8666,21 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"dependencies": {
|
||||||
|
"siginfo": "^2.0.0",
|
||||||
|
"stackback": "0.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"why-is-node-running": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/widest-line": {
|
"node_modules/widest-line": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"build": "astro check && astro build",
|
"build": "astro check && astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.8.1",
|
"@astrojs/check": "^0.8.1",
|
||||||
|
@ -26,7 +27,8 @@
|
||||||
"playwright": "*",
|
"playwright": "*",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"swiper": "^11.1.4"
|
"swiper": "^11.1.4",
|
||||||
|
"vitest": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apify/tsconfig": "^0.1.0",
|
"@apify/tsconfig": "^0.1.0",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { TreeNode } from "../types/tree";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Category of Products.
|
* Category of Products.
|
||||||
*/
|
*/
|
||||||
|
@ -22,10 +24,18 @@ export interface Category {
|
||||||
* Description of Product Category.
|
* Description of Product Category.
|
||||||
*/
|
*/
|
||||||
description: string;
|
description: string;
|
||||||
|
// /**
|
||||||
|
// * Sub-categories.
|
||||||
|
// */
|
||||||
|
// categories?: Category[];
|
||||||
|
// /**
|
||||||
|
// * Parent category (do not assign this, it will be automatically created.)
|
||||||
|
// */
|
||||||
|
// parentCategory?: Category;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Static properties of categories.
|
||||||
*/
|
*/
|
||||||
export class StaticCategory {
|
export class StaticCategory {
|
||||||
/**
|
/**
|
||||||
|
@ -42,80 +52,81 @@ export class StaticCategory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const ALL_CATEGORIES = TreeNode.createRoot<Category>({
|
||||||
* A list of all the categories.
|
id: 0,
|
||||||
*/
|
category: "All Categories",
|
||||||
export const ALL_CATEGORIES: Category[] = [
|
slug: "all-categories",
|
||||||
{
|
description: "All categories in the whole store."
|
||||||
|
}).addChild({
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Vehicle Essentials",
|
category: "Vehicle Essentials",
|
||||||
slug: "vehicle-essentials",
|
slug: "vehicle-essentials",
|
||||||
imageUrl: "/assets/vehicle-essentials.png",
|
imageUrl: "/assets/vehicle-essentials.png",
|
||||||
description: "Essential items for your vehicle to ensure smooth deliveries.",
|
description: "Essential items for your vehicle to ensure smooth deliveries.",
|
||||||
},
|
}).addChild({
|
||||||
{
|
id: StaticCategory.nextId(),
|
||||||
|
category: "Deer Alert Warnings",
|
||||||
|
slug: "vehicle-essentials/deer-alert-warnings",
|
||||||
|
// imageUrl: "",
|
||||||
|
description: "Deer alert warning systems give you peace of mind.",
|
||||||
|
}).root().addChild({
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Delivery Gear",
|
category: "Delivery Gear",
|
||||||
slug: "delivery-gear",
|
slug: "delivery-gear",
|
||||||
imageUrl: "/assets/delivery-gear-3.jpg",
|
imageUrl: "/assets/delivery-gear-3.jpg",
|
||||||
description: "Gear to help you deliver food efficiently and keep it in top condition.",
|
description: "Gear to help you deliver food efficiently and keep it in top condition.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Personal Items",
|
category: "Personal Items",
|
||||||
slug: "personal-items",
|
slug: "personal-items",
|
||||||
imageUrl: "/assets/personal-items.jpg",
|
imageUrl: "/assets/personal-items.jpg",
|
||||||
description: "Personal essentials to keep you comfortable and prepared on the go.",
|
description: "Personal essentials to keep you comfortable and prepared on the go.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Safety Equipment",
|
category: "Safety Equipment",
|
||||||
slug: "safety-equipment",
|
slug: "safety-equipment",
|
||||||
imageUrl: "/assets/safety-equipment.jpg",
|
imageUrl: "/assets/safety-equipment.jpg",
|
||||||
description: "Safety gear to protect you during deliveries.",
|
description: "Safety gear to protect you during deliveries.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Tech Gadgets",
|
category: "Tech Gadgets",
|
||||||
slug: "tech-gadgets",
|
slug: "tech-gadgets",
|
||||||
imageUrl: "/assets/tech-gadgets.jpg",
|
imageUrl: "/assets/tech-gadgets.jpg",
|
||||||
description: "Technology tools to enhance your efficiency and connectivity.",
|
description: "Technology tools to enhance your efficiency and connectivity.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Biking Gear",
|
category: "Biking Gear",
|
||||||
slug: "biking-gear",
|
slug: "biking-gear",
|
||||||
imageUrl: "/assets/biking-gear.jpg",
|
imageUrl: "/assets/biking-gear.jpg",
|
||||||
description: "Equipment for Dashers who deliver by bike.",
|
description: "Equipment for Dashers who deliver by bike.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Comfort and Convenience",
|
category: "Comfort and Convenience",
|
||||||
slug: "comfort-convenience",
|
slug: "comfort-convenience",
|
||||||
imageUrl: "/assets/comfort-convenience.png",
|
imageUrl: "/assets/comfort-convenience.png",
|
||||||
description: "Items to increase comfort and convenience during deliveries.",
|
description: "Items to increase comfort and convenience during deliveries.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Health and Wellness",
|
category: "Health and Wellness",
|
||||||
slug: "health-wellness",
|
slug: "health-wellness",
|
||||||
imageUrl: "/assets/wearable-tech.jpg",
|
imageUrl: "/assets/wearable-tech.jpg",
|
||||||
description: "Products to help you stay healthy and well during your shifts.",
|
description: "Products to help you stay healthy and well during your shifts.",
|
||||||
},
|
}).root().addChild({
|
||||||
{
|
|
||||||
id: StaticCategory.nextId(),
|
id: StaticCategory.nextId(),
|
||||||
category: "Miscellaneous",
|
category: "Miscellaneous",
|
||||||
slug: "misc",
|
slug: "misc",
|
||||||
imageUrl: "/assets/misc.jpg",
|
imageUrl: "/assets/misc.jpg",
|
||||||
description: "Various other items that can be useful for Dashers.",
|
description: "Various other items that can be useful for Dashers.",
|
||||||
}
|
}).root();
|
||||||
].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
||||||
|
|
||||||
export function getCategoryIdForSlug(slug: string): number|null {
|
export function getCategoryNodeForSlug(slug: string): TreeNode<Category>|undefined {
|
||||||
for (const category of ALL_CATEGORIES) {
|
return ALL_CATEGORIES.findOneRecursive(category => category.slug === slug) || undefined;
|
||||||
if (category.slug == slug) {
|
|
||||||
return category.id;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
return null;
|
export function getCategoryForSlug(slug: string): Category|undefined {
|
||||||
|
return getCategoryNodeForSlug(slug)?.value || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCategoryIdForSlug(slug: string): number|undefined {
|
||||||
|
return getCategoryForSlug(slug)?.id || undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import StarRating from '../components/StarRating.astro';
|
||||||
import ImageCarousel from '../components/ImageCarousel.astro';
|
import ImageCarousel from '../components/ImageCarousel.astro';
|
||||||
import markdownIt from 'markdown-it';
|
import markdownIt from 'markdown-it';
|
||||||
import markdownItAttrs from 'markdown-it-attrs';
|
import markdownItAttrs from 'markdown-it-attrs';
|
||||||
|
import type { TreeNode } from '../types/tree';
|
||||||
const md = markdownIt({
|
const md = markdownIt({
|
||||||
html: true,
|
html: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
|
@ -34,7 +35,8 @@ const formatAsCurrency = (amount: number) => amount.toLocaleString('en-US', { st
|
||||||
|
|
||||||
const { productLookup } = Astro.params;
|
const { productLookup } = Astro.params;
|
||||||
const product: Product = ALL_PRODUCTS.find(p => p.slug === productLookup)!;
|
const product: Product = ALL_PRODUCTS.find(p => p.slug === productLookup)!;
|
||||||
const category: Category = ALL_CATEGORIES.find(c => c.id === product.categoryId)!;
|
const categoryNode: TreeNode<Category> = ALL_CATEGORIES.findOneRecursive(c => c.id === product.categoryId)!;
|
||||||
|
const category: Category = categoryNode.value;
|
||||||
const brand: Brand = ALL_BRANDS.find(b => b.slug === product.brandStoreSlug)!;
|
const brand: Brand = ALL_BRANDS.find(b => b.slug === product.brandStoreSlug)!;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
---
|
---
|
||||||
import Layout from '../../layouts/Layout.astro';
|
import Layout from '../../layouts/Layout.astro';
|
||||||
// import { useState, useEffect } from 'react';
|
// import { useState, useEffect } from 'react';
|
||||||
import { ALL_CATEGORIES } from '../../data/categories';
|
import { ALL_CATEGORIES, type Category, getCategoryNodeForSlug } from '../../data/categories';
|
||||||
import { ALL_PRODUCTS } from '../../data/products';
|
import { ALL_PRODUCTS } from '../../data/products';
|
||||||
import ProductCard from '../../components/ProductCard.astro';
|
import ProductCard from '../../components/ProductCard.astro';
|
||||||
|
import { DeepArray } from '../../types/deep-array';
|
||||||
|
|
||||||
type CategoryStaticPath = { params: { categoryLookup: string }};
|
type CategoryStaticPath = { params: { categoryLookup: string }};
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
return ALL_CATEGORIES.map<CategoryStaticPath>((category) => { return {
|
return ALL_CATEGORIES.getAllNodes().map(categoryNode => { return {
|
||||||
params: {
|
params: {
|
||||||
categoryLookup: category.slug
|
categoryLookup: categoryNode.value.slug
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { categoryLookup } = Astro.params;
|
const { categoryLookup } = Astro.params;
|
||||||
const category = ALL_CATEGORIES.find(c => c.slug === categoryLookup)!;
|
const categoryNode = getCategoryNodeForSlug(categoryLookup);
|
||||||
|
const category = categoryNode!.value;
|
||||||
const categoryProducts = ALL_PRODUCTS.filter(p => p.categoryId === category.id)!;
|
const categoryProducts = ALL_PRODUCTS.filter(p => p.categoryId === category.id)!;
|
||||||
const isAnyAmazon = categoryProducts.find(p => p.amazonLink) || false;
|
const isAnyAmazon = categoryProducts.find(p => p.amazonLink) || false;
|
||||||
---
|
---
|
|
@ -14,9 +14,9 @@ import { getProductsForCategoryId } from '../data/products';
|
||||||
Your one-stop shop for all your after-market Dasher supplies.
|
Your one-stop shop for all your after-market Dasher supplies.
|
||||||
</p>
|
</p>
|
||||||
<ul role="list" class="link-card-grid">
|
<ul role="list" class="link-card-grid">
|
||||||
{ALL_CATEGORIES.filter(category => getProductsForCategoryId(category.id)?.length > 0).map(category => (
|
{ALL_CATEGORIES.children.filter(categoryNode => getProductsForCategoryId(categoryNode.value.id).length > 0).map(categoryNode => (
|
||||||
<CategoryCard
|
<CategoryCard
|
||||||
category={category}
|
category={categoryNode.value}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
174
src/types/tree.ts
Normal file
174
src/types/tree.ts
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/**
|
||||||
|
* A simple tree data structure.
|
||||||
|
*/
|
||||||
|
export class TreeNode<NodeT> {
|
||||||
|
private _value: NodeT;
|
||||||
|
private _children: TreeNode<NodeT>[];
|
||||||
|
private _parent?: TreeNode<NodeT>;
|
||||||
|
constructor(value: NodeT) {
|
||||||
|
this._value = value;
|
||||||
|
this._children = [];
|
||||||
|
this._parent = undefined;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Creates a TreeNode root node.
|
||||||
|
* @param value Root node value.
|
||||||
|
* @returns TreeNode root node.
|
||||||
|
*/
|
||||||
|
public static createRoot<NodeT>(value: NodeT): TreeNode<NodeT> {
|
||||||
|
return new TreeNode(value);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Adds value node to parent TreeNode and returns child TreeNode.
|
||||||
|
* @param value Child node value.
|
||||||
|
* @returns Child tree node.
|
||||||
|
*/
|
||||||
|
public addChild(value: NodeT): TreeNode<NodeT> {
|
||||||
|
let childTreeNode = new TreeNode(value);
|
||||||
|
childTreeNode._parent = this;
|
||||||
|
let length = this._children.push(childTreeNode);
|
||||||
|
return this._children[length-1];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets value of node.
|
||||||
|
*/
|
||||||
|
public get value(): NodeT {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets value of node.
|
||||||
|
*/
|
||||||
|
public set value(value: NodeT) {
|
||||||
|
this._value = value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets TreeNode parent of current TreeNode.
|
||||||
|
* @returns Parent TreeNode of current TreeNode.
|
||||||
|
*/
|
||||||
|
public parent(): TreeNode<NodeT> {
|
||||||
|
return this._parent||this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets root TreeNode node of tree.
|
||||||
|
* @returns Root TreeNode node of tree.
|
||||||
|
*/
|
||||||
|
public root(): TreeNode<NodeT> {
|
||||||
|
if (this.parent() === this) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.parent().root();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets new parent of node and returns this TreeNode node.
|
||||||
|
* @param parent Parent of node.
|
||||||
|
* @returns This TreeNode node.
|
||||||
|
*/
|
||||||
|
public setParent(parent: TreeNode<NodeT>) {
|
||||||
|
this._parent = parent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets children of this TreeNode node as an Array of node values.
|
||||||
|
*/
|
||||||
|
public get children(): TreeNode<NodeT>[] {
|
||||||
|
return this._children;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks to see if the current node value matches the predicate.
|
||||||
|
* @param predicate Predicate determines truthiness of the match.
|
||||||
|
* @returns Boolean indicating if this tree node value matches the predicate.
|
||||||
|
*/
|
||||||
|
public is(predicate: (value: NodeT) => boolean): boolean {
|
||||||
|
const result = predicate(this.value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Performs shallow search for the predicate among itself and its TreeNode children, checking the values against the predicate.
|
||||||
|
* @param predicate Predicate determines truthiness of the match.
|
||||||
|
* @returns The TreeNode matching the predicate, if it exists. Otherwise, undefined.
|
||||||
|
*/
|
||||||
|
public findOne(predicate: (value: NodeT) => boolean): TreeNode<NodeT> | undefined {
|
||||||
|
const initialIs = this.is(predicate);
|
||||||
|
if (initialIs) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const child of this.children) {
|
||||||
|
const result = child.is(predicate);
|
||||||
|
if (result) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Performs shallow search for the predicates among itself and its TreeNode children, checking the values against the predicate.
|
||||||
|
* @param predicate Predicate determines truthiness of the matches.
|
||||||
|
* @returns An array of TreeNodes matching the predicate, or an empty array if no predicates exist.
|
||||||
|
*/
|
||||||
|
public findAll(predicate: (value: NodeT) => boolean): TreeNode<NodeT>[] {
|
||||||
|
let results: TreeNode<NodeT>[] = [];
|
||||||
|
if (this.is(predicate)) {
|
||||||
|
results.push(this);
|
||||||
|
}
|
||||||
|
for (const child of this.children) {
|
||||||
|
if (child.is(predicate)) {
|
||||||
|
results.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Performs deep search for value among itself and its TreeNode children, checking the values against the predicate.
|
||||||
|
* @param predicate Predicate determines truthiness of the match.
|
||||||
|
* @returns The TreeNode matching the predicate, if it exists. Otherwise, undefined.
|
||||||
|
*/
|
||||||
|
public findOneRecursive(predicate: (value: NodeT) => boolean): TreeNode<NodeT> | undefined {
|
||||||
|
const initialFindOne = this.findOne(predicate);
|
||||||
|
if (initialFindOne) {
|
||||||
|
return initialFindOne;
|
||||||
|
}
|
||||||
|
if (this.children.length) {
|
||||||
|
for (let child of this.children) {
|
||||||
|
let result = child.findOneRecursive(predicate);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Performs deep search for value among TreeNode children, checking the values against the predicate.
|
||||||
|
* @param predicate Predicate determines truthiness of the matches.
|
||||||
|
* @returns The TreeNode matching the predicate, if it exists. Otherwise, undefined.
|
||||||
|
*/
|
||||||
|
public findAllRecursive(predicate: (value: NodeT) => boolean): TreeNode<NodeT>[] {
|
||||||
|
let results: TreeNode<NodeT>[] = [];
|
||||||
|
if (this.is(predicate)) {
|
||||||
|
results.push(this);
|
||||||
|
}
|
||||||
|
if (this.children.length) {
|
||||||
|
for (let child of this.children) {
|
||||||
|
let childResults = child.findAllRecursive(predicate);
|
||||||
|
if (childResults.length) {
|
||||||
|
results.push(...childResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Flattens the tree into an array of TreeNodes.
|
||||||
|
*/
|
||||||
|
public getAllNodes(): TreeNode<NodeT>[] {
|
||||||
|
let nodes: TreeNode<NodeT>[] = [];
|
||||||
|
nodes.push(this);
|
||||||
|
for (let child of this.children) {
|
||||||
|
nodes.push(...child.getAllNodes());
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
}
|
83
test/tree.test.ts
Normal file
83
test/tree.test.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import { assert, expect, test } from 'vitest';
|
||||||
|
import { TreeNode } from '../src/types/tree';
|
||||||
|
|
||||||
|
test('Tree of numbers', () => {
|
||||||
|
const VALUE_NOT_UNDER_TEST = -1000;
|
||||||
|
const TEST_VALUE_1 = 1;
|
||||||
|
const TEST_VALUE_10 = 10;
|
||||||
|
const TEST_VALUE_100 = 100;
|
||||||
|
const ZERO_CHILDREN = 0;
|
||||||
|
const ONE_CHILD = 1;
|
||||||
|
const TWO_CHILDREN = 2;
|
||||||
|
const THREE_CHILDREN = 3;
|
||||||
|
|
||||||
|
let fluentNode = TreeNode.createRoot(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.children.length).toBe(ZERO_CHILDREN);
|
||||||
|
|
||||||
|
fluentNode = fluentNode.addChild(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.parent().value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.children.length).toBe(ZERO_CHILDREN);
|
||||||
|
expect(fluentNode.parent().children.length).toBe(ONE_CHILD);
|
||||||
|
|
||||||
|
fluentNode = fluentNode.addChild(TEST_VALUE_100);
|
||||||
|
expect(fluentNode.value).toBe(TEST_VALUE_100);
|
||||||
|
expect(fluentNode.parent().value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.parent().parent().value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.parent().parent()).toBe(fluentNode.root());
|
||||||
|
expect(fluentNode.parent().parent().parent().parent().parent().parent().value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.root().value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.root().root().root().root().root().root().root().value).toBe(TEST_VALUE_1);
|
||||||
|
|
||||||
|
fluentNode = fluentNode.root();
|
||||||
|
expect(fluentNode.value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.children.length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.children[0].children.length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.children[0].children[0].children.length).toBe(ZERO_CHILDREN);
|
||||||
|
|
||||||
|
expect(fluentNode.is(value => value === TEST_VALUE_1)).toBe(true);
|
||||||
|
expect(fluentNode.is(value => value === TEST_VALUE_10)).toBe(false);
|
||||||
|
expect(fluentNode.is(value => value === TEST_VALUE_100)).toBe(false);
|
||||||
|
expect(fluentNode.is(value => value === VALUE_NOT_UNDER_TEST)).toBe(false);
|
||||||
|
|
||||||
|
expect(fluentNode.findOne(value => value === TEST_VALUE_1)?.value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findOne(value => value === TEST_VALUE_10)?.value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.findOne(value => value === TEST_VALUE_100)?.value).toBe(undefined);
|
||||||
|
expect(fluentNode.findOne(value => value === TEST_VALUE_1)?.value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findOne(value => value === VALUE_NOT_UNDER_TEST)).toBe(undefined);
|
||||||
|
|
||||||
|
expect(fluentNode.findOneRecursive(value => value === TEST_VALUE_1)?.value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findOneRecursive(value => value === TEST_VALUE_10)?.value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.findOneRecursive(value => value === TEST_VALUE_100)?.value).toBe(TEST_VALUE_100);
|
||||||
|
expect(fluentNode.findOneRecursive(value => value === VALUE_NOT_UNDER_TEST)).toBe(undefined);
|
||||||
|
|
||||||
|
expect(fluentNode.findAll(value => value === TEST_VALUE_1).length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.findAll(value => value === TEST_VALUE_1)[0].value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findAll(value => value === TEST_VALUE_10).length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.findAll(value => value === TEST_VALUE_10)[0].value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.findAll(value => value === TEST_VALUE_100).length).toBe(ZERO_CHILDREN);
|
||||||
|
expect(fluentNode.findAll(value => value === VALUE_NOT_UNDER_TEST).length).toBe(ZERO_CHILDREN);
|
||||||
|
expect(fluentNode.findAll(value => value >= TEST_VALUE_1).length).toBe(TWO_CHILDREN);
|
||||||
|
expect(fluentNode.findAll(value => value >= TEST_VALUE_1)[0].value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findAll(value => value >= TEST_VALUE_1)[1].value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.findAll(value => value >= TEST_VALUE_10).length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.findAll(value => value >= TEST_VALUE_10)[0].value).toBe(TEST_VALUE_10);
|
||||||
|
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === TEST_VALUE_1).length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === TEST_VALUE_1)[0].value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === TEST_VALUE_10).length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === TEST_VALUE_10)[0].value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === TEST_VALUE_100).length).toBe(ONE_CHILD);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === TEST_VALUE_100)[0].value).toBe(TEST_VALUE_100);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value === VALUE_NOT_UNDER_TEST).length).toBe(ZERO_CHILDREN);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value >= TEST_VALUE_1).length).toBe(THREE_CHILDREN);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value >= TEST_VALUE_1)[0].value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value >= TEST_VALUE_1)[1].value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.findAllRecursive(value => value >= TEST_VALUE_1)[2].value).toBe(TEST_VALUE_100);
|
||||||
|
|
||||||
|
expect(fluentNode.getAllNodes().length).toBe(THREE_CHILDREN);
|
||||||
|
expect(fluentNode.getAllNodes()[0].value).toBe(TEST_VALUE_1);
|
||||||
|
expect(fluentNode.getAllNodes()[1].value).toBe(TEST_VALUE_10);
|
||||||
|
expect(fluentNode.getAllNodes()[2].value).toBe(TEST_VALUE_100);
|
||||||
|
})
|
9
vitest.config.ts
Normal file
9
vitest.config.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/// <reference types="vitest" />
|
||||||
|
import { getViteConfig } from 'astro/config';
|
||||||
|
|
||||||
|
export default getViteConfig({
|
||||||
|
test: {
|
||||||
|
/* for example, use global to avoid globals imports (describe, test, expect): */
|
||||||
|
// globals: true,
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user