From 6d3299a285f6fb2c34d2feff2dc462622fe99174 Mon Sep 17 00:00:00 2001 From: Matt Godbolt Date: Sat, 14 Sep 2024 20:50:17 -0500 Subject: [PATCH] Add Ceefax tests (#445) Closes #92 - I carefully checked the output and it looks good, so this is more of a regression test than anything else --- package-lock.json | 486 +++++++++++++++++- package.json | 2 + tests/integration/ceefax.js | 100 ++++ tests/integration/ceefax/expected_flash_0.png | Bin 0 -> 11428 bytes tests/integration/ceefax/expected_flash_1.png | Bin 0 -> 11541 bytes .../ceefax/expected_reveal_flash_0.png | Bin 0 -> 11707 bytes .../ceefax/expected_reveal_flash_1.png | Bin 0 -> 11911 bytes tests/integration/output/.gitignore | 1 + tests/test-machine.js | 17 + 9 files changed, 603 insertions(+), 3 deletions(-) create mode 100644 tests/integration/ceefax.js create mode 100644 tests/integration/ceefax/expected_flash_0.png create mode 100644 tests/integration/ceefax/expected_flash_1.png create mode 100644 tests/integration/ceefax/expected_reveal_flash_0.png create mode 100644 tests/integration/ceefax/expected_reveal_flash_1.png create mode 100644 tests/integration/output/.gitignore diff --git a/package-lock.json b/package-lock.json index 8dba81c..bc7bfcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,9 @@ "lint-staged": "^15.2.10", "mini-css-extract-plugin": "^2.9.1", "npm-run-all": "^4.1.5", + "pixelmatch": "^6.0.0", "prettier": "^3.3.3", + "sharp": "^0.33.5", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", @@ -52,6 +54,16 @@ "node": ">=10.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -448,9 +460,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -549,6 +561,367 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2260,6 +2633,19 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2278,6 +2664,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -2917,6 +3313,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -6342,6 +6747,18 @@ "node": ">=0.10.0" } }, + "node_modules/pixelmatch": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-6.0.0.tgz", + "integrity": "sha512-FYpL4XiIWakTnIqLqvt3uN4L9B3TsuHIvhLILzTiJZMJUsGvmKNeL4H3b6I99LRyerK9W4IuOXw+N28AtRgK2g==", + "dev": true, + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -6406,6 +6823,15 @@ "node": ">=8" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -7763,6 +8189,45 @@ "node": ">=8" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7829,6 +8294,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", diff --git a/package.json b/package.json index a9efd3b..931d9c3 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "lint-staged": "^15.2.10", "mini-css-extract-plugin": "^2.9.1", "npm-run-all": "^4.1.5", + "pixelmatch": "^6.0.0", "prettier": "^3.3.3", + "sharp": "^0.33.5", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", diff --git a/tests/integration/ceefax.js b/tests/integration/ceefax.js new file mode 100644 index 0000000..6649c6f --- /dev/null +++ b/tests/integration/ceefax.js @@ -0,0 +1,100 @@ +import { describe, it } from "vitest"; +import { TestMachine } from "../test-machine.js"; +import assert from "assert"; +import { Video } from "../../video.js"; +import sharp from "sharp"; +import pixelmatch from "pixelmatch"; + +class CapturingVideo extends Video { + constructor() { + super(false, new Uint32Array(1024 * 1024), () => {}); + this.paint_ext = (left, top, right, bottom) => this._onPaint(left, top, right, bottom); + this._capturing = false; + this._captureSharp = null; + } + + _onPaint(left, top, right, bottom) { + if (this._capturing) { + const width = right - left; + const height = bottom - top; + const bufferCopy = new Uint8Array(this.fb32.buffer.slice(0)); + this._captureSharp = sharp(bufferCopy, { + raw: { width: 1024, height: 1024, channels: 4 }, + }).extract({ left: left, top: top, width, height }); + this._capturing = false; + } + } + + async capture(testMachine) { + this._capturing = true; + await testMachine.runUntilVblank(); + if (this._capturing) throw new Error("Should have captured by now"); + return this._captureSharp; + } +} + +async function setupTestMachine(video) { + const testMachine = new TestMachine(null, { video: video }); + await testMachine.initialise(); + await testMachine.runUntilInput(); + await testMachine.loadDisc("discs/eng_test.ssd"); + await testMachine.type("*EXEC !BOOT"); + await testMachine.runFor(3 * 1000 * 1000); + return testMachine; +} + +const rootDir = "tests/integration/ceefax"; +const outputDir = `tests/integration/output`; + +async function compare(video, testMachine, expectedName) { + const outputName = `${expectedName.replace("expected", "actual")}`; + const captureSharp = await video.capture(testMachine); + const outputFile = `${outputDir}/${outputName}`; + await captureSharp.removeAlpha().toFile(outputFile); + const expectedFile = `${rootDir}/${expectedName}`; + const diffFile = `${outputDir}/${outputName.replace(".png", ".diff.png")}`; + + const { data: expectedData, info } = await sharp(expectedFile) + .ensureAlpha() + .raw() + .toBuffer({ resolveWithObject: true }); + const actualData = await sharp(outputFile).ensureAlpha().raw().toBuffer(); + const diffData = new Uint8Array(info.width * info.height * info.channels); + + const numDiffPixels = pixelmatch(expectedData, actualData, diffData, info.width, info.height, { + threshold: 0.1, + }); + await sharp(diffData, { raw: info }).removeAlpha().toFile(diffFile); + assert.equal( + numDiffPixels, + 0, + `Images do not match - expected ${expectedFile}, got ${outputFile}, diffs: ${diffFile}}`, + ); +} + +describe("Test Ceefax test page", () => { + it("should match the Ceefax test page (no flash)", async () => { + const video = new CapturingVideo(); + const testMachine = await setupTestMachine(video); + await compare(video, testMachine, `expected_flash_0.png`); + }); + it("should match the Ceefax test page (flash)", async () => { + const video = new CapturingVideo(); + const testMachine = await setupTestMachine(video); + await testMachine.runFor(1500000); + await compare(video, testMachine, `expected_flash_1.png`); + }); + it("should match the Ceefax test page after reveal (no flash)", async () => { + const video = new CapturingVideo(); + const testMachine = await setupTestMachine(video); + await testMachine.type(" "); + await compare(video, testMachine, `expected_reveal_flash_0.png`); + }); + it("should match the Ceefax test page after reveal (flash)", async () => { + const video = new CapturingVideo(); + const testMachine = await setupTestMachine(video); + await testMachine.type(" "); + await testMachine.runFor(1500000); + await compare(video, testMachine, `expected_reveal_flash_1.png`); + }); +}); diff --git a/tests/integration/ceefax/expected_flash_0.png b/tests/integration/ceefax/expected_flash_0.png new file mode 100644 index 0000000000000000000000000000000000000000..4998da646010ddc0ab08eed5e0ea6fdd0d79816a GIT binary patch literal 11428 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 IpS}M7025jdvj6}9 literal 0 HcmV?d00001 diff --git a/tests/integration/ceefax/expected_flash_1.png b/tests/integration/ceefax/expected_flash_1.png new file mode 100644 index 0000000000000000000000000000000000000000..7ed7d16a1f1c02e3a606494dac426b88fbe99b81 GIT binary patch literal 11541 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 literal 0 HcmV?d00001 diff --git a/tests/integration/ceefax/expected_reveal_flash_0.png b/tests/integration/ceefax/expected_reveal_flash_0.png new file mode 100644 index 0000000000000000000000000000000000000000..cf4d6b4e82b1a000473de0a5724cc5c1dd0c665d GIT binary patch literal 11707 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 E0LHDbWB>pF literal 0 HcmV?d00001 diff --git a/tests/integration/ceefax/expected_reveal_flash_1.png b/tests/integration/ceefax/expected_reveal_flash_1.png new file mode 100644 index 0000000000000000000000000000000000000000..91e1cd9bad08fc52f31884f3a85b781ff2722cfd GIT binary patch literal 11911 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 { + if (addr === 0xdd15) { + hit = true; + return true; + } + }); + await this.runFor(10 * 1000 * 1000); + hook.remove(); + assert(hit, "did not hit appropriate breakpoint in time"); + } + async runUntilInput(secs) { if (!secs) secs = 120; console.log("Running until keyboard input requested"); @@ -111,6 +125,9 @@ export class TestMachine { } else if (ch === "*") { ch = utils.keyCodes.APOSTROPHE; shift = true; + } else if (ch === "!") { + ch = utils.keyCodes.K1; + shift = true; } else if (ch === ".") { ch = utils.keyCodes.PERIOD; } else ch = ch.toUpperCase().charCodeAt(0);