From 04610fe2d2e953c4721b8fc6bbb0518dec9086f7 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 13:12:21 +0400 Subject: [PATCH 01/11] update csstype version --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44d3d36..af3ff91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10960,7 +10960,7 @@ "@emotion/memoize": "^0.8.1", "@emotion/unitless": "^0.8.1", "@emotion/utils": "^1.2.1", - "csstype": "3.1.2" + "csstype": "^3.0.2" } }, "@emotion/sheet": { @@ -11381,7 +11381,7 @@ "@mui/utils": "^5.15.0", "@types/react-transition-group": "^4.4.9", "clsx": "^2.0.0", - "csstype": "3.1.2", + "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -11411,7 +11411,7 @@ "requires": { "@babel/runtime": "^7.23.5", "@emotion/cache": "^11.11.0", - "csstype": "3.1.2", + "csstype": "^3.1.2", "prop-types": "^15.8.1" } }, @@ -11426,7 +11426,7 @@ "@mui/types": "^7.2.11", "@mui/utils": "^5.15.0", "clsx": "^2.0.0", - "csstype": "3.1.2", + "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "dependencies": { @@ -11698,7 +11698,7 @@ "requires": { "@types/prop-types": "*", "@types/scheduler": "*", - "csstype": "3.1.2" + "csstype": "^3.0.2" } }, "@types/react-dom": { @@ -12607,7 +12607,7 @@ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "requires": { "@babel/runtime": "^7.8.7", - "csstype": "3.1.2" + "csstype": "^3.0.2" } }, "ee-first": { From dceb960a449144162e843910e472f4098202f488 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 13:13:06 +0400 Subject: [PATCH 02/11] add env variables support --- .gitignore | 1 + env/.shared | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 env/.shared diff --git a/.gitignore b/.gitignore index 0bc7e7e..0c9e81d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dist dev-dist dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/env/.shared b/env/.shared new file mode 100644 index 0000000..1343baa --- /dev/null +++ b/env/.shared @@ -0,0 +1,4 @@ +# replace with your environment variables + +VITE_ENV_VAR_1= +VITE_ENV_VAR_2= From 8f4ccbb556857b71f51eb595607a337bd2052fb0 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 13:30:23 +0400 Subject: [PATCH 03/11] add unit tests --- package-lock.json | 911 +++++++++++++++++++++++++++- package.json | 16 +- src/utils/insertIf/index.ts | 27 + src/utils/insertIf/insertIf.spec.ts | 18 + vite.config.ts | 4 + 5 files changed, 969 insertions(+), 7 deletions(-) create mode 100644 src/utils/insertIf/index.ts create mode 100644 src/utils/insertIf/insertIf.spec.ts diff --git a/package-lock.json b/package-lock.json index af3ff91..84880e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,8 @@ "prettier": "3.1.1", "typescript": "^5.3.3", "vite": "^5.0.9", - "vite-plugin-pwa": "^0.17.4" + "vite-plugin-pwa": "^0.17.4", + "vitest": "^1.1.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2518,6 +2519,18 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -3114,6 +3127,12 @@ "win32" ] }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -3501,6 +3520,135 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/expect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.1.3.tgz", + "integrity": "sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.1.3", + "@vitest/utils": "1.1.3", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.1.3.tgz", + "integrity": "sha512-Va2XbWMnhSdDEh/OFxyUltgQuuDRxnarK1hW5QNN4URpQrqq6jtt8cfww/pQQ4i0LjoYxh/3bYWvDFlR9tU73g==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.1.3", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.1.3.tgz", + "integrity": "sha512-U0r8pRXsLAdxSVAyGNcqOU2H3Z4Y2dAAGGelL50O0QRMdi1WWeYHdrH/QWpN1e8juWfVKsb8B+pyJwTC+4Gy9w==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/spy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.1.3.tgz", + "integrity": "sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.1.3.tgz", + "integrity": "sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@vitest/utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/@vue/compiler-core": { "version": "3.2.41", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", @@ -3638,6 +3786,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3800,6 +3957,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", @@ -4033,6 +4199,15 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -4075,6 +4250,24 @@ } ] }, + "node_modules/chai": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", + "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4096,6 +4289,18 @@ "node": ">=0.8.0" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -4376,6 +4581,18 @@ } } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4447,6 +4664,15 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5450,6 +5676,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -6496,6 +6731,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -6631,6 +6872,22 @@ "node": ">=18.0.0" } }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6770,6 +7027,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6903,6 +7169,18 @@ "node": "*" } }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7282,6 +7560,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7312,6 +7605,17 @@ "node": ">=0.10" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -7376,6 +7680,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8160,6 +8490,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -8281,6 +8617,12 @@ "wbuf": "^1.7.3" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -8290,6 +8632,12 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -8496,6 +8844,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -8604,6 +8964,30 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", + "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -8666,6 +9050,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", @@ -8769,6 +9162,12 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -9000,6 +9399,28 @@ } } }, + "node_modules/vite-node": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.1.3.tgz", + "integrity": "sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-pwa": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.4.tgz", @@ -9024,6 +9445,90 @@ "workbox-window": "^7.0.0" } }, + "node_modules/vitest": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.1.3.tgz", + "integrity": "sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.1.3", + "@vitest/runner": "1.1.3", + "@vitest/snapshot": "1.1.3", + "@vitest/spy": "1.1.3", + "@vitest/utils": "1.1.3", + "acorn-walk": "^8.3.1", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.1", + "vite": "^5.0.0", + "vite-node": "1.1.3", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "^1.0.0", + "@vitest/ui": "^1.0.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -9147,6 +9652,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/workbox-background-sync": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz", @@ -11264,6 +11785,15 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -11581,6 +12111,12 @@ "dev": true, "optional": true }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -11863,6 +12399,111 @@ "react-refresh": "^0.14.0" } }, + "@vitest/expect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.1.3.tgz", + "integrity": "sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==", + "dev": true, + "requires": { + "@vitest/spy": "1.1.3", + "@vitest/utils": "1.1.3", + "chai": "^4.3.10" + } + }, + "@vitest/runner": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.1.3.tgz", + "integrity": "sha512-Va2XbWMnhSdDEh/OFxyUltgQuuDRxnarK1hW5QNN4URpQrqq6jtt8cfww/pQQ4i0LjoYxh/3bYWvDFlR9tU73g==", + "dev": true, + "requires": { + "@vitest/utils": "1.1.3", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "dependencies": { + "p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/snapshot": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.1.3.tgz", + "integrity": "sha512-U0r8pRXsLAdxSVAyGNcqOU2H3Z4Y2dAAGGelL50O0QRMdi1WWeYHdrH/QWpN1e8juWfVKsb8B+pyJwTC+4Gy9w==", + "dev": true, + "requires": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, + "@vitest/spy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.1.3.tgz", + "integrity": "sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==", + "dev": true, + "requires": { + "tinyspy": "^2.2.0" + } + }, + "@vitest/utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.1.3.tgz", + "integrity": "sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==", + "dev": true, + "requires": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + } + } + }, "@vue/compiler-core": { "version": "3.2.41", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", @@ -11987,6 +12628,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -12109,6 +12756,12 @@ "is-shared-array-buffer": "^1.0.2" } }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", @@ -12282,6 +12935,12 @@ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, "call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -12304,6 +12963,21 @@ "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", "dev": true }, + "chai": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", + "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -12321,6 +12995,15 @@ } } }, + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + }, "cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -12531,6 +13214,15 @@ "ms": "2.1.2" } }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -12583,6 +13275,12 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -13367,6 +14065,12 @@ "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", "dev": true }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, "get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -14131,6 +14835,12 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -14230,6 +14940,16 @@ "wrap-ansi": "^9.0.0" } }, + "local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "requires": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -14326,6 +15046,15 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.1" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -14426,6 +15155,18 @@ "brace-expansion": "^1.1.7" } }, + "mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "requires": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14684,6 +15425,18 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -14702,6 +15455,17 @@ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true }, + "pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -14731,6 +15495,25 @@ "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "dev": true }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -15325,6 +16108,12 @@ "object-inspect": "^1.9.0" } }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -15417,12 +16206,24 @@ "wbuf": "^1.7.3" } }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, + "std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -15562,6 +16363,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "requires": { + "acorn": "^8.10.0" + } + }, "stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -15638,6 +16448,24 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "tinypool": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", + "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", + "dev": true + }, + "tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -15683,6 +16511,12 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", @@ -15752,6 +16586,12 @@ "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true }, + "ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -15880,6 +16720,19 @@ "rollup": "^4.2.0" } }, + "vite-node": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.1.3.tgz", + "integrity": "sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + } + }, "vite-plugin-pwa": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.4.tgz", @@ -15893,6 +16746,52 @@ "workbox-window": "^7.0.0" } }, + "vitest": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.1.3.tgz", + "integrity": "sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==", + "dev": true, + "requires": { + "@vitest/expect": "1.1.3", + "@vitest/runner": "1.1.3", + "@vitest/snapshot": "1.1.3", + "@vitest/spy": "1.1.3", + "@vitest/utils": "1.1.3", + "acorn-walk": "^8.3.1", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.1", + "vite": "^5.0.0", + "vite-node": "1.1.3", + "why-is-node-running": "^2.2.2" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, "wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -15994,6 +16893,16 @@ "has-tostringtag": "^1.0.0" } }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "workbox-background-sync": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz", diff --git a/package.json b/package.json index 1437c6e..65c69f1 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,13 @@ "build": "tsc && vite build", "preview": "vite preview", "https-preview": "serve dist", - "prepare": "husky install" + "prepare": "husky install && cp -n env/.shared .env &", + "prettier:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "lint:check": "eslint --max-warnings=0 \"src/**/*.{js,jsx,ts,tsx,json}\"", + "ts:check": "tsc --noEmit", + "test:unit": "vitest", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui" }, "dependencies": { "@emotion/react": "^11.11.1", @@ -55,7 +61,8 @@ "prettier": "3.1.1", "typescript": "^5.3.3", "vite": "^5.0.9", - "vite-plugin-pwa": "^0.17.4" + "vite-plugin-pwa": "^0.17.4", + "vitest": "^1.1.3" }, "lint-staged": { "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ @@ -75,8 +82,5 @@ "pwa", "starter-kit", "vite" - ], - "overrides": { - "csstype": "3.1.2" - } + ] } diff --git a/src/utils/insertIf/index.ts b/src/utils/insertIf/index.ts new file mode 100644 index 0000000..785a433 --- /dev/null +++ b/src/utils/insertIf/index.ts @@ -0,0 +1,27 @@ +/** + * Helper function to enable the following syntax: + * + * [ + * itemA, + * itemB, + * ...arrayInsertIf(condition, [conditionalItemC, conditionalItemD]) + * ] + */ +function arrayInsertIf(condition: unknown, items: T[]): T[] { + return condition ? items : []; +} + +/** + * Helper function to enable the following syntax: + * + * { + * a: 0, + * b: 1, + * ...objectInsertIf(someCondition, { conditionalItemC: 2, conditionalItemD: 3 }) + * } + */ +function objectInsertIf(condition: unknown, item: T): T | null { + return condition ? item : null; +} + +export { arrayInsertIf, objectInsertIf }; diff --git a/src/utils/insertIf/insertIf.spec.ts b/src/utils/insertIf/insertIf.spec.ts new file mode 100644 index 0000000..7560659 --- /dev/null +++ b/src/utils/insertIf/insertIf.spec.ts @@ -0,0 +1,18 @@ +import { arrayInsertIf, objectInsertIf } from './'; +import { expect, test } from 'vitest'; + +test('arrayInsertIf: returns empty array if condition is false', () => { + expect(arrayInsertIf(false, [1, 2, 3])).toEqual([]); +}); + +test('arrayInsertIf: returns array if condition is true', () => { + expect(arrayInsertIf(true, [1, 2, 3])).toEqual([1, 2, 3]); +}); + +test('objectInsertIf: returns null if condition is false', () => { + expect(objectInsertIf(false, { a: 0, b: 1 })).toEqual(null); +}); + +test('objectInsertIf: returns object if condition is true', () => { + expect(objectInsertIf(true, { a: 0, b: 1 })).toEqual({ a: 0, b: 1 }); +}); diff --git a/vite.config.ts b/vite.config.ts index 49317ac..db2e84f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,4 @@ +/// import * as path from 'path'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; @@ -26,4 +27,7 @@ export default defineConfig({ '@': path.resolve(__dirname, './src'), }, }, + test: { + root: path.resolve(__dirname, './src'), + }, }); From 5c4803acad08651a38618ea1af6c753378a733f3 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 13:59:16 +0400 Subject: [PATCH 04/11] add e2e tests --- .github/workflows/playwright.yml | 27 +++++++++ .gitignore | 4 ++ e2e/hotkeys/hotkeys.spec.ts | 26 +++++++++ e2e/theme/theme.spec.ts | 18 ++++++ package-lock.json | 94 ++++++++++++++++++++++++++++++++ package.json | 1 + playwright.config.ts | 82 ++++++++++++++++++++++++++++ src/sections/Header/Header.tsx | 12 +++- src/sections/HotKeys/HotKeys.tsx | 8 ++- src/sections/Sidebar/Sidebar.tsx | 1 + src/store/hotkeys/index.ts | 17 +++--- src/store/sidebar/index.ts | 17 +++--- src/store/theme/index.ts | 9 ++- 13 files changed, 295 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 e2e/hotkeys/hotkeys.spec.ts create mode 100644 e2e/theme/theme.spec.ts create mode 100644 playwright.config.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..90b6b70 --- /dev/null +++ b/.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@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 0c9e81d..0809695 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ dist-ssr *.njsproj *.sln *.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/e2e/hotkeys/hotkeys.spec.ts b/e2e/hotkeys/hotkeys.spec.ts new file mode 100644 index 0000000..85daf4f --- /dev/null +++ b/e2e/hotkeys/hotkeys.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from '@playwright/test'; + +test.describe('test hotkeys', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + }); + + test('using hotkey "Alt+T" switches theme', async ({ page }) => { + await page.keyboard.press('Alt+T'); + + await expect(page.getByTestId('theme-light')).toBeVisible(); + }); + + test('using hotkey "Alt+S" opens sidebar', async ({ page }) => { + await page.keyboard.press('Alt+S'); + + await expect(page.getByTestId('sidebar')).toBeVisible(); + }); + + test('using hotkey "Alt+K" opens hotkeys dialog', async ({ page }) => { + await page.keyboard.press('Alt+K'); + + await expect(page.getByTestId('hotkeys-dialog')).toBeVisible(); + }); +}); diff --git a/e2e/theme/theme.spec.ts b/e2e/theme/theme.spec.ts new file mode 100644 index 0000000..065efc5 --- /dev/null +++ b/e2e/theme/theme.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from '@playwright/test'; + +test.describe('test theme', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + }); + + test('default theme is dark', async ({ page }) => { + await expect(page.getByTestId('theme-dark')).toBeVisible(); + }); + + test('clicking on the theme toggle changes the theme', async ({ page }) => { + await page.getByTestId('theme-toggle').click(); + + await expect(page.getByTestId('theme-light')).toBeVisible(); + }); +}); diff --git a/package-lock.json b/package-lock.json index 84880e7..a4152f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "recoil": "^0.7.7" }, "devDependencies": { + "@playwright/test": "^1.40.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/node": "^20.10.4", "@types/react": "^18.2.45", @@ -2941,6 +2942,21 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "dependencies": { + "playwright": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -7616,6 +7632,50 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "dependencies": { + "playwright-core": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -12010,6 +12070,15 @@ "fastq": "^1.6.0" } }, + "@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "requires": { + "playwright": "1.40.1" + } + }, "@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -15466,6 +15535,31 @@ "pathe": "^1.1.0" } }, + "playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.40.1" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true + }, "postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", diff --git a/package.json b/package.json index 65c69f1..8817dd9 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "recoil": "^0.7.7" }, "devDependencies": { + "@playwright/test": "^1.40.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/node": "^20.10.4", "@types/react": "^18.2.45", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..3b5ba97 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,82 @@ +import { defineConfig, devices } from '@playwright/test'; + +const PORT = process.env.CI ? 4173 : 5173; +const BASE_URL = `http://localhost:${PORT}`; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + testIdAttribute: 'data-pw', + baseURL: BASE_URL, + }, + + timeout: 30 * 1000, + + /* 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: process.env.CI ? 'npm run build && npm run preview' : 'npm run dev', + url: `http://localhost:${PORT}`, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, +}); diff --git a/src/sections/Header/Header.tsx b/src/sections/Header/Header.tsx index f3ed086..bdd2a24 100644 --- a/src/sections/Header/Header.tsx +++ b/src/sections/Header/Header.tsx @@ -21,7 +21,7 @@ import { getRandomJoke } from './utils'; function Header() { const [, sidebarActions] = useSidebar(); - const [, themeActions] = useTheme(); + const [theme, themeActions] = useTheme(); const [, notificationsActions] = useNotifications(); const [, hotKeysDialogActions] = useHotKeysDialog(); @@ -41,7 +41,7 @@ function Header() { } return ( - + @@ -80,7 +80,13 @@ function Header() { - + diff --git a/src/sections/HotKeys/HotKeys.tsx b/src/sections/HotKeys/HotKeys.tsx index 8b1fcc0..8d7283a 100644 --- a/src/sections/HotKeys/HotKeys.tsx +++ b/src/sections/HotKeys/HotKeys.tsx @@ -25,7 +25,13 @@ function HotKeys() { useHotkeys('alt+k', hotKeysDialogActions.toggle); return ( - + Hot Keys diff --git a/src/sections/Sidebar/Sidebar.tsx b/src/sections/Sidebar/Sidebar.tsx index a5c6f06..f43d911 100644 --- a/src/sections/Sidebar/Sidebar.tsx +++ b/src/sections/Sidebar/Sidebar.tsx @@ -22,6 +22,7 @@ function Sidebar() { onOpen={sidebarActions.open} disableBackdropTransition={false} swipeAreaWidth={30} + data-pw="sidebar" > `${theme.mixins.toolbar.minHeight}px` }}> {Object.values(routes) diff --git a/src/store/hotkeys/index.ts b/src/store/hotkeys/index.ts index 9d85e7a..2311f1f 100644 --- a/src/store/hotkeys/index.ts +++ b/src/store/hotkeys/index.ts @@ -1,3 +1,4 @@ +import { useCallback, useMemo } from 'react'; import { atom, useRecoilState } from 'recoil'; import type { Actions } from './types'; @@ -10,19 +11,21 @@ const hotKeysDialogState = atom({ function useHotKeysDialog(): [boolean, Actions] { const [isOpen, setIsOpen] = useRecoilState(hotKeysDialogState); - function toggle() { + const toggle = useCallback(() => { setIsOpen((isOpen: boolean) => !isOpen); - } + }, [setIsOpen]); - function close() { + const close = useCallback(() => { setIsOpen(false); - } + }, [setIsOpen]); - function open() { + const open = useCallback(() => { setIsOpen(true); - } + }, [setIsOpen]); - return [isOpen, { toggle, close, open }]; + const memoizedActions = useMemo(() => ({ toggle, close, open }), [toggle, close, open]); + + return [isOpen, memoizedActions]; } export default useHotKeysDialog; diff --git a/src/store/sidebar/index.ts b/src/store/sidebar/index.ts index 51f6b52..c66af44 100644 --- a/src/store/sidebar/index.ts +++ b/src/store/sidebar/index.ts @@ -1,3 +1,4 @@ +import { useCallback, useMemo } from 'react'; import { atom, useRecoilState } from 'recoil'; import type { Actions } from './types'; @@ -10,19 +11,21 @@ const sidebarIsOpenState = atom({ function useSidebar(): [boolean, Actions] { const [isOpen, setIsOpen] = useRecoilState(sidebarIsOpenState); - function toggle() { + const toggle = useCallback(() => { setIsOpen((isOpen: boolean) => !isOpen); - } + }, [setIsOpen]); - function close() { + const close = useCallback(() => { setIsOpen(false); - } + }, [setIsOpen]); - function open() { + const open = useCallback(() => { setIsOpen(true); - } + }, [setIsOpen]); - return [isOpen, { toggle, close, open }]; + const memoizedActions = useMemo(() => ({ toggle, close, open }), [toggle, close, open]); + + return [isOpen, memoizedActions]; } export default useSidebar; diff --git a/src/store/theme/index.ts b/src/store/theme/index.ts index 2e5801c..629abd8 100644 --- a/src/store/theme/index.ts +++ b/src/store/theme/index.ts @@ -1,3 +1,4 @@ +import { useCallback, useMemo } from 'react'; import { atom, useRecoilState } from 'recoil'; import { Themes } from '@/theme/types'; @@ -20,11 +21,13 @@ function synchronizeWithLocalStorage({ setSelf, onSet }: AtomEffectParams) { function useTheme(): [Themes, Actions] { const [themeMode, setThemeMode] = useRecoilState(themeModeState); - function toggle() { + const toggle = useCallback(() => { setThemeMode((mode: Themes) => (mode === Themes.DARK ? Themes.LIGHT : Themes.DARK)); - } + }, [setThemeMode]); - return [themeMode, { toggle }]; + const memoizedActions = useMemo(() => ({ toggle }), [toggle]); + + return [themeMode, memoizedActions]; } export default useTheme; From f041078cad09e256573fd7b991a277d5ea87af0d Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 14:03:05 +0400 Subject: [PATCH 05/11] add github actions --- .github/workflows/analyses.yml | 71 ++++++++++++++++++++++++++++++++ .github/workflows/playwright.yml | 27 ------------ .github/workflows/tests:e2e.yml | 27 ++++++++++++ 3 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/analyses.yml delete mode 100644 .github/workflows/playwright.yml create mode 100644 .github/workflows/tests:e2e.yml diff --git a/.github/workflows/analyses.yml b/.github/workflows/analyses.yml new file mode 100644 index 0000000..145169a --- /dev/null +++ b/.github/workflows/analyses.yml @@ -0,0 +1,71 @@ +name: analyses + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] +jobs: + install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Dependencies + run: npm ci + - name: Cache node modules + uses: actions/cache@v2 + with: + path: node_modules + key: ${{ github.sha }} + + lint: + needs: install + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cached node modules + uses: actions/cache@v2 + with: + path: node_modules + key: ${{ github.sha }} + - name: Eslint + run: npm run lint:check + + prettier: + needs: install + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cached node modules + uses: actions/cache@v2 + with: + path: node_modules + key: ${{ github.sha }} + - name: Prettier + run: npm run prettier:check + + typescript: + needs: install + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cached node modules + uses: actions/cache@v2 + with: + path: node_modules + key: ${{ github.sha }} + - name: Typescript + run: npm run ts:check + + unit_tests: + needs: install + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cached node modules + uses: actions/cache@v2 + with: + path: node_modules + key: ${{ github.sha }} + - name: Unit tests + run: npm run test:unit diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index 90b6b70..0000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -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@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run Playwright tests - run: npx playwright test - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/.github/workflows/tests:e2e.yml b/.github/workflows/tests:e2e.yml new file mode 100644 index 0000000..c0c43eb --- /dev/null +++ b/.github/workflows/tests:e2e.yml @@ -0,0 +1,27 @@ +name: e2e tests +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npm run test:e2e + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 From f5c6699abdb0b3c6b2a592b312712fa9a3ee13d6 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 14:06:39 +0400 Subject: [PATCH 06/11] skip lib check --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 5f30c4b..1f6be37 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, - "skipLibCheck": false, + "skipLibCheck": true, "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, From 6af9fa1f988099ecb61887c7b9678d67cdb58010 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 14:11:01 +0400 Subject: [PATCH 07/11] import styled from material/styles --- README.md | 2 +- src/components/styled.ts | 2 +- src/pages/Welcome/styled.ts | 2 +- src/sections/Header/styled.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 763957c..81ea8b5 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ You have access to `theme` object via `sx` prop and `styled-components`: ```js import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import { styled } from '@mui/system'; +import { styled } from '@mui/material/styles'; // styled-components const MyCoolButton = styled(Button)(({ theme }) => ({ diff --git a/src/components/styled.ts b/src/components/styled.ts index a38152a..737ffa1 100644 --- a/src/components/styled.ts +++ b/src/components/styled.ts @@ -1,5 +1,5 @@ import Box from '@mui/material/Box'; -import { styled } from '@mui/system'; +import { styled } from '@mui/material/styles'; const FlexBox = styled(Box)({ display: 'flex', diff --git a/src/pages/Welcome/styled.ts b/src/pages/Welcome/styled.ts index 58e33e8..ee8690b 100644 --- a/src/pages/Welcome/styled.ts +++ b/src/pages/Welcome/styled.ts @@ -1,4 +1,4 @@ -import { styled } from '@mui/system'; +import { styled } from '@mui/material/styles'; const Image = styled('img')({ width: '10%', diff --git a/src/sections/Header/styled.ts b/src/sections/Header/styled.ts index 09b378b..c9e9fcc 100644 --- a/src/sections/Header/styled.ts +++ b/src/sections/Header/styled.ts @@ -1,5 +1,5 @@ import Button from '@mui/material/Button'; -import { styled } from '@mui/system'; +import { styled } from '@mui/material/styles'; const HotKeysButton = styled(Button)(({ theme }) => ({ height: 'fit-content', From 0e828012ba39ffd25dc8b8386cd88e97dcac258b Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sat, 6 Jan 2024 20:36:44 +0400 Subject: [PATCH 08/11] update README according to the latest changes --- README.md | 179 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 134 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 81ea8b5..83e03ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Analyses](https://github.com/suren-atoyan/react-pwa/actions/workflows/analyses.yml/badge.svg)](https://github.com/suren-atoyan/react-pwa/actions/workflows/analyses.yml) +[![E2E Tests](https://github.com/suren-atoyan/react-pwa/actions/workflows/tests:e2e.yml/badge.svg)](https://github.com/suren-atoyan/react-pwa/actions/workflows/tests:e2e.yml) + @@ -15,17 +18,20 @@ It's a combination of essential (and minimal) libraries/components/utils/etc., w Almost all projects need to have a router, a UI framework, store integration, theming, error handling, base file/folder structure, a builder, some developer tools (eslint, prettier, etc), and many more. In this starter kit, we tried to put together the best options available from the above-mentioned fields. Out of the box, it provides a modern production-ready setup created by developers for developers 💚 -## Features +### Tech stack - ✅ [Vite](#vite) - `v5` 🔥 - ✅ [React](#react) - `v18` 🔥 - ✅ [TypeScript](#typescript) -- ✅ [Router](#router) - - `React Router v6` - ✅ [UI-framework](#ui-framework) - `MUI v5` + +### Core features + +- ✅ [Router](#router) + - `React Router v6` - ✅ [Store](#store) - `Recoil` - ✅ [Notifications](#notifications) @@ -36,16 +42,23 @@ Almost all projects need to have a router, a UI framework, store integration, th - ✅ [Hotkeys](#hotkeys) - ✅ [Error Handling](#error-handling) - ✅ [Pages](#pages) -- ✅ [Dev tools](#dev-tools) - - ✅ eslint - - ✅ prettier - - ✅ husky - - ✅ lint-staged - - ✅ https localhost + +### Dev tools and tests + +- ✅ [Tests](#tests) 🚀 + - [unit tests](#unit-tests) - `Vitest` + - [e2e tests](#e2e-tests) - `Playwright` +- ✅ [GitHub Actions](#tests) +- ✅ [Environmental variables](#environmental-variables) +- ✅ [EsLint](#eslint) +- ✅ [Prettier](#prettier) +- ✅ [Husky](#husky) +- ✅ [Lint staged](#lint-staged) +- ✅ [https localhost](#https-localhost) #### Vite -[Vite](https://vitejs.dev/) is a blazingly fast build tool based on [native ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/), [rollup](https://rollupjs.org/guide/en/), and [esbuild](https://esbuild.github.io/). `React-PWA` v1 was based on [CRA](https://reactjs.org/docs/create-a-new-react-app.html). We still love `CRA` and really think it's a great tool, but `Vite` provides a much better developer experience and it's unconditionally faster (maybe, we will also create a `CRA` version of `React-PWA` v2 in near future). +[Vite](https://vitejs.dev/) is a blazingly fast build tool based on [native ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/), [rollup](https://rollupjs.org/guide/en/), and [esbuild](https://esbuild.github.io/). It provides a great developer experience and super fast builds. #### React @@ -140,7 +153,31 @@ Here how the base file/folder structure looks like: -TBD: more information about the file/folder structure will be added soon. +Special attention deserves `pages/`, `sections/` and `components/`. These are the main building blocks of the application and here is the difference between them: + +- `components` - usually (but not necessarily), in `components/` we keep dummy, stateless components, that are being used in different parts of the application, and they don't belong to the particular page/section, like `Button`, `List`, `Table`, `Loading`, `Divider`, `Flex`, `Dialog`, etc. +- `sections` - sections are complete parts of the application, that have their own logic, like `Navigation`, `Sidebar`, `Notifications`, etc. +- `pages` - pages represent the root routes, like `/profile` renders the `Profile` page, `/login` renders `Login` page. Pages are made of sections (no need to have [Page]/sections/ folder). If a section is used on multiple pages it should be moved to `/root/sections`. + - in some project you may see `/features` instead of `/sections` + +Another important mention is any component folder structure. It should look like this: + +``` +- [Component] + - index.ts + - [Component].tsx + - types.ts + - styled.ts + - utils.tsx + - etc. +``` + +It's a good practice to keep all related files in one folder. It makes it easier to find and maintain them. Only the first two files are required. You may or may not have component-related types, styles, utils, etc. But if you have them, keep them in the same folder and separate files. Let's see what each file is responsible for: + +- `index.ts` - this is the entry file for the component. It default exports the "original" version of the component. It may also export other variations of the component, like `memo()`, `lazy()`, `withSomething()` (any HOC), `loading/error` `Suspense` wrapper if you use `React` cuncarrent mode +- `[Component].tsx` - this should contain only the component in its pure form, no HOCs, no other wrappers, no types, no styles, no utilities, etc. +- `types.ts` - contains any type definitions related to the component +- `styled.ts` - contains styled-components (that's why it's styled.ts no styles.ts), may contain also other style-related properties, like constants (DIALOG_MAX_WIDTH or something), etc. Check this [example](./src/sections/Header/styled.ts) #### PWA @@ -184,55 +221,102 @@ From a layout point of view the application consists of 3 main parts: The last one is a router-based switcher. All routes are defined in [src/routes](./src/routes/index.ts). By default, pages are being loaded asynchronously via [asyncComponentLoader](src/utils/loader/loader.tsx). You can use it to asynchronously load any `React` component you want. It uses `React.Suspense` and `React.lazy` with some magic 🧙‍♂️ -# Dev tools +#### Tests + +Tests are a vital part of any project. Sometimes they are boring, sometimes they are hard to write, but they are very important. This setup tries to make the testing process as easy as possible. It contains: + +- unit tests +- e2e tests + +##### Unit tests + +[Vitest](https://vitest.dev/) is used here for unit tests. Check [src/insertIf/insertIf.spec.ts](./src/insertIf/insertIf.spec.ts) for an example. You can run unit tests by running: + +```bash +npm run test:unit # or yarn test:unit +``` + +##### E2E tests + +[Playwright](https://playwright.dev/) is used for e2e tests. Check [e2e/](./e2e/) folder to check examples. You can run e2e tests by running: + +```bash +npm run test:e2e # or yarn test:e2e +``` + +If you want to run e2e tests in UI mode, run: + +```bash +npm run test:e2e:ui # or yarn test:e2e:ui +``` + +[playwright.config.ts](./playwright.config.ts) contains the configuration for e2e tests. Currently, it's configured to run tests in `chromium`, `firefox` and `webkit` browsers. You can add more browsers if you want. + +#### GitHub Actions + +There are 2 GitHub Actions workflows: -- [eslint](https://eslint.org/) +- [analyses.yml](./.github/workflows/analyses.yml) - runs `prettier`, `eslint`, `ts` checks and unit tests on every push and pull request to `main/master` branch. +- [tests:e2e.yml](./.github/workflows/tests:e2e.yml) - runs e2e tests on every push and pull request to `main/master` branch. - The latest version of `eslint` with the latest recommended collection of `eslint` rules is available out of the box. It contains: +#### Environmental variables - - eslint:recommended - - react/recommended - - @typescript-eslint/recommended +Put your environmental variables in the `.env` file (they should be prefixed with `VITE_`) and use them in your code via `import.meta.env.[variable_name]` syntax. `.env` file is not committed to the repository (it's under `.gitignore`), but `env/.shared` is. You can use it as a template. Usually, you will have different `.env` files for different environments (dev, staging, production, etc.), like: - Check the [.eslintrc.json](./eslintrc.json) file for more information. +- `env/.shared` - shared variables +- `env/.dev` - dev variables +- `env/.staging` - staging variables +- `env/.production` - production variables -- [prettier](https://prettier.io/) +Copy the content of `env/.[template_name]` to `.env` file and fill it with your variables. Note: `env/.shared` is being copied to `.env` file automatically after `npm install`. - Stop fighting about styling in code reviews; save your time and energy - configure it once and let the machine format/correct your code. +#### EsLint - Check the [.prettierrc.json](./prettierrc.json) file for more information. +The latest version of [eslint](https://eslint.org/) with the latest recommended collection of `eslint` rules is available out of the box. It contains: - There is an additional configuration for your import statements. They will be automatically ordered and grouped by the given rules (check the `.prettierrc.js`) - one more topic for debates in code reviews :) +- eslint:recommended +- react/recommended +- @typescript-eslint/recommended -- [husky](https://typicode.github.io/husky/#/) +Check the [.eslintrc.json](./eslintrc.json) file for more information. - You can use it to lint your commit messages, run tests, lint code, etc. +#### Prettier - Currently, only `pre-commit` hook is set up. Every time you try to do a commit it will run `prettier` and `eslint` to be sure that everything is according to the rules. +[prettier](https://prettier.io/) - stop fighting about styling in code reviews; save your time and energy - configure it once and let the machine format/correct your code. -- [lint-staged](https://github.com/okonet/lint-staged) +Check the [.prettierrc.json](./prettierrc.json) file for more information. - `lint-staged` helps to run `eslint` and `prettier` only on staged files - it makes the linting process super fast and sensible. +There is an additional configuration for your import statements. They will be automatically ordered and grouped by the given rules (check the `.prettierrc.js`) - one more topic for debates in code reviews :) -- [https localhost](https://github.com/daquinoaldo/https-localhost) +#### Husky - It's a simple way to run your application on localhost with https. +You can use [husky](https://typicode.github.io/husky/#/) to lint your commit messages, run tests, lint code, etc. - Just run: +Currently, only `pre-commit` hook is set up. Every time you try to do a commit it will run `prettier` and `eslint` to be sure that everything is according to the rules. - ```bash - npm run https-preview # or yarn https-preview - ``` +#### Lint-staged - after: +[lint-staged](https://github.com/okonet/lint-staged) helps to run `eslint` and `prettier` only on staged files - it makes the linting process super fast and sensible. - ```bash - npm run build # or yarn build - ``` +#### https localhost - and check `https://localhost` in your browser. +[https localhost](https://github.com/daquinoaldo/https-localhost) is a simple way to run your application on localhost with https. - NOTE: first time it will ask you about installing localhost certificate. For more info check [this](https://github.com/daquinoaldo/https-localhost#root-required) +Just run: + +```bash +npm run https-preview # or yarn https-preview +``` + +after: + +```bash +npm run build # or yarn build +``` + +and check `https://localhost` in your browser. + +NOTE: the first time it will ask you about installing localhost certificate. For more info check [this](https://github.com/daquinoaldo/https-localhost#root-required) ## Usage @@ -260,12 +344,17 @@ In order to do a production build, run: npm run build # yarn build ``` -There are two more scripts: - -`preview` and `https-preview` - -- `preview` command will boot up local static web server that serves the files from `dist` folder. It's an easy way to check if the production build looks OK in your local environment. -- `https-preview` is the same, but with HTTPS. It's handy for testing your PWA capabilities in your local environment. +There are other scripts as well: + +- `prettier:check` - check if all files are formatted according to the rules. +- `lint:check` - check if all files are linted according to the rules. +- `ts:check` - check if all files are typed according to the rules. +- `test:unit` - run unit tests. +- `test:e2e` - run e2e tests. +- `test:e2e:ui` - run e2e tests in UI mode. +- `preview` - boot up local static web server that serves the files from `dist` folder. It's an easy way to check if the production build looks OK in your local environment. +- `https-preview` - is the same as `preview`, but with HTTPS. It's handy for testing your PWA capabilities in your local environment. +- `prepare` - install `husky` and copy the default `env/.shared` file to `.env` file. This script is being run automatically after `npm install` or `yarn`. ## [Live Demo](https://react-pwa.surenatoyan.com/) From 05601979d0f7b0b478d062935dffa54fed8dd9dd Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sun, 7 Jan 2024 15:40:22 +0400 Subject: [PATCH 09/11] apply PR comments --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 83e03ca..dd2f30c 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Special attention deserves `pages/`, `sections/` and `components/`. These are th - `pages` - pages represent the root routes, like `/profile` renders the `Profile` page, `/login` renders `Login` page. Pages are made of sections (no need to have [Page]/sections/ folder). If a section is used on multiple pages it should be moved to `/root/sections`. - in some project you may see `/features` instead of `/sections` -Another important mention is any component folder structure. It should look like this: +Another important mention is any component's folder structure. It should look like this: ``` - [Component] @@ -174,7 +174,7 @@ Another important mention is any component folder structure. It should look like It's a good practice to keep all related files in one folder. It makes it easier to find and maintain them. Only the first two files are required. You may or may not have component-related types, styles, utils, etc. But if you have them, keep them in the same folder and separate files. Let's see what each file is responsible for: -- `index.ts` - this is the entry file for the component. It default exports the "original" version of the component. It may also export other variations of the component, like `memo()`, `lazy()`, `withSomething()` (any HOC), `loading/error` `Suspense` wrapper if you use `React` cuncarrent mode +- `index.ts` - this is the entry file for the component. It `export default`s the "original" version of the component. It may also export other variations of the component, like `memo()`, `lazy()`, `withSomething()` (any HOC), `loading/error` `Suspense` wrapper if you use `React` cuncarrent mode - `[Component].tsx` - this should contain only the component in its pure form, no HOCs, no other wrappers, no types, no styles, no utilities, etc. - `types.ts` - contains any type definitions related to the component - `styled.ts` - contains styled-components (that's why it's styled.ts no styles.ts), may contain also other style-related properties, like constants (DIALOG_MAX_WIDTH or something), etc. Check this [example](./src/sections/Header/styled.ts) @@ -238,7 +238,7 @@ npm run test:unit # or yarn test:unit ##### E2E tests -[Playwright](https://playwright.dev/) is used for e2e tests. Check [e2e/](./e2e/) folder to check examples. You can run e2e tests by running: +[Playwright](https://playwright.dev/) is used for e2e tests. Check [e2e/](./e2e/) folder to see examples. You can run e2e tests by running: ```bash npm run test:e2e # or yarn test:e2e From 4d3713189629ea9eb0b69c5a466badf6538e9107 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sun, 7 Jan 2024 21:59:03 +0400 Subject: [PATCH 10/11] add dev-tools and tests in Motivation section --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd2f30c..02c0f1e 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ ## Synopsis This project (a GitHub template) is an opinionated setup for modern web applications. -It's a combination of essential (and minimal) libraries/components/utils/etc., which developers usually need during the process of making modern React applications. +It's a combination of essential (and minimal) libraries/components/utils/dev-tools/etc., which developers usually need during the process of making modern React applications. ## Motivation -Almost all projects need to have a router, a UI framework, store integration, theming, error handling, base file/folder structure, a builder, some developer tools (eslint, prettier, etc), and many more. In this starter kit, we tried to put together the best options available from the above-mentioned fields. Out of the box, it provides a modern production-ready setup created by developers for developers 💚 +Almost all projects need to have a router, a UI framework, store integration, theming, error handling, base file/folder structure, a builder, some developer tools (eslint, prettier, etc), tests and many more. In this starter kit, we tried to put together the best options available from the above-mentioned fields. Out of the box, it provides a modern production-ready setup created by developers for developers 💚 ### Tech stack From 0858e35df0aa1880e1b82a49ff250507e5cacec3 Mon Sep 17 00:00:00 2001 From: suren-atoyan Date: Sun, 7 Jan 2024 22:02:00 +0400 Subject: [PATCH 11/11] update package version to v2.3.0 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab5cd0..29e353d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ### Releases +## v2.3.0 + +###### _Dec 23, 2023_ + +- packages: update all packages +- env: add environmental variables +- tests: add unit tests (`vitest`) and e2e tests (`playwright`) +- github actions: add github actions + ## v2.2.0 ###### _Dec 23, 2023_ diff --git a/package.json b/package.json index 8817dd9..09cfe8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-pwa", - "version": "2.2.0", + "version": "2.3.0", "type": "module", "description": "Starter kit for modern web applications", "homepage": "https://react-pwa.surenatoyan.com/",