diff --git a/package.json b/package.json index a881eaf..0cb0d4e 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@types/compression": "^1.7.1", "@types/node": "18", "compression": "^1.7.4", - "jsdom": "^22.1.0", + "happy-dom": "^14.12.3", "lint-staged": "^10.5.3", "msw": "^2.3.0", "playwright": "^1.34.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c67d29..bee8aba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,9 @@ devDependencies: compression: specifier: ^1.7.4 version: 1.7.4 - jsdom: - specifier: ^22.1.0 - version: 22.1.0 + happy-dom: + specifier: ^14.12.3 + version: 14.12.3 lint-staged: specifier: ^10.5.3 version: 10.5.4 @@ -79,7 +79,7 @@ devDependencies: version: 5.4.5 vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@22.1.0) + version: 1.6.0(@types/node@18.19.33)(happy-dom@14.12.3) packages: @@ -834,11 +834,6 @@ packages: resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} dev: true - /@tootallnate/once@2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - dev: true - /@tsconfig/node10@1.0.11: resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} dev: true @@ -1092,11 +1087,6 @@ packages: through: 2.3.8 dev: true - /abab@2.0.6: - resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - deprecated: Use your platform's native atob() and btoa() methods instead - dev: true - /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1116,15 +1106,6 @@ packages: hasBin: true dev: true - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -1632,27 +1613,11 @@ packages: which: 2.0.2 dev: true - /cssstyle@3.0.0: - resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==} - engines: {node: '>=14'} - dependencies: - rrweb-cssom: 0.6.0 - dev: true - /dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} dev: true - /data-urls@4.0.0: - resolution: {integrity: sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==} - engines: {node: '>=14'} - dependencies: - abab: 2.0.6 - whatwg-mimetype: 3.0.0 - whatwg-url: 12.0.1 - dev: true - /dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} dev: true @@ -1693,10 +1658,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true - /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -1747,14 +1708,6 @@ packages: engines: {node: '>=0.3.1'} dev: true - /domexception@4.0.0: - resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} - engines: {node: '>=12'} - deprecated: Use your platform's native DOMException instead - dependencies: - webidl-conversions: 7.0.0 - dev: true - /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -2215,6 +2168,15 @@ packages: engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} dev: true + /happy-dom@14.12.3: + resolution: {integrity: sha512-vsYlEs3E9gLwA1Hp+w3qzu+RUDFf4VTT8cyKqVICoZ2k7WM++Qyd2LwzyTi5bqMJFiIC/vNpTDYuxdreENRK/g==} + engines: {node: '>=16.0.0'} + dependencies: + entities: 4.5.0 + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + dev: true + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -2268,13 +2230,6 @@ packages: lru-cache: 6.0.0 dev: true - /html-encoding-sniffer@3.0.0: - resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} - engines: {node: '>=12'} - dependencies: - whatwg-encoding: 2.0.0 - dev: true - /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -2286,27 +2241,6 @@ packages: toidentifier: 1.0.1 dev: true - /http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - /human-signals@1.1.1: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} @@ -2329,13 +2263,6 @@ packages: safer-buffer: 2.1.2 dev: true - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -2401,10 +2328,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true - /is-regexp@1.0.0: resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} engines: {node: '>=0.10.0'} @@ -2495,44 +2418,6 @@ packages: argparse: 2.0.1 dev: true - /jsdom@22.1.0: - resolution: {integrity: sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==} - engines: {node: '>=16'} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - abab: 2.0.6 - cssstyle: 3.0.0 - data-urls: 4.0.0 - decimal.js: 10.4.3 - domexception: 4.0.0 - form-data: 4.0.0 - html-encoding-sniffer: 3.0.0 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.9 - parse5: 7.1.2 - rrweb-cssom: 0.6.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.4 - w3c-xmlserializer: 4.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 - whatwg-url: 12.0.1 - ws: 8.18.0 - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -2977,10 +2862,6 @@ packages: path-key: 4.0.0 dev: true - /nwsapi@2.2.9: - resolution: {integrity: sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==} - dev: true - /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3097,12 +2978,6 @@ packages: lines-and-columns: 1.2.4 dev: true - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - dependencies: - entities: 4.5.0 - dev: true - /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -3282,10 +3157,6 @@ packages: ipaddr.js: 1.9.1 dev: true - /psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - dev: true - /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -3304,10 +3175,6 @@ packages: side-channel: 1.0.6 dev: true - /querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true - /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: true @@ -3423,10 +3290,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true - /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3504,10 +3367,6 @@ packages: fsevents: 2.3.3 dev: true - /rrweb-cssom@0.6.0: - resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - dev: true - /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: @@ -3531,13 +3390,6 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true - /saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - dependencies: - xmlchars: 2.2.0 - dev: true - /secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} dev: true @@ -3907,10 +3759,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true - /text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -3970,27 +3818,10 @@ packages: engines: {node: '>=0.6'} dev: true - /tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - dev: true - /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: true - /tr46@4.1.1: - resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} - engines: {node: '>=14'} - dependencies: - punycode: 2.3.1 - dev: true - /traverse@0.6.8: resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} engines: {node: '>= 0.4'} @@ -4119,11 +3950,6 @@ packages: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: true - /universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - dev: true - /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -4139,13 +3965,6 @@ packages: dependencies: punycode: 2.3.1 - /url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - dev: true - /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true @@ -4228,7 +4047,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.6.0(@types/node@18.19.33)(jsdom@22.1.0): + /vitest@1.6.0(@types/node@18.19.33)(happy-dom@14.12.3): resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4263,7 +4082,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 - jsdom: 22.1.0 + happy-dom: 14.12.3 local-pkg: 0.5.0 magic-string: 0.30.10 pathe: 1.1.2 @@ -4285,13 +4104,6 @@ packages: - terser dev: true - /w3c-xmlserializer@4.0.0: - resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} - engines: {node: '>=14'} - dependencies: - xml-name-validator: 4.0.0 - dev: true - /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true @@ -4301,26 +4113,11 @@ packages: engines: {node: '>=12'} dev: true - /whatwg-encoding@2.0.0: - resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} - engines: {node: '>=12'} - dependencies: - iconv-lite: 0.6.3 - dev: true - /whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} dev: true - /whatwg-url@12.0.1: - resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==} - engines: {node: '>=14'} - dependencies: - tr46: 4.1.1 - webidl-conversions: 7.0.0 - dev: true - /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -4389,15 +4186,6 @@ packages: optional: true dev: true - /xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - dev: true - - /xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true - /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/src/open-api/utils/get-servers.test.ts b/src/open-api/utils/get-servers.test.ts index 8cff804..ccc79ab 100644 --- a/src/open-api/utils/get-servers.test.ts +++ b/src/open-api/utils/get-servers.test.ts @@ -1,6 +1,3 @@ -/** - * @vitest-environment jsdom - */ import { createOpenApiSpec } from '../../../test/support/create-open-api-spec.js' import { getServers } from './get-servers.js' diff --git a/src/traffic/from-traffic.ts b/src/traffic/from-traffic.ts index fedb13b..b663a7b 100644 --- a/src/traffic/from-traffic.ts +++ b/src/traffic/from-traffic.ts @@ -1,7 +1,7 @@ import { invariant } from 'outvariant' import type Har from 'har-format' import { RequestHandler, HttpHandler, cleanUrl, delay } from 'msw' -import { toResponse } from './utils/har-utils.js' +import { matchesQueryParameters, toResponse } from './utils/har-utils.js' export type MapEntryFunction = (entry: Har.Entry) => Har.Entry | undefined @@ -43,18 +43,28 @@ export function fromTraffic( continue } - const { request } = entry - - const requestId = createRequestId(request) + const requestId = createRequestId(entry.request) const isUniqueHandler = !requestIds.has(requestId) - const method = request.method.toLowerCase() - const path = cleanUrl(request.url) + const method = entry.request.method.toLowerCase() + const path = cleanUrl(entry.request.url) const response = toResponse(entry.response) const handler = new HttpHandler( method, path, - async () => { + async ({ request }) => { + // If the recorded request has query parameters and the + // intercepted request doesn't match them all, skip it. + if ( + entry.request.queryString && + !matchesQueryParameters( + new URL(request.url).searchParams, + entry.request.queryString, + ) + ) { + return + } + if (entry.time) { await delay(entry.time) } diff --git a/src/traffic/utils/har-utils.test.ts b/src/traffic/utils/har-utils.test.ts index 42d7075..c8b6d54 100644 --- a/src/traffic/utils/har-utils.test.ts +++ b/src/traffic/utils/har-utils.test.ts @@ -1,4 +1,9 @@ -import { toHeaders, toResponse, toResponseBody } from './har-utils' +import { + matchesQueryParameters, + toHeaders, + toResponse, + toResponseBody, +} from './har-utils' describe(toHeaders, () => { it('supports a single har headers', () => { @@ -76,3 +81,105 @@ describe(toResponseBody, () => { }) describe.todo(toResponse) + +describe(matchesQueryParameters, () => { + it('returns true if search parameters are empty', () => { + expect(matchesQueryParameters(new URLSearchParams([]), [])).toBe(true) + }) + + it('returns true on exact search parameters match', () => { + expect( + matchesQueryParameters( + new URLSearchParams([ + ['a', '1'], + ['b', '2'], + ]), + [ + { name: 'a', value: '1' }, + { name: 'b', value: '2' }, + ], + ), + ).toBe(true) + }) + + it('returns false if missing some search parameters', () => { + expect( + matchesQueryParameters(new URLSearchParams([['a', '1']]), [ + { name: 'a', value: '1' }, + { name: 'b', value: '2' }, + ]), + ).toBe(false) + }) + + it('returns false if search parameter value differs', () => { + expect( + matchesQueryParameters(new URLSearchParams([['a', '000']]), [ + { name: 'a', value: '1' }, + ]), + ).toBe(false) + }) + + it('supports encoded search parameters', () => { + expect( + matchesQueryParameters(new URLSearchParams('?key=hello%20world'), [ + { name: 'key', value: 'hello world' }, + ]), + ).toBe(true) + }) + + it('supports multi-value search parameters', () => { + expect( + matchesQueryParameters( + new URLSearchParams([ + ['a', '1'], + ['a', '2'], + ['b', '3'], + ]), + [ + { name: 'a', value: '1' }, + { name: 'a', value: '2' }, + { name: 'b', value: '3' }, + ], + ), + ).toBe(true) + }) + + it('returns false if search parameters differ in casing', () => { + expect( + matchesQueryParameters(new URLSearchParams([['Key', 'Value']]), [ + { name: 'key', value: 'value' }, + ]), + ).toBe(false) + }) + + it('disregards the order of search parameters', () => { + expect( + matchesQueryParameters( + new URLSearchParams([ + ['b', '2'], + ['a', '1'], + ]), + [ + { name: 'a', value: '1' }, + { name: 'b', value: '2' }, + ], + ), + ).toBe(true) + }) + + it('returns false on extra search parameters', () => { + expect(matchesQueryParameters(new URLSearchParams([['a', '1']]), [])).toBe( + false, + ) + + expect( + matchesQueryParameters( + new URLSearchParams([ + ['b', '2'], + ['a', '1'], + ]), + [{ name: 'a', value: '1' }], + ), + ).toBe(false) + }) +}) diff --git a/src/traffic/utils/har-utils.ts b/src/traffic/utils/har-utils.ts index 06f72a4..14747b0 100644 --- a/src/traffic/utils/har-utils.ts +++ b/src/traffic/utils/har-utils.ts @@ -44,3 +44,38 @@ export function toResponse(responseEntry: Har.Response): Response { }) return response } + +/** + * Check if the search parameters of a request match the recorded parameters. + * @param searchParams Actual request search parameters. + * @param queryString Expected recorded HAR query parameters. + * @returns A boolean indicating whether the search parameters match. + */ +export function matchesQueryParameters( + searchParams: URLSearchParams, + queryString: Array, +): boolean { + for (const { name, value } of queryString) { + if (!searchParams.has(name)) { + return false + } + + // Coerce each search parameter to a multi-value parameter. + if (!searchParams.getAll(name).includes(value)) { + return false + } + + // Delete the search parameters that match. + // Provide an explicit value to support multi-value parameters. + // That will delete only the matching name/value pair. + searchParams.delete(name, value) + } + + // Forbid extra search parameters in the actual request. + // Extra search parameters may indicate a different request altogether. + if (searchParams.size > 0) { + return false + } + + return true +} diff --git a/test/oas/oas-json-schema.test.ts b/test/oas/oas-json-schema.test.ts index cee122f..83d818e 100644 --- a/test/oas/oas-json-schema.test.ts +++ b/test/oas/oas-json-schema.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment happy-dom import { fromOpenApi } from '../../src/open-api/from-open-api.js' import { withHandlers } from '../support/with-handlers.js' import { createOpenApiSpec } from '../support/create-open-api-spec.js' diff --git a/test/oas/oas-response-headers.test.ts b/test/oas/oas-response-headers.test.ts index 1b18008..1acac38 100644 --- a/test/oas/oas-response-headers.test.ts +++ b/test/oas/oas-response-headers.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment happy-dom import { fromOpenApi } from '../../src/open-api/from-open-api.js' import { createOpenApiSpec } from '../../test/support/create-open-api-spec.js' import { InspectedHandler, inspectHandlers } from '../support/inspect.js' diff --git a/test/oas/oas-response.test.ts b/test/oas/oas-response.test.ts index d2b290d..3398a0b 100644 --- a/test/oas/oas-response.test.ts +++ b/test/oas/oas-response.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment happy-dom import { fromOpenApi } from '../../src/open-api/from-open-api.js' import { InspectedHandler, inspectHandlers } from '../support/inspect.js' diff --git a/test/oas/oas-servers.test.ts b/test/oas/oas-servers.test.ts index 1140765..5651d9b 100644 --- a/test/oas/oas-servers.test.ts +++ b/test/oas/oas-servers.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment happy-dom import { fromOpenApi } from '../../src/open-api/from-open-api.js' import { createOpenApiSpec } from '../support/create-open-api-spec.js' import { InspectedHandler, inspectHandlers } from '../support/inspect.js' diff --git a/test/oas/petstore.test.ts b/test/oas/petstore.test.ts index fa95896..853def6 100644 --- a/test/oas/petstore.test.ts +++ b/test/oas/petstore.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment happy-dom import { RequestHandler } from 'msw' import { fromOpenApi } from '../../src/open-api/from-open-api.js' import { withHandlers } from '../../test/support/with-handlers.js' diff --git a/test/support/inspect.ts b/test/support/inspect.ts index d4308f1..b070a7b 100644 --- a/test/support/inspect.ts +++ b/test/support/inspect.ts @@ -85,6 +85,16 @@ export async function inspectHandlers(handlers: Array) { return await Promise.all(handlers.map(inspectHandler)) } +export function normalizeHeaders(headers: Headers) { + const nextHeaders = Array.from(headers.entries()).map<[string, string]>( + ([name, value]) => { + return [name.toLowerCase(), value] + }, + ) + nextHeaders.sort() + return nextHeaders +} + async function serializeResponse( response?: Response, ): Promise { @@ -95,7 +105,7 @@ async function serializeResponse( return { status: response.status, statusText: response.statusText, - headers: Array.from(response.headers), + headers: normalizeHeaders(response.headers), body: await response.text(), } } diff --git a/test/traffic/fixtures/archives/request-parameters.har b/test/traffic/fixtures/archives/request-parameters.har new file mode 100644 index 0000000..befbd48 --- /dev/null +++ b/test/traffic/fixtures/archives/request-parameters.har @@ -0,0 +1,114 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Proxyman", + "version": "5.6.1" + }, + "entries": [ + { + "request": { + "headersSize": 419, + "url": "https://source.mswjs.io/docs/?hello=world&userId=abc-123", + "queryString": [ + { + "name": "hello", + "value": "world" + }, + { + "name": "userId", + "value": "abc-123" + } + ], + "method": "GET", + "cookies": [], + "bodySize": -1, + "headers": [ + { "name": "Host", "value": "source.mswjs.io" }, + { "name": "Sec-Fetch-Site", "value": "none" }, + { "name": "Connection", "value": "keep-alive" }, + { "value": "navigate", "name": "Sec-Fetch-Mode" }, + { + "name": "Accept", + "value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + { + "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15", + "name": "User-Agent" + }, + { "name": "Accept-Language", "value": "en-GB,en;q=0.9" }, + { "value": "document", "name": "Sec-Fetch-Dest" }, + { "name": "Accept-Encoding", "value": "gzip, deflate, br" } + ], + "httpVersion": "HTTP/1.1" + }, + "_remoteDeviceIP": null, + "_isHTTPS": true, + "_id": "121", + "serverIPAddress": "76.76.21.93", + "_clientAddress": "127.0.0.1", + "_serverAddress": "76.76.21.93", + "startedDateTime": "2024-07-09T23:00:16.456Z", + "timings": { + "connect": 1, + "receive": -1, + "send": -1, + "blocked": -1, + "wait": -1, + "ssl": -1, + "dns": -1 + }, + "_clientName": "Safari", + "cache": {}, + "time": 133.98695, + "_isIntercepted": true, + "_webSocketMessages": null, + "_clientPort": 58712, + "comment": "", + "response": { + "headers": [ + { "value": "*", "name": "Access-Control-Allow-Origin" }, + { "name": "Age", "value": "60660" }, + { + "name": "Cache-Control", + "value": "public, max-age=0, must-revalidate" + }, + { "name": "Connection", "value": "keep-alive" }, + { + "value": "inline; filename=\"docs\"", + "name": "Content-Disposition" + }, + { "name": "Content-Encoding", "value": "br" }, + { "name": "Content-Type", "value": "text/html; charset=utf-8" }, + { "value": "Tue, 09 Jul 2024 23:00:16 GMT", "name": "Date" }, + { + "name": "Etag", + "value": "W/\"124caa2b9290c6ef5f915099d3f97b0d\"" + }, + { "name": "Server", "value": "Vercel" }, + { + "name": "Strict-Transport-Security", + "value": "max-age=63072000" + }, + { "name": "Transfer-Encoding", "value": "chunked" } + ], + "httpVersion": "HTTP/1.1", + "content": { + "size": 11, + "mimeType": "text/html", + "compression": 0, + "text": "hello world" + }, + "status": 200, + "statusText": "OK", + "cookies": [], + "redirectURL": "", + "headersSize": 490, + "bodySize": 57865 + }, + "_serverPort": 443, + "_name": "121" + } + ] + } +} diff --git a/test/traffic/request-parameters.test.ts b/test/traffic/request-parameters.test.ts new file mode 100644 index 0000000..dc5338c --- /dev/null +++ b/test/traffic/request-parameters.test.ts @@ -0,0 +1,52 @@ +import crypto from 'node:crypto' +import { fromTraffic } from '../../src/traffic/from-traffic.js' +import { readArchive, normalizeLocalhost, _toHeaders } from './utils/index.js' + +it('responds to a request where all search parameters match', async () => { + const har = readArchive( + 'test/traffic/fixtures/archives/request-parameters.har', + ) + + const handlers = fromTraffic(har, normalizeLocalhost) + const requestHandler = handlers[0]! + + const result = await requestHandler.run({ + request: new Request('https://localhost/docs/?hello=world&userId=abc-123'), + requestId: crypto.randomUUID(), + }) + expect(result?.response).toBeDefined() + + await expect(result?.response?.text()).resolves.toBe('hello world') +}) + +it('does not respond to a request where some search parameters match', async () => { + const har = readArchive( + 'test/traffic/fixtures/archives/request-parameters.har', + ) + + const handlers = fromTraffic(har, normalizeLocalhost) + const requestHandler = handlers[0]! + + const result = await requestHandler.run({ + request: new Request('https://localhost/docs/?hello=world'), + requestId: crypto.randomUUID(), + }) + expect(result?.response).toBeUndefined() +}) + +it('does not respond to a request where no search parameters match', async () => { + const har = readArchive( + 'test/traffic/fixtures/archives/request-parameters.har', + ) + + const handlers = fromTraffic(har, normalizeLocalhost) + const requestHandler = handlers[0]! + + const result = await requestHandler!.run({ + request: new Request('https://localhost/docs', { + method: 'GET', + }), + requestId: 'query-string', + }) + expect(result?.response).toBeUndefined() +}) diff --git a/test/traffic/response-stream.test.ts b/test/traffic/response-stream.test.ts index 16bf145..8001132 100644 --- a/test/traffic/response-stream.test.ts +++ b/test/traffic/response-stream.test.ts @@ -1,3 +1,4 @@ +// @vitest-environment node import { fromTraffic } from '../../src/traffic/from-traffic.js' import { InspectedHandler, inspectHandlers } from '../support/inspect.js' import { normalizeLocalhost, readArchive, _toHeaders } from './utils/index.js' diff --git a/vitest.config.ts b/vitest.config.ts index c91b23a..0163a5e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,9 +4,9 @@ export default defineConfig({ test: { globals: true, setupFiles: ['./vitest.setup.ts'], - environment: 'jsdom', + environment: 'node', environmentOptions: { - jsdom: { + happyDOM: { url: 'http://localhost', }, }, diff --git a/vitest.setup.ts b/vitest.setup.ts index 9449e9b..6e8d8ee 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,5 +1,6 @@ import { invariant } from 'outvariant' import { faker } from '@yellow-ticket/seed-json-schema' +import { normalizeHeaders } from './test/support/inspect' beforeEach(() => { faker.seed(1) @@ -59,7 +60,10 @@ expect.extend({ // Headers. if ( - !this.equals(Array.from(actual.headers), Array.from(expected.headers)) + !this.equals( + normalizeHeaders(actual.headers), + normalizeHeaders(expected.headers), + ) ) { return { pass: false,