From 8310014a2a3323dd92e166093a0761508f19868f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9ban?= Date: Thu, 18 Jul 2024 09:41:42 +0200 Subject: [PATCH] test: add unit and e2e (#13) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 47 +- .gitignore | 5 +- package.json | 5 + playground/auto-outbound-tracking.html | 27 + playground/file-downloads-tracking.html | 28 + playground/src/auto-outbound-tracking.ts | 14 + playground/src/file-downloads-tracking.ts | 16 + playwright.config.ts | 80 ++ pnpm-lock.yaml | 1045 +++++++++++++++++++- src/event.ts | 2 +- src/extensions/utils.ts | 4 +- src/plausible.ts | 8 +- test/e2e/auto-outbound-tracking.spec.ts | 73 ++ test/e2e/file-downloads-tracking.spec.ts | 85 ++ test/e2e/utils.ts | 9 + test/event.dom.spec.ts | 132 +++ test/event.spec.ts | 62 ++ test/extensions/auto-pageviews.dom.spec.ts | 155 +++ test/extensions/utils.dom.spec.ts | 121 +++ test/payload.spec.ts | 62 ++ test/plausible.dom.spec.ts | 349 +++++++ vitest.config.ts | 21 + 22 files changed, 2313 insertions(+), 37 deletions(-) create mode 100644 playground/auto-outbound-tracking.html create mode 100644 playground/file-downloads-tracking.html create mode 100644 playground/src/auto-outbound-tracking.ts create mode 100644 playground/src/file-downloads-tracking.ts create mode 100644 playwright.config.ts create mode 100644 test/e2e/auto-outbound-tracking.spec.ts create mode 100644 test/e2e/file-downloads-tracking.spec.ts create mode 100644 test/e2e/utils.ts create mode 100644 test/event.dom.spec.ts create mode 100644 test/event.spec.ts create mode 100644 test/extensions/auto-pageviews.dom.spec.ts create mode 100644 test/extensions/utils.dom.spec.ts create mode 100644 test/payload.spec.ts create mode 100644 test/plausible.dom.spec.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0a412a..1678c1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,23 +27,36 @@ jobs: - name: Lint files run: pnpm run lint - # test: - # name: Test - # runs-on: ubuntu-latest - - # steps: - # - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - # - run: corepack enable - # - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 - # with: - # node-version: 20.5 - # cache: pnpm - - # - name: Install dependencies - # run: pnpm install - - # - name: Test files - # run: pnpm run test + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - run: corepack enable + - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 + with: + node-version: 20.5 + cache: pnpm + + - name: Install dependencies + run: pnpm install + + - name: Unit tests + run: pnpm run test --coverage + + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + + - name: Playwright tests + run: pnpm exec playwright test + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 build: name: Build diff --git a/.gitignore b/.gitignore index 465495d..c9c3c90 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ .nyc_output build node_modules -test src/**.js coverage *.log dist +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package.json b/package.json index a62b9c9..8bd166f 100644 --- a/package.json +++ b/package.json @@ -50,14 +50,19 @@ "lint:fix": "eslint . --fix", "release": "bumpp", "test": "vitest", + "test:e2e": "playwright test", + "dev": "pnpm run --filter playground dev", "prepublishOnly": "pnpm run build" }, "devDependencies": { "@antfu/eslint-config": "^2.16.0", + "@playwright/test": "^1.45.1", "@types/node": "^18.19.31", + "@vitest/coverage-v8": "^1.5.2", "bumpp": "^9.4.0", "eslint": "^8.57.0", "happy-dom": "^13.10.1", + "jsdom": "^24.1.0", "typescript": "^5.4.5", "unbuild": "^2.0.0", "vitest": "^1.5.2" diff --git a/playground/auto-outbound-tracking.html b/playground/auto-outbound-tracking.html new file mode 100644 index 0000000..10054a8 --- /dev/null +++ b/playground/auto-outbound-tracking.html @@ -0,0 +1,27 @@ + + + + + + + Auto Outbound Tracking + + + + + + External Link + + Internal Link + + External Link (noopener noreferrer) + + External Link (_blank) + + + + + + diff --git a/playground/file-downloads-tracking.html b/playground/file-downloads-tracking.html new file mode 100644 index 0000000..f5daa60 --- /dev/null +++ b/playground/file-downloads-tracking.html @@ -0,0 +1,28 @@ + + + + + + + File Downloads Tracking + + + + + + Download SVG + Download SVG without download attribute + Download SVG targeting blank + Download SVG with full URL + + Download PDF + + Simple link + + + + + + diff --git a/playground/src/auto-outbound-tracking.ts b/playground/src/auto-outbound-tracking.ts new file mode 100644 index 0000000..b67cfa8 --- /dev/null +++ b/playground/src/auto-outbound-tracking.ts @@ -0,0 +1,14 @@ +import { createPlausibleTracker } from '@barbapapazes/plausible-tracker' +import { useAutoOutboundTracking } from '@barbapapazes/plausible-tracker/extensions/auto-outbound-tracking' + +const plausible = createPlausibleTracker({ + ignoredHostnames: [], +}) + +const autoOutboundTracking = useAutoOutboundTracking(plausible) + +autoOutboundTracking.install() + +document.getElementById('cleanup')!.addEventListener('click', () => { + autoOutboundTracking.cleanup() +}) diff --git a/playground/src/file-downloads-tracking.ts b/playground/src/file-downloads-tracking.ts new file mode 100644 index 0000000..4297e55 --- /dev/null +++ b/playground/src/file-downloads-tracking.ts @@ -0,0 +1,16 @@ +import { createPlausibleTracker } from '@barbapapazes/plausible-tracker' +import { useAutoFileDownloadsTracking } from '@barbapapazes/plausible-tracker/extensions/file-downloads-tracking' + +const plausible = createPlausibleTracker({ + ignoredHostnames: [], +}) + +const fileDownloadsTracking = useAutoFileDownloadsTracking(plausible, { + fileTypes: ['svg'], +}) + +fileDownloadsTracking.install() + +document.getElementById('cleanup')!.addEventListener('click', () => { + fileDownloadsTracking.cleanup() +}) diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..1e0a98b --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,80 @@ +import process from 'node:process' +import { defineConfig, devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test/e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + timeout: 5000, + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:5173', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9eafe55..b230fa6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,16 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^2.16.0 - version: 2.16.0(@vue/compiler-sfc@3.4.15)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(happy-dom@13.10.1)) + version: 2.16.0(@vue/compiler-sfc@3.4.15)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0)) + '@playwright/test': + specifier: ^1.45.1 + version: 1.45.1 '@types/node': specifier: ^18.19.31 version: 18.19.31 + '@vitest/coverage-v8': + specifier: ^1.5.2 + version: 1.5.2(vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0)) bumpp: specifier: ^9.4.0 version: 9.4.0 @@ -23,6 +29,9 @@ importers: happy-dom: specifier: ^13.10.1 version: 13.10.1 + jsdom: + specifier: ^24.1.0 + version: 24.1.0 typescript: specifier: ^5.4.5 version: 5.4.5 @@ -31,7 +40,7 @@ importers: version: 2.0.0(typescript@5.4.5) vitest: specifier: ^1.5.2 - version: 1.5.2(@types/node@18.19.31)(happy-dom@13.10.1) + version: 1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0) playground: dependencies: @@ -56,6 +65,10 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@antfu/eslint-config@2.16.0': resolution: {integrity: sha512-K56svKb2tRtJcONE548tyvZJ8ZQPJiHp+20Aez1Dy77nVKQbPiCDFYGHxuk3fn1+NSL6PUxbVm/IEhqIWtZFCQ==} hasBin: true @@ -156,10 +169,18 @@ packages: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.22.20': resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.23.5': resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -177,6 +198,15 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.24.8': + resolution: {integrity: sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.24.8': + resolution: {integrity: sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==} + engines: {node: '>=6.9.0'} + '@babel/standalone@7.23.10': resolution: {integrity: sha512-xqWviI/pt1Zb/d+6ilWa5IDL2mkDzsBnlHbreqnfyP3/QB/ofQ1bNVcHj8YQX154Rf/xZKR6y0s1ydVF3nAS8g==} engines: {node: '>=6.9.0'} @@ -193,9 +223,22 @@ packages: resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.24.9': + resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==} + engines: {node: '>=6.9.0'} + '@barbapapazes/plausible-tracker@0.4.0': resolution: {integrity: sha512-4hhXK62ORb4feJfjnIILXRnb6xfejHM6yvGCid6MwNdKiQyj6YLW0M779zXLH5IBS4mQObmrNul2HUleeavSKw==} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@bundled-es-modules/cookie@2.0.0': + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + '@clack/core@0.3.4': resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} @@ -513,6 +556,26 @@ packages: '@humanwhocodes/object-schema@2.0.2': resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + '@inquirer/confirm@3.1.14': + resolution: {integrity: sha512-nbLSX37b2dGPtKWL3rPuR/5hOuD30S+pqJ/MuFiUEgN6GiMs8UMxiurKAMDzKt6C95ltjupa8zH6+3csXNHWpA==} + engines: {node: '>=18'} + + '@inquirer/core@9.0.2': + resolution: {integrity: sha512-nguvH3TZar3ACwbytZrraRTzGqyxJfYJwv+ZwqZNatAosdWQMP1GV8zvmkNlBe2JeZSaw0WYBHZk52pDpWC9qA==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.3': + resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==} + engines: {node: '>=18'} + + '@inquirer/type@1.4.0': + resolution: {integrity: sha512-AjOqykVyjdJQvtfkNDGUyMYGF8xN50VUxftCQWsOyIo4DFRLr6VQhW0VItGI1JIyQGCGgIpKa7hMMwNhZb4OIw==} + engines: {node: '>=18'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -521,6 +584,10 @@ packages: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + '@jridgewell/resolve-uri@3.1.1': resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} @@ -529,16 +596,31 @@ packages: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} '@jridgewell/trace-mapping@0.3.22': resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jsdevtools/ez-spawn@3.0.4': resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==} engines: {node: '>=10'} + '@mswjs/cookies@1.1.1': + resolution: {integrity: sha512-W68qOHEjx1iD+4VjQudlx26CPIoxmIAtK4ZCexU0/UJBG6jYhcuyzKJx+Iw8uhBIGd9eba64XgWVgo20it1qwA==} + engines: {node: '>=18'} + + '@mswjs/interceptors@0.29.1': + resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -551,6 +633,23 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@playwright/test@1.45.1': + resolution: {integrity: sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==} + engines: {node: '>=18'} + hasBin: true + + '@polka/url@1.0.0-next.25': + resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@rollup/plugin-alias@5.1.0': resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} engines: {node: '>=14.0.0'} @@ -717,10 +816,26 @@ packages: peerDependencies: eslint: '>=8.40.0' + '@testing-library/dom@10.3.2': + resolution: {integrity: sha512-0bxIdP9mmPiOJ6wHLj8bdJRq+51oddObeCGdEf6PNEhYd93ZYAN+lPRnEOVFtheVwDM7+p+tza3LAQgp0PTudg==} + engines: {node: '>=18'} + + '@testing-library/user-event@14.5.2': + resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} @@ -733,9 +848,15 @@ packages: '@types/mdast@3.0.15': resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/node@18.19.31': resolution: {integrity: sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==} + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -748,9 +869,15 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/unist@2.0.10': resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@typescript-eslint/eslint-plugin@7.7.1': resolution: {integrity: sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==} engines: {node: ^18.18.0 || >=20.0.0} @@ -866,9 +993,32 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vitest/browser@2.0.3': + resolution: {integrity: sha512-PQQ89fRaFVm/ja3x92BxAXUIxdxSSuQqu9ijR1rLT8FYCBU+BTzZ7razwLmzMS8AMMaKOFoRXbLg7A2mtSzANg==} + peerDependencies: + playwright: '*' + safaridriver: '*' + vitest: 2.0.3 + webdriverio: '*' + peerDependenciesMeta: + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + + '@vitest/coverage-v8@1.5.2': + resolution: {integrity: sha512-QJqxRnbCwNtbbegK9E93rBmhN3dbfG1bC/o52Bqr0zGCYhQzwgwvrJBG7Q8vw3zilX6Ryy6oa/mkZku2lLJx1Q==} + peerDependencies: + vitest: 1.5.2 + '@vitest/expect@1.5.2': resolution: {integrity: sha512-rf7MTD1WCoDlN3FfYJ9Llfp0PbdtOMZ3FIF0AVkDnKbp3oiMW1c8AmvRZBcqbAhDUAvF52e9zx4WQM1r3oraVA==} + '@vitest/pretty-format@2.0.3': + resolution: {integrity: sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==} + '@vitest/runner@1.5.2': resolution: {integrity: sha512-7IJ7sJhMZrqx7HIEpv3WrMYcq8ZNz9L6alo81Y6f8hV5mIE6yVZsFoivLZmr0D777klm1ReqonE9LyChdcmw6g==} @@ -881,6 +1031,9 @@ packages: '@vitest/utils@1.5.2': resolution: {integrity: sha512-sWOmyofuXLJ85VvXNsroZur7mOJGiQeM0JN3/0D1uU8U9bGFM69X1iqHaRXl6R8BwaLY6yPCogP257zxTzkUdA==} + '@vitest/utils@2.0.3': + resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==} + '@vue/compiler-core@3.4.15': resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==} @@ -910,9 +1063,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -940,6 +1101,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -947,6 +1111,9 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.17: resolution: {integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==} engines: {node: ^10 || ^12 || >=14} @@ -1055,6 +1222,14 @@ packages: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1075,6 +1250,10 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -1099,6 +1278,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + core-js-compat@3.35.1: resolution: {integrity: sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==} @@ -1154,6 +1337,14 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1171,6 +1362,18 @@ packages: supports-color: optional: true + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} @@ -1185,6 +1388,14 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destr@2.0.2: resolution: {integrity: sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg==} @@ -1200,6 +1411,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1507,6 +1721,10 @@ packages: flatted@3.2.9: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1521,6 +1739,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1595,6 +1818,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.9.0: + resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + happy-dom@13.10.1: resolution: {integrity: sha512-9GZLEFvQL5EgfJX2zcBgu1nsPUn98JF/EiJnSfQbdxI6YEQGqpd09lXXxOmYonRBIEFz9JlGCOiPflDzgS1p8w==} engines: {node: '>=16.0.0'} @@ -1611,16 +1838,38 @@ packages: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -1684,6 +1933,9 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1692,6 +1944,9 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -1702,6 +1957,22 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true @@ -1712,6 +1983,9 @@ packages: js-tokens@8.0.3: resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1720,6 +1994,15 @@ packages: resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} engines: {node: '>=12.0.0'} + jsdom@24.1.0: + resolution: {integrity: sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true @@ -1810,6 +2093,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1817,10 +2103,24 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.6: resolution: {integrity: sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==} engines: {node: '>=12'} + magicast@0.3.4: + resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + mdast-util-from-markdown@0.8.5: resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} @@ -1847,6 +2147,14 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 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'} + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -1909,12 +2217,30 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.3.1: + resolution: {integrity: sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.7.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1950,6 +2276,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nwsapi@2.2.12: + resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} + nypm@0.3.6: resolution: {integrity: sha512-2CATJh3pd6CyNfU5VZM7qSwFu0ieyabkEdnogE30Obn1czrmOYiZ8DOZLe1yBdLKWoyD3Mcy2maUs+0MR3yVjQ==} engines: {node: ^14.16.0 || >=16.10.0} @@ -1969,6 +2298,9 @@ packages: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2016,6 +2348,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2039,6 +2374,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-to-regexp@6.2.2: + resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2066,6 +2404,16 @@ packages: pkg-types@1.0.3: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + playwright-core@1.45.1: + resolution: {integrity: sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.45.1: + resolution: {integrity: sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==} + engines: {node: '>=18'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -2261,6 +2609,10 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + pretty-format@27.5.1: + 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} @@ -2269,16 +2621,25 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} rc9@2.1.1: resolution: {integrity: sha512-lNeOl38Ws0eNxpO3+wD1I9rkHGQyj1NU1jlzv4go2CtEnEQEUfqnIvZG7W+bC/aXdJ27n5x/yUjb6RoT9tko+Q==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} @@ -2294,6 +2655,9 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true @@ -2306,6 +2670,9 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2342,9 +2709,22 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scule@1.2.0: resolution: {integrity: sha512-CRCmi5zHQnSoeCik9565PONMg0kfkvYmcSqrbOJY4txFfy1wvVULV4FDaiXhUblUgahdqz3F2NwHZ8i4eBTwUw==} @@ -2381,6 +2761,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2418,9 +2802,16 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2448,6 +2839,9 @@ packages: strip-literal@2.0.0: resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + stylehacks@6.0.2: resolution: {integrity: sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==} engines: {node: ^14 || ^16 || >=18.0} @@ -2471,6 +2865,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.6.2: resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==} engines: {node: '>=12.20'} @@ -2483,6 +2880,10 @@ packages: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2493,6 +2894,10 @@ packages: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.0: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} @@ -2509,6 +2914,18 @@ packages: resolution: {integrity: sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + ts-api-utils@1.0.3: resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} engines: {node: '>=16.13.0'} @@ -2536,6 +2953,10 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -2544,6 +2965,10 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + type-fest@4.21.0: + resolution: {integrity: sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==} + engines: {node: '>=16'} + typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} @@ -2571,6 +2996,10 @@ packages: unist-util-stringify-position@2.0.3: resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -2588,6 +3017,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -2658,14 +3090,30 @@ packages: peerDependencies: eslint: '>=6.0.0' + 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@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2676,6 +3124,10 @@ packages: engines: {node: '>=8'} hasBin: true + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2683,10 +3135,29 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + 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@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + 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==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2721,6 +3192,10 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -2730,7 +3205,12 @@ snapshots: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - '@antfu/eslint-config@2.16.0(@vue/compiler-sfc@3.4.15)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(happy-dom@13.10.1))': + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@antfu/eslint-config@2.16.0(@vue/compiler-sfc@3.4.15)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0))': dependencies: '@antfu/install-pkg': 0.3.3 '@clack/prompts': 0.7.0 @@ -2754,7 +3234,7 @@ snapshots: eslint-plugin-toml: 0.11.0(eslint@8.57.0) eslint-plugin-unicorn: 52.0.0(eslint@8.57.0) eslint-plugin-unused-imports: 3.1.0(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) - eslint-plugin-vitest: 0.5.4(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(happy-dom@13.10.1)) + eslint-plugin-vitest: 0.5.4(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0)) eslint-plugin-vue: 9.25.0(eslint@8.57.0) eslint-plugin-yml: 1.14.0(eslint@8.57.0) eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.15)(eslint@8.57.0) @@ -2854,8 +3334,12 @@ snapshots: '@babel/helper-string-parser@7.23.4': {} + '@babel/helper-string-parser@7.24.8': {} + '@babel/helper-validator-identifier@7.22.20': {} + '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-option@7.23.5': {} '@babel/helpers@7.23.9': @@ -2868,7 +3352,7 @@ snapshots: '@babel/highlight@7.23.4': dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 @@ -2876,6 +3360,15 @@ snapshots: dependencies: '@babel/types': 7.23.9 + '@babel/parser@7.24.8': + dependencies: + '@babel/types': 7.24.9 + + '@babel/runtime@7.24.8': + dependencies: + regenerator-runtime: 0.14.1 + optional: true + '@babel/standalone@7.23.10': {} '@babel/template@7.23.9': @@ -2905,8 +3398,26 @@ snapshots: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + '@babel/types@7.24.9': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + '@barbapapazes/plausible-tracker@0.4.0': {} + '@bcoe/v8-coverage@0.2.3': {} + + '@bundled-es-modules/cookie@2.0.0': + dependencies: + cookie: 0.5.0 + optional: true + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + optional: true + '@clack/core@0.3.4': dependencies: picocolors: 1.0.0 @@ -3097,6 +3608,39 @@ snapshots: '@humanwhocodes/object-schema@2.0.2': {} + '@inquirer/confirm@3.1.14': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + optional: true + + '@inquirer/core@9.0.2': + dependencies: + '@inquirer/figures': 1.0.3 + '@inquirer/type': 1.4.0 + '@types/mute-stream': 0.0.4 + '@types/node': 20.14.10 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + optional: true + + '@inquirer/figures@1.0.3': + optional: true + + '@inquirer/type@1.4.0': + dependencies: + mute-stream: 1.0.0 + optional: true + + '@istanbuljs/schema@0.1.3': {} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -3107,10 +3651,18 @@ snapshots: '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.1': {} '@jridgewell/set-array@1.1.2': {} + '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.4.15': {} '@jridgewell/trace-mapping@0.3.22': @@ -3118,6 +3670,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jsdevtools/ez-spawn@3.0.4': dependencies: call-me-maybe: 1.0.2 @@ -3125,6 +3682,19 @@ snapshots: string-argv: 0.3.2 type-detect: 4.0.8 + '@mswjs/cookies@1.1.1': + optional: true + + '@mswjs/interceptors@0.29.1': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3137,6 +3707,25 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.0 + '@open-draft/deferred-promise@2.2.0': + optional: true + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + optional: true + + '@open-draft/until@2.1.0': + optional: true + + '@playwright/test@1.45.1': + dependencies: + playwright: 1.45.1 + + '@polka/url@1.0.0-next.25': + optional: true + '@rollup/plugin-alias@5.1.0(rollup@3.29.4)': dependencies: slash: 4.0.0 @@ -3284,8 +3873,31 @@ snapshots: - supports-color - typescript + '@testing-library/dom@10.3.2': + dependencies: + '@babel/code-frame': 7.23.5 + '@babel/runtime': 7.24.8 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + optional: true + + '@testing-library/user-event@14.5.2(@testing-library/dom@10.3.2)': + dependencies: + '@testing-library/dom': 10.3.2 + optional: true + '@trysound/sax@0.2.0': {} + '@types/aria-query@5.0.4': + optional: true + + '@types/cookie@0.6.0': + optional: true + '@types/eslint@8.56.10': dependencies: '@types/estree': 1.0.5 @@ -3299,10 +3911,20 @@ snapshots: dependencies: '@types/unist': 2.0.10 + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 18.19.31 + optional: true + '@types/node@18.19.31': dependencies: undici-types: 5.26.5 + '@types/node@20.14.10': + dependencies: + undici-types: 5.26.5 + optional: true + '@types/normalize-package-data@2.4.4': {} '@types/resolve@1.20.2': {} @@ -3311,8 +3933,14 @@ snapshots: '@types/semver@7.5.8': {} + '@types/statuses@2.0.5': + optional: true + '@types/unist@2.0.10': {} + '@types/wrap-ansi@3.0.0': + optional: true + '@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 @@ -3398,7 +4026,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -3483,12 +4111,54 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vitest/browser@2.0.3(playwright@1.45.1)(typescript@5.4.5)(vitest@1.5.2)': + dependencies: + '@testing-library/dom': 10.3.2 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.3.2) + '@vitest/utils': 2.0.3 + magic-string: 0.30.10 + msw: 2.3.1(typescript@5.4.5) + sirv: 2.0.4 + vitest: 1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0) + ws: 8.18.0 + optionalDependencies: + playwright: 1.45.1 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + optional: true + + '@vitest/coverage-v8@1.5.2(vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.10 + magicast: 0.3.4 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 6.0.0 + vitest: 1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0) + transitivePeerDependencies: + - supports-color + '@vitest/expect@1.5.2': dependencies: '@vitest/spy': 1.5.2 '@vitest/utils': 1.5.2 chai: 4.4.1 + '@vitest/pretty-format@2.0.3': + dependencies: + tinyrainbow: 1.2.0 + optional: true + '@vitest/runner@1.5.2': dependencies: '@vitest/utils': 1.5.2 @@ -3512,9 +4182,17 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 + '@vitest/utils@2.0.3': + dependencies: + '@vitest/pretty-format': 2.0.3 + estree-walker: 3.0.3 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + optional: true + '@vue/compiler-core@3.4.15': dependencies: - '@babel/parser': 7.23.9 + '@babel/parser': 7.24.8 '@vue/shared': 3.4.15 entities: 4.5.0 estree-walker: 2.0.2 @@ -3527,13 +4205,13 @@ snapshots: '@vue/compiler-sfc@3.4.15': dependencies: - '@babel/parser': 7.23.9 + '@babel/parser': 7.24.8 '@vue/compiler-core': 3.4.15 '@vue/compiler-dom': 3.4.15 '@vue/compiler-ssr': 3.4.15 '@vue/shared': 3.4.15 estree-walker: 2.0.2 - magic-string: 0.30.6 + magic-string: 0.30.10 postcss: 8.4.38 source-map-js: 1.2.0 @@ -3552,6 +4230,12 @@ snapshots: acorn@8.11.3: {} + agent-base@7.1.1: + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3559,6 +4243,11 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + optional: true + ansi-regex@5.0.1: {} ansi-styles@3.2.1: @@ -3580,10 +4269,17 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + optional: true + array-union@2.1.0: {} assertion-error@1.1.0: {} + asynckit@0.4.0: {} + autoprefixer@10.4.17(postcss@8.4.33): dependencies: browserslist: 4.22.3 @@ -3720,6 +4416,12 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 + cli-spinners@2.9.2: + optional: true + + cli-width@4.1.0: + optional: true + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -3740,6 +4442,10 @@ snapshots: colord@2.9.3: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@7.2.0: {} comment-parser@1.4.1: {} @@ -3754,6 +4460,9 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.5.0: + optional: true + core-js-compat@3.35.1: dependencies: browserslist: 4.22.3 @@ -3837,6 +4546,15 @@ snapshots: dependencies: css-tree: 2.2.1 + cssstyle@4.0.1: + dependencies: + rrweb-cssom: 0.6.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + debug@3.2.7: dependencies: ms: 2.1.3 @@ -3845,6 +4563,12 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.5: + dependencies: + ms: 2.1.2 + + decimal.js@10.4.3: {} + deep-eql@4.1.3: dependencies: type-detect: 4.0.8 @@ -3855,6 +4579,11 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + + dequal@2.0.3: + optional: true + destr@2.0.2: {} diff-sequences@29.6.3: {} @@ -3867,6 +4596,9 @@ snapshots: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: + optional: true + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -4127,13 +4859,13 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - eslint-plugin-vitest@0.5.4(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(happy-dom@13.10.1)): + eslint-plugin-vitest@0.5.4(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0)): dependencies: '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - vitest: 1.5.2(@types/node@18.19.31)(happy-dom@13.10.1) + vitest: 1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0) transitivePeerDependencies: - supports-color - typescript @@ -4308,6 +5040,12 @@ snapshots: flatted@3.2.9: {} + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fraction.js@4.3.7: {} fs-extra@11.2.0: @@ -4322,6 +5060,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -4408,6 +5149,9 @@ snapshots: graphemer@1.4.0: {} + graphql@16.9.0: + optional: true + happy-dom@13.10.1: dependencies: entities: 4.5.0 @@ -4422,12 +5166,39 @@ snapshots: dependencies: function-bind: 1.1.2 + headers-polyfill@4.0.3: + optional: true + hookable@5.5.3: {} hosted-git-info@2.8.9: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + human-signals@5.0.0: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.1: {} import-fresh@3.3.0: @@ -4481,10 +5252,15 @@ snapshots: is-module@1.0.0: {} + is-node-process@1.2.0: + optional: true + is-number@7.0.0: {} is-path-inside@3.0.3: {} + is-potential-custom-element-name@1.0.1: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.5 @@ -4493,18 +5269,69 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jiti@1.21.0: {} js-tokens@4.0.0: {} js-tokens@8.0.3: {} + js-tokens@9.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 jsdoc-type-pratt-parser@4.0.0: {} + jsdom@24.1.0: + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.1.2 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@0.5.0: {} jsesc@2.5.2: {} @@ -4580,6 +5407,11 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + optional: true + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4588,10 +5420,27 @@ snapshots: dependencies: yallist: 4.0.0 + lz-string@1.5.0: + optional: true + + magic-string@0.30.10: + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.6: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magicast@0.3.4: + dependencies: + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + source-map-js: 1.2.0 + + make-dir@4.0.0: + dependencies: + semver: 7.6.0 + mdast-util-from-markdown@0.8.5: dependencies: '@types/mdast': 3.0.15 @@ -4624,6 +5473,12 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-fn@4.0.0: {} min-indent@1.0.1: {} @@ -4691,10 +5546,39 @@ snapshots: mri@1.2.0: {} + mrmime@2.0.0: + optional: true + ms@2.1.2: {} ms@2.1.3: {} + msw@2.3.1(typescript@5.4.5): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@inquirer/confirm': 3.1.14 + '@mswjs/cookies': 1.1.1 + '@mswjs/interceptors': 0.29.1 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + chalk: 4.1.2 + graphql: 16.9.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.2.2 + strict-event-emitter: 0.5.1 + type-fest: 4.21.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.4.5 + optional: true + + mute-stream@1.0.0: + optional: true + nanoid@3.3.7: {} natural-compare-lite@1.4.0: {} @@ -4724,6 +5608,8 @@ snapshots: dependencies: boolbase: 1.0.0 + nwsapi@2.2.12: {} + nypm@0.3.6: dependencies: citty: 0.1.5 @@ -4750,6 +5636,9 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + outvariant@1.4.3: + optional: true + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -4802,6 +5691,10 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse5@7.1.2: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-exists@5.0.0: {} @@ -4814,6 +5707,9 @@ snapshots: path-parse@1.0.7: {} + path-to-regexp@6.2.2: + optional: true + path-type@4.0.0: {} pathe@1.1.2: {} @@ -4834,6 +5730,14 @@ snapshots: mlly: 1.5.0 pathe: 1.1.2 + playwright-core@1.45.1: {} + + playwright@1.45.1: + dependencies: + playwright-core: 1.45.1 + optionalDependencies: + fsevents: 2.3.2 + pluralize@8.0.0: {} postcss-calc@9.0.1(postcss@8.4.33): @@ -5011,6 +5915,13 @@ snapshots: pretty-bytes@6.1.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + optional: true + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -5022,8 +5933,12 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + psl@1.9.0: {} + punycode@2.3.1: {} + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} rc9@2.1.1: @@ -5032,6 +5947,9 @@ snapshots: destr: 2.0.2 flat: 5.0.2 + react-is@17.0.2: + optional: true + react-is@18.2.0: {} read-pkg-up@7.0.1: @@ -5051,6 +5969,9 @@ snapshots: dependencies: picomatch: 2.3.1 + regenerator-runtime@0.14.1: + optional: true + regexp-tree@0.1.27: {} regjsparser@0.10.0: @@ -5059,6 +5980,8 @@ snapshots: require-directory@2.1.1: {} + requires-port@1.0.0: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -5109,10 +6032,20 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.17.0 fsevents: 2.3.3 + rrweb-cssom@0.6.0: {} + + rrweb-cssom@0.7.1: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scule@1.2.0: {} semver@5.7.2: {} @@ -5137,6 +6070,13 @@ snapshots: signal-exit@4.1.0: {} + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.25 + mrmime: 2.0.0 + totalist: 3.0.1 + optional: true + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -5168,8 +6108,14 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.1: + optional: true + std-env@3.7.0: {} + strict-event-emitter@0.5.1: + optional: true + string-argv@0.3.2: {} string-width@4.2.3: @@ -5194,6 +6140,10 @@ snapshots: dependencies: js-tokens: 8.0.3 + strip-literal@2.1.0: + dependencies: + js-tokens: 9.0.0 + stylehacks@6.0.2(postcss@8.4.33): dependencies: browserslist: 4.22.3 @@ -5220,6 +6170,8 @@ snapshots: csso: 5.0.5 picocolors: 1.0.0 + symbol-tree@3.2.4: {} + synckit@0.6.2: dependencies: tslib: 2.6.2 @@ -5235,12 +6187,21 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + text-table@0.2.0: {} tinybench@2.6.0: {} tinypool@0.8.4: {} + tinyrainbow@1.2.0: + optional: true + tinyspy@2.2.0: {} to-fast-properties@2.0.0: {} @@ -5253,6 +6214,20 @@ snapshots: dependencies: eslint-visitor-keys: 3.4.3 + totalist@3.0.1: + optional: true + + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.0.3(typescript@5.4.5): dependencies: typescript: 5.4.5 @@ -5271,10 +6246,16 @@ snapshots: type-fest@0.20.2: {} + type-fest@0.21.3: + optional: true + type-fest@0.6.0: {} type-fest@0.8.1: {} + type-fest@4.21.0: + optional: true + typescript@5.4.5: {} ufo@1.3.2: {} @@ -5319,6 +6300,8 @@ snapshots: dependencies: '@types/unist': 2.0.10 + universalify@0.2.0: {} + universalify@2.0.1: {} untyped@1.4.2: @@ -5343,6 +6326,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -5376,7 +6364,7 @@ snapshots: '@types/node': 18.19.31 fsevents: 2.3.3 - vitest@1.5.2(@types/node@18.19.31)(happy-dom@13.10.1): + vitest@1.5.2(@types/node@18.19.31)(@vitest/browser@2.0.3)(happy-dom@13.10.1)(jsdom@24.1.0): dependencies: '@vitest/expect': 1.5.2 '@vitest/runner': 1.5.2 @@ -5400,7 +6388,9 @@ snapshots: why-is-node-running: 2.2.2 optionalDependencies: '@types/node': 18.19.31 + '@vitest/browser': 2.0.3(playwright@1.45.1)(typescript@5.4.5)(vitest@1.5.2) happy-dom: 13.10.1 + jsdom: 24.1.0 transitivePeerDependencies: - less - lightningcss @@ -5423,10 +6413,25 @@ snapshots: transitivePeerDependencies: - supports-color + 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@3.0.0: {} + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.0.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -5436,6 +6441,13 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + optional: true + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -5444,8 +6456,14 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.0: {} + xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -5475,3 +6493,6 @@ snapshots: yocto-queue@0.1.0: {} yocto-queue@1.0.0: {} + + yoctocolors-cjs@2.1.2: + optional: true diff --git a/src/event.ts b/src/event.ts index 4cb33c1..70b2bd8 100644 --- a/src/event.ts +++ b/src/event.ts @@ -60,7 +60,7 @@ export function createEventData( const { url, referrer, deviceWidth } = data return { - url: url ?? location.href, + url: url ?? window.location.href, referrer: referrer ?? document.referrer, deviceWidth: deviceWidth ?? window.innerWidth, } diff --git a/src/extensions/utils.ts b/src/extensions/utils.ts index e79497e..c4bc9ed 100644 --- a/src/extensions/utils.ts +++ b/src/extensions/utils.ts @@ -16,13 +16,13 @@ export function isOutboundLink(link: HTMLAnchorElement, currentHostname: string) * @param link - Link * @returns - If the link should be followed */ -export function shouldFollowLink(event: MouseEvent, link: HTMLAnchorElement) { +export function shouldFollowLink(event: MouseEvent, link: HTMLAnchorElement): boolean { // If default has been prevented by an external script, Plausible should not intercept navigation. if (event.defaultPrevented) return false const downloadable = link.hasAttribute('download') - const targetsCurrentWindow = !link.target || link.target.match(/^_(self|parent|top)$/i) + const targetsCurrentWindow = !link.target || Boolean(link.target.match(/^_(self|parent|top)$/i)) const isRegularClick = !(event.ctrlKey || event.metaKey || event.shiftKey) && event.type === 'click' return targetsCurrentWindow && isRegularClick && !downloadable diff --git a/src/plausible.ts b/src/plausible.ts index 91c2928..6e006b5 100644 --- a/src/plausible.ts +++ b/src/plausible.ts @@ -15,12 +15,12 @@ import type { EventName, EventOptions, EventPayload, Plausible, PlausibleOptions * @returns Plausible tracker */ export function createPlausibleTracker(initOptions?: Partial) { - const protocol = location.protocol + const protocol = window.location.protocol const defaultOptions: PlausibleOptions = { enabled: true, hashMode: false, - domain: location.hostname, + domain: window.location.hostname, apiHost: 'https://plausible.io', ignoredHostnames: ['localhost'], ignoreSubDomains: false, @@ -56,7 +56,7 @@ export function createPlausibleTracker(initOptions?: Partial) options?.callback?.() } else { - sendEvent(payload, options?.callback) + return sendEvent(payload, options?.callback) } } @@ -64,7 +64,7 @@ export function createPlausibleTracker(initOptions?: Partial) * Send a pageview event. */ function trackPageview(options?: EventOptions) { - trackEvent('pageview', options) + return trackEvent('pageview', options) } return { diff --git a/test/e2e/auto-outbound-tracking.spec.ts b/test/e2e/auto-outbound-tracking.spec.ts new file mode 100644 index 0000000..8d3394e --- /dev/null +++ b/test/e2e/auto-outbound-tracking.spec.ts @@ -0,0 +1,73 @@ +import { expect, test } from '@playwright/test' +import { plausibleEventRequest, plausibleEventURL } from './utils' + +test.describe('`auto-outbound-tracking`', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/auto-outbound-tracking') + }) + + test('should track external link clicks', async ({ page }) => { + const requestPromise = plausibleEventRequest(page) + + await page.getByRole('link', { name: 'External Link', exact: true }).click() + + expect((await requestPromise).postData()).toBe('{"n":"Outbound Link: Click","u":"http://localhost:5173/auto-outbound-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"https://example.com\\"}"}') + }) + + test('should not track internal link clicks', async ({ page }) => { + await page.route(plausibleEventURL, async () => { + throw new Error('Unexpected outbound link click') + }) + + await page.getByRole('link', { name: 'Internal Link' }).click() + }) + + test('should respect external link target', async ({ page, context }) => { + const requestPromise = plausibleEventRequest(page) + const externalPagePromise = context.waitForEvent('page') + + await page.getByRole('link', { name: 'External Link (_blank)' }).click() + + expect((await requestPromise).postData()).toBe('{"n":"Outbound Link: Click","u":"http://localhost:5173/auto-outbound-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"https://example.com\\"}"}') + + expect((await externalPagePromise).url()).toBe('https://example.com/') + expect(page.url()).toBe('http://localhost:5173/auto-outbound-tracking') + }) + + test('should respect rel="noopener noreferrer" attribute', async ({ page }) => { + await page.getByRole('link', { name: 'External Link (noopener noreferrer)' }).click() + + await page.waitForURL('https://example.com/') + + const opener = await page.evaluate(() => window.opener) + expect(opener).toBe(null) + + const referrer = await page.evaluate(() => document.referrer) + expect(referrer).toBe('') + }) + + test('should track links after being added to the DOM', async ({ page }) => { + const requestPromise = plausibleEventRequest(page) + + await page.evaluate(() => { + const link = document.createElement('a') + link.href = 'https://example.com' + link.textContent = 'External Link (Added to DOM)' + + document.body.appendChild(link) + }) + + await page.getByRole('link', { name: 'External Link (Added to DOM)' }).click() + + expect((await requestPromise).postData()).toBe('{"n":"Outbound Link: Click","u":"http://localhost:5173/auto-outbound-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"https://example.com\\"}"}') + }) + + test('should stop tracking once extension is cleaned up', async ({ page }) => { + await page.route(plausibleEventURL, async () => { + throw new Error('Unexpected outbound link click') + }) + + await page.getByRole('button', { name: 'Cleanup' }).click() + await page.getByRole('link', { name: 'External Link', exact: true }).click() + }) +}) diff --git a/test/e2e/file-downloads-tracking.spec.ts b/test/e2e/file-downloads-tracking.spec.ts new file mode 100644 index 0000000..2ca9da3 --- /dev/null +++ b/test/e2e/file-downloads-tracking.spec.ts @@ -0,0 +1,85 @@ +import { expect, test } from '@playwright/test' +import { plausibleEventRequest, plausibleEventURL } from './utils' + +test.describe('`file-downloads-tracking`', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/file-downloads-tracking') + }) + + test('should track file downloads', async ({ page }) => { + const requestPromise = plausibleEventRequest(page) + + await page.getByRole('link', { name: 'Download SVG', exact: true }).click() + + expect((await requestPromise).postData()).toBe('{"n":"File Download","u":"http://localhost:5173/file-downloads-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"/file.svg\\"}"}') + }) + + test('should works without the download attribute', async ({ page }) => { + const requestPromise = plausibleEventRequest(page) + + await page.getByRole('link', { name: 'Download SVG without download attribute', exact: true }).click() + + expect((await requestPromise).postData()).toBe('{"n":"File Download","u":"http://localhost:5173/file-downloads-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"/file.svg\\"}"}') + }) + + test('should track file opened in a new tab', async ({ page, context }) => { + const requestPromise = plausibleEventRequest(page) + const externalPagePromise = context.waitForEvent('page') + + await page.getByRole('link', { name: 'Download SVG targeting blank', exact: true }).click() + + expect((await requestPromise).postData()).toBe('{"n":"File Download","u":"http://localhost:5173/file-downloads-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"/file.svg\\"}"}') + + expect((await externalPagePromise).url()).toBe('http://localhost:5173/file.svg') + expect(page.url()).toBe('http://localhost:5173/file-downloads-tracking') + }) + + test('should track file with full URL', async ({ page }) => { + const requestPromise = plausibleEventRequest(page) + + await page.getByRole('link', { name: 'Download SVG with full URL', exact: true }).click() + + expect((await requestPromise).postData()).toBe('{"n":"File Download","u":"http://localhost:5173/file-downloads-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"/file.svg\\"}"}') + }) + + test('should not track non specified file type', async ({ page }) => { + await page.route(plausibleEventURL, async () => { + throw new Error('Unexpected file download') + }) + + await page.getByRole('link', { name: 'Download PDF', exact: true }).click() + }) + + test('should not track simple links', async ({ page }) => { + await page.route(plausibleEventURL, async () => { + throw new Error('Unexpected file download') + }) + + await page.getByRole('link', { name: 'Simple Link' }).click() + }) + + test('should track file links after being added to the DOM', async ({ page }) => { + const requestPromise = plausibleEventRequest(page) + + await page.evaluate(() => { + const link = document.createElement('a') + link.href = '/file.svg' + link.download = 'file.svg' + link.textContent = 'Download SVG after being added to the DOM' + document.body.appendChild(link) + }) + + await page.getByRole('link', { name: 'Download SVG after being added to the DOM' }).click() + + expect((await requestPromise).postData()).toBe('{"n":"File Download","u":"http://localhost:5173/file-downloads-tracking","d":"localhost","r":"","w":1280,"h":0,"p":"{\\"url\\":\\"/file.svg\\"}"}') + }) + + test('should stop tracking once extension is cleaned up', async ({ page }) => { + await page.route(plausibleEventURL, async () => { + throw new Error('Unexpected file download') + }) + + await page.getByRole('button', { name: 'Cleanup' }).click() + await page.getByRole('link', { name: 'Download SVG', exact: true }).click() + }) +}) diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts new file mode 100644 index 0000000..9a60db8 --- /dev/null +++ b/test/e2e/utils.ts @@ -0,0 +1,9 @@ +import type { Page } from '@playwright/test' + +export const plausibleEventURL = 'https://plausible.io/api/event' + +export function plausibleEventRequest(page: Page) { + return page.waitForRequest((request) => { + return request.url() === plausibleEventURL && request.method() === 'POST' + }) +} diff --git a/test/event.dom.spec.ts b/test/event.dom.spec.ts new file mode 100644 index 0000000..e356f50 --- /dev/null +++ b/test/event.dom.spec.ts @@ -0,0 +1,132 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createEventData, isUserSelfExcluded, sendEvent } from '../src/event' +import type { EventPayload } from '../src/types' + +describe('`isUserSelfExcluded`', () => { + afterEach(() => { + localStorage.clear() + }) + + it('should return `true` if the key `plausible_ignore` is set to \'true\'', () => { + localStorage.setItem('plausible_ignore', 'true') + + expect(isUserSelfExcluded()).toBe(true) + }) + + it('should return `false` if the key `plausible_ignore` is not set', () => { + localStorage.removeItem('plausible_ignore') + + expect(isUserSelfExcluded()).toBe(false) + }) +}) + +describe('`createEventData`', () => { + beforeEach(() => { + // @ts-expect-error - Mocking the location object + vi.spyOn(window, 'location', 'get').mockReturnValue({ + href: 'http://example.com', + }) + vi.spyOn(document, 'referrer', 'get').mockReturnValue('http://referrer.com') + vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1920) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should use the href of the current location as the default URL', () => { + const data = createEventData({ + referrer: 'http://referrer.com', + deviceWidth: 1920, + }) + + expect(data).toEqual({ + url: 'http://example.com', + referrer: 'http://referrer.com', + deviceWidth: 1920, + }) + }) + + it('should use the referrer of the document as the default referrer', () => { + const data = createEventData({ + url: 'http://example.com', + deviceWidth: 1920, + }) + + expect(data).toEqual({ + url: 'http://example.com', + referrer: 'http://referrer.com', + deviceWidth: 1920, + }) + }) + + it('should use the inner width of the window as the default device width', () => { + const data = createEventData({ + url: 'http://example.com', + referrer: 'http://referrer.com', + }) + + expect(data).toEqual({ + url: 'http://example.com', + referrer: 'http://referrer.com', + deviceWidth: 1920, + }) + }) +}) + +describe('`sendEvent`', () => { + const eventPayload: EventPayload = { + n: 'test', + u: 'http://example.com', + d: 'example.com', + r: 'http://referrer.com', + w: 1920, + h: 0, + } + + beforeEach(() => { + vi.spyOn(window, 'fetch').mockResolvedValue({} as Response) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should send an event with the payload to the API `/api/event`', () => { + sendEvent('https://example.com', eventPayload) + + expect(window.fetch).toHaveBeenCalledWith('https://example.com/api/event', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: JSON.stringify(eventPayload), + }) + }) + + it('should call the callback', async () => { + const callback = vi.fn() + + await sendEvent('https://example.com', eventPayload, callback) + + expect(callback).toHaveBeenCalled() + }) + + // waiting for https://github.com/vitest-dev/vitest/pull/6056 + // it('should call the callback after the request is complete', async () => { + // const callback = vi.fn() + + // await sendEvent('https://example.com', eventPayload, callback) + + // expect(callback).toHaveBeenCalledAfter(window.fetch) + // }) + + it('should not call the callback if the request fails', async () => { + const callback = vi.fn() + vi.spyOn(window, 'fetch').mockRejectedValue(new Error('Failed to fetch')) + + await sendEvent('https://example.com', eventPayload, callback).catch(() => {}) + + expect(callback).not.toHaveBeenCalled() + }) +}) diff --git a/test/event.spec.ts b/test/event.spec.ts new file mode 100644 index 0000000..f8e3926 --- /dev/null +++ b/test/event.spec.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest' +import { createEventData, isFile, isIgnored, isUserSelfExcluded } from '../src/event' + +describe('`isFile`', () => { + it('should return `true` if the protocol is `file:`', () => { + expect(isFile('file:')).toBe(true) + }) + + it.each([ + ['http'], + ['https:'], + ['ftp:'], + ['ws:'], + ['wss:'], + ['data:'], + ['blob:'], + ['mailto:'], + ])('should return `false` if the protocol is `%s`', (protocol) => { + expect(isFile(protocol)).toBe(false) + }) +}) + +describe('`isIgnored`', () => { + it('should return `true` if the hostname is ignored', () => { + expect(isIgnored('example.com', ['example.com'], false)).toBe(true) + }) + + it('should return `false` if the hostname is not ignored', () => { + expect(isIgnored('example.com', ['example.org'], false)).toBe(false) + }) + + it('should not ignore subdomains', () => { + expect(isIgnored('sub.example.com', ['example.com'], false)).toBe(false) + }) + + it('should ignore subdomains if the option is enabled', () => { + expect(isIgnored('sub.example.com', ['example.com'], true)).toBe(true) + }) +}) + +describe('`isUserSelfExcluded`', () => { + it('should return `false` if `localStorage` is not available', () => { + expect(globalThis.localStorage).toBeUndefined() + expect(isUserSelfExcluded()).toBe(false) + }) +}) + +describe('`createEventData`', () => { + it('should create a valid event data', () => { + const data = createEventData({ + url: 'http://example.com', + referrer: 'http://referrer.com', + deviceWidth: 1920, + }) + + expect(data).toEqual({ + url: 'http://example.com', + referrer: 'http://referrer.com', + deviceWidth: 1920, + }) + }) +}) diff --git a/test/extensions/auto-pageviews.dom.spec.ts b/test/extensions/auto-pageviews.dom.spec.ts new file mode 100644 index 0000000..b00f648 --- /dev/null +++ b/test/extensions/auto-pageviews.dom.spec.ts @@ -0,0 +1,155 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createPlausibleTracker } from '../../src/plausible' +import { useAutoPageviews } from '../../src/extensions' + +describe('`auto-pageviews extensions`', () => { + const plausibleOptions = { + ignoredHostnames: [], // Ignore no hostnames to avoid ignoring localhost + } + const plausible = createPlausibleTracker(plausibleOptions) + const pageviews = useAutoPageviews(plausible) + + beforeEach(() => { + vi.spyOn(window, 'fetch').mockResolvedValue({} as Response) + }) + + afterEach(() => { + pageviews.cleanup() + vi.restoreAllMocks() + + history.replaceState({}, '', '/') + }) + + it('should send `pageview` events on install', () => { + pageviews.install() + + expect(window.fetch).toHaveBeenCalledTimes(1) + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: JSON.stringify({ + n: 'pageview', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: undefined, + }), + }) + }) + + it('should send event on new `popstate` event', () => { + pageviews.install() + + window.dispatchEvent(new PopStateEvent('popstate')) + + expect(window.fetch).toHaveBeenCalledTimes(2) + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: JSON.stringify({ + n: 'pageview', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: undefined, + }), + }) + }) + + it('should send event on new `hashchange` event', () => { + const pageviews = useAutoPageviews(createPlausibleTracker({ + ...plausibleOptions, + hashMode: true, + })) + + pageviews.install() + + window.dispatchEvent(new HashChangeEvent('hashchange')) + + expect(window.fetch).toHaveBeenCalledTimes(2) + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: JSON.stringify({ + n: 'pageview', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 1, + p: undefined, + }), + }) + + pageviews.cleanup() + }) + + it('should send event when `history.pushState` is called', () => { + pageviews.install() + + history.pushState({}, '', '/new') + + expect(window.fetch).toHaveBeenCalledTimes(2) + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: JSON.stringify({ + n: 'pageview', + u: 'http://localhost:3000/new', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: undefined, + }), + }) + }) + + it('should remove event listeners on cleanup', () => { + pageviews.install() + pageviews.cleanup() + + window.dispatchEvent(new PopStateEvent('popstate')) + window.dispatchEvent(new HashChangeEvent('hashchange')) + history.pushState({}, '', '/new') + + expect(window.fetch).toHaveBeenCalledTimes(1) + }) + + it('should be able to modify options at any time', () => { + pageviews.install() + + pageviews.setEventOptions({ props: { key: 'value' } }) + + window.dispatchEvent(new PopStateEvent('popstate')) + + expect(window.fetch).toHaveBeenCalledTimes(2) + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: JSON.stringify({ + n: 'pageview', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: '{"key":"value"}', + }), + }) + }) +}) diff --git a/test/extensions/utils.dom.spec.ts b/test/extensions/utils.dom.spec.ts new file mode 100644 index 0000000..2552576 --- /dev/null +++ b/test/extensions/utils.dom.spec.ts @@ -0,0 +1,121 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { isOutboundLink, openLink, shouldFollowLink } from '../../src/extensions/utils' + +describe('extensions utils', () => { + let link: HTMLAnchorElement = document.createElement('a') + + beforeEach(() => { + link = document.createElement('a') + link.href = 'http://example.com' + }) + + describe('`isOutboundLink`', () => { + it('should return `true` if hostname is different', () => { + expect(isOutboundLink(link, window.location.host)).toBe(true) + }) + + it('should return `false` if hostname is the same', () => { + link.href = window.location.href + + expect(isOutboundLink(link, window.location.host)).toBe(false) + }) + }) + + describe('`shouldFollowLink`', () => { + it('should not accept prevented events', () => { + const event = new MouseEvent('click') + event.preventDefault() + + expect(shouldFollowLink(event, link)).toBe(true) + }) + + it('should not intercept download links', () => { + const event = new MouseEvent('click') + + link.setAttribute('download', '') + + expect(shouldFollowLink(event, link)).toBe(false) + }) + + it.each([ + '_self', + '_parent', + '_top', + ])('should accept %s target', (target) => { + const event = new MouseEvent('click') + + link.target = target + + expect(shouldFollowLink(event, link)).toBe(true) + }) + + it('should not accept _blank target', () => { + const event = new MouseEvent('click') + + link.target = '_blank' + + expect(shouldFollowLink(event, link)).toBe(false) + }) + + it.each([ + { + it: 'meta', + options: { metaKey: true }, + }, + { + it: 'ctrl', + options: { ctrlKey: true }, + }, + { + it: 'shift', + options: { shiftKey: true }, + }, + ])('should not accept $it key', ({ options }) => { + const event = new MouseEvent('click', options) + + expect(shouldFollowLink(event, link)).toBe(false) + }) + + it.each([ + 'mousedown', + 'mouseup', + 'dblclick', + ])('should not accept %s event', (eventName) => { + const event = new MouseEvent(eventName) + + expect(shouldFollowLink(event, link)).toBe(false) + }) + }) + + describe('`openLink`', () => { + beforeEach(() => { + vi.stubGlobal('open', vi.fn()) + }) + + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('should open the link in the same window', () => { + openLink(link) + + expect(window.open).toHaveBeenCalledWith('http://example.com', '_self', '') + }) + + it('should open the link in a new window', () => { + link.target = '_blank' + + openLink(link) + + expect(window.open).toHaveBeenCalledWith('http://example.com', '_blank', '') + }) + + it('should open the link in a new window with features', () => { + link.rel = 'noopener noreferrer' + + openLink(link) + + expect(window.open).toHaveBeenCalledWith('http://example.com', '_self', 'noopener,noreferrer') + }) + }) +}) diff --git a/test/payload.spec.ts b/test/payload.spec.ts new file mode 100644 index 0000000..62a51b1 --- /dev/null +++ b/test/payload.spec.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest' +import { createPayload } from '../src/payload' +import type { PlausibleOptions } from '../src/types' + +describe('payload', () => { + const plausibleOptions: Required = { + domain: 'example.com', + hashMode: false, + apiHost: 'https://example.com', + enabled: true, + ignoredHostnames: [], + ignoreSubDomains: false, + logIgnored: false, + } + const data = { + url: 'http://example.com', + referrer: 'http://referrer.com', + deviceWidth: 1920, + } + + it('should be correctly formed', () => { + const payload = createPayload('test', plausibleOptions, data) + + expect(payload).toEqual({ + n: 'test', + u: 'http://example.com', + d: 'example.com', + r: 'http://referrer.com', + w: 1920, + h: 0, + p: undefined, + }) + }) + + it('should include props', () => { + const payload = createPayload('test', plausibleOptions, data, { props: { key: 'value' } }) + + expect(payload).toEqual({ + n: 'test', + u: 'http://example.com', + d: 'example.com', + r: 'http://referrer.com', + w: 1920, + h: 0, + p: '{"key":"value"}', + }) + }) + + it('should transform `hashMode` to `1` when enabled', () => { + const payload = createPayload('test', { ...plausibleOptions, hashMode: true }, data) + + expect(payload).toEqual({ + n: 'test', + u: 'http://example.com', + d: 'example.com', + r: 'http://referrer.com', + w: 1920, + h: 1, + p: undefined, + }) + }) +}) diff --git a/test/plausible.dom.spec.ts b/test/plausible.dom.spec.ts new file mode 100644 index 0000000..506ea58 --- /dev/null +++ b/test/plausible.dom.spec.ts @@ -0,0 +1,349 @@ +/* eslint-disable no-console */ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createPlausibleTracker } from '../src/plausible' +import type { PlausibleOptions } from '../src/types' + +describe('`createPlausibleTracker`', () => { + it('should return `trackEvent`, `trackPageview` and `options`', () => { + const plausible = createPlausibleTracker() + + expect(plausible.trackEvent).toBeDefined() + expect(plausible.trackPageview).toBeDefined() + expect(plausible.options).toBeDefined() + }) + + describe('`options`', () => { + it('should return the default options', () => { + const plausible = createPlausibleTracker() + + expect(plausible.options).toEqual({ + enabled: true, + hashMode: false, + domain: 'localhost', + apiHost: 'https://plausible.io', + ignoredHostnames: ['localhost'], + ignoreSubDomains: false, + logIgnored: false, + }) + }) + + it('should override all default options with the init options', () => { + const plausible = createPlausibleTracker({ + enabled: false, + domain: 'example.com', + apiHost: 'https://example.com', + hashMode: true, + ignoredHostnames: ['example.com'], + ignoreSubDomains: true, + logIgnored: true, + }) + + expect(plausible.options).toEqual({ + enabled: false, + hashMode: true, + domain: 'example.com', + apiHost: 'https://example.com', + ignoredHostnames: ['example.com'], + ignoreSubDomains: true, + logIgnored: true, + }) + }) + + it.each([ + { + enabled: false, + }, + { + domain: 'example.com', + }, + { + apiHost: 'https://example.com', + }, + { + hashMode: true, + }, + { + ignoredHostnames: ['example.com'], + }, + { + ignoreSubDomains: true, + }, + { + logIgnored: true, + }, + ])('should override some default options with the init options (%j)', (option) => { + const plausible = createPlausibleTracker(option) + + const defaultOptions: PlausibleOptions = { + enabled: true, + hashMode: false, + domain: location.hostname, + apiHost: 'https://plausible.io', + ignoredHostnames: ['localhost'], + ignoreSubDomains: false, + logIgnored: false, + } + + expect(plausible.options).toEqual({ + ...defaultOptions, + ...option, + }) + }) + }) + + describe('`sendEvent`', () => { + const callback = vi.fn() + const plausibleOptions = { + ignoredHostnames: [], // Ignore no hostnames to avoid ignoring localhost + } + const plausible = createPlausibleTracker(plausibleOptions) + + beforeEach(() => { + vi.spyOn(window, 'fetch').mockResolvedValue({} as Response) + vi.spyOn(console, 'info').mockImplementation(() => { + // Do nothing + }) + }) + + afterEach(() => { + localStorage.clear() + vi.restoreAllMocks() + }) + + it('should send the data to the API', () => { + plausible.trackEvent('test') + + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + body: JSON.stringify({ + n: 'test', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: undefined, + }), + headers: { + 'Content-Type': 'text/plain', + }, + method: 'POST', + }) + expect(console.info).not.toHaveBeenCalled() + }) + + it('should send the data to the API with the props', () => { + plausible.trackEvent('test', { + props: { + key: 'value', + }, + }) + + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + body: JSON.stringify({ + n: 'test', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: '{"key":"value"}', + }), + headers: { + 'Content-Type': 'text/plain', + }, + method: 'POST', + }) + expect(console.info).not.toHaveBeenCalled() + }) + + it('should call the callback', async () => { + await plausible.trackEvent('test', { + callback, + }) + + expect(callback).toHaveBeenCalled() + }) + + // waiting for https://github.com/vitest-dev/vitest/pull/6056 + // it('should call the callback after the response is completed', async () => { + // await plausible.trackEvent('test', { + // callback, + // }) + + // expect(callback).toHaveBeenCalledAfter(window.fetch) + // }) + + it('should not call the callback if the request fails', async () => { + vi.spyOn(window, 'fetch').mockRejectedValue(new Error('Network error')) + + await plausible.trackEvent('test', { + callback, + })?.catch(() => {}) + + expect(callback).not.toHaveBeenCalled() + }) + + it('should not send the payload if plausible is disabled', () => { + const plausible = createPlausibleTracker({ + enabled: false, + }) + + plausible.trackEvent('test', { + callback, + }) + + expect(window.fetch).not.toHaveBeenCalled() + expect(callback).not.toHaveBeenCalled() + expect(console.info).not.toHaveBeenCalled() + }) + + it('should not send the payload if the protocol is file', async () => { + // @ts-expect-error - Mocking the location object + vi.spyOn(window, 'location', 'get').mockReturnValue({ + protocol: 'file:', + }) + + const plausible = createPlausibleTracker(plausibleOptions) + + await plausible.trackEvent('test', { + callback, + }) + + expect(window.fetch).not.toHaveBeenCalled() + expect(callback).toHaveBeenCalled() + expect(console.info).toHaveBeenCalledWith('[Plausible] test', { + n: 'test', + u: undefined, + d: undefined, + r: '', + w: 1024, + h: 0, + p: undefined, + }) + }) + + it('should not send the payload if the hostname is ignored', () => { + const plausible = createPlausibleTracker({ + domain: 'example.com', + ignoredHostnames: ['example.com'], + }) + + plausible.trackEvent('test', { + callback, + }) + + expect(window.fetch).not.toHaveBeenCalled() + expect(callback).toHaveBeenCalled() + expect(console.info).toHaveBeenCalledWith('[Plausible] test', { + n: 'test', + u: 'http://localhost:3000/', + d: 'example.com', + r: '', + w: 1024, + h: 0, + p: undefined, + }) + }) + + it('should not send the payload if the subdomain is ignored', () => { + const plausible = createPlausibleTracker({ + domain: 'sub.example.com', + ignoredHostnames: ['example.com'], + ignoreSubDomains: true, + }) + + plausible.trackEvent('test', { + callback, + }) + + expect(window.fetch).not.toHaveBeenCalled() + expect(callback).toHaveBeenCalled() + expect(console.info).toHaveBeenCalledWith('[Plausible] test', { + n: 'test', + u: 'http://localhost:3000/', + d: 'sub.example.com', + r: '', + w: 1024, + h: 0, + p: undefined, + }) + }) + + it('should not send the payload if the user excluded himself', () => { + localStorage.setItem('plausible_ignore', 'true') + + plausible.trackEvent('test', { + callback, + }) + + expect(window.fetch).not.toHaveBeenCalled() + expect(callback).toHaveBeenCalled() + expect(console.info).toHaveBeenCalledWith('[Plausible] test', { + n: 'test', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: undefined, + }) + }) + + it('should allow to ignore log', () => { + const plausible = createPlausibleTracker({ + logIgnored: true, + }) + + plausible.trackEvent('test', { + callback, + }) + + expect(window.fetch).not.toHaveBeenCalled() + expect(callback).toHaveBeenCalled() + expect(console.info).not.toHaveBeenCalled() + }) + }) + + describe('`trackPageview`', () => { + const callback = vi.fn() + const plausibleOptions = { + ignoredHostnames: [], // Ignore no hostnames to avoid ignoring localhost + } + const plausible = createPlausibleTracker(plausibleOptions) + + beforeEach(() => { + vi.spyOn(window, 'fetch').mockResolvedValue({} as Response) + vi.spyOn(console, 'info').mockImplementation(() => { + // Do nothing + }) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should send a `pageview` event', async () => { + await plausible.trackPageview({ + callback, + }) + + expect(window.fetch).toHaveBeenCalledWith('https://plausible.io/api/event', { + body: JSON.stringify({ + n: 'pageview', + u: 'http://localhost:3000/', + d: 'localhost', + r: '', + w: 1024, + h: 0, + p: undefined, + }), + headers: { + 'Content-Type': 'text/plain', + }, + method: 'POST', + }) + expect(callback).toHaveBeenCalled() + expect(console.info).not.toHaveBeenCalled() + }) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..19db327 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: [ + 'test/**/*.spec.ts', + ], + exclude: [ + 'test/e2e/**/*.spec.ts', + ], + coverage: { + include: [ + 'src/**/*.ts', + ], + }, + environmentMatchGlobs: [ + // all tests ending with `.dom.spec.ts` will run in jsdom + ['test/**/*.dom.spec.ts', 'jsdom'], + ], + }, +})