diff --git a/.eslintignore b/.eslintignore index dd5acd0..82f61ec 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,6 @@ public node_modules *.esm.js out +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/.eslintrc.json b/.eslintrc.json index c5fbd95..323ddb3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,6 +22,10 @@ "consistent-return": "off", "no-else-return": "off", "no-nested-ternary": "off", + "import/no-extraneous-dependencies": [ + "error", + { "devDependencies": ["tests/**", "playwright.config.ts"] } + ], "import/prefer-default-export": "off", "react/function-component-definition": "off", "react/jsx-key": "off", diff --git a/.gitignore b/.gitignore index b274bde..dbaa84d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,8 @@ tsconfig.tsbuildinfo # firebase .firebase firebase-debug.log + +# playwright +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/.prettierignore b/.prettierignore index 84f2918..e582319 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,6 @@ build .contentlayer pnpm-lock.yaml out +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 00a3d91..f221475 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "irongeek.vscode-env", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "ms-playwright.playwright" ] } diff --git a/package.json b/package.json index 9599b41..4b602fe 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "build": "next build", "analyze": "ANALYZE=true next build", "start": "serve out", + "test": "playwright test", + "test:update": "playwright test --update-snapshots", + "test:report": "playwright show-report", "deploy": "firebase deploy --project devtoysweb", "check": "pnpm typecheck && pnpm lint && pnpm format:check", "fix": "pnpm lint:fix && pnpm format:write", @@ -56,6 +59,7 @@ "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.0.1", "@next/bundle-analyzer": "13.4.12", + "@playwright/test": "^1.39.0", "@types/create-hash": "^1.2.2", "@types/html-escaper": "^3.0.0", "@types/js-yaml": "^4.0.5", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..3d2389e --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,58 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: "http://127.0.0.1:3000", + trace: "on-first-retry", + screenshot: "only-on-failure", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "pnpm start", + url: "http://127.0.0.1:3000", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 452de8a..35ad981 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: '6.0' dependencies: '@monaco-editor/react': specifier: ^4.5.1 - version: 4.5.1(monaco-editor@0.41.0)(react-dom@18.2.0)(react@18.2.0) + version: 4.5.1(monaco-editor@0.44.0)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-accordion': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0) @@ -99,6 +99,9 @@ devDependencies: '@next/bundle-analyzer': specifier: 13.4.12 version: 13.4.12 + '@playwright/test': + specifier: ^1.39.0 + version: 1.39.0 '@types/create-hash': specifier: ^1.2.2 version: 1.2.2 @@ -134,10 +137,10 @@ devDependencies: version: 8.41.0 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.28.0)(eslint-plugin-jsx-a11y@6.7.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.32.2)(eslint@8.41.0) + version: 19.0.4(eslint-plugin-import@2.28.1)(eslint-plugin-jsx-a11y@6.7.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.32.2)(eslint@8.41.0) eslint-config-airbnb-typescript: specifier: ^17.0.0 - version: 17.0.0(@typescript-eslint/eslint-plugin@5.59.7)(@typescript-eslint/parser@5.59.7)(eslint-plugin-import@2.28.0)(eslint@8.41.0) + version: 17.0.0(@typescript-eslint/eslint-plugin@5.59.7)(@typescript-eslint/parser@5.59.7)(eslint-plugin-import@2.28.1)(eslint@8.41.0) eslint-config-next: specifier: ~13.4.4 version: 13.4.4(eslint@8.41.0)(typescript@5.0.4) @@ -326,11 +329,11 @@ packages: dependencies: regenerator-runtime: 0.13.11 - /@babel/runtime@7.22.6: - resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==} + /@babel/runtime@7.23.2: + resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} engines: {node: '>=6.9.0'} dependencies: - regenerator-runtime: 0.13.11 + regenerator-runtime: 0.14.0 dev: false /@babel/template@7.21.9: @@ -492,24 +495,24 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - /@monaco-editor/loader@1.3.3(monaco-editor@0.41.0): + /@monaco-editor/loader@1.3.3(monaco-editor@0.44.0): resolution: {integrity: sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==} peerDependencies: monaco-editor: '>= 0.21.0 < 1' dependencies: - monaco-editor: 0.41.0 + monaco-editor: 0.44.0 state-local: 1.0.7 dev: false - /@monaco-editor/react@4.5.1(monaco-editor@0.41.0)(react-dom@18.2.0)(react@18.2.0): + /@monaco-editor/react@4.5.1(monaco-editor@0.44.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==} peerDependencies: monaco-editor: '>= 0.25.0 < 1' react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@monaco-editor/loader': 1.3.3(monaco-editor@0.41.0) - monaco-editor: 0.41.0 + '@monaco-editor/loader': 1.3.3(monaco-editor@0.44.0) + monaco-editor: 0.44.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -644,6 +647,14 @@ packages: tslib: 2.6.1 dev: true + /@playwright/test@1.39.0: + resolution: {integrity: sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright: 1.39.0 + dev: true + /@polka/url@1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true @@ -1778,18 +1789,29 @@ packages: is-string: 1.0.7 dev: true + /array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + get-intrinsic: 1.2.1 + is-string: 1.0.7 + dev: true + /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} dev: true - /array.prototype.findlastindex@1.2.2: - resolution: {integrity: sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==} + /array.prototype.findlastindex@1.2.3: + resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 + define-properties: 1.2.1 + es-abstract: 1.22.2 es-shim-unscopables: 1.0.0 get-intrinsic: 1.2.1 dev: true @@ -1804,6 +1826,16 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + es-shim-unscopables: 1.0.0 + dev: true + /array.prototype.flatmap@1.3.1: resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} engines: {node: '>= 0.4'} @@ -1814,6 +1846,16 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + es-shim-unscopables: 1.0.0 + dev: true + /array.prototype.tosorted@1.1.1: resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} dependencies: @@ -1824,13 +1866,14 @@ packages: get-intrinsic: 1.2.1 dev: true - /arraybuffer.prototype.slice@1.0.1: - resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} + /arraybuffer.prototype.slice@1.0.2: + resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.0 call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 + es-abstract: 1.22.2 get-intrinsic: 1.2.1 is-array-buffer: 3.0.2 is-shared-array-buffer: 1.0.2 @@ -2033,7 +2076,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -2212,7 +2255,7 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} dependencies: - '@babel/runtime': 7.22.6 + '@babel/runtime': 7.23.2 dev: false /debug@2.6.9: @@ -2304,6 +2347,15 @@ packages: titleize: 3.0.0 dev: true + /define-data-property@1.1.0: + resolution: {integrity: sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + gopd: 1.0.1 + has-property-descriptors: 1.0.0 + dev: true + /define-lazy-prop@3.0.0: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} @@ -2317,6 +2369,15 @@ packages: object-keys: 1.1.1 dev: true + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.0 + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + /detect-libc@2.0.1: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} @@ -2426,22 +2487,22 @@ packages: which-typed-array: 1.1.9 dev: true - /es-abstract@1.22.1: - resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} + /es-abstract@1.22.2: + resolution: {integrity: sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==} engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.1 + arraybuffer.prototype.slice: 1.0.2 available-typed-arrays: 1.0.5 call-bind: 1.0.2 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 - function.prototype.name: 1.1.5 + function.prototype.name: 1.1.6 get-intrinsic: 1.2.1 get-symbol-description: 1.0.0 globalthis: 1.0.3 gopd: 1.0.1 - has: 1.0.3 + has: 1.0.4 has-property-descriptors: 1.0.0 has-proto: 1.0.1 has-symbols: 1.0.3 @@ -2457,12 +2518,12 @@ packages: object-inspect: 1.12.3 object-keys: 1.1.1 object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - safe-array-concat: 1.0.0 + regexp.prototype.flags: 1.5.1 + safe-array-concat: 1.0.1 safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 + string.prototype.trim: 1.2.8 + string.prototype.trimend: 1.0.7 + string.prototype.trimstart: 1.0.7 typed-array-buffer: 1.0.0 typed-array-byte-length: 1.0.0 typed-array-byte-offset: 1.0.0 @@ -2522,7 +2583,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.28.0)(eslint@8.41.0): + /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.28.1)(eslint@8.41.0): resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -2531,13 +2592,13 @@ packages: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.41.0 - eslint-plugin-import: 2.28.0(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) object.assign: 4.1.4 object.entries: 1.1.6 semver: 6.3.0 dev: true - /eslint-config-airbnb-typescript@17.0.0(@typescript-eslint/eslint-plugin@5.59.7)(@typescript-eslint/parser@5.59.7)(eslint-plugin-import@2.28.0)(eslint@8.41.0): + /eslint-config-airbnb-typescript@17.0.0(@typescript-eslint/eslint-plugin@5.59.7)(@typescript-eslint/parser@5.59.7)(eslint-plugin-import@2.28.1)(eslint@8.41.0): resolution: {integrity: sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==} peerDependencies: '@typescript-eslint/eslint-plugin': ^5.13.0 @@ -2548,11 +2609,11 @@ packages: '@typescript-eslint/eslint-plugin': 5.59.7(@typescript-eslint/parser@5.59.7)(eslint@8.41.0)(typescript@5.0.4) '@typescript-eslint/parser': 5.59.7(eslint@8.41.0)(typescript@5.0.4) eslint: 8.41.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.28.0)(eslint@8.41.0) - eslint-plugin-import: 2.28.0(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.28.1)(eslint@8.41.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) dev: true - /eslint-config-airbnb@19.0.4(eslint-plugin-import@2.28.0)(eslint-plugin-jsx-a11y@6.7.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.32.2)(eslint@8.41.0): + /eslint-config-airbnb@19.0.4(eslint-plugin-import@2.28.1)(eslint-plugin-jsx-a11y@6.7.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.32.2)(eslint@8.41.0): resolution: {integrity: sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==} engines: {node: ^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -2563,8 +2624,8 @@ packages: eslint-plugin-react-hooks: ^4.3.0 dependencies: eslint: 8.41.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.28.0)(eslint@8.41.0) - eslint-plugin-import: 2.28.0(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.28.1)(eslint@8.41.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.41.0) eslint-plugin-react: 7.32.2(eslint@8.41.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.41.0) @@ -2616,12 +2677,12 @@ packages: - supports-color dev: true - /eslint-import-resolver-node@0.3.8: - resolution: {integrity: sha512-tEe+Pok22qIGaK3KoMP+N96GVDS66B/zreoVVmiavLvRUEmGRtvb4B8wO9jwnb8d2lvHtrkhZ7UD73dWBVnf/Q==} + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: debug: 3.2.7 is-core-module: 2.13.0 - resolve: 1.22.4 + resolve: 1.22.8 transitivePeerDependencies: - supports-color dev: true @@ -2713,8 +2774,8 @@ packages: - supports-color dev: true - /eslint-plugin-import@2.28.0(@typescript-eslint/parser@5.59.7)(eslint@8.41.0): - resolution: {integrity: sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==} + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.59.7)(eslint@8.41.0): + resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2724,23 +2785,22 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.59.7(eslint@8.41.0)(typescript@5.0.4) - array-includes: 3.1.6 - array.prototype.findlastindex: 1.2.2 - array.prototype.flat: 1.3.1 - array.prototype.flatmap: 1.3.1 + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 eslint: 8.41.0 - eslint-import-resolver-node: 0.3.8 + eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.7)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.41.0) - has: 1.0.3 + has: 1.0.4 is-core-module: 2.13.0 is-glob: 4.0.3 minimatch: 3.1.2 - object.fromentries: 2.0.6 - object.groupby: 1.0.0 - object.values: 1.1.6 - resolve: 1.22.4 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 semver: 6.3.1 tsconfig-paths: 3.14.2 transitivePeerDependencies: @@ -3052,6 +3112,14 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true + dev: true + optional: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true optional: true /function-bind@1.1.1: @@ -3067,6 +3135,16 @@ packages: functions-have-names: 1.2.3 dev: true + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + functions-have-names: 1.2.3 + dev: true + /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true @@ -3272,6 +3350,11 @@ packages: dependencies: function-bind: 1.1.1 + /has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + dev: true + /hash-base@3.1.0: resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} engines: {node: '>=4'} @@ -3405,7 +3488,7 @@ packages: /is-core-module@2.13.0: resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: - has: 1.0.3 + has: 1.0.4 dev: true /is-date-object@1.0.5: @@ -3769,8 +3852,8 @@ packages: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: false - /monaco-editor@0.41.0: - resolution: {integrity: sha512-1o4olnZJsiLmv5pwLEAmzHTE/5geLKQ07BrGxlF4Ri/AXAc2yyDGZwHjiTqD8D/ROKUZmwMA28A+yEowLNOEcA==} + /monaco-editor@0.44.0: + resolution: {integrity: sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==} dev: false /mrmime@1.0.1: @@ -3967,12 +4050,21 @@ packages: es-abstract: 1.21.2 dev: true - /object.groupby@1.0.0: - resolution: {integrity: sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==} + /object.fromentries@2.0.7: + resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + + /object.groupby@1.0.1: + resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 get-intrinsic: 1.2.1 dev: true @@ -3992,6 +4084,15 @@ packages: es-abstract: 1.21.2 dev: true + /object.values@1.1.7: + resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + /on-headers@1.0.2: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} engines: {node: '>= 0.8'} @@ -4114,6 +4215,22 @@ packages: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} + /playwright-core@1.39.0: + resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==} + engines: {node: '>=16'} + hasBin: true + dev: true + + /playwright@1.39.0: + resolution: {integrity: sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright-core: 1.39.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /postcss-import@15.1.0(postcss@8.4.23): resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -4386,6 +4503,10 @@ packages: /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + /regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} engines: {node: '>= 0.4'} @@ -4395,6 +4516,15 @@ packages: functions-have-names: 1.2.3 dev: true + /regexp.prototype.flags@1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + set-function-name: 2.0.1 + dev: true + /registry-auth-token@3.3.2: resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} dependencies: @@ -4431,8 +4561,8 @@ packages: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - /resolve@1.22.4: - resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: is-core-module: 2.13.0 @@ -4479,8 +4609,8 @@ packages: dependencies: queue-microtask: 1.2.3 - /safe-array-concat@1.0.0: - resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} + /safe-array-concat@1.0.1: + resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} engines: {node: '>=0.4'} dependencies: call-bind: 1.0.2 @@ -4559,6 +4689,15 @@ packages: - supports-color dev: true + /set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.0 + dev: true + /sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true @@ -4703,6 +4842,15 @@ packages: es-abstract: 1.21.2 dev: true + /string.prototype.trim@1.2.8: + resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + /string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: @@ -4711,6 +4859,14 @@ packages: es-abstract: 1.21.2 dev: true + /string.prototype.trimend@1.0.7: + resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + /string.prototype.trimstart@1.0.6: resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} dependencies: @@ -4719,6 +4875,14 @@ packages: es-abstract: 1.21.2 dev: true + /string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: diff --git a/tests/app/page.spec.ts b/tests/app/page.spec.ts new file mode 100644 index 0000000..5934f11 --- /dev/null +++ b/tests/app/page.spec.ts @@ -0,0 +1,6 @@ +import { expect, test } from "@playwright/test"; + +test("VRT", async ({ page }) => { + await page.goto("/"); + await expect(page).toHaveScreenshot(); +}); diff --git a/tests/app/page.spec.ts-snapshots/VRT-1-chromium-darwin.png b/tests/app/page.spec.ts-snapshots/VRT-1-chromium-darwin.png new file mode 100644 index 0000000..851b93c Binary files /dev/null and b/tests/app/page.spec.ts-snapshots/VRT-1-chromium-darwin.png differ diff --git a/tests/app/page.spec.ts-snapshots/VRT-1-firefox-darwin.png b/tests/app/page.spec.ts-snapshots/VRT-1-firefox-darwin.png new file mode 100644 index 0000000..9a5747a Binary files /dev/null and b/tests/app/page.spec.ts-snapshots/VRT-1-firefox-darwin.png differ diff --git a/tests/app/page.spec.ts-snapshots/VRT-1-webkit-darwin.png b/tests/app/page.spec.ts-snapshots/VRT-1-webkit-darwin.png new file mode 100644 index 0000000..304c478 Binary files /dev/null and b/tests/app/page.spec.ts-snapshots/VRT-1-webkit-darwin.png differ diff --git a/tests/components/sidebar/tool-groups.spec.ts b/tests/components/sidebar/tool-groups.spec.ts new file mode 100644 index 0000000..be5e972 --- /dev/null +++ b/tests/components/sidebar/tool-groups.spec.ts @@ -0,0 +1,35 @@ +import { expect, Page, test } from "@playwright/test"; + +function navExpandedButton(page: Page) { + const nav = page.getByRole("navigation"); + return nav.getByRole("button", { expanded: true }); +} + +test.describe("initial open/close states of accordion", () => { + test("closed when go to /", async ({ page }) => { + const button = navExpandedButton(page); + + await page.goto("/"); + await expect(button).not.toBeVisible(); + }); + + test("closed when go to group pages", async ({ page }) => { + const button = navExpandedButton(page); + + await page.goto("/converters"); + await expect(button).not.toBeVisible(); + + await page.goto("/formatters"); + await expect(button).not.toBeVisible(); + }); + + test("opened when go to tool pages", async ({ page }) => { + const button = navExpandedButton(page); + + await page.goto("/converters/number-base"); + await expect(button).toBeVisible(); + + await page.goto("/formatters/json"); + await expect(button).toBeVisible(); + }); +});