diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..5387bd7 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,21 @@ +name: test +on: [push] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: true + + - uses: actions/setup-node@v3 + with: + node-version: 20 + cache: pnpm + + - run: pnpm lint + - run: pnpm playwright install + - run: pnpm t diff --git a/.gitignore b/.gitignore index c1edf5f..dc9e6f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -dist dev-dist +dist node_modules +test-results +tests/fixtures vendors diff --git a/package.json b/package.json index e637878..d501bc9 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,10 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview", - "lint": "eslint src" + "preview": "vite build && vite preview", + "lint": "eslint src", + "test-roms": "./scripts/download-test-roms.ts", + "test": "playwright test -c tests/e2e" }, "dependencies": { "@fontsource-variable/fredoka": "5.0.8", @@ -72,6 +74,7 @@ "tailwindcss": "3.3.3", "typescript": "5.2.2", "vite": "4.4.9", - "vite-plugin-pwa": "0.16.5" + "vite-plugin-pwa": "0.16.5", + "zx": "7.2.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfdb287..e14cea4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -190,6 +190,9 @@ devDependencies: vite-plugin-pwa: specifier: 0.16.5 version: 0.16.5(vite@4.4.9)(workbox-build@7.0.0)(workbox-window@7.0.0) + zx: + specifier: 7.2.3 + version: 7.2.3 packages: @@ -1806,11 +1809,11 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@maxim_mazurok/gapi.client.discovery-v1@0.0.20200806: - resolution: {integrity: sha512-0pZtrElj8bc0YROIo7nH8VVvks/iww/L68l7r74oC9+ksSVO3Lum1zUR8dXTvJ7TqLGaNYiiNaUZianK6doh/w==} + /@maxim_mazurok/gapi.client.discovery-v1@0.1.20200806: + resolution: {integrity: sha512-Wl6UfmZVDdWbY3PUu8E2ULk9RPLjnMqp/iOA4tcK8Ne+U/GmlnWP/e34IaZNGArfl7iXJNOG+/3Rj9L9jQyF9Q==} dependencies: '@types/gapi.client': 1.0.5 - '@types/gapi.client.discovery': 1.0.9 + '@types/gapi.client.discovery-v1': 0.0.1 dev: true /@maxim_mazurok/gapi.client.drive-v3@0.0.20230910: @@ -2524,17 +2527,17 @@ packages: resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} dev: true - /@types/gapi.client.discovery-v1@0.0.1: - resolution: {integrity: sha512-xHW6FW+ERNxqFYNhC6yo1yZjBMmigqQIkX11VY8S6NFteZAX8eWpzvSrmmwFEUm4OOLTJhz8dxEyUAs4ltIMnw==} + /@types/fs-extra@11.0.2: + resolution: {integrity: sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==} dependencies: - '@maxim_mazurok/gapi.client.discovery-v1': 0.0.20200806 + '@types/jsonfile': 6.1.2 + '@types/node': 20.6.2 dev: true - /@types/gapi.client.discovery@1.0.9: - resolution: {integrity: sha512-51fXpt7DM7+zPG5pgwnNr3KaRXmyszznu66VPpV7+FAu0LGtpTohvdfvkRCnCn2Z6EgNTq/baFAtCa/9ylHOug==} - deprecated: use @types/gapi.client.discovery-v1 instead; see https://github.com/Maxim-Mazurok/google-api-typings-generator/issues/652 for details + /@types/gapi.client.discovery-v1@0.0.1: + resolution: {integrity: sha512-xHW6FW+ERNxqFYNhC6yo1yZjBMmigqQIkX11VY8S6NFteZAX8eWpzvSrmmwFEUm4OOLTJhz8dxEyUAs4ltIMnw==} dependencies: - '@maxim_mazurok/gapi.client.discovery-v1': 0.0.20200806 + '@maxim_mazurok/gapi.client.discovery-v1': 0.1.20200806 dev: true /@types/gapi.client.drive-v3@0.0.1: @@ -2577,6 +2580,12 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/jsonfile@6.1.2: + resolution: {integrity: sha512-8t92P+oeW4d/CRQfJaSqEwXujrhH4OEeHRjGU3v1Q8mUS8GPF3yiX26sw4svv6faL2HfBtGTe2xWIoVgN3dy9w==} + dependencies: + '@types/node': 20.6.2 + dev: true + /@types/lodash-es@4.17.9: resolution: {integrity: sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==} dependencies: @@ -2593,6 +2602,10 @@ packages: '@types/unist': 2.0.8 dev: true + /@types/minimist@1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + /@types/node-fetch@2.6.5: resolution: {integrity: sha512-OZsUlr2nxvkqUFLSaY2ZbA+P1q22q+KrlxWOn/38RX+u5kTkYL2mTujEpzUhGkS+K/QCYp9oagfXG39XOzyySg==} dependencies: @@ -2600,6 +2613,10 @@ packages: form-data: 4.0.0 dev: false + /@types/node@18.17.17: + resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} + dev: true + /@types/node@20.6.2: resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==} @@ -2614,6 +2631,10 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/ps-tree@1.1.2: + resolution: {integrity: sha512-ZREFYlpUmPQJ0esjxoG1fMvB2HNaD3z+mjqdSosZvd3RalncI9NEur73P8ZJz4YQdL64CmV1w0RuqoRUlhQRBw==} + dev: true + /@types/react-dom@18.2.7: resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: @@ -2657,6 +2678,10 @@ packages: resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} dev: true + /@types/which@3.0.0: + resolution: {integrity: sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==} + dev: true + /@types/wicg-file-system-access@2020.9.6: resolution: {integrity: sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==} dev: true @@ -3209,6 +3234,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} dev: true @@ -3363,6 +3393,11 @@ packages: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + /date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -3515,6 +3550,10 @@ packages: - encoding dev: false + /duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: true + /ejs@3.1.9: resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} engines: {node: '>=0.10.0'} @@ -4103,6 +4142,18 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + dev: true + /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} dev: false @@ -4181,6 +4232,14 @@ packages: reusify: 1.0.4 dev: true + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: true + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4254,6 +4313,13 @@ packages: mime-types: 2.1.35 dev: false + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + /fraction.js@4.3.6: resolution: {integrity: sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==} dev: true @@ -4276,6 +4342,19 @@ packages: '@emotion/is-prop-valid': 0.8.8 dev: false + /from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + dev: true + + /fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -4324,6 +4403,11 @@ packages: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true + /fx@30.0.1: + resolution: {integrity: sha512-UAVhGR9ih5tdFsoPxkzpi49KJsyUfPtbvToo0OJrpqpEX0QhGKsZSkqWMoQZO1VS//c0IS3UCMKwyzWjb/D1Xw==} + hasBin: true + dev: true + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4444,6 +4528,17 @@ packages: slash: 3.0.0 dev: true + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + /goodcodes-parser@2.5.0: resolution: {integrity: sha512-bHoolpQjIlY4TdW0r48ZNIorb1uXzdOVtloeNuB7dpc9WEU+niE613+8hGw/P7TzPE6Gcblsg+yqVYiXtCDbbw==} engines: {node: '>= 8.0.0'} @@ -5131,6 +5226,10 @@ packages: sourcemap-codec: 1.4.8 dev: true + /map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + dev: true + /mdast-util-from-markdown@0.8.5: resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} dependencies: @@ -5280,6 +5379,11 @@ packages: tslib: 2.6.2 dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -5292,6 +5396,15 @@ packages: whatwg-url: 5.0.0 dev: false + /node-fetch@3.3.1: + resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true @@ -5572,6 +5685,12 @@ packages: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} dev: true + /pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + dependencies: + through: 2.3.8 + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -5781,6 +5900,14 @@ packages: react-is: 16.13.1 dev: true + /ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + dependencies: + event-stream: 3.3.4 + dev: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -6246,6 +6373,11 @@ packages: engines: {node: '>=8'} dev: true + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -6305,6 +6437,12 @@ packages: engines: {node: '>=12'} dev: false + /split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + dependencies: + through: 2.3.8 + dev: true + /stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} dependencies: @@ -6334,6 +6472,12 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: false + /stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + dependencies: + duplexer: 0.1.2 + dev: true + /string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} dependencies: @@ -6561,6 +6705,10 @@ packages: engines: {node: '>=10'} dev: false + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -6873,6 +7021,11 @@ packages: fsevents: 2.3.3 dev: true + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -6881,6 +7034,11 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true + /webpod@0.0.2: + resolution: {integrity: sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==} + hasBin: true + dev: true + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -6952,6 +7110,14 @@ packages: isexe: 2.0.0 dev: true + /which@3.0.1: + resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + /workbox-background-sync@7.0.0: resolution: {integrity: sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==} dependencies: @@ -7128,3 +7294,25 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zx@7.2.3: + resolution: {integrity: sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==} + engines: {node: '>= 16.0.0'} + hasBin: true + dependencies: + '@types/fs-extra': 11.0.2 + '@types/minimist': 1.2.2 + '@types/node': 18.17.17 + '@types/ps-tree': 1.1.2 + '@types/which': 3.0.0 + chalk: 5.3.0 + fs-extra: 11.1.1 + fx: 30.0.1 + globby: 13.2.2 + minimist: 1.2.8 + node-fetch: 3.3.1 + ps-tree: 1.2.0 + webpod: 0.0.2 + which: 3.0.1 + yaml: 2.3.2 + dev: true diff --git a/scripts/download-test-roms.js b/scripts/download-test-roms.js new file mode 100644 index 0000000..94f6dc3 --- /dev/null +++ b/scripts/download-test-roms.js @@ -0,0 +1,63 @@ +#!/usr/bin/env zx +import { invert } from 'lodash-es' +import path from 'path-browserify' +import { $, cd, fs } from 'zx' + +const testRomsBaseUrl = 'https://buildbot.libretro.com/assets/cores/' + +const testRomsGroups = { + Arcade: ['Alien Arena.zip'], + 'Atari - 2600': ['Sheep It Up.zip'], + 'Bandai - WonderSwan Color': ['swandriving.wsc'], + 'Nintendo - GameBoy': ['Tobu Tobu Girl.zip'], + 'Nintendo - GameBoy Advance': ['Celeste Classic (v1.0).zip'], + 'Nintendo - Nintendo Entertainment System': ['240p Test Suite.nes'], + 'Nintendo - Super Nintendo Entertainment System': ['240pTestSuite-SNES-latest.zip'], + 'Nintendo - Virtual Boy': ['VB Racing (FlashBoy Version) (PVBCC).vb'], + 'Sega - Game Gear': ['ButtonTest.zip'], + 'Sega - Mega Drive - Genesis': ['30YearsOfNintendont.zip'], + 'Sega - Master System - Mark III': ['Genesis 6 Button Controller Test by Charles MacDonald (PD).sms'], + 'SNK - Neo Geo Pocket': ['Gears of Fate.zip'], +} + +const systemFullNameMap = invert({ + arcade: 'Arcade', + atari2600: 'Atari - 2600', + gamegear: 'Sega - Game Gear', + gb: 'Nintendo - GameBoy', + gba: 'Nintendo - GameBoy Advance', + gbc: 'Nintendo - Game Boy Color', + megadrive: 'Sega - Mega Drive - Genesis', + nes: 'Nintendo - Nintendo Entertainment System', + ngp: 'SNK - Neo Geo Pocket', + ngpc: 'SNK - Neo Geo Pocket Color', + sms: 'Sega - Master System - Mark III', + snes: 'Nintendo - Super Nintendo Entertainment System', + vb: 'Nintendo - Virtual Boy', + wonderswan: 'Bandai - WonderSwan', + wonderswancolor: 'Bandai - WonderSwan Color', +}) + +const wd = process.cwd() +async function downloadTestRoms() { + for (const systemFullName in testRomsGroups) { + const system = systemFullNameMap[systemFullName] + const systemRomsDirectory = path.join(wd, 'tests/fixtures/roms', system) + + if (!(await fs.exists(systemRomsDirectory))) { + await $`mkdir -p ${systemRomsDirectory}` + } + + cd(systemRomsDirectory) + + const roms = testRomsGroups[systemFullName] + for (const rom of roms) { + if (!(await fs.exists(rom))) { + const romUrl = `${testRomsBaseUrl}${encodeURIComponent(systemFullName)}/${encodeURIComponent(rom)}` + await $`curl ${romUrl} -o ${rom}` + } + } + } +} + +await downloadTestRoms() diff --git a/src/core/classes/emulator.ts b/src/core/classes/emulator.ts index 907a541..cbd884f 100644 --- a/src/core/classes/emulator.ts +++ b/src/core/classes/emulator.ts @@ -149,13 +149,13 @@ export class Emulator { this.core = core ?? '' this.canvas = document.createElement('canvas') this.canvas.id = 'canvas' - this.canvas.hidden = true this.canvas.width = 900 this.canvas.height = 900 this.previousActiveElement = document.activeElement this.canvas.tabIndex = 0 this.coreConfig = coreConfig this.retroarchConfig = retroarchConfig + this.canvas.dataset.testid = 'emulator' updateStyle(this.canvas, { backgroundColor: 'black', backgroundImage: diff --git a/src/views/components/setup-wizard/local-file-button.tsx b/src/views/components/setup-wizard/local-file-button.tsx index ecd66ad..bcfed5f 100644 --- a/src/views/components/setup-wizard/local-file-button.tsx +++ b/src/views/components/setup-wizard/local-file-button.tsx @@ -38,7 +38,7 @@ export function LocalFileButton() { return ( <> - + {state.loading ? ( <> diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts new file mode 100644 index 0000000..03aee72 --- /dev/null +++ b/tests/e2e/playwright.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: '.', + snapshotDir: 'snapshots', + snapshotPathTemplate: '{snapshotDir}/{testFilePath}/{testName}-{arg}{ext}', + use: { baseURL: 'http://localhost:5173', trace: 'on-first-retry' }, + webServer: { command: 'pnpm dev', url: 'http://127.0.0.1:5173', reuseExistingServer: true }, + expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.05 } }, +}) diff --git a/tests/e2e/setup-wizard/single-rom.spec.ts b/tests/e2e/setup-wizard/single-rom.spec.ts new file mode 100644 index 0000000..a574ec0 --- /dev/null +++ b/tests/e2e/setup-wizard/single-rom.spec.ts @@ -0,0 +1,14 @@ +import { expect, test } from '@playwright/test' + +test('play a single ROM file', async ({ page }) => { + await page.goto('/') + const fileChooserPromise = page.waitForEvent('filechooser') + await page.getByTestId('select-a-rom').click() + const fileChooser = await fileChooserPromise + await fileChooser.setFiles('tests/fixtures/roms/nes/240p Test Suite.zip') + + const emulator = page.getByTestId('emulator') + await page.waitForLoadState('networkidle') + await expect(emulator).toBeVisible() + await expect(emulator).toHaveScreenshot('240p Test Suite.png') +}) diff --git a/tests/e2e/snapshots/setup-wizard/setup-wizard/single-rom.spec.ts/image.png b/tests/e2e/snapshots/setup-wizard/setup-wizard/single-rom.spec.ts/image.png new file mode 100644 index 0000000..cf913d2 Binary files /dev/null and b/tests/e2e/snapshots/setup-wizard/setup-wizard/single-rom.spec.ts/image.png differ diff --git a/tests/e2e/snapshots/setup-wizard/single-rom.spec.ts/play-a-single-ROM-file-240p-Test-Suite.png b/tests/e2e/snapshots/setup-wizard/single-rom.spec.ts/play-a-single-ROM-file-240p-Test-Suite.png new file mode 100644 index 0000000..d04d6c2 Binary files /dev/null and b/tests/e2e/snapshots/setup-wizard/single-rom.spec.ts/play-a-single-ROM-file-240p-Test-Suite.png differ