diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e1f31e8de..05fa736bf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,3 +35,4 @@ jobs: run: | pnpm run typecheck && npm run lint + pnpm run test diff --git a/package.json b/package.json index 5fa25cdba..9359716f2 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "postinstall": "electron-builder install-app-deps", "prepare": "pnpm exec simple-git-hooks && test -f .env || cp .env.example .env", "start": "electron-vite preview", + "test": "vitest", "typecheck": "npm run typecheck:node && npm run typecheck:web", "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false" @@ -112,7 +113,8 @@ "simple-git-hooks": "2.11.1", "tailwindcss": "3.4.3", "typescript": "^5.4.5", - "vite": "^5.2.12" + "vite": "^5.2.12", + "vitest": "1.6.0" }, "pnpm": { "patchedDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9e263cfd..8f85f3b20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,6 +278,9 @@ importers: vite: specifier: ^5.2.12 version: 5.2.12(@types/node@20.12.13) + vitest: + specifier: 1.6.0 + version: 1.6.0(@types/node@20.12.13) packages: @@ -1131,6 +1134,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1702,6 +1709,9 @@ packages: cpu: [x64] os: [win32] + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -1951,6 +1961,21 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 + '@vitest/expect@1.6.0': + resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + + '@vitest/runner@1.6.0': + resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + + '@vitest/snapshot@1.6.0': + resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + + '@vitest/spy@1.6.0': + resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + + '@vitest/utils@1.6.0': + resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} @@ -1963,6 +1988,10 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} @@ -2019,6 +2048,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -2088,6 +2121,9 @@ packages: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -2221,6 +2257,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2245,6 +2285,9 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2506,6 +2549,10 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2569,6 +2616,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dir-compare@3.3.0: resolution: {integrity: sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==} @@ -3085,6 +3136,9 @@ packages: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -3522,6 +3576,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -3694,6 +3751,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -4061,6 +4121,10 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + p-locate@3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} @@ -4149,6 +4213,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -4247,6 +4314,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@3.8.0: resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} @@ -4314,6 +4385,9 @@ packages: react-dom: optional: true + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-merge-refs@1.1.0: resolution: {integrity: sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==} @@ -4578,6 +4652,9 @@ packages: resolution: {integrity: sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==} hasBin: true + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4667,10 +4744,16 @@ packages: resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stat-mode@1.0.0: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} engines: {node: '>= 6'} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -4723,6 +4806,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + style-to-object@1.0.6: resolution: {integrity: sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==} @@ -4796,6 +4882,17 @@ packages: tiny-typed-emitter@2.1.0: resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} + tinybench@2.8.0: + resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + tldts-core@6.1.23: resolution: {integrity: sha512-NoYYa06h5WN9BU4wjpVK/bKg3fw2BlhZSB1omr+CkEygSzhe5Ojp8mTFae93eVV2mv7d/ootxPqVhW1GoCeogw==} @@ -4846,6 +4943,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -5004,6 +5105,11 @@ packages: vfile@6.0.1: resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + vite-node@1.6.0: + resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite@5.2.12: resolution: {integrity: sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -5032,6 +5138,31 @@ packages: terser: optional: true + vitest@1.6.0: + resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.0 + '@vitest/ui': 1.6.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 + vscode-languageserver-textdocument@1.0.11: resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} @@ -5049,6 +5180,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -6060,6 +6196,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -6656,6 +6796,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true + '@sinclair/typebox@0.27.8': {} + '@sindresorhus/is@4.6.0': {} '@szmarczak/http-timer@4.0.6': @@ -6968,6 +7110,35 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@1.6.0': + dependencies: + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + chai: 4.4.1 + + '@vitest/runner@1.6.0': + dependencies: + '@vitest/utils': 1.6.0 + p-limit: 5.0.0 + pathe: 1.1.2 + + '@vitest/snapshot@1.6.0': + dependencies: + magic-string: 0.30.10 + pathe: 1.1.2 + pretty-format: 29.7.0 + + '@vitest/spy@1.6.0': + dependencies: + tinyspy: 2.2.1 + + '@vitest/utils@1.6.0': + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + '@xmldom/xmldom@0.8.10': {} abbrev@1.1.1: {} @@ -6976,6 +7147,8 @@ snapshots: dependencies: acorn: 8.11.3 + acorn-walk@8.3.2: {} + acorn@8.11.3: {} agent-base@6.0.2: @@ -7029,6 +7202,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} any-promise@1.3.0: {} @@ -7168,6 +7343,8 @@ snapshots: assert-plus@1.0.0: optional: true + assertion-error@1.1.0: {} + astral-regex@2.0.0: optional: true @@ -7349,6 +7526,16 @@ snapshots: ccount@2.0.1: {} + chai@4.4.1: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -7370,6 +7557,10 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -7655,6 +7846,10 @@ snapshots: dependencies: mimic-response: 3.1.0 + deep-eql@4.1.4: + dependencies: + type-detect: 4.0.8 + deep-is@0.1.4: {} deepmerge-ts@7.0.1: {} @@ -7706,6 +7901,8 @@ snapshots: didyoumean@1.2.2: {} + diff-sequences@29.6.3: {} + dir-compare@3.3.0: dependencies: buffer-equal: 1.0.1 @@ -8485,6 +8682,8 @@ snapshots: get-east-asian-width@1.2.0: {} + get-func-name@2.0.2: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -8991,6 +9190,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.0: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -9153,6 +9354,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + lowercase-keys@2.0.0: {} lru-cache@10.2.2: {} @@ -9668,6 +9873,10 @@ snapshots: dependencies: yocto-queue: 1.0.0 + p-limit@5.0.0: + dependencies: + yocto-queue: 1.0.0 + p-locate@3.0.0: dependencies: p-limit: 2.3.0 @@ -9749,6 +9958,8 @@ snapshots: pathe@1.1.2: {} + pathval@1.1.1: {} + pend@1.2.0: {} picocolors@1.0.1: {} @@ -9832,6 +10043,12 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + pretty-format@3.8.0: {} process-nextick-args@2.0.1: {} @@ -9883,6 +10100,8 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) + react-is@18.3.1: {} + react-merge-refs@1.1.0: {} react-refresh@0.14.2: {} @@ -10179,6 +10398,8 @@ snapshots: short-unique-id@5.2.0: {} + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -10270,8 +10491,12 @@ snapshots: dependencies: minipass: 3.3.6 + stackback@0.0.2: {} + stat-mode@1.0.0: {} + std-env@3.7.0: {} + string-argv@0.3.2: {} string-ts@2.1.1: {} @@ -10325,6 +10550,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@2.1.0: + dependencies: + js-tokens: 9.0.0 + style-to-object@1.0.6: dependencies: inline-style-parser: 0.2.3 @@ -10435,6 +10664,12 @@ snapshots: tiny-typed-emitter@2.1.0: {} + tinybench@2.8.0: {} + + tinypool@0.8.4: {} + + tinyspy@2.2.1: {} + tldts-core@6.1.23: {} tldts@6.1.23: @@ -10475,6 +10710,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + type-fest@0.13.1: optional: true @@ -10634,6 +10871,23 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 + vite-node@1.6.0(@types/node@20.12.13): + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.1 + vite: 5.2.12(@types/node@20.12.13) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vite@5.2.12(@types/node@20.12.13): dependencies: esbuild: 0.20.2 @@ -10643,6 +10897,39 @@ snapshots: '@types/node': 20.12.13 fsevents: 2.3.3 + vitest@1.6.0(@types/node@20.12.13): + dependencies: + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.1 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.12(@types/node@20.12.13) + vite-node: 1.6.0(@types/node@20.12.13) + why-is-node-running: 2.2.2 + optionalDependencies: + '@types/node': 20.12.13 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vscode-languageserver-textdocument@1.0.11: {} vscode-uri@3.0.8: {} @@ -10657,6 +10944,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.2.2: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 diff --git a/src/renderer/src/lib/route-builder.test.ts b/src/renderer/src/lib/route-builder.test.ts new file mode 100644 index 000000000..b3c61a373 --- /dev/null +++ b/src/renderer/src/lib/route-builder.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, test } from "vitest" + +import { buildGlobRoutes } from "./route-builder" + +const fakePromise = () => Promise.resolve({ default: () => {} }) +describe("test route builder", () => { + test("match snapshot", () => { + expect( + buildGlobRoutes({ + "./pages/(external)/layout.tsx": fakePromise, + "./pages/(external)/(with-layout)/index.tsx": fakePromise, + "./pages/(external)/(with-layout)/layout.tsx": fakePromise, + "./pages/(external)/(with-layout)/feed/[:id]/index.tsx": fakePromise, + "./pages/(external)/(with-layout)/feed/[:id]/layout.tsx": fakePromise, + + "./pages/(main)/layout.tsx": fakePromise, + "./pages/(main)/(context)/layout.tsx": fakePromise, + "./pages/(main)/(context)/discover/layout.tsx": fakePromise, + "./pages/(main)/(context)/discover/index.tsx": fakePromise, + + "./pages/preview.tsx": fakePromise, + "./pages/add/layout.tsx": fakePromise, + "./pages/add/index.tsx": fakePromise, + }), + ).toMatchInlineSnapshot(` + [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "handle": { + "fs": "./pages/(main)/(context)/discover/index/", + "fullPath": "/discover/", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(main)/(context)/discover/layout", + "fullPath": "/discover", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(main)/(context)/discover/discover", + "fullPath": "/discover", + }, + "path": "discover", + }, + ], + "handle": { + "fs": "./pages/(main)/(context)", + "fullPath": "", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(main)", + "fullPath": "", + }, + "lazy": [Function], + "path": "", + }, + { + "children": [ + { + "children": [ + { + "handle": { + "fs": "./pages/add/index/", + "fullPath": "/add/", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/add/layout", + "fullPath": "/add", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/add/add", + "fullPath": "/add", + }, + "path": "add", + }, + { + "handle": { + "fs": "./pages/preview/preview", + "fullPath": "/preview", + }, + "lazy": [Function], + "path": "preview", + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "handle": { + "fs": "./pages/(external)/(with-layout)/feed/[:id]/index/", + "fullPath": "/feed/:id/", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(external)/(with-layout)/feed/[:id]/layout", + "fullPath": "/feed/:id", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(external)/(with-layout)/feed/[:id]/:id", + "fullPath": "/feed/:id", + }, + "path": ":id", + }, + ], + "handle": { + "fs": "./pages/(external)/(with-layout)/feed/feed", + "fullPath": "/feed", + }, + "path": "feed", + }, + { + "handle": { + "fs": "./pages/(external)/(with-layout)/index/", + "fullPath": "/", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(external)/(with-layout)", + "fullPath": "", + }, + "lazy": [Function], + "path": "", + }, + ], + "handle": { + "fs": "./pages/(external)", + "fullPath": "", + }, + "lazy": [Function], + "path": "", + }, + ] + `) + }) +}) diff --git a/src/renderer/src/lib/route-builder.ts b/src/renderer/src/lib/route-builder.ts index cab9ab475..044305db9 100644 --- a/src/renderer/src/lib/route-builder.ts +++ b/src/renderer/src/lib/route-builder.ts @@ -23,7 +23,7 @@ function nestPaths(paths: string[]): NestedStructure { return result } const pathGetterSet = new Set() -export function convertGlobToRoutes( +export function buildGlobRoutes( glob: Record Promise>, ): RouteObject[] { const keys = Object.keys(glob) @@ -35,6 +35,8 @@ export function convertGlobToRoutes( parentKey: string, children: RouteObject[], paths: NestedStructure, + + parentPath = "", ) { const pathKeys = Object.keys(paths) // sort `layout` to the start, and `index` to the end @@ -93,11 +95,15 @@ export function convertGlobToRoutes( } const childrenChildren: RouteObject[] = [] - dtsRoutes(`${segmentPathKey}/`, childrenChildren, paths[key]) + dtsRoutes(`${segmentPathKey}/`, childrenChildren, paths[key], parentPath) children.push({ path: "", lazy: globGetter, children: childrenChildren, + handle: { + fs: segmentPathKey, + fullPath: parentPath, + }, }) } else if (key === "layout") { // if parent key is grouped routes, the layout is handled, so skip this logic @@ -107,13 +113,23 @@ export function convertGlobToRoutes( // if `key` is `layout`, then it's a grouped route const accessPath = `${segmentPathKey}.tsx` const globGetter = get(glob, accessPath) - // console.log(segmentPathKey, key, globGetter, "layout") + const childrenChildren: RouteObject[] = [] - dtsRoutes(parentKey, childrenChildren, omit(paths, "layout")) + // should omit layout, because layout is already handled + dtsRoutes( + parentKey, + childrenChildren, + omit(paths, "layout"), + parentPath, + ) children.push({ path: "", lazy: globGetter, children: childrenChildren, + handle: { + fs: segmentPathKey, + fullPath: parentPath, + }, }) break } else { @@ -136,13 +152,27 @@ export function convertGlobToRoutes( children.push({ path: normalizeKey, lazy: globGetter, + handle: { + fs: `${segmentPathKey}/${normalizeKey}`, + fullPath: `${parentPath}/${normalizeKey}`, + }, }) } else { const childrenChildren: RouteObject[] = [] - dtsRoutes(`${segmentPathKey}/`, childrenChildren, paths[key]) + const fullPath = `${parentPath}/${normalizeKey}` + dtsRoutes( + `${segmentPathKey}/`, + childrenChildren, + paths[key], + fullPath, + ) children.push({ path: normalizeKey, children: childrenChildren, + handle: { + fs: `${segmentPathKey}/${normalizeKey}`, + fullPath, + }, }) } } diff --git a/src/renderer/src/router.tsx b/src/renderer/src/router.tsx index 727bc8a19..23e7f7386 100644 --- a/src/renderer/src/router.tsx +++ b/src/renderer/src/router.tsx @@ -1,10 +1,10 @@ import { createBrowserRouter } from "react-router-dom" import App from "./App" -import { convertGlobToRoutes } from "./lib/route-builder" +import { buildGlobRoutes } from "./lib/route-builder" -const globTree = import.meta.glob("./pages/**/*.{tsx,jsx}") -const tree = convertGlobToRoutes(globTree) +const globTree = import.meta.glob("./pages/**/*.tsx") +const tree = buildGlobRoutes(globTree) // console.log(tree) export const router = createBrowserRouter([