diff --git a/.editorconfig b/.editorconfig index 4bb7fad..4f144d9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,10 +10,7 @@ indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true -[{config.js,*.config.js}] -indent_size = 2 - -[.eslintrc.{cjs,js}] +[*.config.{js,mjs,ts,mts}] indent_size = 2 [*.{json,yml}] diff --git a/.github/workflows/template-build.yml b/.github/workflows/template-build.yml index 34e5ed8..01518d0 100644 --- a/.github/workflows/template-build.yml +++ b/.github/workflows/template-build.yml @@ -14,11 +14,11 @@ jobs: with: version: latest - - name: Configuring Node.js 20 (LTS) + - name: Configuring Node.js 22 (LTS) uses: actions/setup-node@v4 with: cache: pnpm - node-version: 20 + node-version: 22 - name: Installing dependencies run: pnpm run ci @@ -29,6 +29,8 @@ jobs: - name: Checking type consistency run: pnpm run typecheck + - name: Testing the source code + run: pnpm run test - name: Building the source code run: pnpm build - name: Creating the artifact diff --git a/.github/workflows/template-publish.yml b/.github/workflows/template-publish.yml index 405580e..25b9010 100644 --- a/.github/workflows/template-publish.yml +++ b/.github/workflows/template-publish.yml @@ -25,10 +25,10 @@ jobs: with: name: byloth-core - - name: Configuring Node.js 20 (LTS) + - name: Configuring Node.js 22 (LTS) uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: ${{ inputs.registry-url }} - if: ${{ github.event.release.prerelease == true }} diff --git a/.husky/pre-commit b/.husky/pre-commit index 84c031a..0fc24b2 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,3 @@ NODE_ENV="production" pnpm run lint pnpm run typecheck +pnpm run test diff --git a/eslint.config.mjs b/eslint.config.mjs index fbb46c4..c454a1c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,9 +9,9 @@ const __dirname = path.dirname(__filename); const gitignorePath = path.resolve(__dirname, ".gitignore"); export default [includeIgnoreFile(gitignorePath), ...eslintTs, { - rules: { - "no-trailing-spaces": ["error", { "ignoreComments": true }], - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/unified-signatures": "off" - } + rules: { + "no-trailing-spaces": ["error", { "ignoreComments": true }], + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/unified-signatures": "off" + } }]; diff --git a/package.json b/package.json index 794ccb6..46db6dd 100644 --- a/package.json +++ b/package.json @@ -52,16 +52,19 @@ "preview": "vite preview", "typecheck": "tsc", "lint": "eslint .", - "test": "vitest", + "test": "vitest run", "prepare": "husky", "ci": "pnpm install --frozen-lockfile" }, "devDependencies": { - "@byloth/eslint-config-typescript": "^3.0.3", - "@types/node": "^22.10.7", + "@byloth/eslint-config-typescript": "^3.1.0", + "@eslint/compat": "^1.2.6", + "@types/node": "^22.13.0", "husky": "^9.1.7", + "jsdom": "^26.0.0", "typescript": "^5.7.3", - "vite": "^6.0.10" + "vite": "^6.0.11", + "vitest": "^3.0.4" }, - "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c" + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eecd06..3bcf687 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,28 +9,68 @@ importers: .: devDependencies: '@byloth/eslint-config-typescript': - specifier: ^3.0.3 - version: 3.0.3(eslint@9.18.0)(typescript@5.7.3) + specifier: ^3.1.0 + version: 3.1.0(eslint@9.19.0)(typescript@5.7.3) + '@eslint/compat': + specifier: ^1.2.6 + version: 1.2.6(eslint@9.19.0) '@types/node': - specifier: ^22.10.7 - version: 22.10.7 + specifier: ^22.13.0 + version: 22.13.0 husky: specifier: ^9.1.7 version: 9.1.7 + jsdom: + specifier: ^26.0.0 + version: 26.0.0 typescript: specifier: ^5.7.3 version: 5.7.3 vite: - specifier: ^6.0.10 - version: 6.0.10(@types/node@22.10.7) + specifier: ^6.0.11 + version: 6.0.11(@types/node@22.13.0) + vitest: + specifier: ^3.0.4 + version: 3.0.4(@types/node@22.13.0)(jsdom@26.0.0) packages: - '@byloth/eslint-config-typescript@3.0.3': - resolution: {integrity: sha512-97bJOula+nilxjPJDgRIuGlF8KyppcQ2HjxWyA+MVTNEOeWNF3+u3FG3R4+XqSxz2Nc4RNB4wRqhAjdVPW6gVw==} + '@asamuzakjp/css-color@2.8.3': + resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} - '@byloth/eslint-config@3.0.3': - resolution: {integrity: sha512-fXpIxZByU2Ux+95jGcEEweKYb4bxS571xaMZDJ24wsasrDuczjld63EGAulnm9yRAJiLpScruvV0mWLow+16tg==} + '@byloth/eslint-config-typescript@3.1.0': + resolution: {integrity: sha512-BBET8WS/LrPfeLaPIdJiqnjcFv7oJ2CJnCrnAXH6C17BMfsAVFUBOEdzNkrb6eldsGNxkJV7u0SMpmu3+SLcTg==} + + '@byloth/eslint-config@3.1.0': + resolution: {integrity: sha512-o3ku1Ca50amSB0dGVRrOUw2GTbIrv1ft/tZF+GUSDkpiO/NBE3K11QwOjSm0JDnRX6H+RyHHV9kPBhKBTRglqw==} + + '@csstools/color-helpers@5.0.1': + resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.1': + resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.7': + resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} @@ -192,8 +232,8 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.2.5': - resolution: {integrity: sha512-5iuG/StT+7OfvhoBHPlmxkPA9om6aDUFgmD4+mWKAGsYt4vCe8rypneG03AuseyRHBmcCLXQtIH5S26tIoggLg==} + '@eslint/compat@1.2.6': + resolution: {integrity: sha512-k7HNCqApoDHM6XzT30zGoETj+D+uUcZUb+IVAJmar3u6bvHf7hhHJcWx09QHj4/a2qrKZMWU0E16tvkiAdv06Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9.10.0 @@ -201,8 +241,8 @@ packages: eslint: optional: true - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.10.0': @@ -213,12 +253,12 @@ packages: resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.18.0': - resolution: {integrity: sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==} + '@eslint/js@9.19.0': + resolution: {integrity: sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/plugin-kit@0.2.5': @@ -245,6 +285,9 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -257,98 +300,98 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@rollup/rollup-android-arm-eabi@4.31.0': - resolution: {integrity: sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==} + '@rollup/rollup-android-arm-eabi@4.32.1': + resolution: {integrity: sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.31.0': - resolution: {integrity: sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==} + '@rollup/rollup-android-arm64@4.32.1': + resolution: {integrity: sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.31.0': - resolution: {integrity: sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==} + '@rollup/rollup-darwin-arm64@4.32.1': + resolution: {integrity: sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.31.0': - resolution: {integrity: sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==} + '@rollup/rollup-darwin-x64@4.32.1': + resolution: {integrity: sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.31.0': - resolution: {integrity: sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==} + '@rollup/rollup-freebsd-arm64@4.32.1': + resolution: {integrity: sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.31.0': - resolution: {integrity: sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==} + '@rollup/rollup-freebsd-x64@4.32.1': + resolution: {integrity: sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': - resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==} + '@rollup/rollup-linux-arm-gnueabihf@4.32.1': + resolution: {integrity: sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.31.0': - resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==} + '@rollup/rollup-linux-arm-musleabihf@4.32.1': + resolution: {integrity: sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.31.0': - resolution: {integrity: sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==} + '@rollup/rollup-linux-arm64-gnu@4.32.1': + resolution: {integrity: sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.31.0': - resolution: {integrity: sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==} + '@rollup/rollup-linux-arm64-musl@4.32.1': + resolution: {integrity: sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': - resolution: {integrity: sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.32.1': + resolution: {integrity: sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': - resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.32.1': + resolution: {integrity: sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.31.0': - resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==} + '@rollup/rollup-linux-riscv64-gnu@4.32.1': + resolution: {integrity: sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.31.0': - resolution: {integrity: sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==} + '@rollup/rollup-linux-s390x-gnu@4.32.1': + resolution: {integrity: sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.31.0': - resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==} + '@rollup/rollup-linux-x64-gnu@4.32.1': + resolution: {integrity: sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.31.0': - resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==} + '@rollup/rollup-linux-x64-musl@4.32.1': + resolution: {integrity: sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.31.0': - resolution: {integrity: sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==} + '@rollup/rollup-win32-arm64-msvc@4.32.1': + resolution: {integrity: sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.31.0': - resolution: {integrity: sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==} + '@rollup/rollup-win32-ia32-msvc@4.32.1': + resolution: {integrity: sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.31.0': - resolution: {integrity: sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==} + '@rollup/rollup-win32-x64-msvc@4.32.1': + resolution: {integrity: sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==} cpu: [x64] os: [win32] @@ -358,56 +401,85 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.10.7': - resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + '@types/node@22.13.0': + resolution: {integrity: sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==} - '@typescript-eslint/eslint-plugin@8.21.0': - resolution: {integrity: sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==} + '@typescript-eslint/eslint-plugin@8.22.0': + resolution: {integrity: sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.21.0': - resolution: {integrity: sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==} + '@typescript-eslint/parser@8.22.0': + resolution: {integrity: sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@8.21.0': - resolution: {integrity: sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==} + '@typescript-eslint/scope-manager@8.22.0': + resolution: {integrity: sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.21.0': - resolution: {integrity: sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==} + '@typescript-eslint/type-utils@8.22.0': + resolution: {integrity: sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@8.21.0': - resolution: {integrity: sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==} + '@typescript-eslint/types@8.22.0': + resolution: {integrity: sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.21.0': - resolution: {integrity: sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==} + '@typescript-eslint/typescript-estree@8.22.0': + resolution: {integrity: sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.21.0': - resolution: {integrity: sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==} + '@typescript-eslint/utils@8.22.0': + resolution: {integrity: sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@8.21.0': - resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==} + '@typescript-eslint/visitor-keys@8.22.0': + resolution: {integrity: sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/expect@3.0.4': + resolution: {integrity: sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==} + + '@vitest/mocker@3.0.4': + resolution: {integrity: sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.0.4': + resolution: {integrity: sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==} + + '@vitest/runner@3.0.4': + resolution: {integrity: sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==} + + '@vitest/snapshot@3.0.4': + resolution: {integrity: sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==} + + '@vitest/spy@3.0.4': + resolution: {integrity: sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==} + + '@vitest/utils@3.0.4': + resolution: {integrity: sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -418,6 +490,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -428,6 +504,13 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -441,14 +524,26 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -456,6 +551,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -463,6 +562,14 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + cssstyle@4.2.1: + resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==} + engines: {node: '>=18'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -472,9 +579,27 @@ packages: supports-color: optional: true + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -496,8 +621,8 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.18.0: - resolution: {integrity: sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==} + eslint@9.19.0: + resolution: {integrity: sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -522,10 +647,17 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -539,8 +671,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fastq@1.19.0: + resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -561,6 +693,10 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -589,11 +725,27 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} hasBin: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -618,6 +770,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -625,6 +780,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@26.0.0: + resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -648,6 +812,15 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -656,6 +829,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -674,6 +855,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -690,6 +874,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -698,6 +885,13 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + pathe@2.0.2: + resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -728,16 +922,26 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.31.0: - resolution: {integrity: sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==} + rollup@4.32.1: + resolution: {integrity: sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@7.7.0: + resolution: {integrity: sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==} engines: {node: '>=10'} hasBin: true @@ -749,10 +953,19 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -761,10 +974,46 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.76: + resolution: {integrity: sha512-uzhJ02RaMzgQR3yPoeE65DrcHI6LoM4saUqXOt/b5hmb3+mc4YWpdSeAQqVqRUlQ14q8ZuLRWyBR1ictK1dzzg==} + + tldts@6.1.76: + resolution: {integrity: sha512-6U2ti64/nppsDxQs9hw8ephA3nO6nSQvVVfxwRw8wLQPFtLI1cFI1a1eP22g+LUP+1TA2pKKjUTwWB+K2coqmQ==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@5.1.0: + resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + ts-api-utils@2.0.0: resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} engines: {node: '>=18.12'} @@ -786,8 +1035,13 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - vite@6.0.10: - resolution: {integrity: sha512-MEszunEcMo6pFsfXN1GhCFQqnE25tWRH0MA4f0Q7uanACi4y1Us+ZGpTMnITwCTnYzB2b9cpmnelTlxgTBmaBA==} + vite-node@3.0.4: + resolution: {integrity: sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.0.11: + resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -826,42 +1080,141 @@ packages: yaml: optional: true + vitest@3.0.4: + resolution: {integrity: sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.4 + '@vitest/ui': 3.0.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} snapshots: - '@byloth/eslint-config-typescript@3.0.3(eslint@9.18.0)(typescript@5.7.3)': + '@asamuzakjp/css-color@2.8.3': dependencies: - '@byloth/eslint-config': 3.0.3 - '@typescript-eslint/eslint-plugin': 8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3) - '@typescript-eslint/parser': 8.21.0(eslint@9.18.0)(typescript@5.7.3) + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + lru-cache: 10.4.3 + + '@byloth/eslint-config-typescript@3.1.0(eslint@9.19.0)(typescript@5.7.3)': + dependencies: + '@byloth/eslint-config': 3.1.0 + '@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/parser': 8.22.0(eslint@9.19.0)(typescript@5.7.3) transitivePeerDependencies: - eslint - jiti - supports-color - typescript - '@byloth/eslint-config@3.0.3': + '@byloth/eslint-config@3.1.0': dependencies: - '@eslint/compat': 1.2.5(eslint@9.18.0) - '@eslint/js': 9.18.0 - eslint: 9.18.0 + '@eslint/js': 9.19.0 + eslint: 9.19.0 globals: 15.14.0 transitivePeerDependencies: - jiti - supports-color + '@csstools/color-helpers@5.0.1': {} + + '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.1 + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -937,20 +1290,20 @@ snapshots: '@esbuild/win32-x64@0.24.2': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.18.0)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.19.0)': dependencies: - eslint: 9.18.0 + eslint: 9.19.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.5(eslint@9.18.0)': + '@eslint/compat@1.2.6(eslint@9.19.0)': optionalDependencies: - eslint: 9.18.0 + eslint: 9.19.0 - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.19.2': dependencies: - '@eslint/object-schema': 2.1.5 + '@eslint/object-schema': 2.1.6 debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: @@ -974,9 +1327,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.18.0': {} + '@eslint/js@9.19.0': {} - '@eslint/object-schema@2.1.5': {} + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.2.5': dependencies: @@ -996,6 +1349,8 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1006,82 +1361,82 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.19.0 - '@rollup/rollup-android-arm-eabi@4.31.0': + '@rollup/rollup-android-arm-eabi@4.32.1': optional: true - '@rollup/rollup-android-arm64@4.31.0': + '@rollup/rollup-android-arm64@4.32.1': optional: true - '@rollup/rollup-darwin-arm64@4.31.0': + '@rollup/rollup-darwin-arm64@4.32.1': optional: true - '@rollup/rollup-darwin-x64@4.31.0': + '@rollup/rollup-darwin-x64@4.32.1': optional: true - '@rollup/rollup-freebsd-arm64@4.31.0': + '@rollup/rollup-freebsd-arm64@4.32.1': optional: true - '@rollup/rollup-freebsd-x64@4.31.0': + '@rollup/rollup-freebsd-x64@4.32.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': + '@rollup/rollup-linux-arm-gnueabihf@4.32.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.31.0': + '@rollup/rollup-linux-arm-musleabihf@4.32.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.31.0': + '@rollup/rollup-linux-arm64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.31.0': + '@rollup/rollup-linux-arm64-musl@4.32.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': + '@rollup/rollup-linux-loongarch64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.32.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.31.0': + '@rollup/rollup-linux-riscv64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.31.0': + '@rollup/rollup-linux-s390x-gnu@4.32.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.31.0': + '@rollup/rollup-linux-x64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-x64-musl@4.31.0': + '@rollup/rollup-linux-x64-musl@4.32.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.31.0': + '@rollup/rollup-win32-arm64-msvc@4.32.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.31.0': + '@rollup/rollup-win32-ia32-msvc@4.32.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.31.0': + '@rollup/rollup-win32-x64-msvc@4.32.1': optional: true '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} - '@types/node@22.10.7': + '@types/node@22.13.0': dependencies: undici-types: 6.20.0 - '@typescript-eslint/eslint-plugin@8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.21.0(eslint@9.18.0)(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/type-utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3) - '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.21.0 - eslint: 9.18.0 + '@typescript-eslint/parser': 8.22.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/type-utils': 8.22.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/utils': 8.22.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.22.0 + eslint: 9.19.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -1090,72 +1445,114 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3)': + '@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.22.0 debug: 4.4.0 - eslint: 9.18.0 + eslint: 9.19.0 typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.21.0': + '@typescript-eslint/scope-manager@8.22.0': dependencies: - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/visitor-keys': 8.22.0 - '@typescript-eslint/type-utils@8.21.0(eslint@9.18.0)(typescript@5.7.3)': + '@typescript-eslint/type-utils@8.22.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) - '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3) + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.22.0(eslint@9.19.0)(typescript@5.7.3) debug: 4.4.0 - eslint: 9.18.0 + eslint: 9.19.0 ts-api-utils: 2.0.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.21.0': {} + '@typescript-eslint/types@8.22.0': {} - '@typescript-eslint/typescript-estree@8.21.0(typescript@5.7.3)': + '@typescript-eslint/typescript-estree@8.22.0(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/visitor-keys': 8.22.0 debug: 4.4.0 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.0 ts-api-utils: 2.0.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.21.0(eslint@9.18.0)(typescript@5.7.3)': + '@typescript-eslint/utils@8.22.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.18.0) - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) - eslint: 9.18.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0) + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) + eslint: 9.19.0 typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.21.0': + '@typescript-eslint/visitor-keys@8.22.0': dependencies: - '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/types': 8.22.0 eslint-visitor-keys: 4.2.0 + '@vitest/expect@3.0.4': + dependencies: + '@vitest/spy': 3.0.4 + '@vitest/utils': 3.0.4 + chai: 5.1.2 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.0.4(vite@6.0.11(@types/node@22.13.0))': + dependencies: + '@vitest/spy': 3.0.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.0.11(@types/node@22.13.0) + + '@vitest/pretty-format@3.0.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.0.4': + dependencies: + '@vitest/utils': 3.0.4 + pathe: 2.0.2 + + '@vitest/snapshot@3.0.4': + dependencies: + '@vitest/pretty-format': 3.0.4 + magic-string: 0.30.17 + pathe: 2.0.2 + + '@vitest/spy@3.0.4': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@3.0.4': + dependencies: + '@vitest/pretty-format': 3.0.4 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 acorn@8.14.0: {} + agent-base@7.1.3: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -1169,6 +1566,10 @@ snapshots: argparse@2.0.1: {} + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -1184,19 +1585,35 @@ snapshots: dependencies: fill-range: 7.1.1 + cac@6.7.14: {} + callsites@3.1.0: {} + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + check-error@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} cross-spawn@7.0.6: @@ -1205,12 +1622,32 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cssstyle@4.2.1: + dependencies: + '@asamuzakjp/css-color': 2.8.3 + rrweb-cssom: 0.8.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + debug@4.4.0: dependencies: ms: 2.1.3 + decimal.js@10.5.0: {} + + deep-eql@5.0.2: {} + deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + + entities@4.5.0: {} + + es-module-lexer@1.6.0: {} + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -1250,14 +1687,14 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.18.0: + eslint@9.19.0: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.18.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 + '@eslint/config-array': 0.19.2 '@eslint/core': 0.10.0 '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.18.0 + '@eslint/js': 9.19.0 '@eslint/plugin-kit': 0.2.5 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -1305,8 +1742,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + esutils@2.0.3: {} + expect-type@1.1.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -1321,7 +1764,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fastq@1.18.0: + fastq@1.19.0: dependencies: reusify: 1.0.4 @@ -1345,6 +1788,12 @@ snapshots: flatted@3.3.2: {} + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true @@ -1364,8 +1813,30 @@ snapshots: has-flag@4.0.0: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + husky@9.1.7: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.0: @@ -1383,12 +1854,42 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsdom@26.0.0: + dependencies: + cssstyle: 4.2.1 + data-urls: 5.0.0 + decimal.js: 10.5.0 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -1410,6 +1911,14 @@ snapshots: lodash.merge@4.6.2: {} + loupe@3.1.3: {} + + lru-cache@10.4.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + merge2@1.4.1: {} micromatch@4.0.8: @@ -1417,6 +1926,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -1431,6 +1946,8 @@ snapshots: natural-compare@1.4.0: {} + nwsapi@2.2.16: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -1452,10 +1969,18 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-key@3.1.1: {} + pathe@2.0.2: {} + + pathval@2.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -1476,36 +2001,44 @@ snapshots: reusify@1.0.4: {} - rollup@4.31.0: + rollup@4.32.1: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.31.0 - '@rollup/rollup-android-arm64': 4.31.0 - '@rollup/rollup-darwin-arm64': 4.31.0 - '@rollup/rollup-darwin-x64': 4.31.0 - '@rollup/rollup-freebsd-arm64': 4.31.0 - '@rollup/rollup-freebsd-x64': 4.31.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.31.0 - '@rollup/rollup-linux-arm-musleabihf': 4.31.0 - '@rollup/rollup-linux-arm64-gnu': 4.31.0 - '@rollup/rollup-linux-arm64-musl': 4.31.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.31.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.31.0 - '@rollup/rollup-linux-riscv64-gnu': 4.31.0 - '@rollup/rollup-linux-s390x-gnu': 4.31.0 - '@rollup/rollup-linux-x64-gnu': 4.31.0 - '@rollup/rollup-linux-x64-musl': 4.31.0 - '@rollup/rollup-win32-arm64-msvc': 4.31.0 - '@rollup/rollup-win32-ia32-msvc': 4.31.0 - '@rollup/rollup-win32-x64-msvc': 4.31.0 + '@rollup/rollup-android-arm-eabi': 4.32.1 + '@rollup/rollup-android-arm64': 4.32.1 + '@rollup/rollup-darwin-arm64': 4.32.1 + '@rollup/rollup-darwin-x64': 4.32.1 + '@rollup/rollup-freebsd-arm64': 4.32.1 + '@rollup/rollup-freebsd-x64': 4.32.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.32.1 + '@rollup/rollup-linux-arm-musleabihf': 4.32.1 + '@rollup/rollup-linux-arm64-gnu': 4.32.1 + '@rollup/rollup-linux-arm64-musl': 4.32.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.32.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.32.1 + '@rollup/rollup-linux-riscv64-gnu': 4.32.1 + '@rollup/rollup-linux-s390x-gnu': 4.32.1 + '@rollup/rollup-linux-x64-gnu': 4.32.1 + '@rollup/rollup-linux-x64-musl': 4.32.1 + '@rollup/rollup-win32-arm64-msvc': 4.32.1 + '@rollup/rollup-win32-ia32-msvc': 4.32.1 + '@rollup/rollup-win32-x64-msvc': 4.32.1 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - semver@7.6.3: {} + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@7.7.0: {} shebang-command@2.0.0: dependencies: @@ -1513,18 +2046,50 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + source-map-js@1.2.1: {} + stackback@0.0.2: {} + + std-env@3.8.0: {} + strip-json-comments@3.1.1: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.76: {} + + tldts@6.1.76: + dependencies: + tldts-core: 6.1.76 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@5.1.0: + dependencies: + tldts: 6.1.76 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.0.0(typescript@5.7.3): dependencies: typescript: 5.7.3 @@ -1541,19 +2106,107 @@ snapshots: dependencies: punycode: 2.3.1 - vite@6.0.10(@types/node@22.10.7): + vite-node@3.0.4(@types/node@22.13.0): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.2 + vite: 6.0.11(@types/node@22.13.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.0.11(@types/node@22.13.0): dependencies: esbuild: 0.24.2 postcss: 8.5.1 - rollup: 4.31.0 + rollup: 4.32.1 optionalDependencies: - '@types/node': 22.10.7 + '@types/node': 22.13.0 fsevents: 2.3.3 + vitest@3.0.4(@types/node@22.13.0)(jsdom@26.0.0): + dependencies: + '@vitest/expect': 3.0.4 + '@vitest/mocker': 3.0.4(vite@6.0.11(@types/node@22.13.0)) + '@vitest/pretty-format': 3.0.4 + '@vitest/runner': 3.0.4 + '@vitest/snapshot': 3.0.4 + '@vitest/spy': 3.0.4 + '@vitest/utils': 3.0.4 + chai: 5.1.2 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 2.0.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.0.11(@types/node@22.13.0) + vite-node: 3.0.4(@types/node@22.13.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.13.0 + jsdom: 26.0.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yocto-queue@0.1.0: {} diff --git a/src/index.ts b/src/index.ts index 2739fea..f66cd4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export { Clock, Countdown, DeferredPromise, + EnvironmentException, Exception, FatalErrorException, FileException, diff --git a/src/models/aggregators/aggregated-async-iterator.ts b/src/models/aggregators/aggregated-async-iterator.ts index 95df719..48cebfe 100644 --- a/src/models/aggregators/aggregated-async-iterator.ts +++ b/src/models/aggregators/aggregated-async-iterator.ts @@ -39,8 +39,6 @@ import type { MaybeAsyncKeyedIteratee, MaybeAsyncKeyedReducer } from "./types.js * console.log(await results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * @template T The type of the elements to aggregate. */ @@ -58,8 +56,6 @@ export default class AggregatedAsyncIterator * const iterator = new AggregatedAsyncIterator([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]); * ``` * - * --- - * * @param iterable The iterable to aggregate. */ public constructor(iterable: Iterable<[K, T]>); @@ -72,8 +68,6 @@ export default class AggregatedAsyncIterator * const iterator = new AggregatedAsyncIterator(elements); * ``` * - * --- - * * @param iterable The iterable to aggregate. */ public constructor(iterable: AsyncIterable<[K, T]>); @@ -96,8 +90,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param iterator The iterator to aggregate. */ public constructor(iterator: Iterator<[K, T]>); @@ -120,8 +112,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param iterator The iterator to aggregate. */ public constructor(iterator: AsyncIterator<[K, T]>); @@ -141,8 +131,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param generatorFn The generator function to aggregate. */ public constructor(generatorFn: GeneratorFunction<[K, T]>); @@ -162,8 +150,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param generatorFn The generator function to aggregate. */ public constructor(generatorFn: AsyncGeneratorFunction<[K, T]>); @@ -175,8 +161,6 @@ export default class AggregatedAsyncIterator * const iterator = new AggregatedAsyncIterator(asyncKeyedValues); * ``` * - * --- - * * @param argument The iterable, iterator or generator function to aggregate. */ public constructor(argument: MaybeAsyncIteratorLike<[K, T]> | MaybeAsyncGeneratorFunction<[K, T]>); @@ -206,11 +190,10 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the boolean results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the boolean results for each group. */ public async every(predicate: MaybeAsyncKeyedIteratee): Promise> { @@ -252,11 +235,10 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the boolean results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the boolean results for each group. */ public async some(predicate: MaybeAsyncKeyedIteratee): Promise> { @@ -295,8 +277,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link AggregatedAsyncIterator} containing the elements that satisfy the condition. @@ -321,8 +301,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, 5], even: [0, 6] } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -341,11 +319,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - if (await predicate(key, element, index)) { yield [key, element]; } indexes.set(key, index + 1); @@ -374,8 +350,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -389,11 +363,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, V]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [key, await iteratee(key, element, index)]; indexes.set(key, index + 1); @@ -423,11 +395,10 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the reduced results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group. */ public async reduce(reducer: MaybeAsyncKeyedReducer): Promise>; @@ -453,14 +424,13 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. * @param initialValue The initial value for the accumulator. * - * @returns A new {@link ReducedIterator} containing the reduced results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group. */ public async reduce(reducer: MaybeAsyncKeyedReducer, initialValue: MaybePromise) : Promise>; @@ -487,14 +457,13 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: { value: 4 }, even: { value: 16 } } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. * @param initialValue The function that provides the initial value for the accumulator. * - * @returns A new {@link ReducedIterator} containing the reduced results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group. */ public async reduce(reducer: MaybeAsyncKeyedReducer, initialValue: (key: K) => MaybePromise) : Promise>; @@ -548,14 +517,16 @@ export default class AggregatedAsyncIterator * * ```ts * const results = new SmartAsyncIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) - * .groupBy(async ([value, _]) => value % 2 === 0 ? "even" : "odd") + * .groupBy(async (values) => + * { + * const value = values instanceof Array ? values[0] : values; + * return value % 2 === 0 ? "even" : "odd"; + * }) * .flatMap(async (key, values) => values); * * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -569,7 +540,6 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, V]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -606,8 +576,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [3, 5], even: [6, 8] } * ``` * - * --- - * * @param count The number of elements to drop from the beginning of each group. * * @returns A new {@link AggregatedAsyncIterator} containing the remaining elements. @@ -619,7 +587,6 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -655,8 +622,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, -1], even: [0, 2] } * ``` * - * --- - * * @param count The number of elements to take from the beginning of each group. * * @returns A new {@link AggregatedAsyncIterator} containing the taken elements. @@ -668,7 +633,6 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -701,11 +665,11 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 3, even: 2 } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing + * the first element that satisfies the condition for each group. */ public async find(predicate: MaybeAsyncKeyedIteratee): Promise>; @@ -729,8 +693,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: -3, even: 0 } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -739,7 +701,9 @@ export default class AggregatedAsyncIterator * * @param predicate The type guard condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing + * the first element that satisfies the condition for each group. */ public async find(predicate: MaybeAsyncKeyedIteratee) : Promise>; @@ -783,8 +747,6 @@ export default class AggregatedAsyncIterator * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] } * ``` * - * --- - * * @returns A new {@link AggregatedAsyncIterator} containing the enumerated elements. */ public enumerate(): AggregatedAsyncIterator @@ -811,8 +773,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns A new {@link AggregatedAsyncIterator} containing only the unique elements. */ public unique(): AggregatedAsyncIterator @@ -822,11 +782,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const keys = new Map>(); - for await (const [key, element] of elements) { const values = keys.get(key) ?? new Set(); - if (values.has(element)) { continue; } values.add(element); @@ -851,9 +809,8 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * - * @returns A new {@link ReducedIterator} containing the number of elements for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the number of elements for each group. */ public async count(): Promise> { @@ -889,9 +846,9 @@ export default class AggregatedAsyncIterator * }; * ``` * - * --- - * * @param iteratee The function to execute for each element of the iterator. + * + * @returns A {@link Promise} that will resolve once the iteration is complete. */ public async forEach(iteratee: MaybeAsyncKeyedIteratee): Promise { @@ -927,8 +884,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] } * ``` * - * --- - * * @template J The type of the new key. * * @param iteratee The function to determine the new key for each element of the iterator. @@ -943,11 +898,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[J, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [await iteratee(key, element, index), element]; indexes.set(key, index + 1); @@ -974,8 +927,6 @@ export default class AggregatedAsyncIterator * console.log(await keys.toArray()); // ["number", "symbol", "string", "object", "boolean"] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing all the keys of the iterator. */ public keys(): SmartAsyncIterator @@ -985,7 +936,6 @@ export default class AggregatedAsyncIterator return new SmartAsyncIterator(async function* () { const keys = new Set(); - for await (const [key] of elements) { if (keys.has(key)) { continue; } @@ -1016,8 +966,6 @@ export default class AggregatedAsyncIterator * console.log(await entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing all the entries of the iterator. */ public entries(): SmartAsyncIterator<[K, T]> @@ -1044,8 +992,6 @@ export default class AggregatedAsyncIterator * console.log(await values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing all the values of the iterator. */ public values(): SmartAsyncIterator @@ -1071,9 +1017,7 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]] * ``` * - * --- - * - * @returns An {@link Array} of arrays containing the elements of the iterator. + * @returns A {@link Promise} resolving to an {@link Array} containing all the values of the iterator. */ public async toArray(): Promise { @@ -1095,9 +1039,7 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] } * ``` * - * --- - * - * @returns A {@link Map} containing the elements of the iterator. + * @returns A {@link Promise} resolving to a {@link Map} containing all the entries of the iterator. */ public async toMap(): Promise> { @@ -1127,9 +1069,7 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * - * @returns An {@link Object} containing the elements of the iterator. + * @returns A {@link Promise} resolving to an object containing all the entries of the iterator. */ public async toObject(): Promise> { diff --git a/src/models/aggregators/aggregated-iterator.ts b/src/models/aggregators/aggregated-iterator.ts index 9ba4b92..c26cc42 100644 --- a/src/models/aggregators/aggregated-iterator.ts +++ b/src/models/aggregators/aggregated-iterator.ts @@ -30,8 +30,6 @@ import type { KeyedIteratee, KeyedTypeGuardPredicate, KeyedReducer } from "./typ * console.log(results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * @template T The type of the elements to aggregate. */ @@ -49,8 +47,6 @@ export default class AggregatedIterator * const iterator = new AggregatedIterator([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]); * ``` * - * --- - * * @param iterable The iterable to aggregate. */ public constructor(iterable: Iterable<[K, T]>); @@ -73,8 +69,6 @@ export default class AggregatedIterator * }); * ``` * - * --- - * * @param iterator The iterator to aggregate. */ public constructor(iterator: Iterator<[K, T]>); @@ -94,8 +88,6 @@ export default class AggregatedIterator * }); * ``` * - * --- - * * @param generatorFn The generator function to aggregate. */ public constructor(generatorFn: GeneratorFunction<[K, T]>); @@ -107,8 +99,6 @@ export default class AggregatedIterator * const iterator = new AggregatedIterator(keyedValues); * ``` * - * --- - * * @param argument The iterable, iterator or generator function to aggregate. */ public constructor(argument: IteratorLike<[K, T]> | GeneratorFunction<[K, T]>); @@ -138,8 +128,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the boolean results for each group. @@ -184,8 +172,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A {@link ReducedIterator} containing the boolean results for each group. @@ -230,8 +216,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link AggregatedIterator} containing only the elements that satisfy the condition. @@ -259,8 +243,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, 5], even: [0, 6] } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -279,11 +261,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - if (predicate(key, element, index)) { yield [key, element]; } indexes.set(key, index + 1); @@ -312,8 +292,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -327,11 +305,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [key, iteratee(key, element, index)]; indexes.set(key, index + 1); @@ -361,8 +337,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * * @returns A new {@link ReducedIterator} containing the reduced results for each group. @@ -391,8 +365,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -424,8 +396,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: { value: 4 }, even: { value: 16 } } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -482,14 +452,16 @@ export default class AggregatedIterator * * ```ts * const results = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) - * .groupBy(([value, _]) => value % 2 === 0 ? "even" : "odd") + * .groupBy((values) => + * { + * const value = values instanceof Array ? values[0] : values; + * return value % 2 === 0 ? "even" : "odd"; + * }) * .flatMap((key, values) => values); * * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -503,7 +475,6 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -540,8 +511,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [3, 5], even: [6, 8] } * ``` * - * --- - * * @param count The number of elements to drop from the beginning of each group. * * @returns A new {@link AggregatedIterator} containing the remaining elements. @@ -553,7 +522,6 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -589,8 +557,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, -1], even: [0, 2] } * ``` * - * --- - * * @param count The number of elements to take from the beginning of each group. * * @returns A new {@link AggregatedIterator} containing the taken elements. @@ -602,12 +568,10 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; if (index >= limit) { continue; } - yield [key, element]; indexes.set(key, index + 1); @@ -635,8 +599,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 3, even: 2 } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. @@ -663,8 +625,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: -3, even: 0 } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -715,8 +675,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] } * ``` * - * --- - * * @returns A new {@link AggregatedIterator} containing the enumerated elements. */ public enumerate(): AggregatedIterator @@ -743,8 +701,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns A new {@link AggregatedIterator} containing only the unique elements. */ public unique(): AggregatedIterator @@ -754,11 +710,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const keys = new Map>(); - for (const [key, element] of elements) { const values = keys.get(key) ?? new Set(); - if (values.has(element)) { continue; } values.add(element); @@ -783,8 +737,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @returns A new {@link ReducedIterator} containing the number of elements for each group. */ public count(): ReducedIterator @@ -821,18 +773,14 @@ export default class AggregatedIterator * }; * ``` * - * --- - * * @param iteratee The function to execute for each element of the iterator. */ public forEach(iteratee: KeyedIteratee): void { const indexes = new Map(); - for (const [key, element] of this._elements) { const index = indexes.get(key) ?? 0; - iteratee(key, element, index); indexes.set(key, index + 1); @@ -859,8 +807,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] } * ``` * - * --- - * * @template J The type of the new key. * * @param iteratee The function to determine the new key for each element of the iterator. @@ -874,11 +820,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [iteratee(key, element, index), element]; indexes.set(key, index + 1); @@ -905,8 +849,6 @@ export default class AggregatedIterator * console.log(keys.toArray()); // ["number", "symbol", "string", "object", "boolean"] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the keys of the iterator. */ public keys(): SmartIterator @@ -916,7 +858,6 @@ export default class AggregatedIterator return new SmartIterator(function* () { const keys = new Set(); - for (const [key] of elements) { if (keys.has(key)) { continue; } @@ -947,8 +888,6 @@ export default class AggregatedIterator * console.log(entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the entries of the iterator. */ public entries(): SmartIterator<[K, T]> @@ -975,8 +914,6 @@ export default class AggregatedIterator * console.log(values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the values of the iterator. */ public values(): SmartIterator @@ -1002,8 +939,6 @@ export default class AggregatedIterator * console.log(aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]] * ``` * - * --- - * * @returns An {@link Array} of arrays containing the elements of the iterator. */ public toArray(): T[][] @@ -1026,8 +961,6 @@ export default class AggregatedIterator * console.log(aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] } * ``` * - * --- - * * @returns A {@link Map} containing the elements of the iterator. */ public toMap(): Map @@ -1058,8 +991,6 @@ export default class AggregatedIterator * console.log(aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns An {@link Object} containing the elements of the iterator. */ public toObject(): Record diff --git a/src/models/aggregators/reduced-iterator.ts b/src/models/aggregators/reduced-iterator.ts index 1ffe238..ba42569 100644 --- a/src/models/aggregators/reduced-iterator.ts +++ b/src/models/aggregators/reduced-iterator.ts @@ -32,8 +32,6 @@ import type { KeyedIteratee, KeyedReducer, KeyedTypeGuardPredicate } from "./typ * console.log(results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @template K The type of the key used to group the elements. * @template T The type of the elements in the iterator. */ @@ -51,8 +49,6 @@ export default class ReducedIterator * const results = new ReducedIterator([["A", 1], ["B", 2], ["C", 4]]); * ``` * - * --- - * * @param iterable A reduced iterable object. */ public constructor(iterable: Iterable<[K, T]>); @@ -73,8 +69,6 @@ export default class ReducedIterator * }); * ``` * - * --- - * * @param iterator An reduced iterator object. */ public constructor(iterator: Iterator<[K, T]>); @@ -94,8 +88,6 @@ export default class ReducedIterator * }); * ``` * - * --- - * * @param generatorFn A generator function that produces the reduced elements. */ public constructor(generatorFn: GeneratorFunction<[K, T]>); @@ -107,8 +99,6 @@ export default class ReducedIterator * const results = new ReducedIterator(reducedValues); * ``` * - * --- - * * @param argument An iterable, iterator or generator function that produces the reduced elements. */ public constructor(argument: Iterable<[K, T]> | Iterator<[K, T]> | GeneratorFunction<[K, T]>); @@ -139,8 +129,6 @@ export default class ReducedIterator * console.log(results); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if all elements satisfy the condition, `false` otherwise. @@ -177,8 +165,6 @@ export default class ReducedIterator * console.log(results); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if any element satisfies the condition, `false` otherwise. @@ -215,8 +201,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing only the elements that satisfy the condition. @@ -245,8 +229,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: 4 } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the iterator. @@ -293,8 +275,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: 8, even: 32 } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -337,8 +317,6 @@ export default class ReducedIterator * console.log(result); // 20 * ``` * - * --- - * * @param reducer The reducer function to apply to the elements of the iterator. * * @returns The final value after reducing all the elements of the iterator. @@ -352,7 +330,7 @@ export default class ReducedIterator * It will iterate over all the elements of the iterator applying the reducer function. * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the provided initial value. + * The first accumulator value will be the provided initial value. * The last accumulator value will be the final result of the reduction. * * If the iterator is infinite, the method will never return. @@ -366,8 +344,6 @@ export default class ReducedIterator * console.log(result); // { value: 20 } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to the elements of the iterator. @@ -413,7 +389,7 @@ export default class ReducedIterator * new one is and that consuming one of them will consume the other as well. * * ```ts - * const results = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + * const results = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) * .groupBy((value) => value % 2 === 0 ? "even" : "odd") * .reduce((key, accumulator, value) => accumulator.concat([value]), () => []) * .flatMap((key, value) => value); @@ -421,8 +397,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -471,8 +445,6 @@ export default class ReducedIterator * * console.log(results.toObject()); // { even: [0, 2, 6, 8] } * ``` - * - * --- * * @param count The number of elements to drop. * @@ -503,7 +475,7 @@ export default class ReducedIterator * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * - * Only the taken elements will be consumed from the original reduced iterator. + * Only the taken elements will be consumed from the original reduced iterator. * The rest of the original reduced iterator will be available for further consumption. * * ```ts @@ -517,8 +489,6 @@ export default class ReducedIterator * console.log(reduced.toObject()); // { even: [0, 2, 6, 8] } * ``` * - * --- - * * @param count The number of elements to take. * * @returns A new {@link ReducedIterator} containing the taken elements. @@ -532,15 +502,80 @@ export default class ReducedIterator for (const [index, [key, element]] of elements) { if (index >= count) { break; } - yield [key, element]; } }); } - public find() + /** + * Finds the first element of the reduced iterator that satisfies the given condition. + * + * This method will iterate over all the elements of the iterator checking if they satisfy the condition. + * The first element that satisfies the condition will be returned immediately. + * + * Only the elements that are necessary to find the first + * satisfying one will be consumed from the original iterator. + * The rest of the iterator will be available for further consumption. + * + * Also note that: + * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed. + * - If the iterator is infinite and no element satisfies the condition, the method will never return. + * + * ```ts + * const results = new SmartIterator([-3, -3, -1, 0, 1, 2, 5, 6, 8]) + * .groupBy((value) => value % 2 === 0 ? "even" : "odd") + * .reduce((key, accumulator, value) => accumulator + value) + * .find((key, value) => value > 0); + * + * console.log(results); // 16 + * + * @param predicate The condition to check for each element of the iterator. + * + * @returns The first element that satisfies the condition, `undefined` otherwise. + */ + public find(predicate: KeyedIteratee): T | undefined; + + /** + * Finds the first element of the reduced iterator that satisfies the given type guard predicate. + * + * This method will iterate over all the elements of the iterator checking if they satisfy the condition. + * The first element that satisfies the condition will be returned immediately. + * + * Only the elements that are necessary to find the first + * satisfying one will be consumed from the original iterator. + * The rest of the iterator will be available for further consumption. + * + * Also note that: + * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed. + * - If the iterator is infinite and no element satisfies the condition, the method will never return. + * + * ```ts + * const results = new SmartIterator(["-3", -3, "-1", 0, 1, 2, "5", 6, 8]) + * .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd") + * .reduce((key, accumulator, value) => accumulator + value) + * .find((key, value) => typeof value === "number"); + * + * console.log(results); // 16 + * + * @template S + * The type of the elements that satisfy the condition. + * This allows the type-system to infer the correct type of the result. + * + * It must be a subtype of the original type of the elements. + * + * @param predicate The type guard condition to check for each element of the iterator. + * + * @returns The first element that satisfies the condition, `undefined` otherwise. + */ + public find(predicate: KeyedTypeGuardPredicate): S | undefined; + public find(predicate: KeyedIteratee): T | undefined { - // TODO! + for (const [index, [key, element]] of this._elements.enumerate()) + { + if (predicate(key, element, index)) { return element; } + } + + return undefined; } /** @@ -563,8 +598,6 @@ export default class ReducedIterator * console.log(results.toObject()); // [[0, 4], [1, 16]] * ``` * - * --- - * * @returns A new {@link ReducedIterator} object containing the enumerated elements. */ public enumerate(): ReducedIterator @@ -601,7 +634,6 @@ export default class ReducedIterator return new ReducedIterator(function* () { const values = new Set(); - for (const [key, element] of elements) { if (values.has(element)) { continue; } @@ -627,8 +659,6 @@ export default class ReducedIterator * console.log(results); // 2 * ``` * - * --- - * * @returns The number of elements in the iterator. */ public count(): number @@ -658,8 +688,6 @@ export default class ReducedIterator * }); * ``` * - * --- - * * @param iteratee The function to apply to each element of the reduced iterator. */ public forEach(iteratee: KeyedIteratee): void @@ -690,8 +718,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { positive: 4, negative: -12 } * ``` * - * --- - * * @template J The type of the new keys used to group the elements. * * @param iteratee The function to determine the new key of each element of the iterator. @@ -731,8 +757,6 @@ export default class ReducedIterator * console.log(keys.toArray()); // ["odd", "even"] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the keys of the iterator. */ public keys(): SmartIterator @@ -769,8 +793,6 @@ export default class ReducedIterator * console.log(entries.toArray()); // [["odd", 4], ["even", 16]] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the entries of the iterator. */ public entries(): SmartIterator<[K, T]> @@ -798,8 +820,6 @@ export default class ReducedIterator * console.log(values.toArray()); // [4, 16] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the values of the iterator. */ public values(): SmartIterator @@ -829,8 +849,6 @@ export default class ReducedIterator * console.log(reduced.toArray()); // [4, 16] * ``` * - * --- - * * @returns The {@link Array} containing all elements of the iterator. */ public toArray(): T[] @@ -852,8 +870,6 @@ export default class ReducedIterator * console.log(reduced.toMap()); // Map(2) { "odd" => 4, "even" => 16 } * ``` * - * --- - * * @returns The {@link Map} containing all elements of the iterator. */ public toMap(): Map @@ -875,8 +891,6 @@ export default class ReducedIterator * console.log(reduced.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @returns The {@link Object} containing all elements of the iterator. */ public toObject(): Record diff --git a/src/models/aggregators/types.ts b/src/models/aggregators/types.ts index 7169ca0..9178b51 100644 --- a/src/models/aggregators/types.ts +++ b/src/models/aggregators/types.ts @@ -14,8 +14,6 @@ import type { MaybePromise } from "../promises/types.js"; * console.log(results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. @@ -36,8 +34,6 @@ export type KeyedIteratee = (key: K, value: * console.log(await results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. @@ -58,8 +54,6 @@ export type AsyncKeyedIteratee = (key: K, va * console.log(await results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. @@ -86,8 +80,6 @@ export type MaybeAsyncKeyedIteratee = * console.log(results.toObject()); // { odd: ["0", "5", "8"], even: [] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R @@ -119,8 +111,6 @@ export type KeyedTypeGuardPredicate = * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template A The type of the accumulator. @@ -142,8 +132,6 @@ export type KeyedReducer = (key: K, accumulator: A, * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template A The type of the accumulator. @@ -166,8 +154,6 @@ export type AsyncKeyedReducer = * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template A The type of the accumulator. diff --git a/src/models/callbacks/callable-object.ts b/src/models/callbacks/callable-object.ts index 41e7443..c13ea08 100644 --- a/src/models/callbacks/callable-object.ts +++ b/src/models/callbacks/callable-object.ts @@ -25,8 +25,6 @@ const SmartFunction = (Function as unknown) as new { callback.enabled = false; }); * ``` * - * --- - * * @template T * The type signature of the callback function. * It must be a function. Default is `(...args: any[]) => any`. diff --git a/src/models/callbacks/publisher.ts b/src/models/callbacks/publisher.ts index f02ff9e..e27d066 100644 --- a/src/models/callbacks/publisher.ts +++ b/src/models/callbacks/publisher.ts @@ -30,8 +30,6 @@ import type { Callback } from "./types.js"; * }); * ``` * - * --- - * * @template T * A map containing the names of the emittable events and the * related callback signatures that can be subscribed to them. @@ -93,8 +91,6 @@ export default class Publisher * publisher.publish("player:move", { x: 10, y: 20 }); * ``` * - * --- - * * @template K The key of the map containing the callback signature to publish. * * @param event The name of the event to publish. @@ -123,8 +119,6 @@ export default class Publisher * }); * ``` * - * --- - * * @template K The key of the map containing the callback signature to subscribe. * * @param event The name of the event to subscribe to. @@ -162,8 +156,6 @@ export default class Publisher * publisher.subscribe("player:death", () => publisher.unsubscribe("player:move", onPlayerMove)); * ``` * - * --- - * * @template K The key of the map containing the callback signature to unsubscribe. * * @param event The name of the event to unsubscribe from. diff --git a/src/models/callbacks/switchable-callback.ts b/src/models/callbacks/switchable-callback.ts index 592ce6e..4372d21 100644 --- a/src/models/callbacks/switchable-callback.ts +++ b/src/models/callbacks/switchable-callback.ts @@ -20,8 +20,6 @@ import type { Callback } from "./types.js"; * window.addEventListener("pointerup", () => { onPointerMove.switch("released"); }); * ``` * - * --- - * * @template T The type signature of the callback. Default is `(...args: any[]) => any`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -167,8 +165,6 @@ export default class SwitchableCallback = Callbac * onPointerMove.register("released", () => { [...] }); * ``` * - * --- - * * @param key The key that will be associated with the implementation. * @param callback The implementation to register. */ @@ -198,8 +194,6 @@ export default class SwitchableCallback = Callbac * onPointerMove.unregister("released"); * ``` * - * --- - * * @param key The key that is associated with the implementation to unregister. */ public unregister(key: string): void @@ -227,8 +221,6 @@ export default class SwitchableCallback = Callbac * window.addEventListener("pointerup", () => { onPointerMove.switch("released"); }); * ``` * - * --- - * * @param key The key that is associated with the implementation to switch to. */ public switch(key: string): void diff --git a/src/models/callbacks/types.ts b/src/models/callbacks/types.ts index 06ddc71..6fa4cc4 100644 --- a/src/models/callbacks/types.ts +++ b/src/models/callbacks/types.ts @@ -8,8 +8,6 @@ * const callback: Callback<[PointerEvent]> = (evt: PointerEvent): void => { [...] }; * ``` * - * --- - * * @template A * The type of the arguments that the function accepts. * It must be an array of types, even if it's empty. Default is `[]`. diff --git a/src/models/exceptions/core.ts b/src/models/exceptions/core.ts index 13319c3..b3559c6 100644 --- a/src/models/exceptions/core.ts +++ b/src/models/exceptions/core.ts @@ -36,8 +36,6 @@ export default class Exception extends Error * } * ``` * - * --- - * * @param error The caught error to convert. * * @returns An instance of the {@link Exception} class. @@ -68,8 +66,6 @@ export default class Exception extends Error * throw new Exception("An error occurred while processing the request."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"Exception"`. @@ -125,8 +121,6 @@ export class FatalErrorException extends Exception * throw new FatalErrorException("This error should never happen. Please, contact the support team."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FatalErrorException"`. @@ -170,8 +164,6 @@ export class NotImplementedException extends FatalErrorException * throw new NotImplementedException("This method hasn't been implemented yet. Check back later."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"NotImplementedException"`. diff --git a/src/models/exceptions/index.ts b/src/models/exceptions/index.ts index bd0e59b..3050ce6 100644 --- a/src/models/exceptions/index.ts +++ b/src/models/exceptions/index.ts @@ -26,8 +26,6 @@ export class FileException extends Exception * throw new FileException("An error occurred while trying to read the file."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FileException"`. @@ -61,8 +59,6 @@ export class FileExistsException extends FileException * throw new FileExistsException("The file named 'data.json' already exists on the server."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FileExistsException"`. @@ -96,8 +92,6 @@ export class FileNotFoundException extends FileException * throw new FileNotFoundException("The file named 'data.json' wasn't found on the server."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FileNotFoundException"`. @@ -131,8 +125,6 @@ export class KeyException extends Exception * throw new KeyException("The 'id' key wasn't found in the dictionary."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"KeyException"`. @@ -174,8 +166,6 @@ export class NetworkException extends Exception * throw new NetworkException("Couldn't connect to the server. Please, try again later."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"NetworkException"`. @@ -209,8 +199,6 @@ export class PermissionException extends Exception * throw new PermissionException("You don't have permission to access this resource."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"PermissionException"`. @@ -244,8 +232,6 @@ export class ReferenceException extends Exception * throw new ReferenceException("The 'canvas' element wasn't found in the document."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"ReferenceException"`. @@ -281,8 +267,6 @@ export class RuntimeException extends Exception * throw new RuntimeException("The received input seems to be malformed or corrupted."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"RuntimeException"`. @@ -316,8 +300,6 @@ export class EnvironmentException extends RuntimeException * throw new EnvironmentException("The required environment variable 'API_KEY' isn't set."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"EnvironmentException"`. @@ -350,8 +332,6 @@ export class TimeoutException extends Exception * throw new TimeoutException("The task took too long to complete."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"TimeoutException"`. @@ -387,8 +367,6 @@ export class TypeException extends Exception * throw new TypeException("The 'username' argument must be a valid string."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"TypeException"`. @@ -424,8 +402,6 @@ export class ValueException extends Exception * throw new ValueException("The 'grade' argument cannot be negative."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"ValueException"`. @@ -461,8 +437,6 @@ export class RangeException extends ValueException * throw new RangeException("The 'percentage' argument must be between 0 and 100."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"RangeException"`. diff --git a/src/models/index.ts b/src/models/index.ts index 373071f..9a1fd97 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -10,6 +10,7 @@ export { Exception, FatalErrorException, NotImplementedException, + EnvironmentException, FileException, FileExistsException, FileNotFoundException, @@ -25,12 +26,7 @@ export { } from "./exceptions/index.js"; -import GameLoop from "./game-loop.js"; - export { SmartIterator, SmartAsyncIterator } from "./iterators/index.js"; export { JSONStorage } from "./json/index.js"; export { DeferredPromise, SmartPromise, TimedPromise } from "./promises/index.js"; - -export { Clock, Countdown } from "./timers/index.js"; - -export { GameLoop }; +export { Clock, Countdown, GameLoop } from "./timers/index.js"; diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index 2b4629e..5838c34 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -1,5 +1,6 @@ import AggregatedAsyncIterator from "../aggregators/aggregated-async-iterator.js"; import { ValueException } from "../exceptions/index.js"; +import type { MaybePromise } from "../types.js"; import type { GeneratorFunction, @@ -36,8 +37,6 @@ import type { * console.log(await result); // 31 * ``` * - * --- - * * @template T The type of elements in the iterator. * @template R The type of the final result of the iterator. Default is `void`. * @template N The type of the argument passed to the `next` method. Default is `undefined`. @@ -56,8 +55,6 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator(["A", "B", "C"]); * ``` * - * --- - * * @param iterable The iterable object to wrap. */ public constructor(iterable: Iterable); @@ -69,8 +66,6 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); * ``` * - * --- - * * @param iterable The asynchronous iterable object to wrap. */ public constructor(iterable: AsyncIterable); @@ -82,7 +77,7 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator({ * _sum: 0, _count: 0, * - * next: function (value: number) + * next: function(value: number) * { * this._sum += value; * this._count += 1; @@ -92,8 +87,6 @@ export default class SmartAsyncIterator implements A * }) * ``` * - * --- - * * @param iterator The iterator object to wrap. */ public constructor(iterator: Iterator); @@ -105,7 +98,7 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator({ * _sum: 0, _count: 0, * - * next: async function (value: number) + * next: async function(value: number) * { * this._sum += value; * this._count += 1; @@ -115,8 +108,6 @@ export default class SmartAsyncIterator implements A * }) * ``` * - * --- - * * @param iterator The asynchronous iterator object to wrap. */ public constructor(iterator: AsyncIterator); @@ -131,8 +122,6 @@ export default class SmartAsyncIterator implements A * }); * ``` * - * --- - * * @param generatorFn The generator function to wrap. */ public constructor(generatorFn: GeneratorFunction); @@ -147,8 +136,6 @@ export default class SmartAsyncIterator implements A * }); * ``` * - * --- - * * @param generatorFn The asynchronous generator function to wrap. */ public constructor(generatorFn: AsyncGeneratorFunction); @@ -160,8 +147,6 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator(values); * ``` * - * --- - * * @param argument The synchronous or asynchronous iterable, iterator or generator function to wrap. */ public constructor(argument: MaybeAsyncIteratorLike | MaybeAsyncGeneratorFunction); @@ -248,11 +233,10 @@ export default class SmartAsyncIterator implements A * console.log(result); // false * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * - * @returns A promise that will resolve to `true` if all elements satisfy the condition, `false` otherwise. + * @returns + * A {@link Promise} that will resolve to `true` if all elements satisfy the condition, `false` otherwise. */ public async every(predicate: MaybeAsyncIteratee): Promise { @@ -289,11 +273,10 @@ export default class SmartAsyncIterator implements A * console.log(result); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * - * @returns A promise that will resolve to `true` if any element satisfies the condition, `false` otherwise. + * @returns + * A {@link Promise} that will resolve to `true` if any element satisfies the condition, `false` otherwise. */ public async some(predicate: MaybeAsyncIteratee): Promise { @@ -330,8 +313,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [-2, -1] * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link SmartAsyncIterator} containing only the elements that satisfy the condition. @@ -358,8 +339,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [-2, 1] * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -378,11 +357,9 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (true) { const result = await iterator.next(); - if (result.done) { return result.value; } if (await predicate(result.value, index)) { yield result.value; } @@ -411,8 +388,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [2, 1, 0, 1, 2] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -426,7 +401,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (true) { const result = await iterator.next(); @@ -460,11 +434,9 @@ export default class SmartAsyncIterator implements A * console.log(result); // 15 * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * - * @returns A promise that will resolve to the final result of the reduction. + * @returns A {@link Promise} that will resolve to the final result of the reduction. */ public async reduce(reducer: MaybeAsyncReducer): Promise; @@ -487,14 +459,12 @@ export default class SmartAsyncIterator implements A * console.log(result); // 25 * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. * @param initialValue The initial value of the accumulator. * - * @returns A promise that will resolve to the final result of the reduction. + * @returns A {@link Promise} that will resolve to the final result of the reduction. */ public async reduce(reducer: MaybeAsyncReducer, initialValue: A): Promise; public async reduce(reducer: MaybeAsyncReducer, initialValue?: A): Promise @@ -541,8 +511,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -556,7 +524,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (true) { const result = await iterator.next(); @@ -596,8 +563,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [1, 2] * ``` * - * --- - * * @param count The number of elements to drop. * * @returns A new {@link SmartAsyncIterator} containing the remaining elements. @@ -609,7 +574,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (index < count) { const result = await iterator.next(); @@ -651,8 +615,6 @@ export default class SmartAsyncIterator implements A * console.log(await iterator.toArray()); // [1, 2] * ``` * - * --- - * * @param limit The number of elements to take. * * @returns A new {@link SmartAsyncIterator} containing the taken elements. @@ -664,7 +626,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (index < limit) { const result = await iterator.next(); @@ -700,22 +661,21 @@ export default class SmartAsyncIterator implements A * console.log(result); // 1 * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * - * @returns A promise that will resolve to the first element that satisfies the condition, `undefined` otherwise. + * @returns + * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise. */ public async find(predicate: MaybeAsyncIteratee): Promise; /** * Finds the first element of the iterator that satisfies a given condition. * - * This method will iterate over all elements of the iterator checking if they satisfy the condition. + * This method will iterate over all elements of the iterator checking if they satisfy the condition. * The first element that satisfies the condition will be returned immediately. * * Only the elements that are necessary to find the first - * satisfying one will be consumed from the original iterator. + * satisfying one will be consumed from the original iterator. * The rest of the original iterator will be available for further consumption. * * Also note that: @@ -729,8 +689,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // -2 * ``` * - * --- - * * @template S * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. @@ -739,7 +697,8 @@ export default class SmartAsyncIterator implements A * * @param predicate The type guard condition to check for each element of the iterator. * - * @returns A promise that will resolve to the first element that satisfies the condition, `undefined` otherwise. + * @returns + * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise. */ public async find(predicate: MaybeAsyncIteratee): Promise; public async find(predicate: MaybeAsyncIteratee): Promise @@ -778,8 +737,6 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing the enumerated elements. */ public enumerate(): SmartAsyncIterator<[number, T], R> @@ -805,8 +762,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [1, 2, 3, 4, 5] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing only the unique elements. */ public unique(): SmartAsyncIterator @@ -816,11 +771,9 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { const values = new Set(); - while (true) { const result = await iterator.next(); - if (result.done) { return result.value; } if (values.has(result.value)) { continue; } @@ -844,9 +797,7 @@ export default class SmartAsyncIterator implements A * console.log(result); // 5 * ``` * - * --- - * - * @returns A promise that will resolve to the number of elements in the iterator. + * @returns A {@link Promise} that will resolve to the number of elements in the iterator. */ public async count(): Promise { @@ -876,11 +827,9 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @param iteratee The function to apply to each element of the iterator. * - * @returns A promise that will resolve once the iteration is complete. + * @returns A {@link Promise} that will resolve once the iteration is complete. */ public async forEach(iteratee: MaybeAsyncIteratee): Promise { @@ -917,11 +866,10 @@ export default class SmartAsyncIterator implements A * console.log(result); // { done: true, value: undefined } * ``` * - * --- - * * @param values The value to pass to the next element, if required. * - * @returns A promise that will resolve to the result of the iteration, containing the value of the operation. + * @returns + * A {@link Promise} that will resolve to the result of the iteration, containing the value of the operation. */ public next(...values: N extends undefined ? [] : [N]): Promise> { @@ -951,17 +899,17 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @param value The final value of the iterator. * - * @returns A promise that will resolve to the final result of the iterator. + * @returns A {@link Promise} that will resolve to the final result of the iterator. */ - public async return(value?: R): Promise> + public async return(value?: MaybePromise): Promise> { - if (this._iterator.return) { return this._iterator.return(value); } + const _value = (await value) as R; + + if (this._iterator.return) { return await this._iterator.return(_value); } - return { done: true, value: value as R }; + return { done: true, value: _value }; } /** @@ -996,11 +944,9 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @param error The error to throw into the iterator. * - * @returns A promise that will resolve to the final result of the iterator. + * @returns A {@link Promise} that will resolve to the final result of the iterator. */ public throw(error: unknown): Promise> { @@ -1028,8 +974,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * * @param iteratee The key function to apply to each element of the iterator. @@ -1062,7 +1006,7 @@ export default class SmartAsyncIterator implements A * console.log(result); // [0, 1, 2, 3, 4] * ``` * - * @returns A promise that will resolve to an array containing all elements of the iterator. + * @returns A {@link Promise} that will resolve to an array containing all elements of the iterator. */ public toArray(): Promise { diff --git a/src/models/iterators/smart-iterator.ts b/src/models/iterators/smart-iterator.ts index 4649bce..167c8d7 100644 --- a/src/models/iterators/smart-iterator.ts +++ b/src/models/iterators/smart-iterator.ts @@ -28,8 +28,6 @@ import type { GeneratorFunction, Iteratee, TypeGuardPredicate, Reducer, Iterator * console.log(result); // 31 * ``` * - * --- - * * @template T The type of elements in the iterator. * @template R The type of the final result of the iterator. Default is `void`. * @template N The type of the argument required by the `next` method. Default is `undefined`. @@ -48,8 +46,6 @@ export default class SmartIterator implements Iterat * const iterator = new SmartIterator(["A", "B", "C"]); * ``` * - * --- - * * @param iterable The iterable object to wrap. */ public constructor(iterable: Iterable); @@ -61,7 +57,7 @@ export default class SmartIterator implements Iterat * const iterator = new SmartIterator({ * _sum: 0, _count: 0, * - * next: function (value: number) + * next: function(value: number) * { * this._sum += value; * this._count += 1; @@ -71,8 +67,6 @@ export default class SmartIterator implements Iterat * }) * ``` * - * --- - * * @param iterator The iterator object to wrap. */ public constructor(iterator: Iterator); @@ -87,8 +81,6 @@ export default class SmartIterator implements Iterat * }); * ``` * - * --- - * * @param generatorFn The generator function to wrap. */ public constructor(generatorFn: GeneratorFunction); @@ -100,8 +92,6 @@ export default class SmartIterator implements Iterat * const iterator = new SmartIterator(values); * ``` * - * --- - * * @param argument The iterable, iterator or generator function to wrap. */ public constructor(argument: IteratorLike | GeneratorFunction); @@ -141,8 +131,6 @@ export default class SmartIterator implements Iterat * console.log(result); // false * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if all elements satisfy the condition, `false` otherwise. @@ -182,8 +170,6 @@ export default class SmartIterator implements Iterat * console.log(result); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if any element satisfies the condition, `false` otherwise. @@ -223,8 +209,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [-2, -1] * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link SmartIterator} containing only the elements that satisfy the condition. @@ -251,8 +235,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [-2, 1] * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -271,11 +253,9 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { let index = 0; - while (true) { const result = iterator.next(); - if (result.done) { return result.value; } if (predicate(result.value, index)) { yield result.value; } @@ -304,8 +284,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [2, 1, 0, 1, 2] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -319,7 +297,6 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { let index = 0; - while (true) { const result = iterator.next(); @@ -353,8 +330,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 15 * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * * @returns The final result of the reduction. @@ -380,8 +355,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 25 * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -434,8 +407,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -449,7 +420,6 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { let index = 0; - while (true) { const result = iterator.next(); @@ -489,8 +459,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [1, 2] * ``` * - * --- - * * @param count The number of elements to drop. * * @returns A new {@link SmartIterator} containing the remaining elements. @@ -543,8 +511,6 @@ export default class SmartIterator implements Iterat * console.log(iterator.toArray()); // [1, 2] * ``` * - * --- - * * @param limit The number of elements to take. * * @returns A new {@link SmartIterator} containing the taken elements. @@ -591,8 +557,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 1 * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns The first element that satisfies the condition, `undefined` otherwise. @@ -620,8 +584,6 @@ export default class SmartIterator implements Iterat * console.log(result); // -2 * ``` * - * --- - * * @template S * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. @@ -666,8 +628,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [[0, "A"], [1, "M"], [2, "N"], [3, "Z"]] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing the enumerated elements. */ public enumerate(): SmartIterator<[number, T], R> @@ -693,8 +653,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [1, 2, 3, 4, 5] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing only the unique elements. */ public unique(): SmartIterator @@ -704,14 +662,11 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { const values = new Set(); - while (true) { const result = iterator.next(); - if (result.done) { return result.value; } if (values.has(result.value)) { continue; } - values.add(result.value); yield result.value; @@ -732,8 +687,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 5 * ``` * - * --- - * * @returns The number of elements in the iterator. */ public count(): number @@ -764,8 +717,6 @@ export default class SmartIterator implements Iterat * } * ``` * - * --- - * * @param iteratee The function to apply to each element of the iterator. */ public forEach(iteratee: Iteratee): void @@ -803,8 +754,6 @@ export default class SmartIterator implements Iterat * console.log(result); // { done: true, value: undefined } * ``` * - * --- - * * @param values The value to pass to the next element, if required. * * @returns The result of the iteration, containing the value of the operation. @@ -837,8 +786,6 @@ export default class SmartIterator implements Iterat * } * ``` * - * --- - * * @param value The final value of the iterator. * * @returns The result of the iterator. @@ -882,8 +829,6 @@ export default class SmartIterator implements Iterat * } * ``` * - * --- - * * @param error The error to throw into the iterator. * * @returns The final result of the iterator. @@ -897,7 +842,8 @@ export default class SmartIterator implements Iterat /** * An utility method that aggregates the elements of the iterator using a given key function. - * The elements will be grouped by the resulting keys in a new specialized iterator. See {@link AggregatedIterator}. + * The elements will be grouped by the resulting keys in a new specialized iterator. + * See {@link AggregatedIterator}. * * Since the iterator is lazy, the grouping process will * be executed once the resulting iterator is materialized. @@ -913,8 +859,6 @@ export default class SmartIterator implements Iterat * console.log(result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * * @param iteratee The key function to apply to each element of the iterator. diff --git a/src/models/iterators/types.ts b/src/models/iterators/types.ts index f018351..ede99d4 100644 --- a/src/models/iterators/types.ts +++ b/src/models/iterators/types.ts @@ -11,8 +11,6 @@ import type { MaybePromise } from "../promises/types.js"; * } * ``` * - * --- - * * @template T The type of the elements in the iterable. */ export type MaybeAsyncIterable = Iterable | AsyncIterable; @@ -28,8 +26,6 @@ export type MaybeAsyncIterable = Iterable | * } * ``` * - * --- - * * @template T The type of the elements in the iterator. */ export type MaybeAsyncIterator = Iterator | AsyncIterator; @@ -60,8 +56,6 @@ export type MaybeAsyncGenerator = Generator * } * ``` * - * --- - * * @template T The type of the elements generated by the generator. * @template R The type of the return value of the generator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -81,8 +75,6 @@ export type GeneratorFunction = () => Generator = () => AsyncGene * } * ``` * - * --- - * * @template T The type of the elements generated by the generator. * @template R The type of the return value of the generator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -122,8 +112,6 @@ export type MaybeAsyncGeneratorFunction = () => Mayb * console.log(values); // ["1", "2", "3", "4", "5"] * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. */ @@ -142,8 +130,6 @@ export type Iteratee = (value: T, index: number) => R; * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. */ @@ -163,8 +149,6 @@ export type AsyncIteratee = (value: T, index: number) => Promise * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. */ @@ -186,8 +170,6 @@ export type MaybeAsyncIteratee = (value: T, index: number) => Maybe * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R * The type of the elements that pass the type guard. @@ -211,8 +193,6 @@ export type TypeGuardPredicate = (value: T, index: number) => va * console.log(total); // 15 * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template A The type of the accumulator. */ @@ -229,8 +209,6 @@ export type Reducer = (accumulator: A, value: T, index: number) => A; * console.log(result); // 15 * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template A The type of the accumulator. */ @@ -247,8 +225,6 @@ export type AsyncReducer = (accumulator: A, value: T, index: number) => Pr * console.log(result); // 15 * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template A The type of the accumulator. */ @@ -267,8 +243,6 @@ export type MaybeAsyncReducer = (accumulator: A, value: T, index: number) * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iterator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -276,7 +250,7 @@ export type MaybeAsyncReducer = (accumulator: A, value: T, index: number) export type IteratorLike = Iterable | Iterator; /** - * An union type that represents either an iterable or an iterator object that can be asynchronous. + * An union type that represents either an iterable or an iterator object that can be asynchronous. * More in general, it represents an object that can be looped over in one way or another. * * ```ts @@ -288,8 +262,6 @@ export type IteratorLike = Iterable | Itera * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iterator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -310,8 +282,6 @@ export type AsyncIteratorLike = AsyncIterable("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -129,8 +125,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.get("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -150,8 +144,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.get("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -178,8 +170,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.recall("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -195,8 +185,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.recall("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -213,8 +201,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.recall("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -236,8 +222,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.retrieve("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -254,8 +238,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.retrieve("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -273,8 +255,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.retrieve("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -295,8 +275,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.read("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -312,8 +290,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.read("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -330,8 +306,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.read("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -355,8 +329,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * @param persistent * Whether to prefer the persistent {@link localStorage} over the volatile {@link sessionStorage}. @@ -381,8 +353,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * * @returns `true` if the key exists, `false` otherwise. @@ -403,8 +373,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * * @returns `true` if the key exists, `false` otherwise. @@ -424,8 +392,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * * @returns `true` if the key exists, `false` otherwise. @@ -445,8 +411,6 @@ export default class JSONStorage * jsonStorage.set("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to set. * * @param key The key of the value to set. @@ -472,8 +436,6 @@ export default class JSONStorage * jsonStorage.remember("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to set. * * @param key The key of the value to set. @@ -494,8 +456,6 @@ export default class JSONStorage * jsonStorage.write("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to set. * * @param key The key of the value to set. @@ -513,8 +473,6 @@ export default class JSONStorage * jsonStorage.delete("key"); * ``` * - * --- - * * @param key The key of the value to remove. * @param persistent * Whether to prefer the persistent {@link localStorage} over the volatile {@link sessionStorage}. @@ -534,8 +492,6 @@ export default class JSONStorage * jsonStorage.forget("key"); * ``` * - * --- - * * @param key The key of the value to remove. */ public forget(key: string): void @@ -550,8 +506,6 @@ export default class JSONStorage * jsonStorage.erase("key"); * ``` * - * --- - * * @param key The key of the value to remove. */ public erase(key: string): void @@ -567,8 +521,6 @@ export default class JSONStorage * jsonStorage.clear("key"); * ``` * - * --- - * * @param key The key of the value to remove. */ public clear(key: string): void diff --git a/src/models/promises/deferred-promise.ts b/src/models/promises/deferred-promise.ts index 8ea0e94..4e73875 100644 --- a/src/models/promises/deferred-promise.ts +++ b/src/models/promises/deferred-promise.ts @@ -19,8 +19,6 @@ import SmartPromise from "./smart-promise.js"; * promise.resolve("Hello, World!"); * ``` * - * --- - * * @template T The type of value the promise expects to initially be resolved with. Default is `void`. * @template F * The type of value returned by the `onFulfilled` callback. @@ -64,8 +62,6 @@ export default class DeferredPromise extends SmartPr * const promise = new DeferredPromise((value: string) => value.split(" ")); * ``` * - * --- - * * @param onFulfilled The callback to execute once the promise is fulfilled. * @param onRejected The callback to execute once the promise is rejected. */ @@ -83,7 +79,7 @@ export default class DeferredPromise extends SmartPr _reject = reject; }); - this._promise.then(onFulfilled as FulfilledHandler, onRejected); + this._promise = this._promise.then(onFulfilled as FulfilledHandler, onRejected); this._resolve = _resolve!; this._reject = _reject!; @@ -100,8 +96,6 @@ export default class DeferredPromise extends SmartPr * deferred.watch(promise); * ``` * - * --- - * * @param otherPromise The promise to watch. * * @returns The current instance of the {@link DeferredPromise} class. diff --git a/src/models/promises/smart-promise.ts b/src/models/promises/smart-promise.ts index c81420e..87cb0d8 100644 --- a/src/models/promises/smart-promise.ts +++ b/src/models/promises/smart-promise.ts @@ -22,8 +22,6 @@ import type { FulfilledHandler, PromiseExecutor, RejectedHandler } from "./types * console.log(promise.isFulfilled); // true * ``` * - * --- - * * @template T The type of value the promise will eventually resolve to. Default is `void`. */ export default class SmartPromise implements Promise @@ -42,8 +40,6 @@ export default class SmartPromise implements Promise * console.log(smartRequest.isFulfilled); // true * ``` * - * --- - * * @param promise The promise to wrap. * * @returns A new {@link SmartPromise} object that wraps the provided promise. @@ -116,8 +112,6 @@ export default class SmartPromise implements Promise * }); * ``` * - * --- - * * @param executor * The function responsible for eventually resolving or rejecting the promise. * Similarly to the native {@link Promise} object, it's immediately executed after the promise is created. @@ -159,8 +153,6 @@ export default class SmartPromise implements Promise * console.log(await promise.then()); // "Hello, World!" * ``` * - * --- - * * @returns A new {@link Promise} identical to the original one. */ public then(onFulfilled?: null): Promise; @@ -180,8 +172,6 @@ export default class SmartPromise implements Promise * promise.then((result) => console.log(result)); // "Hello, World!" * ``` * - * --- - * * @template F The type of value the new promise will eventually resolve to. Default is `T`. * * @param onFulfilled The callback to execute once the promise is fulfilled. @@ -213,8 +203,6 @@ export default class SmartPromise implements Promise * promise.then(() => console.log("OK!"), () => console.log("KO!")); // "OK!" or "KO!" * ``` * - * --- - * * @template F The type of value the new promise will eventually resolve to. Default is `T`. * @template R The type of value the new promise will eventually resolve to. Default is `never`. * @@ -244,8 +232,6 @@ export default class SmartPromise implements Promise * promise.catch(); // Uncaught Error: An unknown error occurred. * ``` * - * --- - * * @returns A new {@link Promise} identical to the original one. */ public catch(onRejected?: null): Promise; @@ -268,8 +254,6 @@ export default class SmartPromise implements Promise * promise.catch((reason) => console.error(reason)); // "Error: An unknown error occurred." * ``` * - * --- - * * @template R The type of value the new promise will eventually resolve to. Default is `T`. * * @param onRejected The callback to execute once the promise is rejected. @@ -299,8 +283,6 @@ export default class SmartPromise implements Promise * .finally(() => console.log("Done!")); // Always logs "Done!". * ``` * - * --- - * * @param onFinally The callback to execute when once promise is settled. * * @returns A new {@link Promise} that executes the callback once the promise is settled. diff --git a/src/models/promises/timed-promise.ts b/src/models/promises/timed-promise.ts index 9a1a2fa..86b2695 100644 --- a/src/models/promises/timed-promise.ts +++ b/src/models/promises/timed-promise.ts @@ -21,8 +21,6 @@ import type { MaybePromise, PromiseExecutor } from "./types.js"; * .catch((error) => console.error(error)); // TimeoutException: The operation has timed out. * ``` * - * --- - * * @template T The type of value the promise will eventually resolve to. Default is `void`. */ export default class TimedPromise extends SmartPromise @@ -38,8 +36,6 @@ export default class TimedPromise extends SmartPromise * }, 5_000); * ``` * - * --- - * * @param executor * The function responsible for eventually resolving or rejecting the promise. * Similarly to the native {@link Promise} object, it's immediately executed after the promise is created. diff --git a/src/models/promises/types.ts b/src/models/promises/types.ts index 92c5c08..46a932f 100644 --- a/src/models/promises/types.ts +++ b/src/models/promises/types.ts @@ -9,8 +9,6 @@ * } * ``` * - * --- - * * @template T The type of the value. */ export type MaybePromise = T | PromiseLike; @@ -26,8 +24,6 @@ export type MaybePromise = T | PromiseLike; * .then(onFulfilled); * ``` * - * --- - * * @template T The type of value accepted by the function. Default is `void`. * @template R The type of value returned by the function. Default is `T`. */ @@ -44,8 +40,6 @@ export type FulfilledHandler = (value: T) => MaybePromise; * .catch(onRejected); * ``` * - * --- - * * @template E The type of value accepted by the function. Default is `unknown`. * @template R The type of value returned by the function. Default is `never`. */ @@ -61,8 +55,6 @@ export type RejectedHandler = (reason: E) => MaybePromis * await new Promise((resolve) => { _resolve = resolve; }); * ``` * - * --- - * * @template T The type of the value accepted by the function. Default is `void`. */ export type PromiseResolver = (result: MaybePromise) => void; @@ -77,8 +69,6 @@ export type PromiseResolver = (result: MaybePromise) => void; * await new Promise((_, reject) => { _reject = reject; }); * ``` * - * --- - * * @template E The type of the value accepted by the function. Default is `unknown`. */ export type PromiseRejecter = (reason?: MaybePromise) => void; @@ -96,8 +86,6 @@ export type PromiseRejecter = (reason?: MaybePromise) => void; * await new Promise(executor); * ``` * - * --- - * * @template T The type of value accepted by the `resolve` function. Default is `void`. * @template E The type of value accepted by the `reject` function. Default is `unknown`. */ diff --git a/src/models/timers/clock.ts b/src/models/timers/clock.ts index 6d2c03f..cc261bb 100644 --- a/src/models/timers/clock.ts +++ b/src/models/timers/clock.ts @@ -2,9 +2,10 @@ import { TimeUnit } from "../../utils/date.js"; import Publisher from "../callbacks/publisher.js"; import { FatalErrorException, RangeException, RuntimeException } from "../exceptions/index.js"; -import GameLoop from "../game-loop.js"; import type { Callback } from "../types.js"; +import GameLoop from "./game-loop.js"; + interface ClockEventMap { start: () => void; @@ -45,8 +46,6 @@ export default class Clock extends GameLoop * const clock = new Clock(); * ``` * - * --- - * * @param msIfNotBrowser * The interval in milliseconds at which the clock will tick if the environment is not a browser. * `TimeUnit.Second` by default. @@ -68,8 +67,6 @@ export default class Clock extends GameLoop * clock.start(); * ``` * - * --- - * * @param elapsedTime The elapsed time to set as default when the clock starts. Default is `0`. */ public override start(elapsedTime = 0): void @@ -113,8 +110,6 @@ export default class Clock extends GameLoop * clock.start(); * ``` * - * --- - * * @param callback The callback that will be executed when the clock ticks. * @param tickStep * The minimum time in milliseconds that must pass from the previous execution of the callback to the next one. diff --git a/src/models/timers/countdown.ts b/src/models/timers/countdown.ts index 2640faa..7b76fe6 100644 --- a/src/models/timers/countdown.ts +++ b/src/models/timers/countdown.ts @@ -2,10 +2,11 @@ import { TimeUnit } from "../../utils/date.js"; import Publisher from "../callbacks/publisher.js"; import { FatalErrorException, RangeException, RuntimeException } from "../exceptions/index.js"; -import GameLoop from "../game-loop.js"; import { DeferredPromise, SmartPromise } from "../promises/index.js"; import type { Callback } from "../types.js"; +import GameLoop from "./game-loop.js"; + interface CountdownEventMap { start: () => void; @@ -78,8 +79,6 @@ export default class Countdown extends GameLoop * const countdown = new Countdown(10_000); * ``` * - * --- - * * @param duration * The total duration of the countdown in milliseconds. * @@ -146,8 +145,6 @@ export default class Countdown extends GameLoop * countdown.start(); * ``` * - * --- - * * @param remainingTime * The remaining time to set as default when the countdown starts. * Default is the {@link Countdown.duration} itself. @@ -177,8 +174,6 @@ export default class Countdown extends GameLoop * countdown.stop(); * ``` * - * --- - * * @param reason * The reason why the countdown has stopped. * @@ -221,8 +216,6 @@ export default class Countdown extends GameLoop * countdown.start(); * ``` * - * --- - * * @param callback The callback that will be executed when the countdown ticks. * @param tickStep * The minimum time in milliseconds that must pass from the previous execution of the callback to the next one. @@ -239,7 +232,7 @@ export default class Countdown extends GameLoop if (tickStep < 0) { throw new RangeException("The tick step must be a non-negative number."); } if (tickStep === 0) { return this._publisher.subscribe("tick", callback); } - let lastTick = 0; + let lastTick = this.remainingTime; return this._publisher.subscribe("tick", (remainingTime: number) => { diff --git a/src/models/game-loop.ts b/src/models/timers/game-loop.ts similarity index 95% rename from src/models/game-loop.ts rename to src/models/timers/game-loop.ts index 87038c3..5e36ff7 100644 --- a/src/models/game-loop.ts +++ b/src/models/timers/game-loop.ts @@ -1,9 +1,9 @@ -import type { Interval } from "../core/types.js"; -import { isBrowser } from "../helpers.js"; +import type { Interval } from "../../core/types.js"; +import { isBrowser } from "../../helpers.js"; -import Publisher from "./callbacks/publisher.js"; -import { FatalErrorException, RuntimeException } from "./exceptions/index.js"; -import type { Callback } from "./types.js"; +import Publisher from "../callbacks/publisher.js"; +import { FatalErrorException, RuntimeException } from "../exceptions/index.js"; +import type { Callback } from "../types.js"; interface GameLoopEventMap { @@ -120,8 +120,6 @@ export default class GameLoop * const loop = new GameLoop((elapsedTime: number) => { [...] }); * ``` * - * --- - * * @param callback The function that will be executed at each iteration of the game loop. * @param msIfNotBrowser * The interval in milliseconds that will be used if the current environment isn't a browser. Default is `40`. @@ -171,8 +169,6 @@ export default class GameLoop * loop.start(); * ``` * - * --- - * * @param elapsedTime The elapsed time to set as default when the game loop starts. Default is `0`. */ public start(elapsedTime = 0): void @@ -218,8 +214,6 @@ export default class GameLoop * loop.onStart(() => { console.log("The game loop has started."); }); * ``` * - * --- - * * @param callback The function that will be executed when the game loop starts. * * @returns A function that can be used to unsubscribe from the event. @@ -236,8 +230,6 @@ export default class GameLoop * loop.onStop(() => { console.log("The game loop has stopped."); }); * ``` * - * --- - * * @param callback The function that will be executed when the game loop stops. * * @returns A function that can be used to unsubscribe from the event. diff --git a/src/models/timers/index.ts b/src/models/timers/index.ts index ab1d3b5..c98a2f1 100644 --- a/src/models/timers/index.ts +++ b/src/models/timers/index.ts @@ -1,4 +1,5 @@ import Clock from "./clock.js"; import Countdown from "./countdown.js"; +import GameLoop from "./game-loop.js"; -export { Clock, Countdown }; +export { Clock, Countdown, GameLoop }; diff --git a/src/utils/async.ts b/src/utils/async.ts index a655a8c..bde8212 100644 --- a/src/utils/async.ts +++ b/src/utils/async.ts @@ -8,11 +8,9 @@ * doSomethingElse(); * ``` * - * --- - * * @param milliseconds The number of milliseconds to wait before resolving the promise. * - * @returns A promise that resolves after the specified number of milliseconds. + * @returns A {@link Promise} that resolves after the specified number of milliseconds. */ export function delay(milliseconds: number): Promise { @@ -31,9 +29,7 @@ export function delay(milliseconds: number): Promise * $el.style.opacity = "1"; * ``` * - * --- - * - * @returns A promise that resolves on the next animation frame. + * @returns A {@link Promise} that resolves on the next animation frame. */ export function nextAnimationFrame(): Promise { @@ -53,9 +49,7 @@ export function nextAnimationFrame(): Promise * } * ``` * - * --- - * - * @returns A promise that resolves on the next microtask. + * @returns A {@link Promise} that resolves on the next microtask. */ export function yieldToEventLoop(): Promise { diff --git a/src/utils/curve.ts b/src/utils/curve.ts index ee3c3b1..460b36a 100644 --- a/src/utils/curve.ts +++ b/src/utils/curve.ts @@ -20,8 +20,6 @@ export default class Curve * } * ``` * - * --- - * * @param values The number of values to generate. * * @returns A {@link SmartIterator} object that generates the values following a linear curve. @@ -47,8 +45,6 @@ export default class Curve * } * ``` * - * --- - * * @param values The number of values to generate. * @param base * The base of the exponential curve. Default is `2`. diff --git a/src/utils/date.ts b/src/utils/date.ts index a3f95e8..21c1c18 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -1,5 +1,5 @@ -import { SmartIterator } from "../models/index.js"; +import { RangeException, SmartIterator } from "../models/index.js"; /** * An enumeration that represents the time units and their conversion factors. @@ -116,8 +116,6 @@ export enum WeekDay * dateDifference(start, end, TimeUnit.Minute); // 43200 * ``` * - * --- - * * @param start The start date. * @param end The end date. * @param unit The time unit to express the difference. `TimeUnit.Day` by default. @@ -126,10 +124,15 @@ export enum WeekDay */ export function dateDifference(start: string | Date, end: string | Date, unit = TimeUnit.Day): number { + let _round: (value: number) => number; + start = new Date(start); end = new Date(end); - return Math.floor((end.getTime() - start.getTime()) / unit); + if (start < end) { _round = Math.floor; } + else { _round = Math.ceil; } + + return _round((end.getTime() - start.getTime()) / unit); } /** @@ -146,16 +149,20 @@ export function dateDifference(start: string | Date, end: string | Date, unit = * } * ``` * - * --- - * * @param start The start date (included). - * @param end The end date (excluded). + * @param end + * The end date (excluded). + * + * Must be greater than the start date. Otherwise, a {@link RangeException} will be thrown. + * * @param step The time unit to express the step between the dates. `TimeUnit.Day` by default. * * @returns A {@link SmartIterator} object that generates the dates in the range. */ export function dateRange(start: string | Date, end: string | Date, step = TimeUnit.Day): SmartIterator { + if (start >= end) { throw new RangeException("The end date must be greater than the start date."); } + return new SmartIterator(function* () { const endTime = new Date(end).getTime(); @@ -180,17 +187,33 @@ export function dateRange(start: string | Date, end: string | Date, step = TimeU * dateRound(date, TimeUnit.Hour); // 2025-01-01T12:00:00.000Z * ``` * - * --- - * * @param date The date to round. - * @param unit The time unit to express the rounding. `TimeUnit.Day` by default. + * @param unit + * The time unit to express the rounding. `TimeUnit.Day` by default. + * + * Must be greater than a millisecond and less than or equal to a day. + * Otherwise, a {@link RangeException} will be thrown. * * @returns The rounded date. */ export function dateRound(date: string | Date, unit = TimeUnit.Day): Date { - date = new Date(date); + if (unit <= TimeUnit.Millisecond) + { + throw new RangeException( + "Rounding a timestamp by milliseconds or less makes no sense." + + "Use the timestamp value directly instead." + ); + } + if (unit > TimeUnit.Day) + { + throw new RangeException( + "Rounding by more than a day leads to unexpected results. " + + "Consider using other methods to round dates by weeks, months or years." + ); + } + date = new Date(date); return new Date(Math.floor(date.getTime() / unit) * unit); } @@ -204,8 +227,6 @@ export function dateRound(date: string | Date, unit = TimeUnit.Day): Date * getWeek(date, WeekDay.Monday); // 2024-12-30 * ``` * - * --- - * * @param date The date to get the week of. * @param firstDay The first day of the week. `WeekDay.Sunday` by default. * diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 2bbf6a9..8a6b0ec 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -6,13 +6,11 @@ * await loadScript("https://analytics.service/script.js?id=0123456789"); * ``` * - * --- - * * @param scriptUrl The URL of the script to load. * @param scriptType The type of the script to load. Default is `"text/javascript"`. * * @returns - * A promise that resolves when the script has been loaded successfully or rejects if an error occurs. + * A {@link Promise} that resolves when the script has been loaded successfully or rejects if an error occurs. */ export function loadScript(scriptUrl: string, scriptType = "text/javascript"): Promise { diff --git a/src/utils/iterator.ts b/src/utils/iterator.ts index 7e363a3..60edf99 100644 --- a/src/utils/iterator.ts +++ b/src/utils/iterator.ts @@ -1,4 +1,4 @@ -import { SmartIterator } from "../models/index.js"; +import { RangeException, SmartIterator } from "../models/index.js"; /** * An utility function that chains multiple iterables into a single one. @@ -17,8 +17,6 @@ import { SmartIterator } from "../models/index.js"; * } * ``` * - * --- - * * @template T The type of elements in the iterables. * * @param iterables The list of iterables to chain. @@ -47,8 +45,6 @@ export function chain(...iterables: readonly Iterable[]): SmartIterator * count([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // 10 * ``` * - * --- - * * @template T The type of elements in the iterable. * * @param elements The iterable to count. @@ -83,8 +79,6 @@ export function count(elements: Iterable): number * } * ``` * - * --- - * * @template T The type of elements in the iterable. * * @param elements The iterable to enumerate. @@ -96,7 +90,6 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> return new SmartIterator<[number, T]>(function* () { let index = 0; - for (const element of elements) { yield [index, element]; @@ -119,8 +112,6 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> * } * ``` * - * --- - * * @param end * The end value (excluded). * @@ -143,8 +134,6 @@ export function range(end: number): SmartIterator; * } * ``` * - * --- - * * @param start * The start value (included). * @@ -155,23 +144,39 @@ export function range(end: number): SmartIterator; * * If the `end` value is less than the `start` value, the iterator will generate the numbers in reverse order. * - * @param step The step between the numbers. Default is `1`. + * @param step + * The step between the numbers. Default is `1`. + * + * Must be a positive number. Otherwise, a {@link RangeError} will be thrown. * * @returns A {@link SmartIterator} object that generates the numbers in the range. */ export function range(start: number, end: number, step?: number): SmartIterator; export function range(start: number, end?: number, step = 1): SmartIterator { - return new SmartIterator(function* () + if (step <= 0) { - if (end === undefined) - { - end = start; - start = 0; - } + throw new RangeException( + "Step must be always a positive number, even when generating numbers in reverse order." + ); + } - if (start > end) { step = step ?? -1; } + if (end === undefined) + { + end = start; + start = 0; + } + if (start > end) + { + return new SmartIterator(function* () + { + for (let index = start; index > end; index -= step) { yield index; } + }); + } + + return new SmartIterator(function* () + { for (let index = start; index < end; index += step) { yield index; } }); } @@ -191,8 +196,6 @@ export function range(start: number, end?: number, step = 1): SmartIterator(iterable: Iterable): T[] * } * ``` * - * --- - * * @template T The type of elements in the iterable. * * @param elements The iterable to filter. @@ -236,7 +237,6 @@ export function unique(elements: Iterable): SmartIterator return new SmartIterator(function* () { const values = new Set(); - for (const element of elements) { if (values.has(element)) { continue; } @@ -261,8 +261,6 @@ export function unique(elements: Iterable): SmartIterator * } * ``` * - * --- - * * @template T The type of elements in the first iterable. * @template U The type of elements in the second iterable. * @@ -273,11 +271,11 @@ export function unique(elements: Iterable): SmartIterator */ export function zip(first: Iterable, second: Iterable): SmartIterator<[T, U]> { + const firstIterator = first[Symbol.iterator](); + const secondIterator = second[Symbol.iterator](); + return new SmartIterator<[T, U]>(function* () { - const firstIterator = first[Symbol.iterator](); - const secondIterator = second[Symbol.iterator](); - while (true) { const firstResult = firstIterator.next(); diff --git a/src/utils/math.ts b/src/utils/math.ts index 4f808c2..3b24b2a 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -10,8 +10,6 @@ import { zip } from "./iterator.js"; * average([6, 8.5, 4], [3, 2, 1]); // 6.5 * ``` * - * --- - * * @template T The type of the values in the list. It must be or extend a `number` object. * * @param values @@ -63,7 +61,7 @@ export function average(values: Iterable, weights?: Iterabl } if (_index === 0) { throw new ValueException("You must provide at least one value and weight."); } - if (_count > 0) { throw new ValueException("The sum of weights must be greater than zero."); } + if (_count <= 0) { throw new ValueException("The sum of weights must be greater than zero."); } return _sum / _count; } @@ -80,8 +78,6 @@ export function average(values: Iterable, weights?: Iterabl * hash("How are you?"); // 1761539132 * ``` * - * --- - * * @param value The string to hash. * * @returns The hash of the specified string. @@ -107,8 +103,6 @@ export function hash(value: string): number * sum([1, 2, 3, 4, 5]); // 15 * ``` * - * --- - * * @template T The type of the values in the list. It must be or extend a `number` object. * * @param values The list of values to sum. diff --git a/src/utils/random.ts b/src/utils/random.ts index 6252b05..b0ac914 100644 --- a/src/utils/random.ts +++ b/src/utils/random.ts @@ -19,8 +19,6 @@ export default class Random * } * ``` * - * --- - * * @param ratio * The probability of generating `true`. * @@ -40,8 +38,6 @@ export default class Random * Random.Integer(5); // 0, 1, 2, 3, 4 * ``` * - * --- - * * @param max The maximum value (excluded). * * @returns A random integer value. @@ -55,8 +51,6 @@ export default class Random * Random.Integer(2, 7); // 2, 3, 4, 5, 6 * ``` * - * --- - * * @param min The minimum value (included). * @param max The maximum value (excluded). * @@ -77,8 +71,6 @@ export default class Random * Random.Decimal(); // 0.123456789 * ``` * - * --- - * * @returns A random decimal value. */ public static Decimal(): number; @@ -90,8 +82,6 @@ export default class Random * Random.Decimal(5); // 2.3456789 * ``` * - * --- - * * @param max The maximum value (excluded). * * @returns A random decimal value. @@ -105,8 +95,6 @@ export default class Random * Random.Decimal(2, 7); // 4.56789 * ``` * - * --- - * * @param min The minimum value (included). * @param max The maximum value (excluded). * diff --git a/src/utils/string.ts b/src/utils/string.ts index 424cb47..b348248 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -5,8 +5,6 @@ * capitalize('hello'); // 'Hello' * ``` * - * --- - * * @param value The string to capitalize. * * @returns The capitalized string. diff --git a/tests/models/aggregators/aggregated-async-iterator.test.ts b/tests/models/aggregators/aggregated-async-iterator.test.ts new file mode 100644 index 0000000..fa3d11e --- /dev/null +++ b/tests/models/aggregators/aggregated-async-iterator.test.ts @@ -0,0 +1,514 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { delay } from "../../../src/index.js"; + +import { SmartAsyncIterator } from "../../../src/index.js"; + +describe("AggregatedAsyncIterator", () => +{ + const _toAsync = (elements: Iterable) => + { + return async function* () + { + for (const element of elements) + { + await delay(100); + + yield element; + } + }; + }; + + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should check if every element in each group satisfies a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-4, -2, 1, 3, 5, 6, 7])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.every(async (key, value) => value >= 0) + .then((results) => + { + resolved = true; + + expect(results.toObject()).toEqual({ odd: true, even: false }); + }); + + await vi.advanceTimersByTimeAsync(600); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should check if some elements in each group satisfy a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-5, -4, -3, -2, -1, 0])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.some(async (key, value) => value >= 0) + .then((results) => + { + resolved = true; + + expect(results.toObject()).toEqual({ odd: false, even: true }); + }); + + await vi.advanceTimersByTimeAsync(500); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should filter elements based on a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .filter(async (key, value) => value >= 0); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should map elements using a transformation function", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .map(async (key, value) => Math.abs(value)); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should reduce elements using a reducer function", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.reduce(async (key, acc, value) => acc + value) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 4, even: 16 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should reduce elements using a reducer function with an initial value", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.reduce(async (key, acc, value) => acc + value, 10) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 14, even: 26 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should reduce elements using a reducer function with a function that returns an initial value", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + + const _reducer = async (key: string, acc: number, value: number) => acc + value; + const _initializer = async (key: string) => key === "odd" ? 10 : 0; + aggregator.reduce(_reducer, _initializer) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 14, even: 16 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should flatten elements using a transformation function", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([[-3, -1], 0, 2, 3, 5, [6, 8]])) + .groupBy(async (values) => + { + const value = values instanceof Array ? values[0] : values; + return value % 2 === 0 ? "even" : "odd"; + }) + .flatMap(async (key, values) => values); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(500); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should drop a given number of elements from the beginning of each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .drop(2); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [3, 5], even: [6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should take a given number of elements from the beginning of each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .take(2); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1], even: [0, 2] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should find the first element of each group that satisfies a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.find(async (key, value) => value > 5) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: undefined, even: 6 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should enumerate the elements with their indices within each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .enumerate(); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should remove all duplicate elements within each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .unique(); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(1_300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should count the number of elements within each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.count() + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 4, even: 4 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should iterate over the elements", async () => + { + const results: [string, number, number][] = []; + const _iteratee = vi.fn((key: string, value: number, index: number) => { results.push([key, value, index]); }); + + const aggregator = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.forEach(_iteratee) + .then(() => + { + resolved = true; + + expect(results).toEqual([ + ["odd", -3, 0], + ["even", 0, 0], + ["even", 2, 1], + ["odd", -1, 1], + ["odd", 3, 2] + ]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + + expect(_iteratee).toHaveBeenCalledTimes(5); + }); + + it("Should change the key of each element on which the iterator is aggregated", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .map(async (key, value, index) => index % 2 === 0 ? value : -value) + .reorganizeBy(async (key, value) => value >= 0 ? "+" : "-"); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should return all keys", async () => + { + const keys = new SmartAsyncIterator(_toAsync([-3, Symbol(), "A", { }, null, [1, 2, 3], false])) + .groupBy(async (value) => typeof value) + .keys(); + + let resolved = false; + keys.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual(["number", "symbol", "string", "object", "boolean"]); + }); + + await vi.advanceTimersByTimeAsync(600); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return all entries", async () => + { + const entries = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .entries(); + + let resolved = false; + entries.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([ + ["odd", -3], + ["even", 0], + ["even", 2], + ["odd", -1], + ["odd", 3] + ]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return all values", async () => + { + const values = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .values(); + + let resolved = false; + values.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should materialize the iterator into an array of arrays", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should materialize the iterator into a map", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.toMap() + .then((result) => + { + resolved = true; + + expect(result).toEqual(new Map([ + ["odd", [-3, -1, 3, 5]], + ["even", [0, 2, 6, 8]] + ])); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should materialize the iterator into an object", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); +}); diff --git a/tests/models/aggregators/aggregated-iterator.test.ts b/tests/models/aggregators/aggregated-iterator.test.ts new file mode 100644 index 0000000..1970e89 --- /dev/null +++ b/tests/models/aggregators/aggregated-iterator.test.ts @@ -0,0 +1,210 @@ +import { describe, expect, it, vi } from "vitest"; + +import { SmartIterator } from "../../../src/index.js"; + +describe("AggregatedIterator", () => +{ + it("Should check if every element in each group satisfies a condition", () => + { + const aggregator = new SmartIterator([-4, -2, 1, 3, 5, 6, 7]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.every((key, value) => value >= 0); + expect(results.toObject()).toEqual({ odd: true, even: false }); + }); + it("Should check if some elements in each group satisfy a condition", () => + { + const aggregator = new SmartIterator([-5, -4, -3, -2, -1, 0]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.some((key, value) => value >= 0); + expect(results.toObject()).toEqual({ odd: false, even: true }); + }); + + it("Should filter elements based on a condition", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.filter((key, value) => value > 0); + expect(results.toObject()).toEqual({ odd: [3, 5], even: [2, 6, 8] }); + }); + it("Should map elements using a transformation function", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.map((key, value) => Math.abs(value)); + expect(results.toObject()).toEqual({ odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }); + }); + + it("Should reduce elements using a reducer function", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.reduce((key, acc, value) => acc + value); + expect(results.toObject()).toEqual({ odd: 4, even: 16 }); + }); + it("Should reduce elements using a reducer function with an initial value", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.reduce((key, acc, value) => acc + value, 10); + expect(results.toObject()).toEqual({ odd: 14, even: 26 }); + }); + it("Should reduce elements using a reducer function with a function that returns an initial value", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.reduce((key, acc, value) => acc + value, (key) => key === "odd" ? 10 : 0); + expect(results.toObject()).toEqual({ odd: 14, even: 16 }); + }); + + it("Should flatten elements using a transformation function", () => + { + const aggregator = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) + .groupBy((values) => + { + const value = values instanceof Array ? values[0] : values; + return value % 2 === 0 ? "even" : "odd"; + }); + + const results = aggregator.flatMap((key, values) => values); + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + it("Should drop a given number of elements from the beginning of each group", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.drop(2); + expect(results.toObject()).toEqual({ odd: [3, 5], even: [6, 8] }); + }); + it("Should take a given number of elements from the beginning of each group", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.take(2); + expect(results.toObject()).toEqual({ odd: [-3, -1], even: [0, 2] }); + }); + + it("Should find the first element of each group that satisfies a condition", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.find((key, value) => value > 5); + expect(results.toObject()).toEqual({ odd: undefined, even: 6 }); + }); + + it("Should enumerate the elements with their indices within each group", () => + { + const aggregator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.enumerate(); + expect(results.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + }); + it("Should remove all duplicate elements within each group", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.unique(); + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + it("Should count the number of elements within each group", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.count(); + expect(results.toObject()).toEqual({ odd: 4, even: 4 }); + }); + + it("Should iterate over all elements", () => + { + const results: [string, number, number][] = []; + const _iteratee = vi.fn((key: string, value: number, index: number) => { results.push([key, value, index]); }); + + const iterator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + iterator.forEach(_iteratee); + + expect(results).toEqual([["odd", -3, 0], ["even", 0, 0], ["even", 2, 1], ["odd", -1, 1], ["odd", 3, 2]]); + expect(_iteratee).toHaveBeenCalledTimes(5); + }); + + it("Should change the key of each element on which the iterator is aggregated", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.map((key, value, index) => index % 2 === 0 ? value : -value) + .reorganizeBy((key, value) => value >= 0 ? "+" : "-"); + + expect(results.toObject()).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); + }); + + it("Should return all keys", () => + { + const aggregator = new SmartIterator([-3, Symbol(), "A", { }, null, [1, 2, 3], false]) + .groupBy((value) => typeof value); + + const keys = aggregator.keys(); + expect(keys.toArray()).toEqual(["number", "symbol", "string", "object", "boolean"]); + }); + it("Should return all entries", () => + { + const aggregator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const entries = aggregator.entries(); + expect(entries.toArray()).toEqual([ + ["odd", -3], + ["even", 0], + ["even", 2], + ["odd", -1], + ["odd", 3] + ]); + }); + it("Should return all values", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const values = aggregator.values(); + expect(values.toArray()).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); + }); + + it("Should materialize the iterator into an array of arrays", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(aggregator.toArray()).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); + }); + it("Should materialize the iterator into a map", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(aggregator.toMap()).toEqual(new Map([ + ["odd", [-3, -1, 3, 5]], + ["even", [0, 2, 6, 8]] + ])); + }); + it("Should materialize the iterator into an object", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(aggregator.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); +}); diff --git a/tests/models/aggregators/reduced-iterator.test.ts b/tests/models/aggregators/reduced-iterator.test.ts new file mode 100644 index 0000000..494793e --- /dev/null +++ b/tests/models/aggregators/reduced-iterator.test.ts @@ -0,0 +1,253 @@ +import { describe, expect, it, vi } from "vitest"; + +import { SmartIterator } from "../../../src/index.js"; + +describe("ReducedIterator", () => +{ + it("Should return `true` if every element matches the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.every((key, value) => value > 0); + expect(results).toBe(true); + }); + it("Should return `false` if not every element matches the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.every((key, value) => value > 0); + expect(results).toBe(false); + }); + + it("Should return `true` if some elements match the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.some((key, value) => value > 0); + expect(results).toBe(true); + }); + it("Should return `false` if no elements match the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, -2, -3, -5, 6, -8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.some((key, value) => value > 0); + expect(results).toBe(false); + }); + + it("Should filter elements based on a condition", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.filter((key, value) => value > 0); + expect(results.toObject()).toEqual({ even: 16 }); + }); + it("Should map elements using a transformation function", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.map((key, value) => Math.abs(value)); + expect(results.toObject()).toEqual({ odd: 2, even: 16 }); + }); + + it("Should reduce elements using a reducer function", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.reduce((key, acc, value) => acc + value); + expect(results).toBe(20); + }); + it("Should reduce elements using a reducer function with an initial value", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.reduce((key, acc, value) => acc + value, 10); + expect(results).toBe(30); + }); + + it("Should flatten elements using a transformation function", () => + { + const _initializer = (key: string) => key === "odd" ? [] : 0; + const _reducer = (key: string, acc: number | number[], value: number) => + { + if (key === "odd") { (acc as number[]).push(value); } + else { (acc as number) += value; } + + return acc; + + }; + + const results = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce(_reducer, _initializer) + .flatMap((key, value) => value); + + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [16] }); + }); + + it("Should drop a specified number of elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.drop(1); + expect(results.toObject()).toEqual({ even: 16 }); + }); + it("Should take a specified number of elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.take(1); + expect(results.toObject()).toEqual({ odd: 4 }); + }); + + it("Should find the first element that satisfies a condition", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.find((key, value) => value > 0); + expect(results).toBe(16); + }); + it("Should return `undefined` when no matching element is found", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.find((key, value) => value < 0); + expect(results).toBeUndefined(); + }); + + it("Should enumerate elements with their indices", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.enumerate(); + expect(results.toObject()).toEqual({ odd: [0, 4], even: [1, 16] }); + }); + it("Should remove duplicate elements", () => + { + const reduced = new SmartIterator([3, 1, 0, 2, 3, 6, 3, 1, 1, 5, 6, 8, 7, 2]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.unique(); + expect(results.toObject()).toEqual({ odd: 24 }); + }); + it("Should count the number of elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.count(); + expect(results).toBe(2); + }); + + it("Should iterate over all elements", () => + { + const results: [string, number, number][] = []; + const _iteratee = vi.fn((key: string, value: number, index: number) => { results.push([key, value, index]); }); + + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + iterator.forEach(_iteratee); + + expect(results).toEqual([["odd", 4, 0], ["even", 16, 1]]); + expect(_iteratee).toHaveBeenCalledTimes(2); + }); + + it("Should reorganize elements by a new key", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.reorganizeBy((key, value) => value >= 0 ? "+" : "-"); + + expect(results.toObject()).toEqual({ "-": [-2], "+": [16] }); + }); + + it("Should return all keys", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const keys = reduced.keys(); + expect(keys.toArray()).toEqual(["odd", "even"]); + }); + it("Should return all entries", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const entries = reduced.entries(); + expect(entries.toArray()).toEqual([ + ["odd", 4], + ["even", 16] + ]); + }); + it("Should return all values", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const values = reduced.values(); + expect(values.toArray()).toEqual([4, 16]); + }); + + it("Should materialize the iterator into an array", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + expect(reduced.toArray()).toEqual([4, 16]); + }); + it("Should materialize the iterator into a map", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + expect(reduced.toMap()).toEqual(new Map([ + ["odd", 4], + ["even", 16] + ])); + }); + it("Should materialize the iterator into an object", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + expect(reduced.toObject()).toEqual({ odd: 4, even: 16 }); + }); +}); diff --git a/tests/models/callbacks/callable-object.test.ts b/tests/models/callbacks/callable-object.test.ts new file mode 100644 index 0000000..2e58bd9 --- /dev/null +++ b/tests/models/callbacks/callable-object.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; + +import { CallableObject } from "../../../src/index.js"; + +class Multiply extends CallableObject<(arg: number) => number> +{ + public multiplier; + + public constructor(multiplier = 2) + { + super(); + + this.multiplier = multiplier; + } + + protected _invoke(value: number): number + { + return value * this.multiplier; + } +} + +describe("CallableObject", () => +{ + it("Should be callable and return the correct result", () => + { + const multiply = new Multiply(); + + const result = multiply(5); + expect(result).toBe(10); + }); + it("Should bind the correct context", () => + { + const multiply = new Multiply(3); + + const result = multiply(6); + expect(result).toBe(18); + }); +}); diff --git a/tests/models/callbacks/publisher.test.ts b/tests/models/callbacks/publisher.test.ts new file mode 100644 index 0000000..1107242 --- /dev/null +++ b/tests/models/callbacks/publisher.test.ts @@ -0,0 +1,85 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { ReferenceException } from "../../../src/index.js"; +import { Publisher } from "../../../src/index.js"; + +interface EventsMap { + "player:spawn": (evt: { x: number, y: number }) => void; + "player:move": (coords: { x: number, y: number }) => void; + "player:death": () => void; +} + +describe("Publisher", () => +{ + let publisher: Publisher; + + beforeEach(() => { publisher = new Publisher(); }); + + it("Should subscribe and publish events", () => + { + const _spawnHandler = vi.fn(); + const _moveHandler = vi.fn(); + + publisher.subscribe("player:spawn", _spawnHandler); + publisher.subscribe("player:move", _moveHandler); + + publisher.publish("player:spawn", { x: 10, y: 20 }); + publisher.publish("player:move", { x: 30, y: 40 }); + + expect(_spawnHandler).toHaveBeenCalledWith({ x: 10, y: 20 }); + expect(_moveHandler).toHaveBeenCalledWith({ x: 30, y: 40 }); + }); + + it("Should unsubscribe from events", () => + { + const _moveHandler = vi.fn(); + const unsubscribe = publisher.subscribe("player:move", _moveHandler); + + unsubscribe(); + expect(() => unsubscribe()).toThrow(ReferenceException); + + publisher.publish("player:move", { x: 30, y: 40 }); + + expect(_moveHandler).not.toHaveBeenCalled(); + }); + + it("Should clear all subscribers", () => + { + const _spawnHandler = vi.fn(); + const _moveHandler = vi.fn(); + const _deathHandler = vi.fn(); + + publisher.subscribe("player:spawn", _spawnHandler); + publisher.subscribe("player:move", _moveHandler); + publisher.subscribe("player:death", _deathHandler); + + publisher.clear(); + + publisher.publish("player:spawn", { x: 10, y: 20 }); + publisher.publish("player:move", { x: 30, y: 40 }); + publisher.publish("player:death"); + + expect(_spawnHandler).not.toHaveBeenCalled(); + expect(_moveHandler).not.toHaveBeenCalled(); + expect(_deathHandler).not.toHaveBeenCalled(); + }); + + it("Should not throw `ReferenceException` when unsubscribing a non-existent subscriber", () => + { + const _moveHandler = vi.fn(); + + expect(() => publisher.unsubscribe("player:move", _moveHandler)).not.toThrow(ReferenceException); + }); + + it("Should return an array of return values from subscribers", () => + { + const _moveHandler1 = vi.fn(() => "handler1"); + const _moveHandler2 = vi.fn(() => "handler2"); + + publisher.subscribe("player:move", _moveHandler1); + publisher.subscribe("player:move", _moveHandler2); + + const results = publisher.publish("player:move", { x: 30, y: 40 }); + expect(results).toEqual(["handler1", "handler2"]); + }); +}); diff --git a/tests/models/callbacks/switchable-callback.test.ts b/tests/models/callbacks/switchable-callback.test.ts new file mode 100644 index 0000000..4a520ce --- /dev/null +++ b/tests/models/callbacks/switchable-callback.test.ts @@ -0,0 +1,143 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { KeyException, NotImplementedException, RuntimeException } from "../../../src/index.js"; +import { SwitchableCallback } from "../../../src/index.js"; + +interface Point +{ + x: number; + y: number; +} + +describe("SwitchableCallback", () => +{ + let callback: SwitchableCallback<(point: Point) => void>; + + const _newPoint = () => + { + return { + x: Math.floor(Math.random() * 1920), + y: Math.floor(Math.random() * 1080) + }; + }; + + beforeEach(() => { callback = new SwitchableCallback(); }); + + it("Should throw `NotImplementedException` if no callback is registered", () => + { + expect(() => callback(_newPoint())).toThrow(NotImplementedException); + }); + + it("Should register a new callback and set it as default", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + const result = _newPoint(); + + callback.register("default", _callback); + + callback(result); + + expect(callback.key).toBe("default"); + expect(_callback).toHaveBeenCalledWith(result); + }); + + it("Should throw `KeyException` if trying to register a callback with an existing key", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + callback.register("default", _callback); + + expect(() => callback.register("default", _callback)).toThrow(KeyException); + }); + + it("Should enable & disable the callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + + callback.register("default", _callback); + callback(_newPoint()); + + expect(callback.isEnabled).toBe(true); + + callback.disable(); + callback(_newPoint()); + + expect(callback.isEnabled).toBe(false); + + callback.enable(); + callback(_newPoint()); + + expect(callback.isEnabled).toBe(true); + expect(_callback).toHaveBeenCalledTimes(2); + }); + + it("Should throw `RuntimeException` if enabling an already enabled callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + callback.register("default", _callback); + + expect(() => callback.enable()).toThrow(RuntimeException); + }); + it("Should throw `RuntimeException` if disabling an already disabled callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + callback.register("default", _callback); + callback.disable(); + + expect(() => callback.disable()).toThrow(RuntimeException); + }); + + it("Should switch to a different registered callback", () => + { + const _callback1 = vi.fn((point: Point) => { /* ... */ }); + const _callback2 = vi.fn((point: Point) => { /* ... */ }); + + callback.register("first", _callback1); + callback(_newPoint()); + + expect(callback.key).toBe("first"); + + callback.register("second", _callback2); + callback(_newPoint()); + + expect(callback.key).toBe("first"); + + callback.switch("second"); + callback(_newPoint()); + + expect(callback.key).toBe("second"); + expect(_callback1).toHaveBeenCalledTimes(2); + expect(_callback2).toHaveBeenCalledTimes(1); + }); + + it("Should throw `KeyException` if switching to a non-existent callback", () => + { + expect(() => callback.switch("nonexistent")).toThrow(KeyException); + }); + + it("Should unregister a callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + + callback.register("default", _callback); + callback.register("second", _callback); + + callback.switch("second"); + callback.unregister("default"); + + expect(() => callback.switch("default")).toThrow(KeyException); + }); + + it("Should throw `KeyException` if unregistering the currently selected callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + + callback.register("default", _callback); + + expect(() => callback.unregister("default")).toThrow(KeyException); + }); + + it("Should throw `KeyException` if unregistering a non-existent callback", () => + { + expect(() => callback.unregister("nonexistent")).toThrow(KeyException); + }); +}); diff --git a/tests/models/iterators/smart-async-iterator.test.ts b/tests/models/iterators/smart-async-iterator.test.ts new file mode 100644 index 0000000..f598410 --- /dev/null +++ b/tests/models/iterators/smart-async-iterator.test.ts @@ -0,0 +1,524 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { delay, ValueException } from "../../../src/index.js"; +import type { MaybePromise } from "../../../src/index.js"; + +import { SmartAsyncIterator } from "../../../src/index.js"; + +describe("SmartAsyncIterator", () => +{ + const _toAsync = (elements: Iterable) => + { + return async function* () + { + for (const element of elements) + { + await delay(100); + + yield element; + } + }; + }; + + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should initialize with an iterable", async () => + { + const _iterable = [1, 2, 3]; + + const iterator = new SmartAsyncIterator(_iterable); + expect(await iterator.toArray()).toEqual([1, 2, 3]); + }); + it("Should initialize with an iterator", async () => + { + const _iterator = { + _index: 0, + + next: async function() + { + await delay(100); + + if (this._index < 3) + { + this._index += 1; + + return { done: false, value: this._index }; + } + + return { done: true, value: undefined }; + } + }; + + const iterator = new SmartAsyncIterator(_iterator); + + let resolved = false; + iterator.toArray() + .then((results) => + { + resolved = true; + + expect(results).toEqual([1, 2, 3]); + }); + + await vi.advanceTimersByTimeAsync(300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should initialize with a generator function", async () => + { + const _generatorFn = async function* () + { + for (let i = 1; i < 4; i += 1) + { + await delay(100); + + yield i; + } + + await delay(100); + }; + + const iterator = new SmartAsyncIterator(_generatorFn); + + let resolved = false; + iterator.toArray() + .then((results) => + { + resolved = true; + + expect(results).toEqual([1, 2, 3]); + }); + + await vi.advanceTimersByTimeAsync(300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should return `true` if every value matches the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([2, 4, 6, 8, 10])); + + let resolved = false; + iterator.every(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(true); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return `false` if not every value matches the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([2, 4, 5, 6, 7, 8, 10])); + + let resolved = false; + iterator.every(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(false); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should return `true` if some values match the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.some(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(true); + }); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return `false` if no values match the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 3, 5, 7, 9])); + + let resolved = false; + iterator.some(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(false); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should filter values based on a condition", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.filter(async (value) => value % 2 === 0); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([2, 4]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should map values using a transformation function", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.map(async (value) => value * 2); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([2, 4, 6, 8, 10]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should reduce values using a reducer function", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.reduce(async (acc, value) => acc + value) + .then((results) => + { + resolved = true; + + expect(results).toBe(15); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should reduce values using a reducer function with initial value", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.reduce(async (acc, value) => acc + value, 10) + .then((results) => + { + resolved = true; + + expect(results).toBe(25); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should throw `ValueException` when reducing an empty iterator without initial value", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([])); + + try + { + await iterator.reduce((acc, value) => acc + value); + } + catch (error) + { + expect(error).toBeInstanceOf(ValueException); + } + }); + + it("Should flatten values using a transformation function", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, [2, 3], 4, 5, [6, 7, 8]])); + const results = iterator.flatMap(async (value) => value); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should drop the specified number of values", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.drop(3); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([4, 5]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should take the specified number of values", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.take(3); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([1, 2, 3]); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should find the first value that satisfies a condition", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.find(async (value) => value > 3) + .then((results) => + { + resolved = true; + + expect(results).toBe(4); + }); + + await vi.advanceTimersByTimeAsync(300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return `undefined` when no matching value is found", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3])); + + let resolved = false; + iterator.find(async (value) => value > 3) + .then((results) => + { + resolved = true; + + expect(results).toBeUndefined(); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should enumerate values with their indices", async () => + { + const iterator = new SmartAsyncIterator(_toAsync(["A", "B", "C"])); + const results = iterator.enumerate(); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([[0, "A"], [1, "B"], [2, "C"]]); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should remove duplicate values", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 2, 1, 3, 1, 4, 3, 4, 5, 5])); + const results = iterator.unique(); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([1, 2, 3, 4, 5]); + }); + + await vi.advanceTimersByTimeAsync(1_000); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should count the number of values", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.count(); + + let resolved = false; + results.then((_results) => + { + resolved = true; + + expect(_results).toBe(5); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should iterate over all values", async () => + { + const results: number[] = []; + const _iteratee = vi.fn(async (x: MaybePromise) => { results.push(await x); }); + + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.forEach(_iteratee) + .then(() => + { + resolved = true; + + expect(results).toEqual([1, 2, 3, 4, 5]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should handle return method correctly", async () => + { + const _iterator: AsyncIterableIterator = { + next: async () => ({ done: false, value: 1 }), + return: async (value?: string) => + { + return { done: true, value: value ?? "Naturally done!" }; + }, + + [Symbol.asyncIterator]: () => _iterator + }; + + const iterator = new SmartAsyncIterator(_iterator as AsyncIterator); + const results = iterator.return("Prematurely done!"); + + let resolved = false; + results.then((_results) => + { + resolved = true; + + expect(_results).toEqual({ done: true, value: "Prematurely done!" }); + }); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should handle throw method correctly", async () => + { + const _iterator: AsyncIterator = { + next: async () => ({ done: false, value: 1 }), + throw: async (error: unknown) => { throw error; } + }; + + const iterator = new SmartAsyncIterator(_iterator); + const reason = new Error("Something went wrong!"); + + try + { + await iterator.throw(reason); + } + catch (error) + { + expect(error).toBe(reason); + } + }); + + it("Should group values by key", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5, 6])); + const results = iterator.groupBy(async (value) => (value % 2 === 0 ? "even" : "odd")); + + let resolved = false; + results.toObject() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual({ odd: [1, 3, 5], even: [2, 4, 6] }); + }); + + await vi.advanceTimersByTimeAsync(500); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); +}); diff --git a/tests/models/iterators/smart-iterator.test.ts b/tests/models/iterators/smart-iterator.test.ts new file mode 100644 index 0000000..bfeecf7 --- /dev/null +++ b/tests/models/iterators/smart-iterator.test.ts @@ -0,0 +1,225 @@ +import { describe, expect, it, vi } from "vitest"; + +import { ValueException } from "../../../src/index.js"; +import { SmartIterator } from "../../../src/index.js"; + +describe("SmartIterator", () => +{ + it("Should initialize with an iterable", () => + { + const _iterable = [1, 2, 3]; + + const iterator = new SmartIterator(_iterable); + expect(iterator.toArray()).toEqual([1, 2, 3]); + }); + it("Should initialize with an iterator", () => + { + const _iterator = { + _index: 0, + + next: function() + { + if (this._index < 3) + { + this._index += 1; + + return { done: false, value: this._index }; + } + + return { done: true, value: undefined }; + } + }; + + const iterator = new SmartIterator(_iterator); + expect(iterator.toArray()).toEqual([1, 2, 3]); + }); + it("Should initialize with a generator function", () => + { + const _generatorFn = function* () + { + yield 1; + yield 2; + yield 3; + }; + + const iterator = new SmartIterator(_generatorFn); + expect(iterator.toArray()).toEqual([1, 2, 3]); + }); + + it("Should return `true` if every value matches the predicate", () => + { + const iterator = new SmartIterator([2, 4, 6, 8, 10]); + + const results = iterator.every((value) => value % 2 === 0); + expect(results).toBe(true); + }); + it("Should return `false` if not every value matches the predicate", () => + { + const iterator = new SmartIterator([2, 4, 5, 6, 7, 8, 10]); + + const results = iterator.every((value) => value % 2 === 0); + expect(results).toBe(false); + }); + + it("Should return `true` if some values match the predicate", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.some((value) => value % 2 === 0); + expect(results).toBe(true); + }); + it("Should return `false` if no values match the predicate", () => + { + const iterator = new SmartIterator([1, 3, 5, 7, 9]); + + const results = iterator.some((value) => value % 2 === 0); + expect(results).toBe(false); + }); + + it("Should filter values based on a condition", () => + { + const iterator = new SmartIterator([1, 2, 3, 4]); + + const results = iterator.filter((x) => x % 2 === 0); + expect(results.toArray()).toEqual([2, 4]); + }); + it("Should map values using a transformation function", () => + { + const iterator = new SmartIterator([1, 2, 3]); + + const results = iterator.map((x) => x * 2); + expect(results.toArray()).toEqual([2, 4, 6]); + }); + + it("Should reduce values using a reducer function", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.reduce((acc, value) => acc + value); + expect(results).toBe(15); + }); + it("Should reduce values using a reducer function with initial value", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.reduce((acc, value) => acc + value, 10); + expect(results).toBe(25); + }); + + it("Should throw `ValueException` when reducing an empty iterator without initial value", () => + { + const iterator = new SmartIterator([]); + + expect(() => iterator.reduce((acc, value) => acc + value)).toThrow(ValueException); + }); + + it("Should flatten values using a transformation function", () => + { + const iterator = new SmartIterator([1, [2, 3], 4, 5, [6, 7, 8]]); + + const results = iterator.flatMap((x) => x); + expect(results.toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + }); + + it("Should drop the specified number of values", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.drop(3); + expect(results.toArray()).toEqual([4, 5]); + }); + it("Should take the specified number of values", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.take(3); + expect(results.toArray()).toEqual([1, 2, 3]); + }); + + it("Should find the first value that satisfies a condition", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.find((x) => x > 3); + expect(results).toBe(4); + }); + it("Should return `undefined` when no matching value is found", () => + { + const iterator = new SmartIterator([1, 2, 3]); + + const results = iterator.find((x) => x > 3); + expect(results).toBeUndefined(); + }); + + it("Should enumerate values with their indices", () => + { + const iterator = new SmartIterator(["A", "B", "C"]); + + const results = iterator.enumerate(); + expect(results.toArray()).toEqual([[0, "A"], [1, "B"], [2, "C"]]); + }); + it("Should remove duplicate values", () => + { + const iterator = new SmartIterator([1, 2, 2, 1, 3, 1, 4, 3, 4, 5, 5]); + + const results = iterator.unique(); + expect(results.toArray()).toEqual([1, 2, 3, 4, 5]); + }); + it("Should count the number of values", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.count(); + expect(results).toBe(5); + }); + + it("Should iterate over all values", () => + { + const results: number[] = []; + const _iteratee = vi.fn((x: number) => { results.push(x); }); + + const iterator = new SmartIterator([1, 2, 3]); + iterator.forEach(_iteratee); + + expect(results).toEqual([1, 2, 3]); + expect(_iteratee).toHaveBeenCalledTimes(3); + }); + + it("Should handle return method correctly", () => + { + const _iterator: IterableIterator = { + next: () => ({ done: false, value: 1 }), + return: (value?: string) => + { + return { done: true, value: value ?? "Naturally done!" }; + }, + + [Symbol.iterator]: () => _iterator + }; + + const iterator = new SmartIterator(_iterator as Iterator); + const results = iterator.return("Prematurely done!"); + + expect(results).toEqual({ done: true, value: "Prematurely done!" }); + }); + it("Should handle throw method correctly", () => + { + const _iterator = { + next: () => ({ done: false, value: 1 }), + throw: (error: unknown) => { throw error; } + }; + + const iterator = new SmartIterator(_iterator); + const reason = new Error("Something went wrong!"); + + expect(() => iterator.throw(reason)).toThrow(reason); + }); + + it("Should group values by key", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5, 6]); + + const results = iterator.groupBy((x) => x % 2 === 0 ? "even" : "odd"); + expect(results.toObject()).toEqual({ odd: [1, 3, 5], even: [2, 4, 6] }); + }); +}); diff --git a/tests/models/json/json-storage.test.ts b/tests/models/json/json-storage.test.ts new file mode 100644 index 0000000..a12acde --- /dev/null +++ b/tests/models/json/json-storage.test.ts @@ -0,0 +1,138 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import * as helpers from "../../../src/helpers.js"; + +import { EnvironmentException } from "../../../src/index.js"; +import { JSONStorage } from "../../../src/index.js"; + +describe("JSONStorage", () => +{ + let jsonStorage: JSONStorage; + + beforeEach(() => + { + vi.spyOn(helpers, "isBrowser", "get") + .mockReturnValue(true); + + const mockStorage = () => + { + let store: Record = { }; + + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { store[key] = value; }, + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + removeItem: (key: string) => delete store[key], + clear: () => { store = { }; } + }; + }; + + Object.defineProperty(window, "localStorage", { value: mockStorage() }); + Object.defineProperty(window, "sessionStorage", { value: mockStorage() }); + + jsonStorage = new JSONStorage(); + }); + + it("Should throw an EnvironmentException if not in a browser environment", () => + { + vi.spyOn(helpers, "isBrowser", "get") + .mockReturnValue(false); + + expect(() => new JSONStorage()).toThrow(EnvironmentException); + }); + + it("Should store and retrieve a value in `localStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + + const retrievedValue = jsonStorage.read(key); + expect(retrievedValue).toEqual(value); + }); + it("Should store and retrieve a value in `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.remember(key, value); + + const retrievedValue = jsonStorage.recall(key); + expect(retrievedValue).toEqual(value); + }); + + it("Should remove a value from `localStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + jsonStorage.erase(key); + + const retrievedValue = jsonStorage.read(key); + expect(retrievedValue).toBeUndefined(); + }); + it("Should remove a value from `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.remember(key, value); + jsonStorage.forget(key); + + const retrievedValue = jsonStorage.recall(key); + expect(retrievedValue).toBeUndefined(); + }); + + it("Should prefer `localStorage` over `sessionStorage` when retrieving a value", () => + { + const key = "testKey"; + const localStorageValue = { test: "localStorageValue" }; + const sessionStorageValue = { test: "sessionStorageValue" }; + + jsonStorage.write(key, localStorageValue); + jsonStorage.remember(key, sessionStorageValue); + + const retrievedValue = jsonStorage.get(key); + expect(retrievedValue).toEqual(localStorageValue); + }); + + it("Should check if a key exists in `localStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + + const exists = jsonStorage.exists(key); + expect(exists).toBe(true); + }); + it("Should check if a key exists in `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.remember(key, value); + const exists = jsonStorage.knows(key); + + expect(exists).toBe(true); + }); + + it("Should clear a key from both `localStorage` and `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + jsonStorage.remember(key, value); + jsonStorage.clear(key); + + const localStorageValue = jsonStorage.read(key); + const sessionStorageValue = jsonStorage.recall(key); + + expect(localStorageValue).toBeUndefined(); + expect(sessionStorageValue).toBeUndefined(); + }); +}); diff --git a/tests/models/promises/deferred-promise.test.ts b/tests/models/promises/deferred-promise.test.ts new file mode 100644 index 0000000..9419f84 --- /dev/null +++ b/tests/models/promises/deferred-promise.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it, vi } from "vitest"; + +import { DeferredPromise } from "../../../src/index.js"; + +describe("DeferredPromise", () => +{ + it("Should resolve with the correct value", async () => + { + const deferred = new DeferredPromise((value: string) => value.split(" ")); + + deferred.resolve("Hello, world!"); + deferred.then((result) => { expect(result).toEqual(["Hello,", "world!"]); }); + }); + + it("Should reject with the correct reason", async () => + { + const deferred = new DeferredPromise((value: string) => value.split(" ")); + const reason = new Error("Something went wrong"); + + deferred.reject(reason); + deferred.catch((error) => { expect(error).toBe(reason); }); + }); + + it("Should watch another promise and resolve when the other promise resolves", async () => + { + vi.useFakeTimers(); + + const otherPromise = new Promise((resolve) => setTimeout(() => resolve("Hello, world!"), 100)); + const deferred = new DeferredPromise((value: string) => value.split(" ")); + + deferred.watch(otherPromise); + deferred.then((result) => { expect(result).toEqual(["Hello,", "world!"]); }); + + await vi.advanceTimersByTimeAsync(100); + + vi.clearAllTimers(); + }); + + it("Should watch another promise and reject when the other promise rejects", async () => + { + vi.useFakeTimers(); + + const reason = new Error("Something went wrong"); + const otherPromise = new Promise((_, reject) => setTimeout(() => reject(reason), 100)); + const deferred = new DeferredPromise((value: string) => value.split(" ")); + + deferred.watch(otherPromise); + deferred.catch((error) => { expect(error).toBe(reason); }); + + await vi.advanceTimersByTimeAsync(100); + + vi.clearAllTimers(); + }); +}); diff --git a/tests/models/promises/smart-promise.test.ts b/tests/models/promises/smart-promise.test.ts new file mode 100644 index 0000000..146cc1a --- /dev/null +++ b/tests/models/promises/smart-promise.test.ts @@ -0,0 +1,124 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { SmartPromise } from "../../../src/index.js"; + +describe("SmartPromise", () => +{ + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should be pending initially", () => + { + const _callback = vi.fn(); + const promise = new SmartPromise(_callback); + + expect(promise.isPending).toBe(true); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(false); + + expect(_callback).toHaveBeenCalled(); + }); + + it("Should be fulfilled after resolving", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + promise.then((value) => { expect(value).toBe("Hello, world!"); }); + + await vi.advanceTimersByTimeAsync(100); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); + it("Should be rejected after rejecting", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => reject(new Error("An error occurred")), 100); + }); + + promise.catch((error) => { expect(error).toEqual(new Error("An error occurred")); }); + + await vi.advanceTimersByTimeAsync(100); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(true); + }); + + it("Should wrap an existing promise", async () => + { + const _promise = new Promise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + const promise = SmartPromise.FromPromise(_promise); + expect(promise.isPending).toBe(true); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(false); + + promise.then((value) => { expect(value).toBe("Hello, world!"); }); + + await vi.advanceTimersByTimeAsync(100); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); + + it("Should handle then callbacks", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + promise.then((value) => `${value}!!`) + .then((value) => { expect(value).toBe("Hello, world!!!"); }); + + await vi.advanceTimersByTimeAsync(100); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); + it("Should handle catch callbacks", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => reject(new Error("An error occurred")), 100); + }); + + promise.catch((error) => "Recovered from error") + .then((value) => { expect(value).toBe("Recovered from error"); }); + + await vi.advanceTimersByTimeAsync(100); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(true); + }); + it("Should handle finally callbacks", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + let finallyCalled = false; + promise.finally(() => { finallyCalled = true; }); + + await vi.advanceTimersByTimeAsync(100); + + expect(finallyCalled).toBe(true); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); +}); diff --git a/tests/models/promises/timed-promise.test.ts b/tests/models/promises/timed-promise.test.ts new file mode 100644 index 0000000..4b4d12a --- /dev/null +++ b/tests/models/promises/timed-promise.test.ts @@ -0,0 +1,77 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { TimeoutException } from "../../../src/index.js"; +import { TimedPromise } from "../../../src/index.js"; + +describe("TimedPromise", () => +{ + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should resolve before timeout", async () => + { + const promise = new TimedPromise((resolve) => + { + setTimeout(() => resolve("Hello, world!"), 100); + + }, 500); + + promise.then((result) => { expect(result).toBe("Hello, world!"); }); + + await vi.advanceTimersByTimeAsync(500); + }); + it("Should reject with `TimeoutException` after timeout", async () => + { + const promise = new TimedPromise((resolve) => + { + setTimeout(() => resolve("Hello, world!"), 1_000); + + }, 500); + + promise.catch((error) => + { + expect(error).toBeInstanceOf(TimeoutException); + expect((error as TimeoutException).message).toBe("The operation has timed out."); + }); + + await vi.advanceTimersByTimeAsync(500); + }); + it("Should reject with provided reason before timeout", async () => + { + const reason = new Error("An error occurred"); + const promise = new TimedPromise((_, reject) => + { + setTimeout(() => reject(reason), 100); + + }, 500); + + promise.catch((error) => { expect(error).toBe(reason); }); + + await vi.advanceTimersByTimeAsync(500); + }); + + it("Should resolve immediately if no timeout is provided", async () => + { + const promise = new TimedPromise((resolve) => + { + resolve("Immediate resolve"); + }); + + promise.then((result) => { expect(result).toBe("Immediate resolve"); }); + }); + it("Should reject immediately if no timeout is provided", async () => + { + const promise = new TimedPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + promise.catch((error) => + { + expect(error).toBeInstanceOf(TimeoutException); + expect((error as TimeoutException).message).toBe("The operation has timed out."); + }); + + await vi.advanceTimersByTimeAsync(100); + }); +}); diff --git a/tests/models/timers/clock.test.ts b/tests/models/timers/clock.test.ts new file mode 100644 index 0000000..6382ddb --- /dev/null +++ b/tests/models/timers/clock.test.ts @@ -0,0 +1,91 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { Clock } from "../../../src/index.js"; +import { FatalErrorException, RangeException, RuntimeException } from "../../../src/index.js"; + +describe("Clock", () => +{ + let clock: Clock; + + beforeEach(() => + { + clock = new Clock(); + + vi.useFakeTimers(); + }); + afterEach(() => vi.clearAllTimers()); + + it("Should start the clock and publish start event", () => + { + const _callback = vi.fn(); + + clock.onStart(_callback); + clock.start(); + + expect(_callback).toHaveBeenCalled(); + expect(clock.isRunning).toBe(true); + }); + it("Should throw `RuntimeException` if start is called when clock is already running", () => + { + clock.start(); + + expect(() => clock.start()).toThrow(RuntimeException); + }); + + it("Should stop the clock and publish stop event", () => + { + const _callback = vi.fn(); + + clock.onStop(_callback); + clock.start(); + clock.stop(); + + expect(_callback).toHaveBeenCalled(); + expect(clock.isRunning).toBe(false); + }); + it("Should throw `RuntimeException` if stop is called when clock isn't running", () => + { + expect(() => clock.stop()).toThrow(RuntimeException); + }); + + it("Should throw `FatalErrorException` if stop is called without a handle", () => + { + clock.start(); + clock["_handle"] = undefined; + + expect(() => clock.stop()).toThrow(FatalErrorException); + }); + + it("Should publish tick event at each tick", async () => + { + const _callback = vi.fn(); + + clock.onTick(_callback); + clock.start(); + + await vi.advanceTimersByTimeAsync(304); + + expect(_callback).toHaveBeenCalledTimes(20); + }); + + it("Should execute tick callback only if elapsed time is greater than tickStep", async () => + { + const _callback = vi.fn(); + + clock.onTick(_callback, 600); + clock.start(); + + await vi.advanceTimersByTimeAsync(216); + expect(_callback).toHaveBeenCalledTimes(0); + + await vi.advanceTimersByTimeAsync(1_000); + expect(_callback).toHaveBeenCalledTimes(2); + + await vi.advanceTimersByTimeAsync(1_216); + expect(_callback).toHaveBeenCalledTimes(4); + }); + it("Should throw `RangeException` if tickStep is negative", () => + { + expect(() => clock.onTick(() => { /* ... */ }, -1)).toThrow(RangeException); + }); +}); diff --git a/tests/models/timers/countdown.test.ts b/tests/models/timers/countdown.test.ts new file mode 100644 index 0000000..f742deb --- /dev/null +++ b/tests/models/timers/countdown.test.ts @@ -0,0 +1,102 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { RuntimeException, RangeException } from "../../../src/index.js"; +import { Countdown } from "../../../src/index.js"; + +describe("Countdown", () => +{ + const duration = 10_000; + let countdown: Countdown; + + beforeEach(() => + { + countdown = new Countdown(duration); + + vi.useFakeTimers(); + }); + afterEach(() => vi.clearAllTimers()); + + it("Should initialize with the correct duration", () => + { + expect(countdown.duration).toBe(duration); + }); + it("Should calculate remaining time correctly", () => + { + expect(countdown.remainingTime).toBe(duration); + }); + + it("Should start the countdown and publish start event", () => + { + const _callback = vi.fn(); + + countdown.onStart(_callback); + countdown.start(); + + expect(_callback).toHaveBeenCalled(); + }); + it("Should throw `RuntimeException` if start is called while running", () => + { + countdown.start(); + + expect(() => countdown.start()).toThrow(RuntimeException); + }); + + it("Should stop the countdown and publish stop event", async () => + { + const _callback = vi.fn(); + + countdown.onStop(_callback); + + const { rejects } = expect(countdown.start()); + const reason = new Error("An error occurred"); + countdown.stop(reason); + + await rejects.toBe(reason); + + expect(_callback).toHaveBeenCalledWith(reason); + }); + it("Should throw `RuntimeException` if stop is called before start", () => + { + expect(() => countdown.stop()).toThrow(RuntimeException); + }); + + it("Should publish tick events", async () => + { + const _callback = vi.fn(); + + countdown.onTick(_callback); + countdown.start(); + + await vi.advanceTimersByTimeAsync(304); + + expect(_callback).toHaveBeenCalledTimes(20); + }); + it("Should execute tick callback only if elapsed time is greater than tickStep", async () => + { + const _callback = vi.fn(); + + countdown.onTick(_callback, 250); + countdown.start(); + + await vi.advanceTimersByTimeAsync(1_024); + + expect(_callback).toHaveBeenCalledTimes(4); + }); + + it("Should publish expire event when time is up", async () => + { + const _callback = vi.fn(); + + countdown.onExpire(_callback); + countdown.start(); + + await vi.advanceTimersByTimeAsync(10_000); + + expect(_callback).toHaveBeenCalled(); + }); + + it("Should throw `RangeException` if tickStep is negative", () => + { + expect(() => countdown.onTick(() => { /* ... */ }, -1)).toThrow(RangeException); + }); +}); diff --git a/tests/models/timers/game-loop.test.ts b/tests/models/timers/game-loop.test.ts new file mode 100644 index 0000000..298491d --- /dev/null +++ b/tests/models/timers/game-loop.test.ts @@ -0,0 +1,87 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { FatalErrorException, RuntimeException } from "../../../src/index.js"; +import { GameLoop } from "../../../src/index.js"; + +describe("GameLoop", () => +{ + let callback: FrameRequestCallback; + let gameLoop: GameLoop; + + beforeEach(() => + { + callback = vi.fn(); + gameLoop = new GameLoop(callback); + + vi.useFakeTimers(); + }); + + afterEach(() => vi.clearAllTimers()); + + it("Should initialize with default values", () => + { + expect(gameLoop.startTime).toBe(0); + expect(gameLoop.isRunning).toBe(false); + }); + + it("Should start the game loop", () => + { + gameLoop.start(); + + expect(gameLoop.isRunning).toBe(true); + expect(callback).toHaveBeenCalled(); + }); + it("Should throw `RuntimeException` if start is called while already running", () => + { + gameLoop.start(); + + expect(() => gameLoop.start()).toThrow(RuntimeException); + }); + + it("Should calculate elapsed time correctly", () => + { + gameLoop.start(); + + expect(gameLoop.elapsedTime).toBeGreaterThanOrEqual(0); + }); + + it("Should stop the game loop", () => + { + gameLoop.start(); + gameLoop.stop(); + + expect(gameLoop.isRunning).toBe(false); + }); + it("Should throw `RuntimeException` if stop is called while not running", () => + { + expect(() => gameLoop.stop()).toThrow(RuntimeException); + }); + + it("Should throw `FatalErrorException` if stop is called without a handle", () => + { + gameLoop.start(); + gameLoop["_handle"] = undefined; + + expect(() => gameLoop.stop()).toThrow(FatalErrorException); + }); + + it("Should subscribe to start event", () => + { + const _callback = vi.fn(); + + gameLoop.onStart(_callback); + gameLoop.start(); + + expect(_callback).toHaveBeenCalled(); + }); + it("Should subscribe to stop event", () => + { + const _callback = vi.fn(); + + gameLoop.onStop(_callback); + gameLoop.start(); + gameLoop.stop(); + + expect(_callback).toHaveBeenCalled(); + }); +}); diff --git a/tests/utils/async.test.ts b/tests/utils/async.test.ts new file mode 100644 index 0000000..d83066f --- /dev/null +++ b/tests/utils/async.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it, vi } from "vitest"; + +import { delay, nextAnimationFrame, yieldToEventLoop } from "../../src/index.js"; +import type { Timeout } from "../../src/index.js"; + +describe("delay", () => +{ + it("Should resolve after the specified number of milliseconds", async () => + { + vi.useFakeTimers(); + + const milliseconds = 100; + + let resolved = false; + delay(milliseconds) + .then(() => { resolved = true; }); + + await vi.advanceTimersByTimeAsync(milliseconds - 1); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(1); + expect(resolved).toBe(true); + + vi.clearAllTimers(); + }); +}); + +describe("nextAnimationFrame", () => +{ + it("Should resolve on the next animation frame", async () => + { + const _requestAnimationFrame = vi.spyOn(window, "requestAnimationFrame") + .mockImplementation((callback) => + { + callback(0); + + return -1; + }); + + await nextAnimationFrame(); + expect(_requestAnimationFrame).toHaveBeenCalled(); + + _requestAnimationFrame.mockRestore(); + }); +}); + +describe("yieldToEventLoop", () => +{ + it("Should resolve on the next microtask", async () => + { + const setTimeoutSpy = vi.spyOn(window, "setTimeout") + .mockImplementation((callback): Timeout => + { + callback(); + + return (-1 as unknown) as Timeout; + }); + + await yieldToEventLoop(); + expect(setTimeoutSpy).toHaveBeenCalled(); + + setTimeoutSpy.mockRestore(); + }); +}); diff --git a/tests/utils/curve.test.ts b/tests/utils/curve.test.ts new file mode 100644 index 0000000..c2be9a6 --- /dev/null +++ b/tests/utils/curve.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; + +import { SmartIterator, ValueException } from "../../src/index.js"; +import { Curve } from "../../src/index.js"; + +describe("Curve", () => +{ + describe("Linear", () => + { + it("Should return an instance of `SmartIterator`", () => + { + const iterator = Curve.Linear(5); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should generate a linear sequence of values", () => + { + const values = Array.from(Curve.Linear(5)); + + expect(values).toEqual([0, 0.25, 0.5, 0.75, 1]); + }); + }); + + describe("Exponential", () => + { + it("Should return an instance of `SmartIterator`", () => + { + const iterator = Curve.Exponential(6); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + + it("Should generate an exponential sequence of values with default base", () => + { + const values = Array.from(Curve.Exponential(6)); + + expect(values).toEqual([0, 0.04000000000000001, 0.16000000000000003, 0.36, 0.6400000000000001, 1]); + }); + it("Should generate an exponential sequence of values with custom base", () => + { + const values = Array.from(Curve.Exponential(6, 3)); + + expect(values).toEqual( + [0, Math.pow(1 / 5, 3), Math.pow(2 / 5, 3), Math.pow(3 / 5, 3), Math.pow(4 / 5, 3), 1] + ); + }); + + it("Should throw `ValueException` if base is negative", () => + { + expect(() => Curve.Exponential(6, -1)).toThrow(ValueException); + }); + }); +}); diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts new file mode 100644 index 0000000..c6a17a1 --- /dev/null +++ b/tests/utils/date.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, it } from "vitest"; + +import { RangeException, SmartIterator } from "../../src/index.js"; +import { TimeUnit, WeekDay, dateDifference, dateRange, dateRound, getWeek } from "../../src/index.js"; + +describe("TimeUnit", () => +{ + it("Should have correct conversion factors", () => + { + expect(TimeUnit.Millisecond).toBe(1); + expect(TimeUnit.Second).toBe(1000); + expect(TimeUnit.Minute).toBe(60 * TimeUnit.Second); + expect(TimeUnit.Hour).toBe(60 * TimeUnit.Minute); + expect(TimeUnit.Day).toBe(24 * TimeUnit.Hour); + expect(TimeUnit.Week).toBe(7 * TimeUnit.Day); + expect(TimeUnit.Month).toBe(30 * TimeUnit.Day); + expect(TimeUnit.Year).toBe(365 * TimeUnit.Day); + }); +}); + +describe("WeekDay", () => +{ + it("Should have correct day values", () => + { + expect(WeekDay.Sunday).toBe(0); + expect(WeekDay.Monday).toBe(1); + expect(WeekDay.Tuesday).toBe(2); + expect(WeekDay.Wednesday).toBe(3); + expect(WeekDay.Thursday).toBe(4); + expect(WeekDay.Friday).toBe(5); + expect(WeekDay.Saturday).toBe(6); + }); +}); + +describe("dateDifference", () => +{ + it("Should calculate the difference in days by default", () => + { + const start = new Date("2025-01-01"); + const end = new Date("2025-01-31"); + + expect(dateDifference(start, end)).toBe(30); + }); + it("Should calculate the difference in specified `TimeUnit`", () => + { + const start = new Date("2025-01-01"); + const end = new Date("2025-01-31"); + + expect(dateDifference(start, end, TimeUnit.Minute)).toBe(43200); + }); + + it("Should return negative difference if start date is after end date", () => + { + const start = new Date("2025-01-31"); + const end = new Date("2025-01-01"); + + expect(dateDifference(start, end)).toBe(-30); + }); +}); + +describe("dateRange", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const start = new Date("2025-01-01"); + const end = new Date("2025-01-05"); + + const iterator = dateRange(start, end); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + + it("Should generate dates in the specified range", () => + { + const start = new Date("2025-01-01"); + const end = new Date("2025-01-05"); + const iterator = dateRange(start, end); + const dates = Array.from(iterator); + + expect(dates.length).toBe(4); + + expect(dates[0].toISOString().slice(0, 10)).toBe("2025-01-01"); + expect(dates[3].toISOString().slice(0, 10)).toBe("2025-01-04"); + }); + + it("Should throw `RangeException` if start date isn't less than end date", () => + { + const start = new Date("2025-01-05"); + const end = new Date("2025-01-01"); + + expect(() => dateRange(start, end)).toThrow(RangeException); + }); +}); + +describe("dateRound", () => +{ + it("Should round date to the previous time unit", () => + { + const date = new Date("2025-01-01T12:34:56.789Z"); + + expect(dateRound(date, TimeUnit.Hour).toISOString()).toBe("2025-01-01T12:00:00.000Z"); + }); + + it("Should throw `RangeException` if unit is less than or equal to a millisecond", () => + { + const date = new Date("2025-01-01T12:34:56.789Z"); + + expect(() => dateRound(date, TimeUnit.Millisecond)).toThrow(RangeException); + }); + it("Should throw `RangeException` if unit is greater than a day", () => + { + const date = new Date("2025-01-01T12:34:56.789Z"); + + expect(() => dateRound(date, TimeUnit.Week)).toThrow(RangeException); + }); +}); + +describe("getWeek", () => +{ + it("Should get the first day of the week for the specified date", () => + { + const date = new Date("2025-01-01"); + + expect(getWeek(date, WeekDay.Monday).toISOString() + .slice(0, 10)).toBe("2024-12-30"); + }); + it("Should get the first day of the week for the specified date with default first day", () => + { + const date = new Date("2025-01-01"); + + expect(getWeek(date).toISOString() + .slice(0, 10)).toBe("2024-12-29"); + }); +}); diff --git a/tests/utils/dom.test.ts b/tests/utils/dom.test.ts new file mode 100644 index 0000000..8e69a16 --- /dev/null +++ b/tests/utils/dom.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it, vi } from "vitest"; + +import { loadScript } from "../../src/index.js"; + +describe("loadScript", () => +{ + it("Should load a script successfully", async () => + { + const scriptUrl = "https://example.com/script.js"; + + const _appendChild = vi.spyOn(document.body, "appendChild") + .mockImplementation((element) => + { + const _script = element as HTMLScriptElement; + _script.onload!(new Event("load")); + + return _script; + }); + + const { resolves } = expect(loadScript(scriptUrl)); + await resolves.toBeUndefined(); + + expect(_appendChild).toHaveBeenCalled(); + + const script = _appendChild.mock.calls[0][0] as HTMLScriptElement; + expect(script).toBeInstanceOf(HTMLScriptElement); + expect(script.src).toBe(scriptUrl); + + _appendChild.mockRestore(); + }); + it("Should fail to load a script", async () => + { + const scriptUrl = "https://example.com/script.js"; + + const _appendChild = vi.spyOn(document.body, "appendChild") + .mockImplementation((element) => + { + const _script = element as HTMLScriptElement; + _script.onerror!(new Event("error")); + + return _script; + }); + + const { rejects } = expect(loadScript(scriptUrl)); + await rejects.toBeInstanceOf(Event); + + expect(_appendChild).toHaveBeenCalled(); + + const script = _appendChild.mock.calls[0][0] as HTMLScriptElement; + expect(script).toBeInstanceOf(HTMLScriptElement); + expect(script.src).toBe(scriptUrl); + + _appendChild.mockRestore(); + }); + + it("Should set the correct script type", async () => + { + const scriptUrl = "https://example.com/script.js"; + + const scriptType = "module"; + const _appendChild = vi.spyOn(document.body, "appendChild") + .mockImplementation((element) => + { + const _script = element as HTMLScriptElement; + _script.onload!(new Event("load")); + + return _script; + }); + + const { resolves } = expect(loadScript(scriptUrl, scriptType)); + await resolves.toBeUndefined(); + + expect(_appendChild).toHaveBeenCalled(); + + const script = _appendChild.mock.calls[0][0] as HTMLScriptElement; + expect(script).toBeInstanceOf(HTMLScriptElement); + expect(script.src).toBe(scriptUrl); + expect(script.type).toBe(scriptType); + + _appendChild.mockRestore(); + }); +}); diff --git a/tests/utils/iterator.test.ts b/tests/utils/iterator.test.ts new file mode 100644 index 0000000..f25fd84 --- /dev/null +++ b/tests/utils/iterator.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it } from "vitest"; + +import { RangeException, SmartIterator } from "../../src/index.js"; +import { chain, count, enumerate, range, shuffle, unique, zip } from "../../src/index.js"; + +describe("chain", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = chain([1, 2, 3], [4, 5, 6]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should chain multiple iterables into a single one", () => + { + const results = Array.from(chain([1, 2, 3], [4, 5, 6], [7, 8, 9])); + + expect(results).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); +}); + +describe("count", () => +{ + it("Should count the number of elements in an iterable", () => + { + expect(count([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])).toBe(10); + expect(count(new Set([1, 2, 3, 4, 5]))).toBe(5); + }); +}); + +describe("enumerate", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = enumerate(["A", "M", "N", "Z"]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should enumerate the elements of an iterable", () => + { + const results = Array.from(enumerate(["A", "M", "N", "Z"])); + + expect(results).toEqual([[0, "A"], [1, "M"], [2, "N"], [3, "Z"]]); + }); +}); + +describe("range", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = range(5); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + + it("Should throw `RangeException` if step is lower than or equal to 0", () => + { + expect(() => range(2, 7, 0)).toThrow(RangeException); + expect(() => range(5, 0, -1)).toThrow(RangeException); + }); + + it("Should generate an iterator over a range of numbers", () => + { + expect(Array.from(range(5))).toEqual([0, 1, 2, 3, 4]); + expect(Array.from(range(2, 7))).toEqual([2, 3, 4, 5, 6]); + }); + it("Should generate an iterator over a range of numbers with a specified step", () => + { + expect(Array.from(range(0, 10, 2))).toEqual([0, 2, 4, 6, 8]); + expect(Array.from(range(2, 13, 3))).toEqual([2, 5, 8, 11]); + }); + + it("Should generate an iterator over a range of numbers in reverse", () => + { + expect(Array.from(range(5, 0))).toEqual([5, 4, 3, 2, 1]); + expect(Array.from(range(7, 2))).toEqual([7, 6, 5, 4, 3]); + }); + it("Should generate an iterator over a range of numbers in reverse with a specified step", () => + { + expect(Array.from(range(10, 0, 2))).toEqual([10, 8, 6, 4, 2]); + expect(Array.from(range(13, 2, 3))).toEqual([13, 10, 7, 4]); + }); +}); + +describe("shuffle", () => +{ + it("Should shuffle the elements of an iterable", () => + { + const array = [1, 2, 3, 4, 5]; + const shuffled = shuffle(array); + + expect(shuffled).toHaveLength(array.length); + expect(new Set(shuffled)).toEqual(new Set(array)); + }); +}); + +describe("unique", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = unique([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should filter the elements of an iterable ensuring they are all unique", () => + { + const results = Array.from(unique([1, 1, 2, 3, 2, 3, 4, 5, 5, 4])); + + expect(results).toEqual([1, 2, 3, 4, 5]); + }); +}); + +describe("zip", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = zip([1, 2, 3, 4], ["A", "M", "N", "Z"]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should zip two iterables into a single one", () => + { + const results = Array.from(zip([1, 2, 3, 4], ["A", "M", "N", "Z"])); + + expect(results).toEqual([[1, "A"], [2, "M"], [3, "N"], [4, "Z"]]); + }); +}); diff --git a/tests/utils/math.test.ts b/tests/utils/math.test.ts new file mode 100644 index 0000000..0143d08 --- /dev/null +++ b/tests/utils/math.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; + +import { ValueException } from "../../src/index.js"; +import { average, hash, sum } from "../../src/index.js"; + +describe("average", () => +{ + it("Should compute the average of a list of values", () => + { + expect(average([1, 2, 3, 4, 5])).toBe(3); + }); + it("Should compute the weighted average of a list of values", () => + { + expect(average([6, 8.5, 4], [3, 2, 1])).toBe(6.5); + }); + + it("Should throw `ValueException` if no values are provided", () => + { + expect(() => average([])).toThrow(ValueException); + }); + it("Should throw `ValueException` if weights are provided and one of them is zero or negative", () => + { + expect(() => average([1, 2, 3], [1, 0, 1])).toThrow(ValueException); + expect(() => average([1, 2, 3], [1, -1, 1])).toThrow(ValueException); + }); + it("Should throw `ValueException` if the sum of weights isn't greater than zero", () => + { + expect(() => average([1, 2, 3], [0, 0, 0])).toThrow(ValueException); + }); +}); + +describe("hash", () => +{ + it("Should compute the hash of a given string", () => + { + expect(hash("Hello, world!")).toBe(-1880044555); + expect(hash("How are you?")).toBe(1761539132); + }); +}); + +describe("sum", () => +{ + it("Should sum all the values of a given list", () => + { + expect(sum([1, 2, 3, 4, 5])).toBe(15); + }); +}); diff --git a/tests/utils/random.test.ts b/tests/utils/random.test.ts new file mode 100644 index 0000000..f486160 --- /dev/null +++ b/tests/utils/random.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, it } from "vitest"; + +import { ValueException } from "../../src/index.js"; +import { Random } from "../../src/index.js"; + +describe("Random", () => +{ + describe("Boolean", () => + { + it("Should return a boolean value", () => + { + const result = Random.Boolean(); + + expect(typeof result).toBe("boolean"); + }); + + it("Should return true approximately 50% of the time with default ratio", () => + { + const results = Array.from({ length: 1000 }, () => Random.Boolean()); + const trueCount = results.filter(Boolean).length; + + expect(trueCount).toBeGreaterThan(400); + expect(trueCount).toBeLessThan(600); + }); + it("Should return true approximately 70% of the time with ratio 0.7", () => + { + const results = Array.from({ length: 1000 }, () => Random.Boolean(0.7)); + const trueCount = results.filter(Boolean).length; + + expect(trueCount).toBeGreaterThan(650); + expect(trueCount).toBeLessThan(750); + }); + }); + + describe("Integer", () => + { + it("Should return an integer between 0 and max (exclusive)", () => + { + const max = 5; + const result = Random.Integer(max); + + expect(result).toBeGreaterThanOrEqual(0); + expect(result).toBeLessThan(max); + }); + it("Should return an integer between min and max (exclusive)", () => + { + const min = 2; + const max = 7; + const result = Random.Integer(min, max); + + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + }); + }); + + describe("Decimal", () => + { + it("Should return a decimal between 0 and 1 (exclusive)", () => + { + const result = Random.Decimal(); + + expect(result).toBeGreaterThanOrEqual(0); + expect(result).toBeLessThan(1); + }); + it("Should return a decimal between 0 and max (exclusive)", () => + { + const max = 5; + const result = Random.Decimal(max); + + expect(result).toBeGreaterThanOrEqual(0); + expect(result).toBeLessThan(max); + }); + it("Should return a decimal between min and max (exclusive)", () => + { + const min = 2; + const max = 7; + const result = Random.Decimal(min, max); + + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + }); + }); + + describe("Index", () => + { + it("Should return a valid index from the array", () => + { + const elements = [1, 2, 3, 4, 5]; + const index = Random.Index(elements); + + expect(index).toBeGreaterThanOrEqual(0); + expect(index).toBeLessThan(elements.length); + }); + + it("Should throw `ValueException` if the array is empty", () => + { + expect(() => Random.Index([])).toThrow(ValueException); + }); + }); + + describe("Choice", () => + { + it("Should return a random element from the array", () => + { + const elements = [1, 2, 3, 4, 5]; + const choice = Random.Choice(elements); + + expect(elements).toContain(choice); + }); + + it("Should throw `ValueException` if the array is empty", () => + { + expect(() => Random.Choice([])).toThrow(ValueException); + }); + }); +}); diff --git a/tests/utils/string.test.ts b/tests/utils/string.test.ts new file mode 100644 index 0000000..575c440 --- /dev/null +++ b/tests/utils/string.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { capitalize } from "../../src/index.js"; + +describe("capitalize", () => +{ + it("Should capitalize the first letter of a string", () => + { + expect(capitalize("hello")).toBe("Hello"); + }); + it("Should handle strings that are already capitalized", () => + { + expect(capitalize("Hello")).toBe("Hello"); + }); + it("Should not change the case of other letters", () => + { + expect(capitalize("hELLo")).toBe("HELLo"); + }); + + it("Should return an empty string if input is an empty string", () => + { + expect(capitalize("")).toBe(""); + }); + it("Should handle single character strings", () => + { + expect(capitalize("a")).toBe("A"); + }); + it("Should handle strings with special characters", () => + { + expect(capitalize("!hello")).toBe("!hello"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 2afaf04..8e7fb3a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,10 @@ "forceConsistentCasingInFileNames": true, "baseUrl": "." }, - "include": ["src"], + "include": [ + "src", + "tests" + ], "exclude": [ "dist", "node_modules" diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4fd1437 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig, configDefaults } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + exclude: configDefaults.exclude, + root: fileURLToPath(new URL("./", import.meta.url)) + } +});