diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6250963..6d155ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,11 +39,88 @@ jobs: - name: Prettier Check run: pnpm run prettier-check + Test: + name: Run Tests and Coverage + runs-on: ubuntu-latest + needs: [Prettier-check] + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + id: cache-primes + with: + node-version-file: '.nvmrc' + + - name: Setup PNPM environment + if: steps.cache-primes.outputs.cache-hit != 'true' + run: | + npm install -g pnpm@latest + pnpm config set store-dir ~/.pnpm-store + + - name: Cache PNPM dependencies + uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-v3-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-v3- + + - name: Install dependencies + if: steps.cache-primes.outputs.cache-hit != 'true' + run: pnpm install --frozen-lockfile + + - name: Run Tests with Coverage + run: pnpm run test:coverage + + - name: Check Coverage Thresholds + run: | + COVERAGE_OUTPUT=$(cat coverage/coverage-summary.json) + + STATEMENTS_PCT=$(echo $COVERAGE_OUTPUT | jq -r '.total.statements.pct') + BRANCHES_PCT=$(echo $COVERAGE_OUTPUT | jq -r '.total.branches.pct') + FUNCTIONS_PCT=$(echo $COVERAGE_OUTPUT | jq -r '.total.functions.pct') + LINES_PCT=$(echo $COVERAGE_OUTPUT | jq -r '.total.lines.pct') + + THRESHOLD=1 + + if (( $(echo "$STATEMENTS_PCT < $THRESHOLD" | bc -l) )); then + echo "❌ Statement coverage ($STATEMENTS_PCT%) is below threshold ($THRESHOLD%)" + exit 1 + fi + + if (( $(echo "$BRANCHES_PCT < $THRESHOLD" | bc -l) )); then + echo "❌ Branch coverage ($BRANCHES_PCT%) is below threshold ($THRESHOLD%)" + exit 1 + fi + + if (( $(echo "$FUNCTIONS_PCT < $THRESHOLD" | bc -l) )); then + echo "❌ Function coverage ($FUNCTIONS_PCT%) is below threshold ($THRESHOLD%)" + exit 1 + fi + + if (( $(echo "$LINES_PCT < $THRESHOLD" | bc -l) )); then + echo "❌ Line coverage ($LINES_PCT%) is below threshold ($THRESHOLD%)" + exit 1 + fi + + echo "✅ All coverage thresholds met!" + echo "Statements: $STATEMENTS_PCT%" + echo "Branches: $BRANCHES_PCT%" + echo "Functions: $FUNCTIONS_PCT%" + echo "Lines: $LINES_PCT%" + + - name: Archive code coverage results + uses: actions/upload-artifact@v4 + with: + name: code-coverage-report + path: coverage/ + retention-days: 30 Build: name: Build project runs-on: ubuntu-latest - needs: [ Prettier-check ] + needs: [Prettier-check, Test] steps: - name: Checkout repo uses: actions/checkout@v3 @@ -73,3 +150,19 @@ jobs: - name: Build run: pnpm run build + + Status-Check: + name: Status Check + runs-on: ubuntu-latest + needs: [Prettier-check, Test, Build] + if: always() + steps: + - name: Check status + run: | + if [ "${{ needs.Prettier-check.result }}" != "success" ] || \ + [ "${{ needs.Test.result }}" != "success" ] || \ + [ "${{ needs.Build.result }}" != "success" ]; then + echo "One or more jobs failed" + exit 1 + fi + echo "All jobs passed successfully" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6e8fb74..f2f7b23 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dist .idea .vscode + # storybook storybook-static @@ -16,4 +17,7 @@ storybook-static # public folder temp files public -/stories/Uploader.stories.js \ No newline at end of file +/stories/Uploader.stories.js + +# Tests +coverage \ No newline at end of file diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 0000000..ac08519 --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,46 @@ +/** @type {import('jest').Config} */ +module.exports = { + roots: ['/src'], + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapper: { + '^frontend/(.*)$': '/src/frontend/$1', + '^backend/(.*)$': '/src/backend/$1', + '^lib/(.*)$': '/src/lib/$1', + '\\.css$': 'identity-obj-proxy', + '^@/(.*)$': '/src/$1', + }, + transform: { + '^.+\\.(t|j)sx?$': [ + 'ts-jest', + { + tsconfig: 'tsconfig.json', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!(@mui|framer-motion)/)'], + testMatch: [ + '/src/**/__tests__/**/*.{ts,tsx}', + '/src/**/*.{spec,test}.{ts,tsx}', + ], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/version.ts', + ], + coverageReporters: [ + 'json-summary', // Creates coverage-summary.json used by CI + 'text', // Console output + 'lcov', // Detailed HTML report + 'clover', // Additional machine-readable format + ], + coverageThreshold: { + global: { + branches: 1, + functions: 1, + lines: 1, + statements: 1, + }, + }, +} diff --git a/package.json b/package.json index 05ab2cb..a6d0dc8 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,10 @@ "build:browser": "tsdx build --entry ./src/index.browser.ts --tsconfig ./tsconfig.browser.json --format esm,cjs --name browser", "build:node": "tsdx build --entry ./src/index.node.ts --tsconfig ./tsconfig.node.json --format cjs --name node", "build": "node scripts/build.js", - "test": "tsdx test --passWithNoTests", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "test:ci": "jest --coverage --ci --maxWorkers=2", "lint": "tsdx lint", "prepare": "tsdx build", "size": "size-limit", @@ -99,6 +102,10 @@ "@storybook/react": "^7.6.20", "@storybook/react-webpack5": "^7.6.20", "@storybook/testing-library": "^0.2.2", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", + "@types/jest": "29.5.11", "@types/node": "^20.10.0", "@types/pako": "^2.0.3", "@types/react": "^18.2.39", @@ -110,6 +117,10 @@ "babel-loader": "^9.1.3", "highlight.js": "10.7.3", "husky": "^8.0.3", + "identity-obj-proxy": "^3.0.0", + "jest": "29.7.0", + "jest-canvas-mock": "^2.5.2", + "jest-environment-jsdom": "29.7.0", "lowlight": "1.20.0", "postcss": "^8.4.31", "prettier": "3.0.3", @@ -124,6 +135,7 @@ "size-limit": "^10.0.3", "storybook": "^7.5.3", "tailwindcss": "^3.3.5", + "ts-jest": "29.1.1", "tsdx": "^0.14.1", "tslib": "^2.6.2", "typescript": "^5.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7552ecb..e9843c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,6 +148,18 @@ importers: '@storybook/testing-library': specifier: ^0.2.2 version: 0.2.2 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 + '@testing-library/react': + specifier: ^16.0.1 + version: 16.0.1(@testing-library/dom@9.3.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: ^14.5.2 + version: 14.5.2(@testing-library/dom@9.3.4) + '@types/jest': + specifier: 29.5.11 + version: 29.5.11 '@types/node': specifier: ^20.10.0 version: 20.17.9 @@ -181,6 +193,18 @@ importers: husky: specifier: ^8.0.3 version: 8.0.3 + identity-obj-proxy: + specifier: ^3.0.0 + version: 3.0.0 + jest: + specifier: 29.7.0 + version: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1) + jest-canvas-mock: + specifier: ^2.5.2 + version: 2.5.2 + jest-environment-jsdom: + specifier: 29.7.0 + version: 29.7.0 lowlight: specifier: 1.20.0 version: 1.20.0 @@ -223,6 +247,9 @@ importers: tailwindcss: specifier: ^3.3.5 version: 3.4.16 + ts-jest: + specifier: 29.1.1 + version: 29.1.1(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.18.20)(jest@29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1))(typescript@5.7.2) tsdx: specifier: ^0.14.1 version: 0.14.1(@types/babel__core@7.20.5)(@types/node@20.17.9) @@ -241,6 +268,9 @@ importers: packages: + '@adobe/css-tools@4.4.1': + resolution: {integrity: sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -673,6 +703,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-flow@7.26.0': resolution: {integrity: sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==} engines: {node: '>=6.9.0'} @@ -737,6 +773,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.25.9': resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} engines: {node: '>=6.9.0'} @@ -1600,26 +1648,68 @@ packages: resolution: {integrity: sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==} engines: {node: '>= 8.3'} + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/core@25.5.4': resolution: {integrity: sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==} engines: {node: '>= 8.3'} + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: 10.0.1 + peerDependenciesMeta: + node-notifier: + optional: true + '@jest/environment@25.5.0': resolution: {integrity: sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==} engines: {node: '>= 8.3'} + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/fake-timers@25.5.0': resolution: {integrity: sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==} engines: {node: '>= 8.3'} + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/globals@25.5.2': resolution: {integrity: sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==} engines: {node: '>= 8.3'} + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/reporters@25.5.1': resolution: {integrity: sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==} engines: {node: '>= 8.3'} + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: 10.0.1 + peerDependenciesMeta: + node-notifier: + optional: true + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1628,14 +1718,26 @@ packages: resolution: {integrity: sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==} engines: {node: '>= 8.3'} + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-result@25.5.0': resolution: {integrity: sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==} engines: {node: '>= 8.3'} + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-sequencer@25.5.4': resolution: {integrity: sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==} engines: {node: '>= 8.3'} + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/transform@25.5.1': resolution: {integrity: sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==} engines: {node: '>= 8.3'} @@ -2331,6 +2433,12 @@ packages: '@sinonjs/commons@1.8.6': resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@size-limit/esbuild@10.0.3': resolution: {integrity: sha512-5z7qhfKqR3zJJMsbgIiFiqZmBqBYboG67g6vzIovBI/CyOvKTWUrvhx17BHYWNwi+cZgkR+oJjhgJc5hO7rYxQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2886,6 +2994,25 @@ packages: resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.0.1': + resolution: {integrity: sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@testing-library/user-event@14.5.2': resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} engines: {node: '>=12', npm: '>=6'} @@ -3005,6 +3132,12 @@ packages: '@types/jest@25.2.3': resolution: {integrity: sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==} + '@types/jest@29.5.11': + resolution: {integrity: sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==} + + '@types/jsdom@20.0.1': + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -3098,6 +3231,9 @@ packages: '@types/stack-utils@1.0.1': resolution: {integrity: sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -3599,6 +3735,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + babel-loader@9.2.1: resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==} engines: {node: '>= 14.15.0'} @@ -3627,6 +3769,10 @@ packages: resolution: {integrity: sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==} engines: {node: '>= 8.3'} + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-macros@2.8.0: resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} @@ -3665,12 +3811,23 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + babel-preset-jest@25.5.0: resolution: {integrity: sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==} engines: {node: '>= 8.3'} peerDependencies: '@babel/core': ^7.0.0 + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -3846,6 +4003,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} @@ -3928,6 +4089,10 @@ packages: cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone-deep@4.0.1: resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} engines: {node: '>=6'} @@ -4106,6 +4271,11 @@ packages: engines: {node: '>=0.8'} hasBin: true + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -4147,11 +4317,17 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + cssfontparser@1.2.1: + resolution: {integrity: sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==} + cssnano-preset-default@5.2.14: resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} engines: {node: ^10 || ^12 || >=14.0} @@ -4242,6 +4418,14 @@ packages: dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -4337,6 +4521,10 @@ packages: resolution: {integrity: sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==} engines: {node: '>= 8.3'} + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4355,6 +4543,9 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-converter@0.2.0: resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} @@ -4416,6 +4607,10 @@ packages: electron-to-chromium@1.5.68: resolution: {integrity: sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + emoji-regex@7.0.3: resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} @@ -4766,6 +4961,10 @@ packages: resolution: {integrity: sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==} engines: {node: '>= 8.3'} + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + express@4.21.1: resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} @@ -5186,6 +5385,9 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + harmony-reflect@1.6.2: + resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -5349,6 +5551,10 @@ packages: peerDependencies: postcss: ^8.1.0 + identity-obj-proxy@3.0.0: + resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} + engines: {node: '>=4'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -5701,6 +5907,10 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} @@ -5725,43 +5935,105 @@ packages: engines: {node: '>=10'} hasBin: true + jest-canvas-mock@2.5.2: + resolution: {integrity: sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==} + jest-changed-files@25.5.0: resolution: {integrity: sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==} engines: {node: '>= 8.3'} + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-cli@25.5.4: resolution: {integrity: sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==} engines: {node: '>= 8.3'} hasBin: true + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: 10.0.1 + peerDependenciesMeta: + node-notifier: + optional: true + jest-config@25.5.4: resolution: {integrity: sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==} engines: {node: '>= 8.3'} + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + jest-diff@25.5.0: resolution: {integrity: sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==} engines: {node: '>= 8.3'} + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-docblock@25.3.0: resolution: {integrity: sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==} engines: {node: '>= 8.3'} + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-each@25.5.0: resolution: {integrity: sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==} engines: {node: '>= 8.3'} + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-environment-jsdom@25.5.0: resolution: {integrity: sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==} engines: {node: '>= 8.3'} + jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + jest-environment-node@25.5.0: resolution: {integrity: sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==} engines: {node: '>= 8.3'} + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-get-type@25.2.6: resolution: {integrity: sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==} engines: {node: '>= 8.3'} + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-haste-map@25.5.1: resolution: {integrity: sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==} engines: {node: '>= 8.3'} @@ -5778,14 +6050,26 @@ packages: resolution: {integrity: sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==} engines: {node: '>= 8.3'} + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@25.5.0: resolution: {integrity: sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==} engines: {node: '>= 8.3'} + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-message-util@25.5.0: resolution: {integrity: sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==} engines: {node: '>= 8.3'} + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-mock@25.5.0: resolution: {integrity: sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==} engines: {node: '>= 8.3'} @@ -5794,6 +6078,10 @@ packages: resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-pnp-resolver@1.2.3: resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} @@ -5815,19 +6103,35 @@ packages: resolution: {integrity: sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==} engines: {node: '>= 8.3'} + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-resolve@25.5.1: resolution: {integrity: sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==} engines: {node: '>= 8.3'} + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runner@25.5.4: resolution: {integrity: sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==} engines: {node: '>= 8.3'} + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runtime@25.5.4: resolution: {integrity: sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==} engines: {node: '>= 8.3'} hasBin: true + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-serializer@25.5.0: resolution: {integrity: sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==} engines: {node: '>= 8.3'} @@ -5836,6 +6140,10 @@ packages: resolution: {integrity: sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==} engines: {node: '>= 8.3'} + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-util@25.5.0: resolution: {integrity: sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==} engines: {node: '>= 8.3'} @@ -5848,6 +6156,10 @@ packages: resolution: {integrity: sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==} engines: {node: '>= 8.3'} + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-watch-typeahead@0.5.0: resolution: {integrity: sha512-4r36w9vU8+rdg48hj0Z7TvcSqVP6Ao8dk04grlHQNgduyCB0SqrI0xWIl85ZhXrzYvxQ0N5H+rRLAejkQzEHeQ==} @@ -5855,6 +6167,10 @@ packages: resolution: {integrity: sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==} engines: {node: '>= 8.3'} + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-worker@24.9.0: resolution: {integrity: sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==} engines: {node: '>= 6'} @@ -5876,6 +6192,16 @@ packages: engines: {node: '>= 8.3'} hasBin: true + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: 10.0.1 + peerDependenciesMeta: + node-notifier: + optional: true + jiti@1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true @@ -6469,6 +6795,9 @@ packages: mlly@1.7.3: resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} + moo-color@1.0.3: + resolution: {integrity: sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -7291,6 +7620,10 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-hrtime@1.0.3: resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} engines: {node: '>= 0.8'} @@ -7365,6 +7698,9 @@ packages: resolution: {integrity: sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==} engines: {node: '>=8.16.0'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -7547,6 +7883,10 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + reflect.getprototypeof@1.0.7: resolution: {integrity: sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==} engines: {node: '>= 0.4'} @@ -7670,6 +8010,10 @@ packages: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} deprecated: https://github.com/lydell/resolve-url#deprecated + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} @@ -7986,6 +8330,9 @@ packages: resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -8047,6 +8394,10 @@ packages: resolution: {integrity: sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==} engines: {node: '>=8'} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -8086,6 +8437,10 @@ packages: resolution: {integrity: sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==} engines: {node: '>=8'} + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + string-width@2.1.1: resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} engines: {node: '>=4'} @@ -8166,6 +8521,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-indent@4.0.0: resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} engines: {node: '>=12'} @@ -8418,6 +8777,27 @@ packages: jest: '>=25 <26' typescript: '>=3.4 <4.0' + ts-jest@29.1.1: + resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -8692,6 +9072,10 @@ packages: resolution: {integrity: sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==} engines: {node: 8.x.x || >=10.10.0} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -8898,6 +9282,10 @@ packages: y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -8917,10 +9305,18 @@ packages: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} @@ -8940,6 +9336,8 @@ packages: snapshots: + '@adobe/css-tools@4.4.1': {} + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -9861,6 +10259,11 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -9921,6 +10324,16 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -10914,6 +11327,15 @@ snapshots: jest-util: 25.5.0 slash: 3.0.0 + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + '@jest/core@25.5.4': dependencies: '@jest/console': 25.5.0 @@ -10950,12 +11372,67 @@ snapshots: - supports-color - utf-8-validate + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0(node-notifier@10.0.1) + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + optionalDependencies: + node-notifier: 10.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/environment@25.5.0': dependencies: '@jest/fake-timers': 25.5.0 '@jest/types': 25.5.0 jest-mock: 25.5.0 + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + '@jest/fake-timers@25.5.0': dependencies: '@jest/types': 25.5.0 @@ -10964,16 +11441,34 @@ snapshots: jest-util: 25.5.0 lolex: 5.1.2 + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.17.9 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + '@jest/globals@25.5.2': dependencies: '@jest/environment': 25.5.0 '@jest/types': 25.5.0 expect: 25.5.0 - '@jest/reporters@25.5.1': + '@jest/globals@29.7.0': dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 25.5.0 + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@25.5.1': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 25.5.0 '@jest/test-result': 25.5.0 '@jest/transform': 25.5.1 '@jest/types': 25.5.0 @@ -11001,6 +11496,37 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/reporters@29.7.0(node-notifier@10.0.1)': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.17.9 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + optionalDependencies: + node-notifier: 10.0.1 + transitivePeerDependencies: + - supports-color + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -11011,6 +11537,12 @@ snapshots: graceful-fs: 4.2.11 source-map: 0.6.1 + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + '@jest/test-result@25.5.0': dependencies: '@jest/console': 25.5.0 @@ -11018,6 +11550,13 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + '@jest/test-sequencer@25.5.4': dependencies: '@jest/test-result': 25.5.0 @@ -11026,7 +11565,17 @@ snapshots: jest-runner: 25.5.4 jest-runtime: 25.5.4 transitivePeerDependencies: + - bufferutil + - canvas - supports-color + - utf-8-validate + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 '@jest/transform@25.5.1': dependencies: @@ -11727,6 +12276,14 @@ snapshots: dependencies: type-detect: 4.0.8 + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@size-limit/esbuild@10.0.3(size-limit@10.0.3)': dependencies: esbuild: 0.19.12 @@ -12906,6 +13463,26 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@testing-library/jest-dom@6.6.3': + dependencies: + '@adobe/css-tools': 4.4.1 + aria-query: 5.3.2 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@16.0.1(@testing-library/dom@9.3.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@testing-library/dom': 9.3.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@testing-library/user-event@14.5.2(@testing-library/dom@9.3.4)': dependencies: '@testing-library/dom': 9.3.4 @@ -13036,6 +13613,17 @@ snapshots: jest-diff: 25.5.0 pretty-format: 25.5.0 + '@types/jest@29.5.11': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/jsdom@20.0.1': + dependencies: + '@types/node': 20.17.9 + '@types/tough-cookie': 4.0.5 + parse5: 7.2.1 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -13128,6 +13716,8 @@ snapshots: '@types/stack-utils@1.0.1': {} + '@types/stack-utils@2.0.3': {} + '@types/tough-cookie@4.0.5': {} '@types/unist@2.0.11': {} @@ -13698,6 +14288,19 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.89.0(@swc/core@1.10.0)(esbuild@0.18.20)): dependencies: '@babel/core': 7.26.0 @@ -13731,6 +14334,13 @@ snapshots: '@babel/types': 7.26.0 '@types/babel__traverse': 7.20.6 + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + babel-plugin-macros@2.8.0: dependencies: '@babel/runtime': 7.26.0 @@ -13799,12 +14409,37 @@ snapshots: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + babel-preset-jest@25.5.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 babel-plugin-jest-hoist: 25.5.0 babel-preset-current-node-syntax: 0.1.4(@babel/core@7.26.0) + babel-preset-jest@29.6.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -14011,6 +14646,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + char-regex@1.0.2: {} + character-entities-legacy@1.1.4: {} character-entities@1.2.4: {} @@ -14088,6 +14725,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone-deep@4.0.1: dependencies: is-plain-object: 2.0.4 @@ -14250,6 +14893,21 @@ snapshots: exit-on-epipe: 1.0.1 printj: 1.1.2 + create-jest@29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -14298,8 +14956,12 @@ snapshots: css-what@6.1.0: {} + css.escape@1.5.1: {} + cssesc@3.0.0: {} + cssfontparser@1.2.1: {} + cssnano-preset-default@5.2.14(postcss@8.4.49): dependencies: css-declaration-sorter: 6.4.1(postcss@8.4.49) @@ -14404,6 +15066,10 @@ snapshots: dedent@0.7.0: {} + dedent@1.5.3(babel-plugin-macros@3.1.0): + optionalDependencies: + babel-plugin-macros: 3.1.0 + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -14511,6 +15177,8 @@ snapshots: diff-sequences@25.2.6: {} + diff-sequences@29.6.3: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -14527,6 +15195,8 @@ snapshots: dom-accessibility-api@0.5.16: {} + dom-accessibility-api@0.6.3: {} + dom-converter@0.2.0: dependencies: utila: 0.4.0 @@ -14599,6 +15269,8 @@ snapshots: electron-to-chromium@1.5.68: {} + emittery@0.13.1: {} + emoji-regex@7.0.3: {} emoji-regex@8.0.0: {} @@ -15143,6 +15815,14 @@ snapshots: jest-message-util: 25.5.0 jest-regex-util: 25.2.6 + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + express@4.21.1: dependencies: accepts: 1.3.8 @@ -15685,6 +16365,8 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + harmony-reflect@1.6.2: {} + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -15855,6 +16537,10 @@ snapshots: dependencies: postcss: 8.4.49 + identity-obj-proxy@3.0.0: + dependencies: + harmony-reflect: 1.6.2 + ieee754@1.2.1: {} ignore@4.0.6: {} @@ -16171,6 +16857,16 @@ snapshots: transitivePeerDependencies: - supports-color + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 @@ -16211,12 +16907,49 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.2 + jest-canvas-mock@2.5.2: + dependencies: + cssfontparser: 1.2.1 + moo-color: 1.0.3 + jest-changed-files@25.5.0: dependencies: '@jest/types': 25.5.0 execa: 3.4.0 throat: 5.0.0 + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0(babel-plugin-macros@3.1.0): + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3(babel-plugin-macros@3.1.0) + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-cli@25.5.4: dependencies: '@jest/core': 25.5.4 @@ -16239,6 +16972,27 @@ snapshots: - supports-color - utf-8-validate + jest-cli@29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + optionalDependencies: + node-notifier: 10.0.1 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-config@25.5.4: dependencies: '@babel/core': 7.26.0 @@ -16266,6 +17020,36 @@ snapshots: - supports-color - utf-8-validate + jest-config@29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0): + dependencies: + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.9 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-diff@25.5.0: dependencies: chalk: 3.0.0 @@ -16273,10 +17057,21 @@ snapshots: jest-get-type: 25.2.6 pretty-format: 25.5.0 + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + jest-docblock@25.3.0: dependencies: detect-newline: 3.1.0 + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + jest-each@25.5.0: dependencies: '@jest/types': 25.5.0 @@ -16285,6 +17080,14 @@ snapshots: jest-util: 25.5.0 pretty-format: 25.5.0 + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + jest-environment-jsdom@25.5.0: dependencies: '@jest/environment': 25.5.0 @@ -16299,6 +17102,21 @@ snapshots: - supports-color - utf-8-validate + jest-environment-jsdom@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 20.17.9 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 22.1.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jest-environment-node@25.5.0: dependencies: '@jest/environment': 25.5.0 @@ -16308,8 +17126,19 @@ snapshots: jest-util: 25.5.0 semver: 6.3.1 + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jest-get-type@25.2.6: {} + jest-get-type@29.6.3: {} + jest-haste-map@25.5.1: dependencies: '@jest/types': 25.5.0 @@ -16375,6 +17204,11 @@ snapshots: jest-get-type: 25.2.6 pretty-format: 25.5.0 + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + jest-matcher-utils@25.5.0: dependencies: chalk: 3.0.0 @@ -16382,6 +17216,13 @@ snapshots: jest-get-type: 25.2.6 pretty-format: 25.5.0 + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + jest-message-util@25.5.0: dependencies: '@babel/code-frame': 7.26.2 @@ -16393,6 +17234,18 @@ snapshots: slash: 3.0.0 stack-utils: 1.0.5 + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + jest-mock@25.5.0: dependencies: '@jest/types': 25.5.0 @@ -16402,10 +17255,20 @@ snapshots: '@jest/types': 27.5.1 '@types/node': 20.17.9 + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + jest-util: 29.7.0 + jest-pnp-resolver@1.2.3(jest-resolve@25.5.1): optionalDependencies: jest-resolve: 25.5.1 + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + jest-regex-util@25.2.6: {} jest-regex-util@29.6.3: {} @@ -16416,6 +17279,13 @@ snapshots: jest-regex-util: 25.2.6 jest-snapshot: 25.5.1 + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + jest-resolve@25.5.1: dependencies: '@jest/types': 25.5.0 @@ -16428,6 +17298,18 @@ snapshots: resolve: 1.22.8 slash: 3.0.0 + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.3 + slash: 3.0.0 + jest-runner@25.5.4: dependencies: '@jest/console': 25.5.0 @@ -16455,6 +17337,32 @@ snapshots: - supports-color - utf-8-validate + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + jest-runtime@25.5.4: dependencies: '@jest/console': 25.5.0 @@ -16489,6 +17397,33 @@ snapshots: - supports-color - utf-8-validate + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + chalk: 4.1.2 + cjs-module-lexer: 1.4.1 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + jest-serializer@25.5.0: dependencies: graceful-fs: 4.2.11 @@ -16511,6 +17446,31 @@ snapshots: pretty-format: 25.5.0 semver: 6.3.1 + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.2 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + jest-util@25.5.0: dependencies: '@jest/types': 25.5.0 @@ -16537,6 +17497,15 @@ snapshots: leven: 3.1.0 pretty-format: 25.5.0 + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + jest-watch-typeahead@0.5.0: dependencies: ansi-escapes: 4.3.2 @@ -16556,6 +17525,17 @@ snapshots: jest-util: 25.5.0 string-length: 3.1.0 + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.9 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + jest-worker@24.9.0: dependencies: merge-stream: 2.0.0 @@ -16590,6 +17570,20 @@ snapshots: - supports-color - utf-8-validate + jest@29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1) + optionalDependencies: + node-notifier: 10.0.1 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jiti@1.21.6: {} jpjs@1.2.1: {} @@ -17388,6 +18382,10 @@ snapshots: pkg-types: 1.2.1 ufo: 1.5.4 + moo-color@1.0.3: + dependencies: + color-name: 1.1.4 + mri@1.2.0: {} ms@2.0.0: {} @@ -18126,6 +19124,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + pretty-hrtime@1.0.3: {} printj@1.1.2: {} @@ -18213,6 +19217,8 @@ snapshots: - supports-color - utf-8-validate + pure-rand@6.1.0: {} + qs@6.13.0: dependencies: side-channel: 1.0.6 @@ -18431,6 +19437,11 @@ snapshots: dependencies: resolve: 1.22.8 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + reflect.getprototypeof@1.0.7: dependencies: call-bind: 1.0.7 @@ -18578,6 +19589,8 @@ snapshots: resolve-url@0.2.1: {} + resolve.exports@2.0.3: {} + resolve@1.1.7: {} resolve@1.17.0: @@ -18944,6 +19957,11 @@ snapshots: atob: 2.1.2 decode-uri-component: 0.2.2 + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -18996,6 +20014,10 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stackframe@1.3.4: {} static-extend@0.1.2: @@ -19035,6 +20057,11 @@ snapshots: astral-regex: 1.0.0 strip-ansi: 5.2.0 + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + string-width@2.1.1: dependencies: is-fullwidth-code-point: 2.0.0 @@ -19137,6 +20164,10 @@ snapshots: strip-final-newline@3.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-indent@4.0.0: dependencies: min-indent: 1.0.1 @@ -19438,6 +20469,24 @@ snapshots: typescript: 3.9.10 yargs-parser: 18.1.3 + ts-jest@29.1.1(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.18.20)(jest@29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1))(typescript@5.7.2): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.7.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) + esbuild: 0.18.20 + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -19769,6 +20818,12 @@ snapshots: convert-source-map: 1.9.0 source-map: 0.7.4 + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -20012,6 +21067,8 @@ snapshots: y18n@4.0.3: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} @@ -20025,6 +21082,8 @@ snapshots: camelcase: 5.3.1 decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -20039,6 +21098,16 @@ snapshots: y18n: 4.0.3 yargs-parser: 18.1.3 + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 diff --git a/src/__tests__/backend/s3-generate-presigned-url.test.ts b/src/__tests__/backend/s3-generate-presigned-url.test.ts new file mode 100644 index 0000000..94bac0a --- /dev/null +++ b/src/__tests__/backend/s3-generate-presigned-url.test.ts @@ -0,0 +1,237 @@ +import { S3Client } from '@aws-sdk/client-s3' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import s3GeneratePresignedUrl from '../../backend/lib/aws/s3/s3-generate-presigned-url' +import s3UpdateCORS from '../../backend/lib/aws/s3/s3-update-cors' +import { + Provider, + UploadError, + UploadErrorType, +} from '../../shared/types/StorageSDK' + +// Mock external dependencies +jest.mock('@aws-sdk/client-s3', () => ({ + S3Client: jest.fn().mockImplementation(config => { + // Validate required config parameters + if ( + !config.credentials?.accessKeyId || + !config.credentials?.secretAccessKey + ) { + throw new Error('Missing required credentials') + } + if (!config.region) { + throw new Error('Missing required region') + } + return { + config, + } + }), + PutObjectCommand: jest.fn(), +})) +jest.mock('@aws-sdk/s3-request-presigner') +jest.mock('../../backend/lib/aws/s3/s3-update-cors') + +describe('s3GeneratePresignedUrl', () => { + // Test data setup + const mockFileParams = { + name: 'test.jpg', + type: 'image/jpeg', + size: 1024, + } + + const mockBucketName = 'test-bucket' + const mockS3Config = { + region: 'us-east-1', + credentials: { + accessKeyId: 'test-key', + secretAccessKey: 'test-secret', + }, + } + + const mockOrigin = 'http://localhost:3000' + const mockExpiresIn = 3600 + const mockProvider = Provider.AWS + + beforeEach(() => { + // Clear all mocks before each test + jest.clearAllMocks() + + // Setup default mock implementations + ;(getSignedUrl as jest.Mock).mockResolvedValue( + 'https://test-presigned-url.com', + ) + ;(s3UpdateCORS as jest.Mock).mockResolvedValue('CORS updated') + }) + + it('should generate a presigned URL successfully', async () => { + const result = await s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: mockS3Config, + origin: mockOrigin, + expiresIn: mockExpiresIn, + provider: mockProvider, + }) + + // Verify the response structure + expect(result).toEqual({ + key: expect.stringContaining('uploads/'), + publicUrl: expect.stringContaining('https://'), + uploadUrl: 'https://test-presigned-url.com', + expiresIn: mockExpiresIn, + }) + + // Verify S3Client was initialized correctly + expect(S3Client).toHaveBeenCalledWith(mockS3Config) + + // Verify CORS was updated + expect(s3UpdateCORS).toHaveBeenCalledWith( + mockOrigin, + mockBucketName, + mockS3Config, + mockProvider, + ) + }) + + it('should use default expiresIn when not provided', async () => { + const result = await s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: mockS3Config, + origin: mockOrigin, + provider: mockProvider, + }) + + expect(result.expiresIn).toBe(3600) // Default value + }) + + it('should handle file validation errors', async () => { + const invalidFileParams = { + name: '', // Invalid empty name + type: 'image/jpeg', + size: 1024, + } + + await expect( + s3GeneratePresignedUrl({ + fileParams: invalidFileParams, + bucketName: mockBucketName, + s3ClientConfig: mockS3Config, + origin: mockOrigin, + provider: mockProvider, + }), + ).rejects.toThrow(UploadError) + }) + + it('should handle CORS configuration errors', async () => { + // Mock CORS update to fail + ;(s3UpdateCORS as jest.Mock).mockRejectedValue( + new UploadError( + 'CORS update failed', + UploadErrorType.CORS_CONFIG_ERROR, + ), + ) + + await expect( + s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: mockS3Config, + origin: mockOrigin, + provider: mockProvider, + }), + ).rejects.toThrow(UploadError) + }) + + it('should handle presigned URL generation errors', async () => { + // Mock getSignedUrl to fail + ;(getSignedUrl as jest.Mock).mockRejectedValue( + new Error('Failed to generate URL'), + ) + + await expect( + s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: mockS3Config, + origin: mockOrigin, + provider: mockProvider, + }), + ).rejects.toThrow(UploadError) + }) + + it('should generate correct key format', async () => { + const result = await s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: mockS3Config, + origin: mockOrigin, + provider: mockProvider, + }) + + // Verify key format: uploads/timestamp-filename + expect(result.key).toMatch(/^uploads\/\d+-test\.jpg$/) + }) + + it('should throw error when s3ClientConfig is missing required credentials', async () => { + const invalidConfig = { + region: 'us-east-1', + // Missing credentials + } + + await expect( + s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: invalidConfig, + origin: mockOrigin, + provider: mockProvider, + }), + ).rejects.toThrow(UploadError) + + expect(S3Client).toHaveBeenCalledWith(invalidConfig) + }) + + it('should throw error when region is missing from s3ClientConfig', async () => { + const invalidConfig = { + credentials: { + accessKeyId: 'test-key', + secretAccessKey: 'test-secret', + }, + // Missing region + } + + await expect( + s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: invalidConfig, + origin: mockOrigin, + provider: mockProvider, + }), + ).rejects.toThrow(UploadError) + + expect(S3Client).toHaveBeenCalledWith(invalidConfig) + }) + + it('should throw error when credentials are incomplete', async () => { + const invalidConfig = { + region: 'us-east-1', + credentials: { + accessKeyId: 'test-key', + // Missing secretAccessKey + }, + } + + await expect( + s3GeneratePresignedUrl({ + fileParams: mockFileParams, + bucketName: mockBucketName, + s3ClientConfig: invalidConfig as any, + origin: mockOrigin, + provider: mockProvider, + }), + ).rejects.toThrow(UploadError) + + expect(S3Client).toHaveBeenCalledWith(invalidConfig) + }) +}) diff --git a/src/__tests__/frontend/MetaVersion.test.tsx b/src/__tests__/frontend/MetaVersion.test.tsx new file mode 100644 index 0000000..c1b51a4 --- /dev/null +++ b/src/__tests__/frontend/MetaVersion.test.tsx @@ -0,0 +1,84 @@ +import { render, screen } from '@testing-library/react' +import * as React from 'react' +import MetaVersion from '../../frontend/components/MetaVersion' +import { LIB_VERSION } from '../../version' + +describe('MetaVersion', () => { + it('renders basic version info', () => { + render() + + // Check version number + expect(screen.getByText(`Upup v${LIB_VERSION}`)).toBeInTheDocument() + + // Check file size limit info + expect(screen.getByText(/up to 10MB/i)).toBeInTheDocument() + + // Check file count limit + expect(screen.getByText(/Max 5 files/i)).toBeInTheDocument() + + // Check powered by text + expect(screen.getByText(/Powered by uNotes/i)).toBeInTheDocument() + }) + + it('renders custom message when provided', () => { + const customMessage = 'Test custom message' + render( + , + ) + + expect(screen.getByText(new RegExp(customMessage))).toBeInTheDocument() + }) + + it('handles undefined limit correctly', () => { + render( + , + ) + + // Should not show max files text + expect(screen.queryByText(/Max.*files/i)).not.toBeInTheDocument() + }) + + it('handles different file size units', () => { + const units = ['KB', 'MB', 'GB', 'TB'] as const + units.forEach(unit => { + const { unmount } = render( + , + ) + expect(screen.getByText(`up to 10${unit}.`)).toBeInTheDocument() + unmount() + }) + }) + + it('combines all optional properties correctly', () => { + render( + , + ) + + const text = screen.getByText(/Custom test, Max 5 files, up to 10MB./i) + expect(text).toBeInTheDocument() + }) + + it('handles edge case file sizes', () => { + render( + , + ) + expect(screen.getByText(/up to 0.5MB/i)).toBeInTheDocument() + }) +}) diff --git a/src/__tests__/shared/checkFileType.test.ts b/src/__tests__/shared/checkFileType.test.ts new file mode 100644 index 0000000..b016ea3 --- /dev/null +++ b/src/__tests__/shared/checkFileType.test.ts @@ -0,0 +1,60 @@ +import checkFileType from '../../shared/lib/checkFileType' + +const JPEGImage = { + type: 'image/jpeg', +} as File +const PNGImage = { + type: 'image/png', +} as File +const GIFImage = { + type: 'image/gif', +} as File +const PDFFile = { + type: 'application/pdf', +} as File + +describe('checkFileType', () => { + it('accepts any file when accept is *', () => { + expect(checkFileType('*', JPEGImage)).toBe(true) + expect(checkFileType('*', PDFFile)).toBe(true) + }) + + it('handles mime type wildcards', () => { + expect(checkFileType('image/*', JPEGImage)).toBe(true) + expect(checkFileType('image/*', PNGImage)).toBe(true) + expect(checkFileType('image/*', PDFFile)).toBe(false) + }) + + it('handles specific mime types', () => { + expect(checkFileType('image/jpeg', JPEGImage)).toBe(true) + expect(checkFileType('image/jpeg', PNGImage)).toBe(false) + }) + + it('handles multiple accept values', () => { + expect(checkFileType('image/jpeg,image/png', JPEGImage)).toBe(true) + expect(checkFileType('image/jpeg,image/png', PNGImage)).toBe(true) + expect(checkFileType('image/jpeg,image/png', GIFImage)).toBe(false) + }) + + it('handles empty or invalid inputs', () => { + expect(checkFileType('', JPEGImage)).toBe(false) // Empty accept string + expect(checkFileType('*', {} as File)).toBe(false) // Empty file type + }) + + it('handles whitespace in accept string', () => { + expect(checkFileType(' image/jpeg , image/png ', JPEGImage)).toBe(true) + expect(checkFileType('image/jpeg, image/png', PNGImage)).toBe(true) + }) + + it('handles case sensitivity', () => { + expect(checkFileType('IMAGE/JPEG', JPEGImage)).toBe(true) + expect( + checkFileType('image/jpeg', { type: 'IMAGE/JPEG' } as File), + ).toBe(true) + }) + + it('handles partial mime types correctly', () => { + expect(checkFileType('image/*', { type: 'image/' } as File)).toBe(false) // Invalid file type + expect(checkFileType('image/*', { type: 'image' } as File)).toBe(false) // Invalid file type + }) +}) diff --git a/src/lib/storage/provider.ts b/src/lib/storage/provider.ts deleted file mode 100644 index 9b31bd8..0000000 --- a/src/lib/storage/provider.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { - PresignedUrlResponse, - Provider, - StorageConfig, - StorageSDK, - UploadError, - UploadErrorType, - UploadOptions, - UploadResult, -} from '../../types/StorageSDK' - -type UploadConfig = StorageConfig & { - constraints?: { - multiple: boolean - accept: string - maxFileSize?: number - } -} - -export class ProviderSDK implements StorageSDK { - private config: UploadConfig - private uploadCount = 0 - - constructor(config: UploadConfig) { - this.config = config - this.validateConfig() - this.uploadCount = 0 - } - - async upload( - file: File, - options = {} as UploadOptions, - ): Promise { - try { - // Check if multiple files are allowed - if (!this.config.constraints?.multiple && this.uploadCount > 0) - throw new Error('Multiple file uploads are not allowed') - - options.onFileUploadStart?.(file) - // Get presigned URL from backend - const presignedData = await this.getPresignedUrl(file) - - // Increment upload count - this.uploadCount++ - - // Upload using presigned URL - const uploadResponse = await this.uploadWithProgress( - presignedData.uploadUrl, - file, - options, - ) - - if (!uploadResponse.ok) - throw new Error( - `Upload failed with status ${uploadResponse.status}`, - ) - - options.onFileUploadComplete?.(file, presignedData.key) - - return { - key: presignedData.key, - httpStatus: uploadResponse.status, - } - } catch (error) { - console.error('Upload error:', error) - - options.onFileUploadFail?.(file, error as Error) - throw this.handleError(error) - } - } - - private async getPresignedUrl(file: File): Promise { - try { - const requestBody = { - name: file.name, - type: file.type, - size: file.size, - provider: this.config.provider, - ...this.config.constraints, - } - - const response = await fetch(this.config.tokenEndpoint, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody), - }) - - if (!response.ok) { - const errorData = await response.json() - console.error('Presigned URL request failed:', { - status: response.status, - error: errorData, - }) - throw new Error(errorData.details || 'Failed to get upload URL') - } - - const data = await response.json() - - return data - } catch (error) { - console.error('Error getting presigned URL:', error) - throw error - } - } - - private async uploadWithProgress( - url: string, - file: File, - { onFileUploadProgress, onTotalUploadProgress }: UploadOptions, - ): Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest() - - xhr.upload.addEventListener('progress', event => { - if (event.lengthComputable && onFileUploadProgress) { - onFileUploadProgress(file, { - loaded: event.loaded, - total: event.total, - percentage: (event.loaded / event.total) * 100, - }) - } - if (event.lengthComputable && onTotalUploadProgress) { - onTotalUploadProgress(this.uploadCount) - } - }) - - xhr.addEventListener('load', () => { - const isValidStatus = xhr.status >= 200 && xhr.status < 300 - - if (isValidStatus) { - resolve( - new Response(xhr.response, { - status: xhr.status, - headers: - this.config.provider !== Provider.Azure - ? new Headers({ - ETag: - xhr.getResponseHeader('ETag') || - '', - }) - : undefined, - }), - ) - } else { - reject( - new Error( - `Upload failed with status ${xhr.status}: ${xhr.responseText}`, - ), - ) - } - }) - - xhr.addEventListener('error', () => - reject(new Error('Network error during upload')), - ) - - xhr.open('PUT', url) - if (this.config.provider === Provider.Azure) - xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob') - xhr.send(file) - }) - } - - validateConfig(): boolean { - const required = ['tokenEndpoint'] as const - const missing = required.filter(key => !this.config[key]) - - if (missing.length > 0) - throw new Error( - `Missing required configuration: ${missing.join(', ')}`, - ) - - return true - } - - private handleError(error: unknown): never { - if (error instanceof UploadError) throw error - - const err = error as Error - if (err.message.includes('unauthorized')) - throw new UploadError( - 'Unauthorized access to Provider', - UploadErrorType.PERMISSION_ERROR, - ) - - if (err.message.includes('expired')) - throw new UploadError( - 'Presigned URL has expired', - UploadErrorType.EXPIRED_URL, - true, - ) - - throw new UploadError( - `Upload failed: ${err.message}`, - UploadErrorType.UNKNOWN_UPLOAD_ERROR, - true, - ) - } -} diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 0000000..105f919 --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1,37 @@ +import '@testing-library/jest-dom' +import 'jest-canvas-mock' + +// Declare global window with google property +declare global { + var google: any // Use var instead of redefining window +} + +// Mock google global +global.google = { + accounts: { + oauth2: { + initTokenClient: jest.fn().mockReturnValue({ + requestAccessToken: jest.fn(), + }), + }, + }, +} + +// Mock framer-motion +jest.mock('framer-motion', () => ({ + motion: { + div: jest.fn().mockImplementation((props: any) => { + const { children, ...rest } = props + return { + type: 'div', + props: { ...rest, children }, + } + }), + }, + AnimatePresence: jest.fn().mockImplementation(({ children }) => children), +})) + +// Mock version +jest.mock('./version.ts', () => ({ + LIB_VERSION: '2.4.0', +})) diff --git a/src/shared/lib/checkFileType.ts b/src/shared/lib/checkFileType.ts index ad32668..4a56414 100644 --- a/src/shared/lib/checkFileType.ts +++ b/src/shared/lib/checkFileType.ts @@ -5,19 +5,33 @@ export default function checkFileType( file: File, onFileTypeMismatch?: BaseConfigs['onFileTypeMismatch'], ) { - const fileType = file.type - const acceptedTypes = accept.split(',').map(t => t.trim()) - const isValidType = - acceptedTypes.includes('*') || - acceptedTypes.some(type => { - if (type.includes('/*')) { - const [mainType] = type.split('/') - return fileType.startsWith(mainType) - } - return type === fileType - }) + try { + const fileType = file.type - if (!isValidType && onFileTypeMismatch) onFileTypeMismatch(file, accept) + // Return false for invalid inputs + if (!accept || !fileType) throw new Error('Invalid inputs') - return isValidType + // Validate fileType has proper MIME format (type/subtype) + const [type, subtype] = fileType.split('/') + if (!type || !subtype) + throw new Error('Invalid MIME format (type/subtype)') + + const acceptedTypes = accept.split(',').map(t => t.trim()) + const isValidType = + acceptedTypes.includes('*') || + acceptedTypes.some(type => { + if (type.includes('/*')) { + const [mainType] = type.split('/') + return fileType.startsWith(mainType) + } + return type.toLowerCase() === fileType.toLowerCase() + }) + + if (!isValidType) throw new Error('Invalid type') + + return isValidType + } catch (error) { + onFileTypeMismatch?.(file, accept) + return false + } } diff --git a/todo.md b/todo.md deleted file mode 100644 index c640523..0000000 --- a/todo.md +++ /dev/null @@ -1,19 +0,0 @@ -# todo - -## CURRENT - -- useProgress - -- Handle chunked uploads for large files -- Add support for resumeable uploads -- Add retry logic -- Improved error handling - -## IMPROVEMENTS: Later - -- Component props for proper error and info updates on ongoing processes: onError, onUploading, etc - -## DOCUMENTATION - -- IDE documentation for the component -- unit tests