diff --git a/package-lock.json b/package-lock.json index 96a28156d2..3a64755047 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "i18next": "^19.0.2", "i18next-browser-languagedetector": "^4.0.1", "lint-staged": "^15.1.0", + "resemblejs": "^5.0.0", "rollup": "^4.9.6", "rollup-plugin-string": "^3.0.0", "rollup-plugin-visualizer": "^5.12.0", @@ -1164,6 +1165,67 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@mswjs/interceptors": { "version": "0.29.1", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz", @@ -2531,6 +2593,13 @@ "node": ">=16.5.0" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2671,6 +2740,13 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true, + "optional": true + }, "node_modules/archiver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", @@ -2763,6 +2839,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3098,6 +3204,22 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -3263,6 +3385,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -3411,6 +3543,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -3620,6 +3762,13 @@ "qs": "^6.3.1" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "optional": true + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3816,6 +3965,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3889,6 +4051,13 @@ "node": ">= 14" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "optional": true + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3898,6 +4067,16 @@ "node": ">=6" } }, + "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, + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -5024,6 +5203,39 @@ "node": ">= 10.0.0" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5053,6 +5265,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dev": true, + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "optional": true + }, "node_modules/geckodriver": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.4.tgz", @@ -5397,6 +5638,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -6767,6 +7015,22 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -7690,6 +7954,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7711,6 +7988,53 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -7866,6 +8190,13 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "dev": true, + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -7944,6 +8275,22 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -8007,6 +8354,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -8019,6 +8380,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -9150,6 +9521,15 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/resemblejs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resemblejs/-/resemblejs-5.0.0.tgz", + "integrity": "sha512-+B0eP9k9VDP/YhBbH+ZdYmHiotdtuc6blVI+h8wwkY2cOow+uiIpSmgkBBBtrEAL0D31/gR/AJPwDeX5TcwmIA==", + "dev": true, + "optionalDependencies": { + "canvas": "2.11.2" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -9691,6 +10071,39 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -10106,6 +10519,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -10131,6 +10562,23 @@ "streamx": "^2.15.0" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + }, "node_modules/terser": { "version": "5.31.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", @@ -11237,6 +11685,16 @@ "node": ">=8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 802c048821..302d1ea4ec 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "i18next": "^19.0.2", "i18next-browser-languagedetector": "^4.0.1", "lint-staged": "^15.1.0", + "resemblejs": "^5.0.0", "rollup": "^4.9.6", "rollup-plugin-string": "^3.0.0", "rollup-plugin-visualizer": "^5.12.0", diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 2dde427048..90184ab915 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -140,7 +140,6 @@ p5.prototype.loadImage = async function( successCallback, failureCallback, (pImg => { - self._decrementPreload(); resolve(pImg); }).bind(self) ); @@ -148,7 +147,6 @@ p5.prototype.loadImage = async function( console.error(e.toString(), e.stack); if (typeof failureCallback === 'function') { failureCallback(e); - self._decrementPreload(); } else { console.error(e); } @@ -160,7 +158,6 @@ p5.prototype.loadImage = async function( e => { if (typeof failureCallback === 'function') { failureCallback(e); - self._decrementPreload(); } else { console.error(e); } @@ -182,14 +179,12 @@ p5.prototype.loadImage = async function( successCallback(pImg); } resolve(); - self._decrementPreload(); }; img.onerror = e => { p5._friendlyFileLoadError(0, img.src); if (typeof failureCallback === 'function') { failureCallback(e); - self._decrementPreload(); } else { console.error(e); } @@ -215,7 +210,6 @@ p5.prototype.loadImage = async function( p5._friendlyFileLoadError(0, path); if (typeof failureCallback === 'function') { failureCallback(e); - self._decrementPreload(); } else { console.error(e); } diff --git a/src/io/files.js b/src/io/files.js index 02fb681e32..e6711ea20f 100644 --- a/src/io/files.js +++ b/src/io/files.js @@ -240,7 +240,7 @@ import Renderer from '../core/p5.Renderer'; * * */ -p5.prototype.loadJSON = function (...args) { +p5.prototype.loadJSON = async function (...args) { p5._validateParameters('loadJSON', args); const path = args[0]; let callback; @@ -266,8 +266,7 @@ p5.prototype.loadJSON = function (...args) { } } - const self = this; - this.httpDo( + await new Promise(resolve => this.httpDo( path, 'GET', options, @@ -280,7 +279,7 @@ p5.prototype.loadJSON = function (...args) { callback(resp); } - self._decrementPreload(); + resolve() }, err => { // Error handling @@ -292,7 +291,7 @@ p5.prototype.loadJSON = function (...args) { throw err; } } - ); + )); return ret; }; @@ -431,7 +430,7 @@ p5.prototype.loadJSON = function (...args) { * * */ -p5.prototype.loadStrings = function (...args) { +p5.prototype.loadStrings = async function (...args) { p5._validateParameters('loadStrings', args); const ret = []; @@ -448,8 +447,7 @@ p5.prototype.loadStrings = function (...args) { } } - const self = this; - p5.prototype.httpDo.call( + await new Promise(resolve => p5.prototype.httpDo.call( this, args[0], 'GET', @@ -476,7 +474,7 @@ p5.prototype.loadStrings = function (...args) { callback(ret); } - self._decrementPreload(); + resolve() }, function (err) { // Error handling @@ -488,7 +486,7 @@ p5.prototype.loadStrings = function (...args) { throw err; } } - ); + )); return ret; }; @@ -563,7 +561,7 @@ p5.prototype.loadStrings = function (...args) { * * */ -p5.prototype.loadTable = function (path) { +p5.prototype.loadTable = async function (path) { // p5._validateParameters('loadTable', arguments); let callback; let errorCallback; @@ -604,8 +602,7 @@ p5.prototype.loadTable = function (path) { const t = new p5.Table(); - const self = this; - this.httpDo( + await new Promise(resolve => this.httpDo( path, 'GET', 'table', @@ -737,7 +734,7 @@ p5.prototype.loadTable = function (path) { callback(t); } - self._decrementPreload(); + resolve() }, err => { // Error handling @@ -749,7 +746,7 @@ p5.prototype.loadTable = function (path) { console.error(err); } } - ); + )); return t; }; @@ -929,7 +926,7 @@ function makeObject(row, headers) { * * */ -p5.prototype.loadXML = function (...args) { +p5.prototype.loadXML = async function (...args) { const ret = new p5.XML(); let callback, errorCallback; @@ -944,8 +941,7 @@ p5.prototype.loadXML = function (...args) { } } - const self = this; - this.httpDo( + await new Promise(resolve => this.httpDo( args[0], 'GET', 'xml', @@ -957,7 +953,7 @@ p5.prototype.loadXML = function (...args) { callback(ret); } - self._decrementPreload(); + resolve() }, function (err) { // Error handling @@ -969,7 +965,7 @@ p5.prototype.loadXML = function (...args) { throw err; } } - ); + )); return ret; }; @@ -1000,11 +996,10 @@ p5.prototype.loadXML = function (...args) { * } * */ -p5.prototype.loadBytes = function (file, callback, errorCallback) { +p5.prototype.loadBytes = async function (file, callback, errorCallback) { const ret = {}; - const self = this; - this.httpDo( + await new Promise(resolve => this.httpDo( file, 'GET', 'arrayBuffer', @@ -1015,7 +1010,7 @@ p5.prototype.loadBytes = function (file, callback, errorCallback) { callback(ret); } - self._decrementPreload(); + resolve(); }, err => { // Error handling @@ -1027,7 +1022,7 @@ p5.prototype.loadBytes = function (file, callback, errorCallback) { throw err; } } - ); + )); return ret; }; diff --git a/src/typography/loading_displaying.js b/src/typography/loading_displaying.js index f625db3d99..14410e66b9 100644 --- a/src/typography/loading_displaying.js +++ b/src/typography/loading_displaying.js @@ -126,12 +126,11 @@ import '../core/friendly_errors/fes_core'; * * */ -p5.prototype.loadFont = function(path, onSuccess, onError) { +p5.prototype.loadFont = async function(path, onSuccess, onError) { p5._validateParameters('loadFont', arguments); const p5Font = new p5.Font(this); - const self = this; - opentype.load(path, (err, font) => { + await new Promise(resolve => opentype.load(path, (err, font) => { if (err) { p5._friendlyFileLoadError(4, path); if (typeof onError !== 'undefined') { @@ -146,8 +145,7 @@ p5.prototype.loadFont = function(path, onSuccess, onError) { if (typeof onSuccess !== 'undefined') { onSuccess(p5Font); } - - self._decrementPreload(); + resolve(); // check that we have an acceptable font type const validFontTypes = ['ttf', 'otf', 'woff', 'woff2']; @@ -174,7 +172,7 @@ p5.prototype.loadFont = function(path, onSuccess, onError) { ); document.head.appendChild(newStyle); } - }); + })); return p5Font; }; diff --git a/src/webgl/loading.js b/src/webgl/loading.js index bef14a8e15..414c62315b 100755 --- a/src/webgl/loading.js +++ b/src/webgl/loading.js @@ -335,7 +335,7 @@ import './p5.Geometry'; * @param {Boolean} [options.flipV] * @return {p5.Geometry} new p5.Geometry object. */ -p5.prototype.loadModel = function (path, options) { +p5.prototype.loadModel = async function (path, options) { p5._validateParameters('loadModel', arguments); let normalize = false; let successCallback; @@ -420,7 +420,7 @@ p5.prototype.loadModel = function (path, options) { } } if (fileType.match(/\.stl$/i)) { - this.httpDo( + await new Promise(resolve => this.httpDo( path, 'GET', 'arrayBuffer', @@ -439,15 +439,15 @@ p5.prototype.loadModel = function (path, options) { model.flipV(); } - self._decrementPreload(); + resolve(); if (typeof successCallback === 'function') { successCallback(model); } }, failureCallback - ); + )); } else if (fileType.match(/\.obj$/i)) { - this.loadStrings( + await new Promise(resolve => this.loadStrings( path, async lines => { try { @@ -474,14 +474,14 @@ p5.prototype.loadModel = function (path, options) { model.flipV(); } - self._decrementPreload(); + resolve(); if (typeof successCallback === 'function') { successCallback(model); } } }, failureCallback - ); + )); } else { p5._friendlyFileLoadError(3, path); if (failureCallback) { @@ -665,6 +665,7 @@ function parseObj(model, lines, materials = {}) { model.vertexColors.push(materialDiffuseColor[0]); model.vertexColors.push(materialDiffuseColor[1]); model.vertexColors.push(materialDiffuseColor[2]); + model.vertexColors.push(1); } } else { hasColorlessVertices = true; diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index c2fe4003c1..e5e8a9be7a 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -87,7 +87,7 @@ visualSuite('WebGL', function() { visualTest('OBJ model with MTL file displays diffuse colors correctly', function(p5, screenshot) { return new Promise(resolve => { p5.createCanvas(50, 50, p5.WEBGL); - p5.loadModel('unit/assets/octa-color.obj', model => { + p5.loadModel('/unit/assets/octa-color.obj', model => { p5.background(255); p5.rotateX(10 * 0.01); p5.rotateY(10 * 0.01); @@ -101,7 +101,7 @@ visualSuite('WebGL', function() { visualTest('Object with no colors takes on fill color', function(p5, screenshot) { return new Promise(resolve => { p5.createCanvas(50, 50, p5.WEBGL); - p5.loadModel('unit/assets/cube.obj', model => { + p5.loadModel('/unit/assets/cube.obj', model => { p5.background(255); p5.fill('blue'); // Setting a fill color p5.rotateX(p5.frameCount * 0.01); @@ -117,8 +117,8 @@ visualSuite('WebGL', function() { 'Object with different texture coordinates per use of vertex keeps the coordinates intact', async function(p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); - const tex = await new Promise(resolve => p5.loadImage('unit/assets/cat.jpg', resolve)); - const cube = await new Promise(resolve => p5.loadModel('unit/assets/cube-textures.obj', resolve)); + const tex = await new Promise(resolve => p5.loadImage('/unit/assets/cat.jpg', resolve)); + const cube = await new Promise(resolve => p5.loadModel('/unit/assets/cube-textures.obj', resolve)); cube.normalize(); p5.background(255); p5.texture(tex); diff --git a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png b/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png index 2df2de7125..9412be3440 100644 Binary files a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png and b/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png differ diff --git a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png b/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png index a30e8a60b5..a88804ae71 100644 Binary files a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png and b/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png b/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png index 0426415f31..d3f8182293 100644 Binary files a/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png and b/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png b/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png index dcba4010ac..e928fe3cb0 100644 Binary files a/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png and b/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png b/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png index 5d7801aa04..28dd2a7fd5 100644 Binary files a/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png and b/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png b/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png index d2daba574f..f56c898bc5 100644 Binary files a/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png and b/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png index b3cc6ed609..d4000e5a14 100644 Binary files a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png and b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png index 74fb513043..788178ce2f 100644 Binary files a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png and b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png differ diff --git a/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png b/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png index 346374eb47..a6ba205f7c 100644 Binary files a/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png and b/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png index 90ccca1c5d..197629c93f 100644 Binary files a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png and b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png differ diff --git a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png index 90ccca1c5d..197629c93f 100644 Binary files a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png and b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png differ diff --git a/test/unit/visual/visualTest.js b/test/unit/visual/visualTest.js index e6ec8af501..7e41fc0f6c 100644 --- a/test/unit/visual/visualTest.js +++ b/test/unit/visual/visualTest.js @@ -1,15 +1,11 @@ import p5 from '../../../src/app.js'; +import { server } from '@vitest/browser/context' +import resemble from 'resemblejs' +const { readFile, writeFile } = server.commands -/** - * A helper class to contain an error and also the screenshot data that - * caused the error. - */ -class ScreenshotError extends Error { - constructor(message, actual, expected) { - super(message); - this.actual = actual; - this.expected = expected; - } +function writeImageFile(filename, base64Data) { + const prefix = /^data:image\/\w+;base64,/; + writeFile(filename, base64Data.replace(prefix, ''), 'base64'); } function toBase64(img) { @@ -37,45 +33,49 @@ export function visualSuite( callback, { focus = false, skip = false } = {} ) { - const lastPrefix = namePrefix; - namePrefix += escapeName(name) + '/'; - - let suiteFn = suite; + let suiteFn = describe; if (focus) { suiteFn = suiteFn.only; } if (skip) { suiteFn = suiteFn.skip; } - suiteFn(name, callback); + suiteFn(name, () => { + let lastPrefix; + beforeAll(() => { + lastPrefix = namePrefix; + namePrefix += escapeName(name) + '/'; + }) - namePrefix = lastPrefix; + callback() + + afterAll(() => { + namePrefix = lastPrefix; + }); + }); } -export function checkMatch(actual, expected, p5) { +export async function checkMatch(actual, expected, p5) { const maxSide = 50; const scale = Math.min(maxSide/expected.width, maxSide/expected.height); + for (const img of [actual, expected]) { img.resize( Math.ceil(img.width * scale), Math.ceil(img.height * scale) ); } - const diff = p5.createImage(actual.width, actual.height); - diff.drawingContext.drawImage(actual.canvas, 0, 0); - diff.drawingContext.globalCompositeOperation = 'difference'; - diff.drawingContext.drawImage(expected.canvas, 0, 0); - diff.filter(p5.ERODE, false); - diff.loadPixels(); - - let ok = true; - for (let i = 0; i < diff.pixels.length; i++) { - if (i % 4 === 3) continue; // Skip alpha checks - if (Math.abs(diff.pixels[i]) > 10) { - ok = false; - break; - } - } + + resemble.outputSettings({ useCrossOrigin: false }); + const diff = await new Promise(resolve => resemble(toBase64(actual)) + .compareTo(toBase64(expected)) + .ignoreAntialiasing() + .onComplete((data) => { + resolve(data) + }) + ) + const ok = diff.rawMisMatchPercentage === 0; + return { ok, diff }; } @@ -103,8 +103,7 @@ export function visualTest( callback, { focus = false, skip = false } = {} ) { - const name = namePrefix + escapeName(testName); - let suiteFn = suite; + let suiteFn = describe; if (focus) { suiteFn = suiteFn.only; } @@ -113,9 +112,11 @@ export function visualTest( } suiteFn(testName, function() { + let name; let myp5; beforeAll(function() { + name = namePrefix + escapeName(testName); return new Promise(res => { myp5 = new p5(function(p) { p.setup = function() { @@ -132,20 +133,15 @@ export function visualTest( test('matches expected screenshots', async function() { let expectedScreenshots; try { - metadata = await fetch( - `unit/visual/screenshots/${name}/metadata.json` - ).then(res => res.json()); + const metadata = JSON.parse(await readFile( + `../screenshots/${name}/metadata.json` + )); expectedScreenshots = metadata.numScreenshots; } catch (e) { + console.log(e); expectedScreenshots = 0; } - if (!window.shouldGenerateScreenshots && !expectedScreenshots) { - // If running on CI, all expected screenshots should already - // be generated - throw new Error('No expected screenshots found'); - } - const actual = []; // Generate screenshots @@ -163,34 +159,31 @@ export function visualTest( ); } if (!expectedScreenshots) { - writeTextFile( - `unit/visual/screenshots/${name}/metadata.json`, + await writeFile( + `../screenshots/${name}/metadata.json`, JSON.stringify({ numScreenshots: actual.length }, null, 2) ); } const expectedFilenames = actual.map( - (_, i) => `unit/visual/screenshots/${name}/${i.toString().padStart(3, '0')}.png` + (_, i) => `../screenshots/${name}/${i.toString().padStart(3, '0')}.png` ); const expected = expectedScreenshots ? ( await Promise.all( - expectedFilenames.map(path => new Promise((resolve, reject) => { - myp5.loadImage(path, resolve, reject); - })) + expectedFilenames.map(path => myp5.loadImage('/unit/visual' + path.slice(2))) ) ) : []; for (let i = 0; i < actual.length; i++) { if (expected[i]) { - if (!checkMatch(actual[i], expected[i], myp5).ok) { - throw new ScreenshotError( - `Screenshots do not match! Expected:\n${toBase64(expected[i])}\n\nReceived:\n${toBase64(actual[i])}\n\n` + - 'If this is unexpected, paste these URLs into your browser to inspect them, or run grunt yui:dev and go to http://127.0.0.1:9001/test/visual.html.\n\n' + - `If this change is expected, please delete the test/unit/visual/screenshots/${name} folder and run tests again to generate a new screenshot.`, - actual[i], - expected[i] + const result = await checkMatch(actual[i], expected[i], myp5); + if (!result.ok) { + throw new Error( + `Screenshots do not match! Expected:\n${toBase64(expected[i])}\n\nReceived:\n${toBase64(actual[i])}\n\nDiff:\n${result.diff.getImageDataUrl()}\n\n` + + 'If this is unexpected, paste these URLs into your browser to inspect them.\n\n' + + `If this change is expected, please delete the screenshots/${name} folder and run tests again to generate a new screenshot.`, ); } } else { diff --git a/vitest.workspace.mjs b/vitest.workspace.mjs index 991ff2ce94..18ec5254c1 100644 --- a/vitest.workspace.mjs +++ b/vitest.workspace.mjs @@ -10,18 +10,6 @@ const plugins = [ ]; export default defineWorkspace([ - { - plugins, - publicDir: './test', - test: { - name: 'visual', - root: './', - include: [ - './test/unit/visual/*' - ], - environment: 'node' - } - }, { plugins, publicDir: './test', @@ -34,7 +22,7 @@ export default defineWorkspace([ exclude: [ './test/unit/spec.js', './test/unit/assets/**/*', - './test/unit/visual/**/*' + './test/unit/visual/visualTest.js', ], testTimeout: 1000, globals: true,