diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 06e43db..5b62b98 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -40,6 +40,8 @@ jobs: cache: 'pnpm' - name: Install dependencies run: pnpm install + - name: Run tests + run: pnpm vitest run - name: Build run: pnpm run build:docs - name: Setup Pages diff --git a/package.json b/package.json index e738fdb..f734bee 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "build": "tsc && vite build", "build:docs": "vite build --config vite-docs.config.ts", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest" }, "dependencies": { "pdfmake": "^0.2.8", @@ -35,7 +36,8 @@ "prettier": "^3.1.0", "typescript": "^5.2.2", "vite": "^5.0.0", - "vite-plugin-dts": "^3.6.3" + "vite-plugin-dts": "^3.6.3", + "vitest": "^0.34.6" }, "files": [ "dist" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60df95f..4f89a3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,6 +67,9 @@ devDependencies: vite-plugin-dts: specifier: ^3.6.3 version: 3.6.3(@types/node@20.9.1)(typescript@5.2.2)(vite@5.0.0) + vitest: + specifier: ^0.34.6 + version: 0.34.6 packages: @@ -393,6 +396,17 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + /@microsoft/api-extractor-model@7.28.2(@types/node@20.9.1): resolution: {integrity: sha512-vkojrM2fo3q4n4oPh4uUZdjJ2DxQ2+RnDQL/xhTWSRUNPF6P4QyrvY357HBxbnltKcYu+nNNolVqc6TIGQ73Ig==} dependencies: @@ -601,6 +615,10 @@ packages: string-argv: 0.3.2 dev: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@swc/core-darwin-arm64@1.3.96: resolution: {integrity: sha512-8hzgXYVd85hfPh6mJ9yrG26rhgzCmcLO0h1TIl8U31hwmTbfZLzRitFQ/kqMJNbIBCwmNH1RU2QcJnL3d7f69A==} engines: {node: '>=10'} @@ -728,6 +746,16 @@ packages: resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} dev: true + /@types/chai-subset@1.3.5: + resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==} + dependencies: + '@types/chai': 4.3.10 + dev: true + + /@types/chai@4.3.10: + resolution: {integrity: sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==} + dev: true + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true @@ -933,6 +961,44 @@ packages: - '@swc/helpers' dev: true + /@vitest/expect@0.34.6: + resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + dependencies: + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + chai: 4.3.10 + dev: true + + /@vitest/runner@0.34.6: + resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + dependencies: + '@vitest/utils': 0.34.6 + p-limit: 4.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@0.34.6: + resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@0.34.6: + resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/utils@0.34.6: + resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + /@volar/language-core@1.10.10: resolution: {integrity: sha512-nsV1o3AZ5n5jaEAObrS3MWLBWaGwUj/vAsc15FVNIv+DbpizQRISg9wzygsHBr56ELRH8r4K75vkYNMtsSNNWw==} dependencies: @@ -1012,6 +1078,11 @@ packages: engines: {node: '>=0.4.0'} dev: false + /acorn-walk@8.3.0: + resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} @@ -1052,6 +1123,11 @@ packages: color-convert: 2.0.1 dev: true + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: @@ -1071,6 +1147,10 @@ packages: engines: {node: '>=8'} dev: true + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /ast-transform@0.0.0: resolution: {integrity: sha512-e/JfLiSoakfmL4wmTGPjv0HpTICVmxwXgYOB8x+mzozHL8v+dSfCbrJ8J8hJ0YBP0XcYu1aLZ6b/3TnxNK3P2A==} dependencies: @@ -1155,6 +1235,11 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: false + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /call-bind@1.0.5: resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} dependencies: @@ -1168,6 +1253,19 @@ packages: engines: {node: '>=6'} dev: true + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} + engines: {node: '>=4'} + 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.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1176,6 +1274,12 @@ packages: supports-color: 7.2.0 dev: true + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -1274,6 +1378,13 @@ packages: ms: 2.1.2 dev: true + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-equal@1.1.2: resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} engines: {node: '>= 0.4'} @@ -1311,6 +1422,11 @@ packages: resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} dev: false + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1691,6 +1807,10 @@ packages: resolution: {integrity: sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==} dev: false + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} dependencies: @@ -1927,6 +2047,10 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -1959,6 +2083,11 @@ packages: type-check: 0.4.0 dev: true + /local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + dev: true + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1989,6 +2118,12 @@ packages: js-tokens: 4.0.0 dev: false + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2002,6 +2137,13 @@ packages: sourcemap-codec: 1.4.8 dev: false + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /merge-source-map@1.0.4: resolution: {integrity: sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==} dependencies: @@ -2038,6 +2180,15 @@ packages: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: false + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.11.2 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.2 + dev: true + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true @@ -2114,6 +2265,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -2159,6 +2317,14 @@ packages: engines: {node: '>=8'} dev: true + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + /pdfmake@0.2.8: resolution: {integrity: sha512-lI+amfIaUL8CrPhndxFdhIgMj9JB49Sj4DARltKC1gLm/5NsPohZqfB+D+II8HymtPB6eugUFD5oBxmzO57qHA==} engines: {node: '>=12'} @@ -2178,6 +2344,14 @@ packages: engines: {node: '>=8.6'} dev: true + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.2 + pathe: 1.1.1 + dev: true + /png-js@1.0.0: resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} dev: false @@ -2207,6 +2381,15 @@ packages: hasBin: true dev: true + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: false @@ -2239,6 +2422,10 @@ packages: scheduler: 0.23.0 dev: false + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /react-reconciler@0.29.0(react@18.2.0): resolution: {integrity: sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==} engines: {node: '>=0.10.0'} @@ -2413,6 +2600,10 @@ packages: engines: {node: '>=8'} dev: true + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -2450,6 +2641,10 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + /static-eval@2.1.0: resolution: {integrity: sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==} dependencies: @@ -2475,6 +2670,10 @@ packages: through2: 2.0.5 dev: false + /std-env@3.5.0: + resolution: {integrity: sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==} + dev: true + /string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2498,6 +2697,12 @@ packages: engines: {node: '>=8'} dev: true + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.11.2 + dev: true + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2528,6 +2733,20 @@ packages: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} dev: false + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + dev: true + + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -2563,6 +2782,11 @@ packages: prelude-ls: 1.2.1 dev: true + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -2592,6 +2816,10 @@ packages: hasBin: true dev: true + /ufo@1.3.2: + resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + dev: true + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: true @@ -2630,6 +2858,28 @@ packages: engines: {node: '>= 0.10'} dev: true + /vite-node@0.34.6(@types/node@20.9.1): + resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.4.2 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.0.0(@types/node@20.9.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-plugin-dts@3.6.3(@types/node@20.9.1)(typescript@5.2.2)(vite@5.0.0): resolution: {integrity: sha512-NyRvgobl15rYj65coi/gH7UAEH+CpSjh539DbGb40DfOTZSvDLNYTzc8CK4460W+LqXuMK7+U3JAxRB3ksrNPw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2690,6 +2940,71 @@ packages: fsevents: 2.3.3 dev: true + /vitest@0.34.6: + resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.10 + '@types/chai-subset': 1.3.5 + '@types/node': 20.9.1 + '@vitest/expect': 0.34.6 + '@vitest/runner': 0.34.6 + '@vitest/snapshot': 0.34.6 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + acorn: 8.11.2 + acorn-walk: 8.3.0 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4 + local-pkg: 0.4.3 + 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.7.0 + vite: 5.0.0(@types/node@20.9.1) + vite-node: 0.34.6(@types/node@20.9.1) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vue-template-compiler@2.7.15: resolution: {integrity: sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==} dependencies: @@ -2717,6 +3032,15 @@ packages: isexe: 2.0.0 dev: true + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2746,6 +3070,11 @@ packages: engines: {node: '>=10'} dev: true + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + /z-schema@5.0.5: resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} engines: {node: '>=8.0.0'} diff --git a/src/PdfPreview.tsx b/src/PdfPreview.tsx index 6a132eb..35262a4 100644 --- a/src/PdfPreview.tsx +++ b/src/PdfPreview.tsx @@ -48,7 +48,7 @@ const usePdfObjectLink = (content: Content): string | null => { }); blobPromise.then((blob) => { - console.log("New PDF rendered"); + // console.log("New PDF rendered"); setLink(URL.createObjectURL(blob)); }); }, [content]); @@ -60,7 +60,7 @@ const useRenderContent = (pdfElement: PdfNode): Content => { const [content, setContent] = useState([]); useEffect(() => { - console.log("Pdf has changed"); + // console.log("Pdf has changed"); const { unmount } = PdfRenderer.render(pdfElement, setContent); return () => { diff --git a/src/PdfRenderer.ts b/src/PdfRenderer.ts index cfc3d94..d575f04 100644 --- a/src/PdfRenderer.ts +++ b/src/PdfRenderer.ts @@ -3,6 +3,7 @@ import ReactReconciler from "react-reconciler"; import { hostConfig } from "./hostConfig.ts"; import { ContentUpdateHandler } from "./types/ContentUpdateHandler.ts"; import { Container } from "./types/Container.ts"; +import { Content } from "pdfmake/interfaces"; const ReactReconcilerInst = ReactReconciler(hostConfig); @@ -23,7 +24,7 @@ export const PdfRenderer = { null, ); - console.log("root", root); + // console.log("root", root); ReactReconcilerInst.updateContainer(reactElement, root, null); ReactReconcilerInst.injectIntoDevTools({ @@ -34,10 +35,17 @@ export const PdfRenderer = { }); const unmount = () => { - console.log("Unmounting"); + // console.log("Unmounting"); ReactReconcilerInst.updateContainer([], root, null); }; return { container, root, unmount }; }, + renderOnce: async (renderElement: PdfNode): Promise => + new Promise((resolve) => { + const { unmount } = PdfRenderer.render(renderElement, (content) => { + resolve(content); + unmount(); + }); + }), }; diff --git a/src/__tests__/PdfRenderer.test.tsx b/src/__tests__/PdfRenderer.test.tsx new file mode 100644 index 0000000..ec4aa32 --- /dev/null +++ b/src/__tests__/PdfRenderer.test.tsx @@ -0,0 +1,279 @@ +import { describe, expect, test } from "vitest"; +import { PdfRenderer } from "../PdfRenderer.ts"; + +describe("PdfRenderer", () => { + describe("PDF Make Content", () => { + test("string", () => { + expect(PdfRenderer.renderOnce("Hello World!")).resolves.toEqual([ + "Hello World!", + ]); + }); + + test("number", () => { + expect(PdfRenderer.renderOnce(1)).resolves.toEqual(["1"]); + }); + + test("text", () => { + expect( + PdfRenderer.renderOnce( + <> + Hello World! + Hello World! + Hello World! + Hello World! + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + bold: true, + }, + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + link: "https://google.com", + }, + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + id: "1", + }, + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + tocItem: "1", + }, + ]); + }); + + test("columns", () => { + expect( + PdfRenderer.renderOnce( + + Hello World! + + Hello World! + + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-columns", + columns: [ + "Hello World!", + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + width: "50%", + }, + ], + }, + ]); + }); + + test("stack", () => { + expect( + PdfRenderer.renderOnce( + + Hello World! + Hello World! + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-stack", + stack: [ + "Hello World!", + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + }, + ], + fillColor: "#000000", + }, + ]); + }); + + test("ol", () => { + expect( + PdfRenderer.renderOnce( + + Hello World! + + Hello World! + + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-ol", + ol: [ + "Hello World!", + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + counter: 10, + }, + ], + type: "lower-alpha", + }, + ]); + }); + + test("ul", () => { + expect( + PdfRenderer.renderOnce( + + Hello World! + + Hello World! + + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-ul", + ul: [ + "Hello World!", + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + listType: "circle", + }, + ], + type: "square", + }, + ]); + }); + + test("table", () => { + expect( + PdfRenderer.renderOnce( + + + + Hello World! + + Hello World! + + + + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-table", + table: { + $__reactPdfMakeType: "pdf-tbody", + headerRows: 1, + body: [ + [ + "Hello World!", + { + $__reactPdfMakeType: "pdf-text", + text: "Hello World!", + rowSpan: 1, + }, + ], + ], + }, + layout: "lightHorizontalLines", + }, + ]); + }); + + test("pageReference", () => { + expect( + PdfRenderer.renderOnce( + Hello World!, + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-pageReference", + pageReference: "Hello World!", + }, + ]); + }); + + test("textReference", () => { + expect( + PdfRenderer.renderOnce( + Hello World!, + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-textReference", + textReference: "Hello World!", + }, + ]); + }); + + test("toc", () => { + expect( + PdfRenderer.renderOnce( + + Title + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-toc", + title: { + $__reactPdfMakeType: "pdf-text", + text: "Title", + }, + numberStyle: "numberStyle", + }, + ]); + }); + + test("image", () => { + expect( + PdfRenderer.renderOnce( + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-image", + image: "https://example.com/logo.png", + }, + ]); + }); + + test("svg", () => { + expect( + PdfRenderer.renderOnce(), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-svg", + svg: "", + }, + ]); + }); + + test("qr", () => { + expect( + PdfRenderer.renderOnce(), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-qr", + qr: "Hello World!", + }, + ]); + }); + + test("canvas", () => { + expect( + PdfRenderer.renderOnce( + , + ), + ).resolves.toEqual([ + { + $__reactPdfMakeType: "pdf-canvas", + canvas: [{ type: "rect", x: 0, y: 0, w: 10, h: 10 }], + }, + ]); + }); + }); +}); diff --git a/src/hostConfig.ts b/src/hostConfig.ts index 059b1c2..5aaf5d0 100644 --- a/src/hostConfig.ts +++ b/src/hostConfig.ts @@ -2,8 +2,6 @@ import { HostConfig } from "react-reconciler"; import { Content } from "pdfmake/interfaces"; import { DefaultEventPriority } from "react-reconciler/constants"; import { - PdfCellProps, - PdfPrimitiveType, PdfReconcilerElement, PdfReconcilerIntrinsicType, PdfReconcilerNode, @@ -38,23 +36,11 @@ export const hostConfig: HostConfig< supportsPersistence: true, supportsHydration: false, createInstance: (type, props) => { - console.log("createInstance"); - console.log("Type", type); + // console.log("createInstance"); + // console.log("Type", type); if (type === "pdf-array") return []; - if (type === "pdf-cell") { - // Cell doesn't constitute an element, but carries extra props to element below - const { children, ...tableCellProps } = props as unknown as PdfCellProps; - - console.log("Cell", children); - - return { - $__reactPdfMakeType: children.type as PdfReconcilerIntrinsicType, - ...tableCellProps, - } satisfies PdfReconcilerElement; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars const { children: _, ...restProps } = props; @@ -64,7 +50,7 @@ export const hostConfig: HostConfig< }; }, createTextInstance: (text) => { - console.log("create text instance", text); + // console.log("create text instance", text); return text; }, appendInitialChild: (parent, child) => { @@ -94,14 +80,13 @@ export const hostConfig: HostConfig< getInstanceFromNode: () => null, // Persistence mode cloneInstance: ( - instance, - updatePayload, + _instance, + _updatePayload, type, - oldProps, + _oldProps, newProps, _, keepChildren, - recyclableInstance, ) => { const { children, ...restNewProps } = newProps; @@ -117,29 +102,16 @@ export const hostConfig: HostConfig< ...restNewProps, }; - console.log("Clone instance", { - newInstance, - instance, - updatePayload, - type, - oldProps, - newProps, - keepChildren, - recyclableInstance, - }); - return newInstance; }, - createContainerChildSet: (container) => { - console.log("Container", container); - + createContainerChildSet: () => { return []; }, appendChildToContainerChildSet: (childSet, child) => { childSet.push(child); }, finalizeContainerChildren: (container, newChildren) => { - console.log("finalizeContainerChildren", container, newChildren); + // console.log("finalizeContainerChildren", container, newChildren); container[container.$__reactPdfMakeType] = newChildren; container.onUpdate(newChildren as Content); }, @@ -168,70 +140,119 @@ const removePdfTypePrefix = ( const pdfAppendChild = ( parent: PdfReconcilerNode, - child: PdfReconcilerNode, + _child: PdfReconcilerNode, ): void => { - console.log("append child", parent, child); + // console.log("append child", parent, child); + const child = getRealChild(_child); // TextInstance if (typeof parent !== "object") { throw new Error("Cannot append to text instance"); } - // Inline + // Array if (Array.isArray(parent)) { parent.push(child); return; } - const contentKey = getContentKey(parent.$__reactPdfMakeType); + const parentType = parent.$__reactPdfMakeType; + + const contentKey = getContentKey(parentType); if (contentKey === null) { return; } // Typical object content + const currentChildren = getChildrenFromElement(parent); - const content = parent[contentKey]; - - if (content === undefined) { + if (currentChildren === undefined) { // First append - if (contentKey === "body") { + if ( + contentKey === "body" || + contentKey === "columns" || + contentKey === "stack" + ) { + // Always exist as array parent[contentKey] = [child]; return; } + // First item does not need to exist as array parent[contentKey] = child; - } else if (!Array.isArray(content)) { + } else if (!Array.isArray(currentChildren)) { // create array and append child - parent[contentKey] = [content, child]; + parent[contentKey] = [currentChildren, child]; } else { - console.log("Content already array", content, child); - // reuse array and append child - parent[contentKey] = [...content, child]; + parent[contentKey] = [...currentChildren, child]; } }; const getContentKey = ( key: PdfReconcilerIntrinsicType, -): PdfPrimitiveType | "body" | "title" | null => { +): VirtualPdfPrimitiveType | "body" | "title" => { const baseType = removePdfTypePrefix(key); - if (baseType === "array" || baseType === "cell" || baseType === "root") { - return null; - } - if (baseType === "tbody") { return "body"; } - if (baseType === "anchor" || baseType === "tocItem") { - return "text"; - } - if (baseType === "toc") { return "title"; } return baseType; }; + +const isPdfReconcilerElement = ( + node: PdfReconcilerNode, +): node is PdfReconcilerElement => + typeof node === "object" && node !== null && !Array.isArray(node); + +const getChildrenFromElement = ( + elem: PdfReconcilerElement, +): PdfReconcilerNode | undefined => { + const type = elem.$__reactPdfMakeType; + + const contentKey = getContentKey(type); + + if (contentKey === null) return undefined; + + return elem[contentKey] as PdfReconcilerNode | undefined; +}; + +/** + * Resolves virtual elements only used to pass extra props + */ +const getRealChild = (child: PdfReconcilerNode): PdfReconcilerNode => { + if (isPdfReconcilerElement(child)) { + const childType = child.$__reactPdfMakeType; + + if ( + childType === "pdf-cell" || + childType === "pdf-column" || + childType === "pdf-li" + ) { + const grandChild = getChildrenFromElement(child); + if (grandChild === undefined || !isPdfReconcilerElement(grandChild)) { + console.log( + "This element requires a single PDF element as children. Received: ", + grandChild, + ); + return child; + } + + const newGrandChild = { + ...child, + ...grandChild, + }; + + delete newGrandChild[getContentKey(child.$__reactPdfMakeType)]; + + return newGrandChild; + } + } + return child; +}; diff --git a/src/types/PdfElements.ts b/src/types/PdfElements.ts index 23cb77a..2c4d9b8 100644 --- a/src/types/PdfElements.ts +++ b/src/types/PdfElements.ts @@ -1,4 +1,5 @@ import { + ColumnProperties, ContentAnchor, ContentCanvas, ContentColumns, @@ -14,9 +15,11 @@ import { ContentTextReference, ContentTocItem, ContentUnorderedList, + OrderedListElementProperties, Table, TableCellProperties, TableOfContent, + UnorderedListElementProperties, } from "pdfmake/interfaces"; import { PdfElement, PdfNode } from "./PdfNode.ts"; @@ -27,11 +30,13 @@ export interface PdfElementsSansPrefix { text: { children?: PdfNode } & ( | Omit | Omit + | Omit + | Omit ); columns: { children?: PdfNode } & Omit; stack: { children?: PdfNode } & Omit; - ol: { children?: PdfNode } & Omit; - ul: { children?: PdfNode } & Omit; + ol: { children?: PdfNode } & Omit; + ul: { children?: PdfNode } & Omit; table: { children?: PdfNode } & Omit; pageReference: { children: string } & Omit< ContentPageReference, @@ -47,17 +52,31 @@ export interface PdfElementsSansPrefix { canvas: ContentCanvas; } -export interface PdfCellProps extends TableCellProperties { +export interface PdfElementWrapperProps { children: PdfElement; } +export interface PdfCellProps + extends TableCellProperties, + PdfElementWrapperProps {} + +export interface PdfColumnProps + extends ColumnProperties, + PdfElementWrapperProps {} + +export type PdfListItemProps = ( + | OrderedListElementProperties + | UnorderedListElementProperties +) & + PdfElementWrapperProps; + export interface VirtualPdfElementsSansPrefix extends PdfElementsSansPrefix { array: { children?: PdfNode }; cell: PdfCellProps; + column: PdfColumnProps; + li: PdfListItemProps; tbody: { children?: PdfNode } & Omit; - anchor: { children?: PdfNode } & Omit; toc: { children?: PdfNode } & Omit; - tocItem: { children?: PdfNode } & Omit; } export type PdfElements = { diff --git a/vite.config.ts b/vite.config.ts index f196e95..6448e21 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,7 +26,7 @@ export default defineConfig({ react(), dts({ copyDtsFiles: true, - exclude: "./src/vite-env.d.ts", + exclude: ["./src/vite-env.d.ts", "**/*.test.{ts,tsx}", "./src/demo/**/*"], }), ], });