From 5a862a5d6f7cb81bc6f10e83cf209322aa1018c3 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 25 Mar 2022 13:50:50 +0100 Subject: [PATCH] feat: add Playwright for end-to-end testing --- index.ts | 16 + pnpm-lock.yaml | 1787 +++++++++++++++-- scripts/snapshot.mjs | 8 +- scripts/test.mjs | 53 +- template/base/{_gitignore => .gitignore} | 0 .../.github/workflows/playwright.yml | 27 + template/config/playwright/.gitignore | 2 + .../config/playwright/e2e/example.spec.js | 411 ++++ .../config/playwright/e2e/example.spec.ts | 398 ++++ template/config/playwright/e2e/vue.spec.js | 6 + template/config/playwright/e2e/vue.spec.ts | 6 + template/config/playwright/package.json | 8 + .../config/playwright/playwright.config.js | 107 + .../config/playwright/playwright.config.ts | 106 + template/config/vitest/package.json | 2 +- utils/generateReadme.ts | 18 + utils/renderTemplate.ts | 10 +- 17 files changed, 2816 insertions(+), 149 deletions(-) rename template/base/{_gitignore => .gitignore} (100%) create mode 100644 template/config/playwright/.github/workflows/playwright.yml create mode 100644 template/config/playwright/.gitignore create mode 100644 template/config/playwright/e2e/example.spec.js create mode 100644 template/config/playwright/e2e/example.spec.ts create mode 100644 template/config/playwright/e2e/vue.spec.js create mode 100644 template/config/playwright/e2e/vue.spec.ts create mode 100644 template/config/playwright/package.json create mode 100644 template/config/playwright/playwright.config.js create mode 100644 template/config/playwright/playwright.config.ts diff --git a/index.ts b/index.ts index 57d51f6b2..422b1a606 100755 --- a/index.ts +++ b/index.ts @@ -56,6 +56,7 @@ async function init() { // --with-tests / --tests (equals to `--vitest --cypress`) // --vitest // --cypress + // --playwright // --eslint // --eslint-with-prettier (only support prettier through eslint for simplicity) // --force (for force overwriting) @@ -80,6 +81,7 @@ async function init() { argv.tests ?? argv.vitest ?? argv.cypress ?? + argv.playwright ?? argv.eslint ) === 'boolean' @@ -112,6 +114,7 @@ async function init() { // - Install Vue Router for SPA development? // - Install Pinia for state management? // - Add Cypress for testing? + // - Add Playwright for end-to-end testing? // - Add ESLint for code quality? // - Add Prettier for code formatting? result = await prompts( @@ -200,6 +203,14 @@ async function init() { active: 'Yes', inactive: 'No' }, + { + name: 'needsPlaywright', + type: () => (isFeatureFlagsUsed ? null : 'toggle'), + message: 'Add Playwright for End-to-End testing?', + initial: false, + active: 'Yes', + inactive: 'No' + }, { name: 'needsEslint', type: () => (isFeatureFlagsUsed ? null : 'toggle'), @@ -244,6 +255,7 @@ async function init() { needsRouter = argv.router, needsPinia = argv.pinia, needsCypress = argv.cypress || argv.tests, + needsPlaywright = argv.playwright, needsVitest = argv.vitest || argv.tests, needsEslint = argv.eslint || argv['eslint-with-prettier'], needsPrettier = argv['eslint-with-prettier'] @@ -294,6 +306,9 @@ async function init() { if (needsCypressCT) { render('config/cypress-ct') } + if (needsPlaywright) { + render('config/playwright') + } if (needsTypeScript) { render('config/typescript') @@ -395,6 +410,7 @@ async function init() { needsTypeScript, needsVitest, needsCypress, + needsPlaywright, needsCypressCT, needsEslint }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1dc73f9e..a569ba017 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,23 @@ importers: start-server-and-test: 1.14.0 vite: 2.8.4 + playground/jsx-pinia-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + pinia: ^2.0.11 + vite: ^2.8.4 + vue: ^3.2.31 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + vite: 2.8.4 + playground/jsx-pinia-vitest: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -180,6 +197,29 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-pinia-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + pinia: ^2.0.11 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-pinia-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -205,6 +245,21 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + vite: ^2.8.4 + vue: ^3.2.31 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + vite: 2.8.4 + playground/jsx-router: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -285,6 +340,25 @@ importers: start-server-and-test: 1.14.0 vite: 2.8.4 + playground/jsx-router-pinia-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + pinia: ^2.0.11 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + vite: 2.8.4 + playground/jsx-router-pinia-vitest: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -335,6 +409,31 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-router-pinia-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + pinia: ^2.0.11 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-router-pinia-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -362,6 +461,23 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-router-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + vite: 2.8.4 + playground/jsx-router-vitest: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -408,6 +524,29 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-router-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-router-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -475,6 +614,27 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/jsx-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -532,6 +692,21 @@ importers: start-server-and-test: 1.14.0 vite: 2.8.4 + playground/pinia-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + pinia: ^2.0.11 + vite: ^2.8.4 + vue: ^3.2.31 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + vite: 2.8.4 + playground/pinia-vitest: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -574,6 +749,27 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/pinia-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + pinia: ^2.0.11 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/pinia-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -597,6 +793,19 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + vite: ^2.8.4 + vue: ^3.2.31 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + vite: 2.8.4 + playground/router: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -669,6 +878,23 @@ importers: start-server-and-test: 1.14.0 vite: 2.8.4 + playground/router-pinia-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + pinia: ^2.0.11 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + vite: 2.8.4 + playground/router-pinia-vitest: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -715,6 +941,29 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/router-pinia-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + pinia: ^2.0.11 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + pinia: 2.0.11_vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/router-pinia-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -740,6 +989,21 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/router-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + vite: 2.8.4 + playground/router-vitest: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -782,6 +1046,27 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/router-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/router-with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -955,38 +1240,32 @@ importers: vite: 2.8.4 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-pinia-vitest: + playground/typescript-jsx-pinia-playwright: specifiers: - '@types/jsdom': ^16.2.14 + '@playwright/test': ^1.19.2 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vitejs/plugin-vue-jsx': ^1.3.7 - '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - jsdom: ^19.0.0 pinia: ^2.0.11 typescript: ~4.5.5 vite: ^2.8.4 - vitest: ^0.5.0 vue: ^3.2.31 vue-tsc: ^0.31.4 dependencies: pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 devDependencies: - '@types/jsdom': 16.2.14 + '@playwright/test': 1.19.2 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vitejs/plugin-vue-jsx': 1.3.7 - '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - jsdom: 19.0.0 typescript: 4.5.5 vite: 2.8.4 - vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-pinia-vitest-cypress: + playground/typescript-jsx-pinia-vitest: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -994,10 +1273,8 @@ importers: '@vitejs/plugin-vue-jsx': ^1.3.7 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 jsdom: ^19.0.0 pinia: ^2.0.11 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 @@ -1013,15 +1290,81 @@ importers: '@vitejs/plugin-vue-jsx': 1.3.7 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-pinia-with-tests: + playground/typescript-jsx-pinia-vitest-cypress: + specifiers: + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 + jsdom: ^19.0.0 + pinia: ^2.0.11 + start-server-and-test: ^1.14.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 + jsdom: 19.0.0 + start-server-and-test: 1.14.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-jsx-pinia-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + jsdom: ^19.0.0 + pinia: ^2.0.11 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + jsdom: 19.0.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-jsx-pinia-with-tests: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1056,6 +1399,29 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 + playground/typescript-jsx-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/tsconfig': ^0.1.3 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + playground/typescript-jsx-router: specifiers: '@types/node': ^16.11.25 @@ -1168,19 +1534,16 @@ importers: vite: 2.8.4 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-router-pinia-vitest: + playground/typescript-jsx-router-pinia-playwright: specifiers: - '@types/jsdom': ^16.2.14 + '@playwright/test': ^1.19.2 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vitejs/plugin-vue-jsx': ^1.3.7 - '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - jsdom: ^19.0.0 pinia: ^2.0.11 typescript: ~4.5.5 vite: ^2.8.4 - vitest: ^0.5.0 vue: ^3.2.31 vue-router: ^4.0.12 vue-tsc: ^0.31.4 @@ -1189,19 +1552,16 @@ importers: vue: 3.2.31 vue-router: 4.0.12_vue@3.2.31 devDependencies: - '@types/jsdom': 16.2.14 + '@playwright/test': 1.19.2 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vitejs/plugin-vue-jsx': 1.3.7 - '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - jsdom: 19.0.0 typescript: 4.5.5 vite: 2.8.4 - vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-router-pinia-vitest-cypress: + playground/typescript-jsx-router-pinia-vitest: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1209,10 +1569,8 @@ importers: '@vitejs/plugin-vue-jsx': ^1.3.7 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 jsdom: ^19.0.0 pinia: ^2.0.11 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 @@ -1230,15 +1588,13 @@ importers: '@vitejs/plugin-vue-jsx': 1.3.7 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-router-pinia-with-tests: + playground/typescript-jsx-router-pinia-vitest-cypress: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1275,8 +1631,9 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-router-vitest: + playground/typescript-jsx-router-pinia-vitest-playwright: specifiers: + '@playwright/test': ^1.19.2 '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 @@ -1284,6 +1641,7 @@ importers: '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 jsdom: ^19.0.0 + pinia: ^2.0.11 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 @@ -1291,9 +1649,11 @@ importers: vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 vue-router: 4.0.12_vue@3.2.31 devDependencies: + '@playwright/test': 1.19.2 '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 @@ -1306,7 +1666,7 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-router-vitest-cypress: + playground/typescript-jsx-router-pinia-with-tests: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1316,6 +1676,7 @@ importers: '@vue/tsconfig': ^0.1.3 cypress: ^9.5.0 jsdom: ^19.0.0 + pinia: ^2.0.11 start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 @@ -1324,6 +1685,7 @@ importers: vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 vue-router: 4.0.12_vue@3.2.31 devDependencies: @@ -1341,20 +1703,15 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-router-with-tests: + playground/typescript-jsx-router-playwright: specifiers: - '@types/jsdom': ^16.2.14 + '@playwright/test': ^1.19.2 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vitejs/plugin-vue-jsx': ^1.3.7 - '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 - jsdom: ^19.0.0 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 - vitest: ^0.5.0 vue: ^3.2.31 vue-router: ^4.0.12 vue-tsc: ^0.31.4 @@ -1362,21 +1719,16 @@ importers: vue: 3.2.31 vue-router: 4.0.12_vue@3.2.31 devDependencies: - '@types/jsdom': 16.2.14 + '@playwright/test': 1.19.2 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vitejs/plugin-vue-jsx': 1.3.7 - '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 - jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 - vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-vitest: + playground/typescript-jsx-router-vitest: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1389,9 +1741,11 @@ importers: vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 + vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 devDependencies: '@types/jsdom': 16.2.14 '@types/node': 16.11.25 @@ -1405,7 +1759,7 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-vitest-cypress: + playground/typescript-jsx-router-vitest-cypress: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1420,9 +1774,11 @@ importers: vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 + vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 devDependencies: '@types/jsdom': 16.2.14 '@types/node': 16.11.25 @@ -1438,161 +1794,177 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-jsx-with-tests: + playground/typescript-jsx-router-vitest-playwright: specifiers: + '@playwright/test': ^1.19.2 '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vitejs/plugin-vue-jsx': ^1.3.7 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 jsdom: ^19.0.0 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 + vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 devDependencies: + '@playwright/test': 1.19.2 '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vitejs/plugin-vue-jsx': 1.3.7 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-pinia: + playground/typescript-jsx-router-with-tests: specifiers: + '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - pinia: ^2.0.11 + cypress: ^9.5.0 + jsdom: ^19.0.0 + start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 + vitest: ^0.5.0 vue: ^3.2.31 + vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: - pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 devDependencies: + '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 + jsdom: 19.0.0 + start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-pinia-cypress: + playground/typescript-jsx-vitest: specifiers: - '@cypress/vite-dev-server': ^2.2.2 - '@cypress/vue': ^3.1.1 + '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 + '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 - pinia: ^2.0.11 - start-server-and-test: ^1.14.0 + jsdom: ^19.0.0 typescript: ~4.5.5 vite: ^2.8.4 + vitest: ^0.5.0 vue: ^3.2.31 vue-tsc: ^0.31.4 dependencies: - pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 devDependencies: - '@cypress/vite-dev-server': 2.2.2_vite@2.8.4 - '@cypress/vue': 3.1.1_cypress@9.5.0+vue@3.2.31 + '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 - start-server-and-test: 1.14.0 + jsdom: 19.0.0 typescript: 4.5.5 vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-pinia-vitest: + playground/typescript-jsx-vitest-cypress: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 jsdom: ^19.0.0 - pinia: ^2.0.11 + start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 vue-tsc: ^0.31.4 dependencies: - pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 devDependencies: '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 jsdom: 19.0.0 + start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-pinia-vitest-cypress: + playground/typescript-jsx-vitest-playwright: specifiers: + '@playwright/test': ^1.19.2 '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 jsdom: ^19.0.0 - pinia: ^2.0.11 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 vue-tsc: ^0.31.4 dependencies: - pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 devDependencies: + '@playwright/test': 1.19.2 '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-pinia-with-tests: + playground/typescript-jsx-with-tests: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 + '@vitejs/plugin-vue-jsx': ^1.3.7 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 cypress: ^9.5.0 jsdom: ^19.0.0 - pinia: ^2.0.11 start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 @@ -1600,12 +1972,12 @@ importers: vue: ^3.2.31 vue-tsc: ^0.31.4 dependencies: - pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 devDependencies: '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vitejs/plugin-vue-jsx': 1.3.7 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 cypress: 9.5.0 @@ -1616,19 +1988,19 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router: + playground/typescript-pinia: specifiers: '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vue/tsconfig': ^0.1.3 + pinia: ^2.0.11 typescript: ~4.5.5 vite: ^2.8.4 vue: ^3.2.31 - vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 - vue-router: 4.0.12_vue@3.2.31 devDependencies: '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 @@ -1637,7 +2009,7 @@ importers: vite: 2.8.4 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router-cypress: + playground/typescript-pinia-cypress: specifiers: '@cypress/vite-dev-server': ^2.2.2 '@cypress/vue': ^3.1.1 @@ -1645,15 +2017,15 @@ importers: '@vitejs/plugin-vue': ^2.2.2 '@vue/tsconfig': ^0.1.3 cypress: ^9.5.0 + pinia: ^2.0.11 start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vue: ^3.2.31 - vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 - vue-router: 4.0.12_vue@3.2.31 devDependencies: '@cypress/vite-dev-server': 2.2.2_vite@2.8.4 '@cypress/vue': 3.1.1_cypress@9.5.0+vue@3.2.31 @@ -1666,8 +2038,9 @@ importers: vite: 2.8.4 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router-pinia: + playground/typescript-pinia-playwright: specifiers: + '@playwright/test': ^1.19.2 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vue/tsconfig': ^0.1.3 @@ -1675,13 +2048,12 @@ importers: typescript: ~4.5.5 vite: ^2.8.4 vue: ^3.2.31 - vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 - vue-router: 4.0.12_vue@3.2.31 devDependencies: + '@playwright/test': 1.19.2 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 @@ -1689,104 +2061,314 @@ importers: vite: 2.8.4 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router-pinia-cypress: + playground/typescript-pinia-vitest: specifiers: - '@cypress/vite-dev-server': ^2.2.2 - '@cypress/vue': ^3.1.1 + '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 + jsdom: ^19.0.0 pinia: ^2.0.11 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 + vitest: ^0.5.0 vue: ^3.2.31 - vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 - vue-router: 4.0.12_vue@3.2.31 devDependencies: - '@cypress/vite-dev-server': 2.2.2_vite@2.8.4 - '@cypress/vue': 3.1.1_cypress@9.5.0+vue@3.2.31 + '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 - start-server-and-test: 1.14.0 + jsdom: 19.0.0 typescript: 4.5.5 vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router-pinia-vitest: + playground/typescript-pinia-vitest-cypress: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 jsdom: ^19.0.0 pinia: ^2.0.11 + start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 - vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 - vue-router: 4.0.12_vue@3.2.31 devDependencies: '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 jsdom: 19.0.0 + start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router-pinia-vitest-cypress: + playground/typescript-pinia-vitest-playwright: specifiers: + '@playwright/test': ^1.19.2 '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 '@vitejs/plugin-vue': ^2.2.2 '@vue/test-utils': ^2.0.0-rc.18 '@vue/tsconfig': ^0.1.3 - cypress: ^9.5.0 jsdom: ^19.0.0 pinia: ^2.0.11 - start-server-and-test: ^1.14.0 typescript: ~4.5.5 vite: ^2.8.4 vitest: ^0.5.0 vue: ^3.2.31 - vue-router: ^4.0.12 vue-tsc: ^0.31.4 dependencies: pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 vue: 3.2.31 - vue-router: 4.0.12_vue@3.2.31 devDependencies: + '@playwright/test': 1.19.2 '@types/jsdom': 16.2.14 '@types/node': 16.11.25 '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 - playground/typescript-router-pinia-with-tests: + playground/typescript-pinia-with-tests: + specifiers: + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 + jsdom: ^19.0.0 + pinia: ^2.0.11 + start-server-and-test: ^1.14.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + devDependencies: + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 + jsdom: 19.0.0 + start-server-and-test: 1.14.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router: + specifiers: + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-cypress: + specifiers: + '@cypress/vite-dev-server': ^2.2.2 + '@cypress/vue': ^3.1.1 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 + start-server-and-test: ^1.14.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@cypress/vite-dev-server': 2.2.2_vite@2.8.4 + '@cypress/vue': 3.1.1_cypress@9.5.0+vue@3.2.31 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 + start-server-and-test: 1.14.0 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-pinia: + specifiers: + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + pinia: ^2.0.11 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-pinia-cypress: + specifiers: + '@cypress/vite-dev-server': ^2.2.2 + '@cypress/vue': ^3.1.1 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 + pinia: ^2.0.11 + start-server-and-test: ^1.14.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@cypress/vite-dev-server': 2.2.2_vite@2.8.4 + '@cypress/vue': 3.1.1_cypress@9.5.0+vue@3.2.31 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 + start-server-and-test: 1.14.0 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-pinia-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + pinia: ^2.0.11 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-pinia-vitest: + specifiers: + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + jsdom: ^19.0.0 + pinia: ^2.0.11 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + jsdom: 19.0.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-pinia-vitest-cypress: specifiers: '@types/jsdom': ^16.2.14 '@types/node': ^16.11.25 @@ -1821,6 +2403,97 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 + playground/typescript-router-pinia-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + jsdom: ^19.0.0 + pinia: ^2.0.11 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + jsdom: 19.0.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-pinia-with-tests: + specifiers: + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + cypress: ^9.5.0 + jsdom: ^19.0.0 + pinia: ^2.0.11 + start-server-and-test: ^1.14.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + pinia: 2.0.11_typescript@4.5.5+vue@3.2.31 + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + cypress: 9.5.0 + jsdom: 19.0.0 + start-server-and-test: 1.14.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-router-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/tsconfig': ^0.1.3 + typescript: ~4.5.5 + vite: ^2.8.4 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + typescript: 4.5.5 + vite: 2.8.4 + vue-tsc: 0.31.4_typescript@4.5.5 + playground/typescript-router-vitest: specifiers: '@types/jsdom': ^16.2.14 @@ -1883,6 +2556,37 @@ importers: vitest: 0.5.0_jsdom@19.0.0 vue-tsc: 0.31.4_typescript@4.5.5 + playground/typescript-router-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + jsdom: ^19.0.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-router: ^4.0.12 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + vue-router: 4.0.12_vue@3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 + jsdom: 19.0.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + playground/typescript-router-with-tests: specifiers: '@types/jsdom': ^16.2.14 @@ -1966,9 +2670,38 @@ importers: '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 '@vue/tsconfig': 0.1.3_@types+node@16.11.25 - cypress: 9.5.0 + cypress: 9.5.0 + jsdom: 19.0.0 + start-server-and-test: 1.14.0 + typescript: 4.5.5 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + vue-tsc: 0.31.4_typescript@4.5.5 + + playground/typescript-vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@types/jsdom': ^16.2.14 + '@types/node': ^16.11.25 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + '@vue/tsconfig': ^0.1.3 + jsdom: ^19.0.0 + typescript: ~4.5.5 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + vue-tsc: ^0.31.4 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@types/jsdom': 16.2.14 + '@types/node': 16.11.25 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + '@vue/tsconfig': 0.1.3_@types+node@16.11.25 jsdom: 19.0.0 - start-server-and-test: 1.14.0 typescript: 4.5.5 vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 @@ -2043,6 +2776,25 @@ importers: vite: 2.8.4 vitest: 0.5.0_jsdom@19.0.0 + playground/vitest-playwright: + specifiers: + '@playwright/test': ^1.19.2 + '@vitejs/plugin-vue': ^2.2.2 + '@vue/test-utils': ^2.0.0-rc.18 + jsdom: ^19.0.0 + vite: ^2.8.4 + vitest: ^0.5.0 + vue: ^3.2.31 + dependencies: + vue: 3.2.31 + devDependencies: + '@playwright/test': 1.19.2 + '@vitejs/plugin-vue': 2.2.2_vite@2.8.4+vue@3.2.31 + '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 + jsdom: 19.0.0 + vite: 2.8.4 + vitest: 0.5.0_jsdom@19.0.0 + playground/with-tests: specifiers: '@vitejs/plugin-vue': ^2.2.2 @@ -2112,6 +2864,12 @@ importers: pinia: 2.0.11_vue@3.2.31 vue: 3.2.31 + template/config/playwright: + specifiers: + '@playwright/test': ^1.19.2 + devDependencies: + '@playwright/test': 1.19.2 + template/config/router: specifiers: vue: ^3.2.31 @@ -2184,6 +2942,29 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/core/7.16.12: + resolution: {integrity: sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.16.7 + '@babel/generator': 7.17.3 + '@babel/helper-compilation-targets': 7.16.7_@babel+core@7.16.12 + '@babel/helper-module-transforms': 7.16.7 + '@babel/helpers': 7.17.2 + '@babel/parser': 7.17.3 + '@babel/template': 7.16.7 + '@babel/traverse': 7.17.3 + '@babel/types': 7.17.0 + convert-source-map: 1.8.0 + debug: 4.3.3 + gensync: 1.0.0-beta.2 + json5: 2.2.0 + semver: 6.3.0 + source-map: 0.5.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/core/7.17.5: resolution: {integrity: sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==} engines: {node: '>=6.9.0'} @@ -2223,6 +3004,19 @@ packages: '@babel/types': 7.17.0 dev: true + /@babel/helper-compilation-targets/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.17.0 + '@babel/core': 7.16.12 + '@babel/helper-validator-option': 7.16.7 + browserslist: 4.19.3 + semver: 6.3.0 + dev: true + /@babel/helper-compilation-targets/7.16.7_@babel+core@7.17.5: resolution: {integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==} engines: {node: '>=6.9.0'} @@ -2236,6 +3030,24 @@ packages: semver: 6.3.0 dev: true + /@babel/helper-create-class-features-plugin/7.17.1_@babel+core@7.16.12: + resolution: {integrity: sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-environment-visitor': 7.16.7 + '@babel/helper-function-name': 7.16.7 + '@babel/helper-member-expression-to-functions': 7.16.7 + '@babel/helper-optimise-call-expression': 7.16.7 + '@babel/helper-replace-supers': 7.16.7 + '@babel/helper-split-export-declaration': 7.16.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-create-class-features-plugin/7.17.1_@babel+core@7.17.5: resolution: {integrity: sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==} engines: {node: '>=6.9.0'} @@ -2346,6 +3158,13 @@ packages: '@babel/types': 7.17.0 dev: true + /@babel/helper-skip-transparent-expression-wrappers/7.16.0: + resolution: {integrity: sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.17.0 + dev: true + /@babel/helper-split-export-declaration/7.16.7: resolution: {integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==} engines: {node: '>=6.9.0'} @@ -2388,6 +3207,141 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + /@babel/plugin-proposal-class-properties/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-create-class-features-plugin': 7.17.1_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.16.12 + dev: true + + /@babel/plugin-proposal-export-namespace-from/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.16.12 + dev: true + + /@babel/plugin-proposal-logical-assignment-operators/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.16.12 + dev: true + + /@babel/plugin-proposal-nullish-coalescing-operator/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.16.12 + dev: true + + /@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.16.12 + dev: true + + /@babel/plugin-proposal-optional-chaining/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.16.12 + dev: true + + /@babel/plugin-proposal-private-methods/7.16.11_@babel+core@7.16.12: + resolution: {integrity: sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-create-class-features-plugin': 7.17.1_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-private-property-in-object/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-create-class-features-plugin': 7.17.1_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.16.12 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.16.12: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.17.5: resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -2397,6 +3351,25 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-jsx/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-jsx/7.16.7_@babel+core@7.17.5: resolution: {integrity: sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==} engines: {node: '>=6.9.0'} @@ -2407,14 +3380,131 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true - /@babel/plugin-syntax-typescript/7.16.7_@babel+core@7.17.5: - resolution: {integrity: sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==} + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.16.12: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.16.12: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.16.12: + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-typescript/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-syntax-typescript/7.16.7_@babel+core@7.17.5: + resolution: {integrity: sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.17.5 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + + /@babel/plugin-transform-modules-commonjs/7.16.8_@babel+core@7.16.12: + resolution: {integrity: sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-module-transforms': 7.16.7 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-simple-access': 7.16.7 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-react-jsx/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-module-imports': 7.16.7 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-jsx': 7.16.7_@babel+core@7.16.12 + '@babel/types': 7.17.0 + dev: true + + /@babel/plugin-transform-typescript/7.16.8_@babel+core@7.16.12: + resolution: {integrity: sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.17.5 + '@babel/core': 7.16.12 + '@babel/helper-create-class-features-plugin': 7.17.1_@babel+core@7.16.12 '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-typescript': 7.16.7_@babel+core@7.16.12 + transitivePeerDependencies: + - supports-color dev: true /@babel/plugin-transform-typescript/7.16.8_@babel+core@7.17.5: @@ -2431,6 +3521,20 @@ packages: - supports-color dev: true + /@babel/preset-typescript/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-validator-option': 7.16.7 + '@babel/plugin-transform-typescript': 7.16.8_@babel+core@7.16.12 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/template/7.16.7: resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==} engines: {node: '>=6.9.0'} @@ -2559,6 +3663,17 @@ packages: '@hapi/hoek': 9.2.1 dev: true + /@jest/types/27.5.1: + resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 17.0.19 + '@types/yargs': 16.0.4 + chalk: 4.1.2 + dev: true + /@jridgewell/resolve-uri/3.0.5: resolution: {integrity: sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==} engines: {node: '>=6.0.0'} @@ -2596,6 +3711,55 @@ packages: fastq: 1.13.0 dev: true + /@playwright/test/1.19.2: + resolution: {integrity: sha512-5oCmlYHjtOL662OxSkZBYGnoHWIQui7b4YHWNeSCYwhQjmjVcV5njRc8oBZlU8IwJgG7ZH2yhDk1haU96ygbWw==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@babel/code-frame': 7.16.7 + '@babel/core': 7.16.12 + '@babel/plugin-proposal-class-properties': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-dynamic-import': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-export-namespace-from': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-logical-assignment-operators': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-numeric-separator': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-optional-chaining': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-private-methods': 7.16.11_@babel+core@7.16.12 + '@babel/plugin-proposal-private-property-in-object': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.16.12 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.16.12 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.16.12 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.16.12 + '@babel/plugin-transform-modules-commonjs': 7.16.8_@babel+core@7.16.12 + '@babel/plugin-transform-react-jsx': 7.16.7_@babel+core@7.16.12 + '@babel/preset-typescript': 7.16.7_@babel+core@7.16.12 + babel-plugin-module-resolver: 4.1.0 + colors: 1.4.0 + commander: 8.3.0 + debug: 4.3.3 + expect: 27.2.5 + jest-matcher-utils: 27.2.5 + jpeg-js: 0.4.3 + json5: 2.2.0 + mime: 3.0.0 + minimatch: 3.0.4 + ms: 2.1.3 + open: 8.4.0 + pirates: 4.0.4 + pixelmatch: 5.2.1 + playwright-core: 1.19.2 + pngjs: 6.0.0 + rimraf: 3.0.2 + source-map-support: 0.4.18 + stack-utils: 2.0.5 + yazl: 2.5.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /@rollup/pluginutils/4.1.2: resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==} engines: {node: '>= 8.0.0'} @@ -2650,10 +3814,26 @@ packages: '@types/node': 17.0.19 dev: true + /@types/istanbul-lib-coverage/2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: true + + /@types/istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + dev: true + + /@types/istanbul-reports/3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + dev: true + /@types/jsdom/16.2.14: resolution: {integrity: sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==} dependencies: - '@types/node': 16.11.25 + '@types/node': 17.0.19 '@types/parse5': 6.0.3 '@types/tough-cookie': 4.0.1 dev: true @@ -2696,15 +3876,29 @@ packages: resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==} dev: true + /@types/stack-utils/2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: true + /@types/tough-cookie/4.0.1: resolution: {integrity: sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==} dev: true + /@types/yargs-parser/21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + dev: true + + /@types/yargs/16.0.4: + resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + /@types/yauzl/2.9.2: resolution: {integrity: sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==} requiresBuild: true dependencies: - '@types/node': 14.18.12 + '@types/node': 17.0.19 dev: true optional: true @@ -3016,6 +4210,11 @@ packages: color-convert: 2.0.1 dev: true + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /ansi-styles/6.1.0: resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} engines: {node: '>=12'} @@ -3082,6 +4281,23 @@ packages: - debug dev: true + /babel-plugin-dynamic-import-node/2.3.3: + resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} + dependencies: + object.assign: 4.1.2 + dev: true + + /babel-plugin-module-resolver/4.1.0: + resolution: {integrity: sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA==} + engines: {node: '>= 8.0.0'} + dependencies: + find-babel-config: 1.2.0 + glob: 7.2.0 + pkg-up: 3.1.0 + reselect: 4.1.5 + resolve: 1.22.0 + dev: true + /babel-walk/3.0.0-canary-5: resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} engines: {node: '>= 10.0.0'} @@ -3298,7 +4514,6 @@ packages: engines: {node: '>=0.1.90'} requiresBuild: true dev: true - optional: true /combined-stream/1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -3528,6 +4743,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /define-lazy-prop/2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: true + /define-properties/1.1.3: resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} engines: {node: '>= 0.4'} @@ -3540,6 +4760,11 @@ packages: engines: {node: '>=0.4.0'} dev: true + /diff-sequences/27.5.1: + resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: true + /dir-glob/3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3898,6 +5123,11 @@ packages: engines: {node: '>=0.8.0'} dev: true + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + /escodegen/2.0.0: resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} engines: {node: '>=6.0'} @@ -3983,10 +5213,36 @@ packages: pify: 2.3.0 dev: true + /expect/27.2.5: + resolution: {integrity: sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + ansi-styles: 5.2.0 + jest-get-type: 27.5.1 + jest-matcher-utils: 27.2.5 + jest-message-util: 27.5.1 + jest-regex-util: 27.5.1 + dev: true + /extend/3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: true + /extract-zip/2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.9.2 + transitivePeerDependencies: + - supports-color + dev: true + /extract-zip/2.0.1_supports-color@8.1.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} @@ -4055,6 +5311,21 @@ packages: to-regex-range: 5.0.1 dev: true + /find-babel-config/1.2.0: + resolution: {integrity: sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==} + engines: {node: '>=4.0.0'} + dependencies: + json5: 0.5.1 + path-exists: 3.0.0 + dev: true + + /find-up/3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + dependencies: + locate-path: 3.0.0 + dev: true + /follow-redirects/1.14.9_debug@4.3.2: resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==} engines: {node: '>=4.0'} @@ -4388,6 +5659,10 @@ packages: side-channel: 1.0.4 dev: true + /ip/1.1.5: + resolution: {integrity: sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=} + dev: true + /is-arrayish/0.2.1: resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} dev: true @@ -4431,6 +5706,12 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-docker/2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: true + /is-expression/4.0.0: resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==} dependencies: @@ -4544,6 +5825,13 @@ packages: call-bind: 1.0.2 dev: true + /is-wsl/2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: true + /isexe/2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} dev: true @@ -4552,6 +5840,51 @@ packages: resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} dev: true + /jest-diff/27.5.1: + resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + dev: true + + /jest-get-type/27.5.1: + resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: true + + /jest-matcher-utils/27.2.5: + resolution: {integrity: sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + dev: true + + /jest-message-util/27.5.1: + resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@babel/code-frame': 7.16.7 + '@jest/types': 27.5.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.9 + micromatch: 4.0.4 + pretty-format: 27.5.1 + slash: 3.0.0 + stack-utils: 2.0.5 + dev: true + + /jest-regex-util/27.5.1: + resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: true + /joi/17.6.0: resolution: {integrity: sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==} dependencies: @@ -4562,6 +5895,10 @@ packages: '@sideway/pinpoint': 2.0.0 dev: true + /jpeg-js/0.4.3: + resolution: {integrity: sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==} + dev: true + /js-stringify/1.0.2: resolution: {integrity: sha1-Fzb939lyTyijaCrcYjCufk6Weds=} dev: true @@ -4634,6 +5971,11 @@ packages: resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=} dev: true + /json5/0.5.1: + resolution: {integrity: sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=} + hasBin: true + dev: true + /json5/2.2.0: resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} engines: {node: '>=6'} @@ -4778,6 +6120,14 @@ packages: engines: {node: '>=14'} dev: true + /locate-path/3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + dev: true + /lodash.once/4.1.1: resolution: {integrity: sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=} dev: true @@ -4861,11 +6211,23 @@ packages: mime-db: 1.51.0 dev: true + /mime/3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: true + /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} dev: true + /minimatch/3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + dependencies: + brace-expansion: 1.1.11 + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -4989,6 +6351,15 @@ packages: mimic-fn: 2.1.0 dev: true + /open/8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + /optionator/0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} engines: {node: '>= 0.8.0'} @@ -5005,6 +6376,20 @@ packages: resolution: {integrity: sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=} dev: true + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-locate/3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + dependencies: + p-limit: 2.3.0 + dev: true + /p-map/4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} @@ -5012,6 +6397,11 @@ packages: aggregate-error: 3.1.0 dev: true + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + /parse-json/4.0.0: resolution: {integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=} engines: {node: '>=4'} @@ -5024,6 +6414,11 @@ packages: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} dev: true + /path-exists/3.0.0: + resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=} + engines: {node: '>=4'} + dev: true + /path-is-absolute/1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} @@ -5132,6 +6527,62 @@ packages: vue-demi: 0.12.1_vue@3.2.31 dev: false + /pirates/4.0.4: + resolution: {integrity: sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==} + engines: {node: '>= 6'} + dev: true + + /pixelmatch/5.2.1: + resolution: {integrity: sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==} + hasBin: true + dependencies: + pngjs: 4.0.1 + dev: true + + /pkg-up/3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + dependencies: + find-up: 3.0.0 + dev: true + + /playwright-core/1.19.2: + resolution: {integrity: sha512-OsL3sJZIo1UxKNWSP7zW7sk3FyUGG06YRHxHeBw51eIOxTCQRx5t+hXd0cvXashN2CHnd3hIZTs2aKa/im4hZQ==} + engines: {node: '>=12'} + hasBin: true + dependencies: + commander: 8.3.0 + debug: 4.3.3 + extract-zip: 2.0.1 + https-proxy-agent: 5.0.0 + jpeg-js: 0.4.3 + mime: 3.0.0 + pngjs: 6.0.0 + progress: 2.0.3 + proper-lockfile: 4.1.2 + proxy-from-env: 1.1.0 + rimraf: 3.0.2 + socks-proxy-agent: 6.1.1 + stack-utils: 2.0.5 + ws: 8.4.2 + yauzl: 2.10.0 + yazl: 2.5.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /pngjs/4.0.1: + resolution: {integrity: sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==} + engines: {node: '>=8.0.0'} + dev: true + + /pngjs/6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + dev: true + /postcss/8.4.6: resolution: {integrity: sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==} engines: {node: ^10 || ^12 || >=14} @@ -5156,6 +6607,20 @@ packages: engines: {node: '>=6'} dev: true + /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} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + + /progress/2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: true + /promise/7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} dependencies: @@ -5170,10 +6635,22 @@ packages: sisteransi: 1.0.5 dev: true + /proper-lockfile/4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + dependencies: + graceful-fs: 4.2.9 + retry: 0.12.0 + signal-exit: 3.0.7 + dev: true + /proxy-from-env/1.0.0: resolution: {integrity: sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=} dev: true + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true + /ps-tree/1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -5298,6 +6775,10 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /react-is/17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true + /read-pkg/3.0.0: resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=} engines: {node: '>=4'} @@ -5313,6 +6794,10 @@ packages: throttleit: 1.0.0 dev: true + /reselect/4.1.5: + resolution: {integrity: sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==} + dev: true + /resolve/1.22.0: resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} hasBin: true @@ -5330,6 +6815,11 @@ packages: signal-exit: 3.0.7 dev: true + /retry/0.12.0: + resolution: {integrity: sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=} + engines: {node: '>= 4'} + dev: true + /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -5447,6 +6937,11 @@ packages: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + /slash/4.0.0: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} @@ -5478,10 +6973,40 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true + /smart-buffer/4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: true + + /socks-proxy-agent/6.1.1: + resolution: {integrity: sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.3 + socks: 2.6.2 + transitivePeerDependencies: + - supports-color + dev: true + + /socks/2.6.2: + resolution: {integrity: sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 1.1.5 + smart-buffer: 4.2.0 + dev: true + /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-support/0.4.18: + resolution: {integrity: sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==} + dependencies: + source-map: 0.5.7 + dev: true + /source-map/0.5.7: resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} engines: {node: '>=0.10.0'} @@ -5539,6 +7064,13 @@ packages: tweetnacl: 0.14.5 dev: true + /stack-utils/2.0.5: + resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + /start-server-and-test/1.14.0: resolution: {integrity: sha512-on5ELuxO2K0t8EmNj9MtVlFqwBMxfWOhu4U7uZD1xccVpFlOQKR93CSe0u98iQzfNxRyaNTb/CdadbNllplTsw==} engines: {node: '>=6'} @@ -6167,6 +7699,19 @@ packages: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} dev: true + /ws/8.4.2: + resolution: {integrity: sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==} + 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 + dev: true + /ws/8.5.0: resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} engines: {node: '>=10.0.0'} @@ -6205,6 +7750,12 @@ packages: fd-slicer: 1.1.0 dev: true + /yazl/2.5.1: + resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} + dependencies: + buffer-crc32: 0.2.13 + dev: true + /zx/5.1.0: resolution: {integrity: sha512-J62b/7sFwqt4zbmPbeKJGwZlvW2X3ynyfWHZPr+xLAGF80h4aY4SgXaadMjljpvIbWGOwRmK38vwEdi5XgUpcQ==} engines: {node: '>= 16.0.0'} diff --git a/scripts/snapshot.mjs b/scripts/snapshot.mjs index 0a859fe0c..4038463cf 100644 --- a/scripts/snapshot.mjs +++ b/scripts/snapshot.mjs @@ -3,7 +3,8 @@ import 'zx/globals' $.verbose = false -const featureFlags = ['typescript', 'jsx', 'router', 'pinia', 'vitest', 'cypress'] +const featureFlags = ['typescript', 'jsx', 'router', 'pinia', 'vitest', 'cypress', 'playwright'] +const featureFlagsDenylist = [['cypress', 'playwright']] // The following code & comments are generated by GitHub CoPilot. function fullCombination(arr) { @@ -34,9 +35,12 @@ function fullCombination(arr) { return combinations } -const flagCombinations = fullCombination(featureFlags) +let flagCombinations = fullCombination(featureFlags) flagCombinations.push(['default']) +// Filter out combinations that are not allowed +flagCombinations = flagCombinations.filter(combination => !featureFlagsDenylist.some(denylist => denylist.every(flag => combination.includes(flag)))) + // `--with-tests` are equivalent of `--vitest --cypress` // Previously it means `--cypress` without `--vitest`. // Here we generate the snapshots only for the sake of easier comparison with older templates. diff --git a/scripts/test.mjs b/scripts/test.mjs index a8c2eaa73..6f789b199 100644 --- a/scripts/test.mjs +++ b/scripts/test.mjs @@ -1,9 +1,16 @@ #!/usr/bin/env zx import 'zx/globals' +// Vitest would otherwise enable watch mode by default. +process.env.CI = '1'; + const playgroundDir = path.resolve(__dirname, '../playground/') +let projects = fs.readdirSync(playgroundDir).filter(name => !name.startsWith('.')); + +if (process.argv[3]) + projects = projects.filter(project => project.includes(process.argv[3])) -for (const projectName of fs.readdirSync(playgroundDir)) { +for (const projectName of projects) { if (projectName.includes('vitest')) { cd(path.resolve(playgroundDir, projectName)) @@ -11,38 +18,34 @@ for (const projectName of fs.readdirSync(playgroundDir)) { await $`pnpm test:unit` } - if (projectName.includes('cypress')) { - cd(path.resolve(playgroundDir, projectName)) + cd(path.resolve(playgroundDir, projectName)) + + const packageJSON = require(path.resolve(playgroundDir, projectName, 'package.json')); - console.log(`Building ${projectName}`) - await $`pnpm build` + console.log(`Building ${projectName}`) + await $`pnpm build` + if ('cypress' in packageJSON.devDependencies) { console.log(`Running e2e tests in ${projectName}`) await $`pnpm test:e2e:ci` + } + if ('@playwright/test' in packageJSON.devDependencies) { + await $`pnpm playwright install --with-deps` + await $`pnpm test:e2e` + } + if ('test:unit:ci' in packageJSON.scripts) { // Without Vitest, the project will use Cypress Component Testing for unit testing - if (!projectName.includes('vitest')) { - // Cypress Component Testing is flaky in CI environment, so we need to tolerate the errors. - try { - await `pnpm test:unit:ci` - } catch (e) { - console.error(`Component Testing in ${projectName} fails:`) - console.error(e) - } + // Cypress Component Testing is flaky in CI environment, so we need to tolerate the errors. + try { + await $`pnpm test:unit:ci` + } catch (e) { + console.error(`Component Testing in ${projectName} fails:`) + console.error(e) + process.exit(1) } - } - - // equivalent of `--vitest --cypress` - if (projectName.endsWith('with-tests')) { - cd(path.resolve(playgroundDir, projectName)) - + } else if ('test:unit' in packageJSON.scripts) { console.log(`Running unit tests in ${projectName}`) await $`pnpm test:unit` - - console.log(`Building ${projectName}`) - await $`pnpm build` - - console.log(`Running e2e tests in ${projectName}`) - await $`pnpm test:e2e:ci` } } diff --git a/template/base/_gitignore b/template/base/.gitignore similarity index 100% rename from template/base/_gitignore rename to template/base/.gitignore diff --git a/template/config/playwright/.github/workflows/playwright.yml b/template/config/playwright/.github/workflows/playwright.yml new file mode 100644 index 000000000..9510a7b29 --- /dev/null +++ b/template/config/playwright/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '14.x' + - name: Install dependencies + run: npm ci + - name: Install Playwright + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npm run test:e2e + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/template/config/playwright/.gitignore b/template/config/playwright/.gitignore new file mode 100644 index 000000000..aaa9103e4 --- /dev/null +++ b/template/config/playwright/.gitignore @@ -0,0 +1,2 @@ +test-results/ +playwright-report/ diff --git a/template/config/playwright/e2e/example.spec.js b/template/config/playwright/e2e/example.spec.js new file mode 100644 index 000000000..ab42855fa --- /dev/null +++ b/template/config/playwright/e2e/example.spec.js @@ -0,0 +1,411 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // Create 1st todo. + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.locator('.view label')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await page.locator('.new-todo').fill(TODO_ITEMS[1]); + await page.locator('.new-todo').press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.locator('.view label')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // Create one todo item. + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + + // Check that input is empty. + await expect(page.locator('.new-todo')).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // Check test using different methods. + await expect(page.locator('.todo-count')).toHaveText('3 items left'); + await expect(page.locator('.todo-count')).toContainText('3'); + await expect(page.locator('.todo-count')).toHaveText(/3/); + + // Check all items in one call. + await expect(page.locator('.view label')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should show #main and #footer when items added', async ({ page }) => { + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + + await expect(page.locator('.main')).toBeVisible(); + await expect(page.locator('.footer')).toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.locator('.toggle-all').check(); + + // Ensure all todos have 'completed' class. + await expect(page.locator('.todo-list li')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + // Check and then immediately uncheck. + await page.locator('.toggle-all').check(); + await page.locator('.toggle-all').uncheck(); + + // Should be no completed classes. + await expect(page.locator('.todo-list li')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.locator('.toggle-all'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.locator('.todo-list li').nth(0); + await firstTodo.locator('.toggle').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.locator('.toggle').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } + + // Check first item. + const firstTodo = page.locator('.todo-list li').nth(0); + await firstTodo.locator('.toggle').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.locator('.todo-list li').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.locator('.toggle').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } + + const firstTodo = page.locator('.todo-list li').nth(0); + const secondTodo = page.locator('.todo-list li').nth(1); + await firstTodo.locator('.toggle').check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodo.locator('.toggle').uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.locator('.todo-list li'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.locator('.edit')).toHaveValue(TODO_ITEMS[1]); + await secondTodo.locator('.edit').fill('buy some sausages'); + await secondTodo.locator('.edit').press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.locator('.todo-list li').nth(1); + await todoItem.dblclick(); + await expect(todoItem.locator('.toggle')).not.toBeVisible(); + await expect(todoItem.locator('label')).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').fill('buy some sausages'); + await todoItems.nth(1).locator('.edit').dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').fill(' buy some sausages '); + await todoItems.nth(1).locator('.edit').press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').fill(''); + await todoItems.nth(1).locator('.edit').press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toContainText('1'); + + await page.locator('.new-todo').fill(TODO_ITEMS[1]); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.locator('.clear-completed')).toHaveText('Clear completed'); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).locator('.toggle').check(); + await page.locator('.clear-completed').click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.locator('.clear-completed').click(); + await expect(page.locator('.clear-completed')).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + for (const item of TODO_ITEMS.slice(0, 2)) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } + + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(0).locator('.toggle').check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.locator('.filters >> text=Active').click(); + await expect(page.locator('.todo-list li')).toHaveCount(2); + await expect(page.locator('.todo-list li')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.locator('.filters >> text=All').click(); + await expect(page.locator('.todo-list li')).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.locator('.filters >> text=Active').click(); + }); + + await test.step('Showing completed items', async () => { + await page.locator('.filters >> text=Completed').click(); + }); + + await expect(page.locator('.todo-list li')).toHaveCount(1); + await page.goBack(); + await expect(page.locator('.todo-list li')).toHaveCount(2); + await page.goBack(); + await expect(page.locator('.todo-list li')).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.locator('.filters >> text=Completed').click(); + await expect(page.locator('.todo-list li')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.locator('.filters >> text=Active').click(); + await page.locator('.filters >> text=Completed').click(); + await page.locator('.filters >> text=All').click(); + await expect(page.locator('.todo-list li')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.locator('.filters >> text=All')).toHaveClass('selected'); + await page.locator('.filters >> text=Active').click(); + // Page change - active items. + await expect(page.locator('.filters >> text=Active')).toHaveClass('selected'); + await page.locator('.filters >> text=Completed').click(); + // Page change - completed items. + await expect(page.locator('.filters >> text=Completed')).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page) { + for (const item of TODO_ITEMS) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } +} + +/** + * @param {import('@playwright/test').Page} page + * @param {number} expected + */ + async function checkNumberOfTodosInLocalStorage(page, expected) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {number} expected + */ + async function checkNumberOfCompletedTodosInLocalStorage(page, expected) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter(i => i.completed).length === e; + }, expected); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} title + */ +async function checkTodosInLocalStorage(page, title) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map(i => i.title).includes(t); + }, title); +} \ No newline at end of file diff --git a/template/config/playwright/e2e/example.spec.ts b/template/config/playwright/e2e/example.spec.ts new file mode 100644 index 000000000..fcbf91236 --- /dev/null +++ b/template/config/playwright/e2e/example.spec.ts @@ -0,0 +1,398 @@ +import { test, expect, Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // Create 1st todo. + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.locator('.view label')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await page.locator('.new-todo').fill(TODO_ITEMS[1]); + await page.locator('.new-todo').press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.locator('.view label')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // Create one todo item. + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + + // Check that input is empty. + await expect(page.locator('.new-todo')).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // Check test using different methods. + await expect(page.locator('.todo-count')).toHaveText('3 items left'); + await expect(page.locator('.todo-count')).toContainText('3'); + await expect(page.locator('.todo-count')).toHaveText(/3/); + + // Check all items in one call. + await expect(page.locator('.view label')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should show #main and #footer when items added', async ({ page }) => { + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + + await expect(page.locator('.main')).toBeVisible(); + await expect(page.locator('.footer')).toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.locator('.toggle-all').check(); + + // Ensure all todos have 'completed' class. + await expect(page.locator('.todo-list li')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + // Check and then immediately uncheck. + await page.locator('.toggle-all').check(); + await page.locator('.toggle-all').uncheck(); + + // Should be no completed classes. + await expect(page.locator('.todo-list li')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.locator('.toggle-all'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.locator('.todo-list li').nth(0); + await firstTodo.locator('.toggle').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.locator('.toggle').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } + + // Check first item. + const firstTodo = page.locator('.todo-list li').nth(0); + await firstTodo.locator('.toggle').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.locator('.todo-list li').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.locator('.toggle').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } + + const firstTodo = page.locator('.todo-list li').nth(0); + const secondTodo = page.locator('.todo-list li').nth(1); + await firstTodo.locator('.toggle').check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodo.locator('.toggle').uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.locator('.todo-list li'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.locator('.edit')).toHaveValue(TODO_ITEMS[1]); + await secondTodo.locator('.edit').fill('buy some sausages'); + await secondTodo.locator('.edit').press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.locator('.todo-list li').nth(1); + await todoItem.dblclick(); + await expect(todoItem.locator('.toggle')).not.toBeVisible(); + await expect(todoItem.locator('label')).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').fill('buy some sausages'); + await todoItems.nth(1).locator('.edit').dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').fill(' buy some sausages '); + await todoItems.nth(1).locator('.edit').press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').fill(''); + await todoItems.nth(1).locator('.edit').press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).locator('.edit').press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + await page.locator('.new-todo').fill(TODO_ITEMS[0]); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toContainText('1'); + + await page.locator('.new-todo').fill(TODO_ITEMS[1]); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.locator('.clear-completed')).toHaveText('Clear completed'); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(1).locator('.toggle').check(); + await page.locator('.clear-completed').click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.locator('.clear-completed').click(); + await expect(page.locator('.clear-completed')).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + for (const item of TODO_ITEMS.slice(0, 2)) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } + + const todoItems = page.locator('.todo-list li'); + await todoItems.nth(0).locator('.toggle').check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.locator('.filters >> text=Active').click(); + await expect(page.locator('.todo-list li')).toHaveCount(2); + await expect(page.locator('.todo-list li')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.locator('.filters >> text=All').click(); + await expect(page.locator('.todo-list li')).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.locator('.filters >> text=Active').click(); + }); + + await test.step('Showing completed items', async () => { + await page.locator('.filters >> text=Completed').click(); + }); + + await expect(page.locator('.todo-list li')).toHaveCount(1); + await page.goBack(); + await expect(page.locator('.todo-list li')).toHaveCount(2); + await page.goBack(); + await expect(page.locator('.todo-list li')).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.locator('.filters >> text=Completed').click(); + await expect(page.locator('.todo-list li')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.locator('.todo-list li .toggle').nth(1).check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.locator('.filters >> text=Active').click(); + await page.locator('.filters >> text=Completed').click(); + await page.locator('.filters >> text=All').click(); + await expect(page.locator('.todo-list li')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.locator('.filters >> text=All')).toHaveClass('selected'); + await page.locator('.filters >> text=Active').click(); + // Page change - active items. + await expect(page.locator('.filters >> text=Active')).toHaveClass('selected'); + await page.locator('.filters >> text=Completed').click(); + // Page change - completed items. + await expect(page.locator('.filters >> text=Completed')).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + for (const item of TODO_ITEMS) { + await page.locator('.new-todo').fill(item); + await page.locator('.new-todo').press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter(i => i.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map(i => i.title).includes(t); + }, title); +} diff --git a/template/config/playwright/e2e/vue.spec.js b/template/config/playwright/e2e/vue.spec.js new file mode 100644 index 000000000..0d8af9fa4 --- /dev/null +++ b/template/config/playwright/e2e/vue.spec.js @@ -0,0 +1,6 @@ +const { test, expect } = require('@playwright/test'); + +test('visits the app root url', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toHaveText('You did it!'); +}) diff --git a/template/config/playwright/e2e/vue.spec.ts b/template/config/playwright/e2e/vue.spec.ts new file mode 100644 index 000000000..42a8b9ee8 --- /dev/null +++ b/template/config/playwright/e2e/vue.spec.ts @@ -0,0 +1,6 @@ +import { test, expect } from '@playwright/test'; + +test('visits the app root url', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toHaveText('You did it!'); +}) diff --git a/template/config/playwright/package.json b/template/config/playwright/package.json new file mode 100644 index 000000000..b40769e83 --- /dev/null +++ b/template/config/playwright/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "test:e2e": "playwright test" + }, + "devDependencies": { + "@playwright/test": "^1.19.2" + } +} diff --git a/template/config/playwright/playwright.config.js b/template/config/playwright/playwright.config.js new file mode 100644 index 000000000..b7594da8d --- /dev/null +++ b/template/config/playwright/playwright.config.js @@ -0,0 +1,107 @@ +// @ts-check +const { devices } = require('@playwright/test') + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + * @type {import('@playwright/test').PlaywrightTestConfig} + */ +const config = { + testDir: './e2e', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* 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', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Only on CI systems run the tests headless */ + headless: !!process.env.CI + }, + + /* 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: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + port: 3000 + } +} + +module.exports = config diff --git a/template/config/playwright/playwright.config.ts b/template/config/playwright/playwright.config.ts new file mode 100644 index 000000000..53f669e9f --- /dev/null +++ b/template/config/playwright/playwright.config.ts @@ -0,0 +1,106 @@ +import type { PlaywrightTestConfig } from '@playwright/test' +import { devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './e2e', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* 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', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Only on CI systems run the tests headless */ + headless: !!process.env.CI + }, + + /* 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: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + port: 3000 + } +} + +export default config diff --git a/template/config/vitest/package.json b/template/config/vitest/package.json index 391760912..114b16a1c 100644 --- a/template/config/vitest/package.json +++ b/template/config/vitest/package.json @@ -1,6 +1,6 @@ { "scripts": { - "test:unit": "vitest --environment jsdom" + "test:unit": "vitest --environment jsdom --root src/" }, "dependencies": { "vue": "^3.2.31" diff --git a/utils/generateReadme.ts b/utils/generateReadme.ts index 16f7b1c3c..021910b2d 100644 --- a/utils/generateReadme.ts +++ b/utils/generateReadme.ts @@ -21,6 +21,7 @@ export default function generateReadme({ needsTypeScript, needsCypress, needsCypressCT, + needsPlaywright, needsVitest, needsEslint }) { @@ -94,6 +95,23 @@ ${getCommand(packageManager, 'test:e2e')} # or \`${getCommand( ` } + if (needsPlaywright) { + npmScriptsDescriptions += ` +### Run End-to-End Tests with [Playwright](https://playwright.dev) + +\`\`\`sh +# Runs the end-to-end tests. +${getCommand(packageManager, 'test:e2e')} +# Runs the tests only on Desktop Chrome. +${getCommand(packageManager, 'test:e2e -- --project="Desktop Chrome"')} +# Runs the tests of a specific file. +${getCommand(packageManager, 'test:e2e -- tests/example.spec.ts')} +# Runs the tests in debug mode. +${getCommand(packageManager, 'test:e2e -- --debug')} +\`\`\` +` + } + if (needsEslint) { npmScriptsDescriptions += ` ### Lint with [ESLint](https://eslint.org/) diff --git a/utils/renderTemplate.ts b/utils/renderTemplate.ts index 971471434..abad602c5 100644 --- a/utils/renderTemplate.ts +++ b/utils/renderTemplate.ts @@ -41,9 +41,13 @@ function renderTemplate(src, dest) { return } - if (filename.startsWith('_')) { - // rename `_file` to `.file` - dest = path.resolve(path.dirname(dest), filename.replace(/^_/, '.')) + if (filename === '.gitignore') { + // merge instead of overwriting + let existing = fs.existsSync(dest) ? fs.readFileSync(dest).toString() : '' + if (existing.length > 0 && existing[existing.length - 1] !== '\n') existing += '\n' + existing += fs.readFileSync(src).toString() + fs.writeFileSync(dest, existing) + return } fs.copyFileSync(src, dest)