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": "*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"swiper": "^11.1.4"
|
||||
"swiper": "^11.1.4",
|
||||
"vitest": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apify/tsconfig": "^0.1.0",
|
||||
|
@ -1857,6 +1858,81 @@
|
|||
"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": {
|
||||
"version": "2.4.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "4.11.5",
|
||||
"resolved": "https://registry.npmjs.org/astro/-/astro-4.11.5.tgz",
|
||||
|
@ -2429,6 +2513,14 @@
|
|||
"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": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
|
||||
|
@ -2529,6 +2621,21 @@
|
|||
"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": {
|
||||
"version": "5.3.0",
|
||||
"license": "MIT",
|
||||
|
@ -2571,6 +2678,14 @@
|
|||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"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": {
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
|
||||
|
@ -3053,6 +3168,14 @@
|
|||
"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": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||
|
@ -3680,6 +3803,14 @@
|
|||
"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": {
|
||||
"version": "8.0.1",
|
||||
"license": "MIT",
|
||||
|
@ -4966,6 +5097,14 @@
|
|||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
|
||||
|
@ -6309,6 +6448,19 @@
|
|||
"version": "6.2.2",
|
||||
"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": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||
|
@ -7237,6 +7389,11 @@
|
|||
"@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": {
|
||||
"version": "4.1.0",
|
||||
"license": "ISC",
|
||||
|
@ -7355,6 +7512,16 @@
|
|||
"version": "1.0.3",
|
||||
"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": {
|
||||
"version": "0.2.2",
|
||||
"license": "MIT",
|
||||
|
@ -7542,6 +7709,35 @@
|
|||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
||||
"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": {
|
||||
"version": "6.1.30",
|
||||
"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": {
|
||||
"version": "2.3.3",
|
||||
"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": {
|
||||
"version": "0.0.59",
|
||||
"resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.59.tgz",
|
||||
|
@ -8386,6 +8666,21 @@
|
|||
"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": {
|
||||
"version": "4.0.1",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
"astro": "astro",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.8.1",
|
||||
|
@ -26,7 +27,8 @@
|
|||
"playwright": "*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"swiper": "^11.1.4"
|
||||
"swiper": "^11.1.4",
|
||||
"vitest": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apify/tsconfig": "^0.1.0",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { TreeNode } from "../types/tree";
|
||||
|
||||
/**
|
||||
* Category of Products.
|
||||
*/
|
||||
|
@ -22,10 +24,18 @@ export interface Category {
|
|||
* Description of Product Category.
|
||||
*/
|
||||
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 {
|
||||
/**
|
||||
|
@ -42,80 +52,81 @@ export class StaticCategory {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all the categories.
|
||||
*/
|
||||
export const ALL_CATEGORIES: Category[] = [
|
||||
{
|
||||
export const ALL_CATEGORIES = TreeNode.createRoot<Category>({
|
||||
id: 0,
|
||||
category: "All Categories",
|
||||
slug: "all-categories",
|
||||
description: "All categories in the whole store."
|
||||
}).addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Vehicle Essentials",
|
||||
slug: "vehicle-essentials",
|
||||
imageUrl: "/assets/vehicle-essentials.png",
|
||||
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(),
|
||||
category: "Delivery Gear",
|
||||
slug: "delivery-gear",
|
||||
imageUrl: "/assets/delivery-gear-3.jpg",
|
||||
description: "Gear to help you deliver food efficiently and keep it in top condition.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Personal Items",
|
||||
slug: "personal-items",
|
||||
imageUrl: "/assets/personal-items.jpg",
|
||||
description: "Personal essentials to keep you comfortable and prepared on the go.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Safety Equipment",
|
||||
slug: "safety-equipment",
|
||||
imageUrl: "/assets/safety-equipment.jpg",
|
||||
description: "Safety gear to protect you during deliveries.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Tech Gadgets",
|
||||
slug: "tech-gadgets",
|
||||
imageUrl: "/assets/tech-gadgets.jpg",
|
||||
description: "Technology tools to enhance your efficiency and connectivity.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Biking Gear",
|
||||
slug: "biking-gear",
|
||||
imageUrl: "/assets/biking-gear.jpg",
|
||||
description: "Equipment for Dashers who deliver by bike.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Comfort and Convenience",
|
||||
slug: "comfort-convenience",
|
||||
imageUrl: "/assets/comfort-convenience.png",
|
||||
description: "Items to increase comfort and convenience during deliveries.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Health and Wellness",
|
||||
slug: "health-wellness",
|
||||
imageUrl: "/assets/wearable-tech.jpg",
|
||||
description: "Products to help you stay healthy and well during your shifts.",
|
||||
},
|
||||
{
|
||||
}).root().addChild({
|
||||
id: StaticCategory.nextId(),
|
||||
category: "Miscellaneous",
|
||||
slug: "misc",
|
||||
imageUrl: "/assets/misc.jpg",
|
||||
description: "Various other items that can be useful for Dashers.",
|
||||
}
|
||||
].sort((a, b) => a.slug.localeCompare(b.slug));
|
||||
}).root();
|
||||
|
||||
export function getCategoryIdForSlug(slug: string): number|null {
|
||||
for (const category of ALL_CATEGORIES) {
|
||||
if (category.slug == slug) {
|
||||
return category.id;
|
||||
}
|
||||
};
|
||||
return null;
|
||||
export function getCategoryNodeForSlug(slug: string): TreeNode<Category>|undefined {
|
||||
return ALL_CATEGORIES.findOneRecursive(category => category.slug === slug) || undefined;
|
||||
}
|
||||
|
||||
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 markdownIt from 'markdown-it';
|
||||
import markdownItAttrs from 'markdown-it-attrs';
|
||||
import type { TreeNode } from '../types/tree';
|
||||
const md = markdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
|
@ -34,7 +35,8 @@ const formatAsCurrency = (amount: number) => amount.toLocaleString('en-US', { st
|
|||
|
||||
const { productLookup } = Astro.params;
|
||||
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)!;
|
||||
---
|
||||
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
---
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
// 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 ProductCard from '../../components/ProductCard.astro';
|
||||
import { DeepArray } from '../../types/deep-array';
|
||||
|
||||
type CategoryStaticPath = { params: { categoryLookup: string }};
|
||||
|
||||
export function getStaticPaths() {
|
||||
return ALL_CATEGORIES.map<CategoryStaticPath>((category) => { return {
|
||||
return ALL_CATEGORIES.getAllNodes().map(categoryNode => { return {
|
||||
params: {
|
||||
categoryLookup: category.slug
|
||||
categoryLookup: categoryNode.value.slug
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
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 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.
|
||||
</p>
|
||||
<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
|
||||
category={category}
|
||||
category={categoryNode.value}
|
||||
/>
|
||||
))}
|
||||
</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