diff --git a/cypress/e2e/motd.cy.ts b/cypress/e2e/motd.cy.ts deleted file mode 100644 index cd237b719..000000000 --- a/cypress/e2e/motd.cy.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {runFrontendTest} from '../support/utils'; - -describe('Motd testing', () => { - before(() => { - cy.visit('/'); - }); - - runFrontendTest('motd'); -}); diff --git a/cypress/e2e/remote-id.cy.ts b/cypress/e2e/remote-id.cy.ts deleted file mode 100644 index c043eca90..000000000 --- a/cypress/e2e/remote-id.cy.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {runFrontendTest} from '../support/utils'; - -describe('RemoteId testing', () => { - before(() => { - cy.visit('/'); - }); - - runFrontendTest('remoteId'); -}); diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index 85eabcc76..d38dfbe37 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -1,13 +1,5 @@ import '../../static/global'; -export function runFrontendTest(name: string) { - it(name, () => { - return cy.window().then(win => { - return win.compilerExplorerFrontendTesting.run(name); - }); - }); -} - export function stubConsoleOutput(win: Cypress.AUTWindow) { cy.stub(win.console, 'log').as('consoleLog'); cy.stub(win.console, 'warn').as('consoleWarn'); diff --git a/docs/internal/FrontendTesting.md b/docs/internal/FrontendTesting.md index 15db1f341..6badd11ae 100644 --- a/docs/internal/FrontendTesting.md +++ b/docs/internal/FrontendTesting.md @@ -1,34 +1,12 @@ # Frontend testing -We have a mixture of typescript in the main website's code (located in `static/tests`) and Cypress (located in -`cypress/e2e`) to test and report on the workings of that code. +We have a mixture of unit tests (located in `static/tests`) and Cypress UI tests(located in `cypress/e2e`). -But there's always the possibility to use Cypress code to do UI checks and testing. +If possible, create unit tests in `static/tests` for the code you are working on. If you can get away with simple state +tests and don't need to do any real DOM manipulation, this is the way to go. Testing "does this filter correctly" or "do +we parse this right" are perfect examples of this. These tests use `happy-dom` for _extremely minimal_ DOM mocking just +enough to get the code running at all. -## Recommended +If you need to check actual behaviour or rely on the pug loading/HTML rendering, you should use the Cypress tests. -The recommended way of testing is to use typescript to test the inner workings of the various interfaces that are -available. - -This has the advantage of having types and being able to verify your code is consistent with the rest of the website and -probably going to run correctly - without having to startup the website and Cypress. - -## Adding a test - -Steps to add a test: - -- Create a new file in `static/tests` (copy paste from `static/tests/hello-world.ts`) -- Make sure to change the `description` as well as the test -- Add the file to the imports of `static/tests/_all.ts` -- Add a test file with a `runFrontendTest()` call in `cypress/e2e` - -## Starting tests locally - -You don't need to install an entire X server to actually run cypress (just xfvb). - -You can find a complete list at https://docs.cypress.io/guides/getting-started/installing-cypress#System-requirements - -If you have the prerequisites installed, you should be able to run `npx cypress run` - however, you will need to start -the CE website separately in another terminal before that. - -Some extra tips can be found [here](../UsingCypress.md) +To run the cypress tests, see [this document](../UsingCypress.md). diff --git a/package-lock.json b/package-lock.json index faafe32e3..48bb646ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,12 +102,14 @@ "css-minimizer-webpack-plugin": "^7.0.2", "cypress": "^14.4.1", "file-loader": "^6.2.0", + "happy-dom": "^18.0.1", "husky": "^9.1.7", "lint-staged": "^16.1.0", "mini-css-extract-plugin": "^2.9.2", "mock-fs": "^5.5.0", "monaco-editor-webpack-plugin": "^7.1.0", "nock": "^14.0.5", + "npm-run-all": "^4.1.5", "sass": "^1.89.2", "sass-loader": "^16.0.5", "source-map-loader": "^5.0.0", @@ -4924,6 +4926,12 @@ "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", "dev": true }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true + }, "node_modules/@types/which": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", @@ -5463,6 +5471,43 @@ } ] }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -5533,6 +5578,15 @@ "node": ">=0.12.0" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5548,6 +5602,21 @@ "node": ">= 4.0.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sdk-client-mock": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.1.0.tgz", @@ -5880,6 +5949,24 @@ "node": ">=6" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -6354,6 +6441,12 @@ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -6842,6 +6935,57 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -6873,6 +7017,40 @@ "node": ">=6" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7141,6 +7319,89 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -7188,6 +7449,23 @@ "node": ">= 0.4" } }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -7699,6 +7977,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7831,6 +8124,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-east-asian-width": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", @@ -7893,6 +8215,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-tsconfig": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", @@ -7973,6 +8312,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glossy": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/glossy/-/glossy-0.1.7.tgz", @@ -8013,6 +8368,56 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/happy-dom": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz", + "integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==", + "dev": true, + "dependencies": { + "@types/node": "^20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/@types/node": { + "version": "20.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.1.tgz", + "integrity": "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/happy-dom/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/happy-dom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8021,6 +8426,33 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -8057,6 +8489,12 @@ "node": ">= 0.4" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8283,6 +8721,20 @@ "node": ">=10" } }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -8300,11 +8752,90 @@ "node": ">= 0.10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "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==" }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -8319,6 +8850,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-expression": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", @@ -8347,6 +8911,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -8356,6 +8935,24 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -8383,6 +8980,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-node-process": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", @@ -8398,6 +9019,22 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -8441,6 +9078,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -8452,6 +9116,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -8470,6 +9182,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -8652,6 +9407,12 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -9117,6 +9878,30 @@ } } }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -9371,6 +10156,15 @@ "url": "https://github.com/sponsors/streamich" } }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -9666,6 +10460,12 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node_modules/nise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", @@ -9725,6 +10525,27 @@ "node": ">= 0.10.0" } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9733,6 +10554,200 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -9776,6 +10791,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/obliterator": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", @@ -9843,6 +10887,23 @@ "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", "dev": true }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -9936,6 +10997,19 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -10050,6 +11124,27 @@ "node": ">=16" } }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -10155,6 +11250,15 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", @@ -10987,6 +12091,20 @@ "node": ">= 0.8" } }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -11031,6 +12149,48 @@ "node": ">= 10.13.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -11219,6 +12379,31 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11238,6 +12423,45 @@ } ] }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-stable-stringify": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", @@ -11438,6 +12662,52 @@ "node": ">= 18" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -11479,6 +12749,18 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shimmer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", @@ -11696,6 +12978,38 @@ "source-map": "^0.6.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true + }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -11749,6 +13063,19 @@ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/streamx": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", @@ -11818,6 +13145,80 @@ "node": ">=8" } }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11843,6 +13244,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -12512,6 +13922,80 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -12525,6 +14009,24 @@ "node": ">=14.17" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/underscore": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", @@ -12643,6 +14145,16 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -13126,6 +14638,97 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/package.json b/package.json index ea93fec93..825948801 100644 --- a/package.json +++ b/package.json @@ -111,12 +111,14 @@ "css-minimizer-webpack-plugin": "^7.0.2", "cypress": "^14.4.1", "file-loader": "^6.2.0", + "happy-dom": "^18.0.1", "husky": "^9.1.7", "lint-staged": "^16.1.0", "mini-css-extract-plugin": "^2.9.2", "mock-fs": "^5.5.0", "monaco-editor-webpack-plugin": "^7.1.0", "nock": "^14.0.5", + "npm-run-all": "^4.1.5", "sass": "^1.89.2", "sass-loader": "^16.0.5", "source-map-loader": "^5.0.0", @@ -156,7 +158,11 @@ "update-browserslist": "npx browserslist@latest -- --update-db", "prepare": "husky", "ts-compile": "tsc", - "ts-check": "tsc -p ./tsconfig.backend.json --noEmit && tsc -p ./tsconfig.frontend.json --noEmit && tsc -p ./tsconfig.tests.json --noEmit", + "ts-check": "npm-run-all ts-check:*", + "ts-check:backend": "tsc -p ./tsconfig.backend.json --noEmit", + "ts-check:frontend": "tsc -p ./tsconfig.frontend.json --noEmit", + "ts-check:tests": "tsc -p ./tsconfig.tests.json --noEmit", + "ts-check:frontend-tests": "tsc -p ./tsconfig.frontend.tests.json --noEmit", "webpack": "node --no-warnings=ExperimentalWarning --import=tsx ./node_modules/webpack-cli/bin/cli.js --node-env=production --config webpack.config.esm.ts" }, "license": "BSD-2-Clause", diff --git a/static/global.ts b/static/global.ts index a807b1232..3ad866396 100644 --- a/static/global.ts +++ b/static/global.ts @@ -23,7 +23,6 @@ // POSSIBILITY OF SUCH DAMAGE. import {Options} from './options.interfaces.js'; -import {IFrontendTesting} from './tests/frontend-testing.interfaces.js'; export type CompilerExplorerOptions = Record & Options; @@ -32,7 +31,6 @@ declare global { httpRoot: string; staticRoot: string; compilerExplorerOptions: CompilerExplorerOptions; - compilerExplorerFrontendTesting: IFrontendTesting; hasUIBeenReset: boolean; PRODUCTION: boolean; onSponsorClick: (sponsorUrl: string) => void; diff --git a/static/main.ts b/static/main.ts index 6382082e4..3fa19f102 100644 --- a/static/main.ts +++ b/static/main.ts @@ -76,11 +76,6 @@ import changelogDocument from './generated/changelog.pug'; import cookiesDocument from './generated/cookies.pug'; import privacyDocument from './generated/privacy.pug'; -if (!window.PRODUCTION && !options.embedded) { - // TODO: Replace with top-level await import() when we move to Vite - require('./tests/_all'); -} - //css import 'bootstrap/dist/css/bootstrap.min.css'; import 'golden-layout/src/css/goldenlayout-base.css'; diff --git a/static/tests/README.md b/static/tests/README.md new file mode 100644 index 000000000..a4b8dc6cc --- /dev/null +++ b/static/tests/README.md @@ -0,0 +1,5 @@ +## Front-end unit tests + +Tests here are _super simple_ and use `jsdom` with a fake document to do quick checks on the front-end code. + +For anything requiring a real browser, use the `cypress` tests in the `cypress` directory. diff --git a/static/tests/_all.ts b/static/tests/_setup-dom.ts similarity index 86% rename from static/tests/_all.ts rename to static/tests/_setup-dom.ts index 6c9b96803..888d1e621 100644 --- a/static/tests/_all.ts +++ b/static/tests/_setup-dom.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023, Compiler Explorer Authors +// Copyright (c) 2025, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -22,7 +22,5 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -import './frontend-testing'; -import './hello-world'; -import './motd'; -import './remote-id'; +globalThis.__webpack_public_path__ = ''; +document.body.innerHTML = '
'; diff --git a/static/tests/components-tests.ts b/static/tests/components-tests.ts new file mode 100644 index 000000000..12fe4b12e --- /dev/null +++ b/static/tests/components-tests.ts @@ -0,0 +1,682 @@ +// Copyright (c) 2025, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import {describe, expect, it} from 'vitest'; + +import { + createComponentConfig, + createLayoutItem, + fromGoldenLayoutConfig, + toGoldenLayoutConfig, +} from '../../static/components.js'; + +import { + COMPILER_COMPONENT_NAME, + DIFF_VIEW_COMPONENT_NAME, + EDITOR_COMPONENT_NAME, + EXECUTOR_COMPONENT_NAME, + OPT_VIEW_COMPONENT_NAME, + OUTPUT_COMPONENT_NAME, + TOOL_COMPONENT_NAME, + TREE_COMPONENT_NAME, +} from '../../static/components.interfaces.js'; + +describe('Components validation', () => { + describe('fromGoldenLayoutConfig', () => { + describe('Input validation', () => { + it('should throw for null input', () => { + expect(() => fromGoldenLayoutConfig(null as any)).toThrow('Invalid configuration: must be an object'); + }); + + it('should throw for undefined input', () => { + expect(() => fromGoldenLayoutConfig(undefined as any)).toThrow( + 'Invalid configuration: must be an object', + ); + }); + + it('should throw for non-object input', () => { + expect(() => fromGoldenLayoutConfig('string' as any)).toThrow( + 'Invalid configuration: must be an object', + ); + expect(() => fromGoldenLayoutConfig(123 as any)).toThrow('Invalid configuration: must be an object'); + }); + + it('should accept arrays as valid objects', () => { + // Arrays are objects in JavaScript, so they pass the initial validation + // The content validation will handle any structure issues later + expect(() => fromGoldenLayoutConfig([] as any)).not.toThrow(); + }); + }); + + describe('Valid configurations', () => { + it('should accept empty configuration', () => { + const config = {}; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept configuration with empty content array', () => { + const config = {content: []}; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept valid compiler component', () => { + const config = { + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: { + source: 1, + lang: 'c++', + }, + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept valid editor component', () => { + const config = { + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: { + id: 1, + lang: 'c++', + }, + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept valid executor component', () => { + const config = { + content: [ + { + type: 'component', + componentName: EXECUTOR_COMPONENT_NAME, + componentState: { + source: 1, + lang: 'c++', + compilationPanelShown: true, + compilerOutShown: false, + }, + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept valid output component', () => { + const config = { + content: [ + { + type: 'component', + componentName: OUTPUT_COMPONENT_NAME, + componentState: { + tree: 1, + compiler: 2, + editor: 3, + }, + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept valid tool component', () => { + const config = { + content: [ + { + type: 'component', + componentName: TOOL_COMPONENT_NAME, + componentState: { + tree: 1, + toolId: 'readelf', + id: 2, + editorid: 3, + }, + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should accept valid layout items', () => { + const config = { + content: [ + { + type: 'row', + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {}, + }, + { + type: 'column', + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: {source: 1, lang: 'c++'}, + }, + ], + }, + ], + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + }); + + describe('Invalid content structure', () => { + it('should throw for non-array content', () => { + const config = {content: 'not-array'} as any; + expect(() => fromGoldenLayoutConfig(config)).toThrow('Configuration content must be an array'); + }); + + it('should throw for content with non-object items', () => { + const config = {content: ['string-item']} as any; + expect(() => fromGoldenLayoutConfig(config)).toThrow('Invalid item 0: must be an object'); + }); + + it('should throw for items missing type', () => { + const config = {content: [{}]} as any; + expect(() => fromGoldenLayoutConfig(config)).toThrow("Invalid item 0: missing 'type' property"); + }); + + it('should throw for items with unknown type', () => { + const config = {content: [{type: 'unknown'}]} as any; + expect(() => fromGoldenLayoutConfig(config)).toThrow("Invalid item 0: unknown type 'unknown'"); + }); + }); + + describe('Invalid component configurations', () => { + it('should throw for component missing componentName', () => { + const config = { + content: [ + { + type: 'component', + componentState: {}, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: missing 'componentName' property", + ); + }); + + it('should throw for component with non-string componentName', () => { + const config = { + content: [ + { + type: 'component', + componentName: 123, + componentState: {}, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: 'componentName' must be a string", + ); + }); + + it('should throw for invalid compiler state', () => { + const config = { + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: { + // Missing required properties + invalidProp: true, + }, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: invalid component state for component 'compiler'", + ); + }); + + it('should throw for invalid executor state missing boolean flags', () => { + const config = { + content: [ + { + type: 'component', + componentName: EXECUTOR_COMPONENT_NAME, + componentState: { + source: 1, + lang: 'c++', + // Missing compilationPanelShown and compilerOutShown + }, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: invalid component state for component 'executor'", + ); + }); + + it('should throw for invalid output state missing numeric properties', () => { + const config = { + content: [ + { + type: 'component', + componentName: OUTPUT_COMPONENT_NAME, + componentState: { + tree: 'not-number', + compiler: 1, + editor: 1, + }, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: invalid component state for component 'output'", + ); + }); + + it('should throw for invalid tool state', () => { + const config = { + content: [ + { + type: 'component', + componentName: TOOL_COMPONENT_NAME, + componentState: { + tree: 1, + // Missing required toolId, id, editorid + }, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: invalid component state for component 'tool'", + ); + }); + + it('should throw for unknown component name', () => { + const config = { + content: [ + { + type: 'component', + componentName: 'unknown-component', + componentState: {}, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: invalid component state for component 'unknown-component'", + ); + }); + }); + + describe('Invalid layout item configurations', () => { + it('should throw for layout item missing content', () => { + const config = { + content: [ + { + type: 'row', + }, + ], + } as any; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: layout items must have a 'content' array", + ); + }); + + it('should throw for layout item with non-array content', () => { + const config = { + content: [ + { + type: 'column', + content: 'not-array', + }, + ], + } as any; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: layout items must have a 'content' array", + ); + }); + }); + + describe('Complex nested validation', () => { + it('should validate deeply nested structures', () => { + const config = { + content: [ + { + type: 'row', + content: [ + { + type: 'column', + content: [ + { + type: 'stack', + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {}, + }, + { + type: 'component', + componentName: DIFF_VIEW_COMPONENT_NAME, + componentState: {}, + }, + ], + }, + ], + }, + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: {source: 1, lang: 'c++'}, + }, + ], + }, + ], + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should throw for invalid nested component', () => { + const config = { + content: [ + { + type: 'row', + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {}, + }, + { + type: 'column', + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: { + // Invalid state - missing required properties + invalidProp: true, + }, + }, + ], + }, + ], + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow('invalid component state for component'); + }); + }); + + describe('Edge cases', () => { + it('should handle null component state', () => { + const config = { + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: null, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).toThrow( + "Invalid item 0: invalid component state for component 'compiler'", + ); + }); + + it('should preserve additional properties', () => { + const config = { + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {}, + title: 'Custom Title', + isClosable: false, + width: 50, + }, + ], + settings: { + showPopoutIcon: false, + }, + maximisedItemId: null, + }; + const result = fromGoldenLayoutConfig(config); + expect(result).toEqual(config); + }); + + it('should handle mixed valid and invalid compiler states', () => { + // Test different valid combinations for compiler + const validConfigs = [ + {source: 1, lang: 'c++'}, + {source: 1, compiler: 'gcc'}, + {tree: 1, lang: 'c++'}, + ]; + + for (const state of validConfigs) { + const config = { + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: state, + }, + ], + }; + expect(() => fromGoldenLayoutConfig(config)).not.toThrow(); + } + }); + }); + }); + + describe('Helper functions', () => { + describe('createComponentConfig', () => { + it('should create valid component config', () => { + const config = createComponentConfig(EDITOR_COMPONENT_NAME, {id: 1, lang: 'c++'}); + expect(config).toEqual({ + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {id: 1, lang: 'c++'}, + }); + }); + + it('should include optional properties', () => { + const config = createComponentConfig( + COMPILER_COMPONENT_NAME, + {source: 1, lang: 'c++'}, + { + title: 'Custom Compiler', + isClosable: false, + width: 50, + height: 200, + }, + ); + expect(config).toEqual({ + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: {source: 1, lang: 'c++'}, + title: 'Custom Compiler', + isClosable: false, + width: 50, + height: 200, + }); + }); + }); + + describe('createLayoutItem', () => { + it('should create valid layout item', () => { + const content = [ + createComponentConfig(EDITOR_COMPONENT_NAME, {}), + createComponentConfig(COMPILER_COMPONENT_NAME, {source: 1, lang: 'c++'}), + ]; + const layoutItem = createLayoutItem('row', content); + expect(layoutItem).toEqual({ + type: 'row', + content, + }); + }); + + it('should include optional properties', () => { + const content = [createComponentConfig(DIFF_VIEW_COMPONENT_NAME, {})]; + const layoutItem = createLayoutItem('stack', content, { + isClosable: true, + width: 100, + activeItemIndex: 0, + }); + expect(layoutItem).toEqual({ + type: 'stack', + content, + isClosable: true, + width: 100, + activeItemIndex: 0, + }); + }); + }); + + describe('toGoldenLayoutConfig', () => { + it('should pass through config unchanged', () => { + const config = { + content: [ + createLayoutItem('row', [ + createComponentConfig(EDITOR_COMPONENT_NAME, {id: 1}), + createComponentConfig(COMPILER_COMPONENT_NAME, {source: 1, lang: 'c++'}), + ]), + ], + settings: {showPopoutIcon: false}, + }; + const result = toGoldenLayoutConfig(config); + expect(result).toBe(config); + }); + }); + }); + + describe('Integration tests', () => { + it('should round-trip through validation', () => { + const originalConfig = { + content: [ + { + type: 'row', + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {id: 1, lang: 'c++'}, + }, + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: {source: 1, lang: 'c++'}, + }, + ], + }, + ], + settings: {showPopoutIcon: false}, + }; + + const validated = fromGoldenLayoutConfig(originalConfig); + const backToGolden = toGoldenLayoutConfig(validated); + + expect(backToGolden).toEqual(originalConfig); + }); + + it('should work with complex real-world config', () => { + const complexConfig = { + content: [ + { + type: 'row', + content: [ + { + type: 'column', + width: 50, + content: [ + { + type: 'component', + componentName: EDITOR_COMPONENT_NAME, + componentState: {id: 1, lang: 'c++', source: '#include '}, + height: 60, + }, + { + type: 'component', + componentName: TREE_COMPONENT_NAME, + componentState: {id: 1, cmakeArgs: '-DCMAKE_BUILD_TYPE=Release'}, + height: 40, + }, + ], + }, + { + type: 'stack', + width: 50, + content: [ + { + type: 'component', + componentName: COMPILER_COMPONENT_NAME, + componentState: { + source: 1, + lang: 'c++', + compiler: 'gcc', + options: '-O2', + }, + title: 'GCC', + }, + { + type: 'component', + componentName: OPT_VIEW_COMPONENT_NAME, + componentState: {id: 1, source: 'test'}, + title: 'Optimization', + }, + ], + }, + ], + }, + ], + settings: { + showPopoutIcon: false, + showMaximiseIcon: true, + showCloseIcon: true, + }, + maximisedItemId: null, + }; + + expect(() => fromGoldenLayoutConfig(complexConfig)).not.toThrow(); + const result = fromGoldenLayoutConfig(complexConfig); + expect(result).toEqual(complexConfig); + }); + }); +}); diff --git a/static/tests/frontend-testing.interfaces.ts b/static/tests/frontend-testing.interfaces.ts deleted file mode 100644 index b0ff68e05..000000000 --- a/static/tests/frontend-testing.interfaces.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2022, Compiler Explorer Authors -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -export interface ITestable { - readonly description: string; - run(): Promise; -} - -export interface IFrontendTesting { - add(test: ITestable): void; - getAllTestNames(): string[]; - run(testToRun: string): Promise; -} diff --git a/static/tests/frontend-testing.ts b/static/tests/frontend-testing.ts deleted file mode 100644 index f86bba166..000000000 --- a/static/tests/frontend-testing.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2022, Compiler Explorer Authors -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -import {IFrontendTesting, ITestable} from './frontend-testing.interfaces.js'; - -class FrontendTesting implements IFrontendTesting { - private testSuites: Array = []; - - public add(test: ITestable) { - this.testSuites.push(test); - } - - public getAllTestNames(): string[] { - return this.testSuites.map(val => val.description); - } - - private findTest(name: string) { - for (const suite of this.testSuites) { - if (suite.description === name) { - return suite; - } - } - - throw new Error(`Can't find test ${name}`); - } - - public async run(testToRun: string) { - const testSuite = this.findTest(testToRun); - await testSuite.run(); - } -} - -window.compilerExplorerFrontendTesting = new FrontendTesting(); diff --git a/static/tests/motd-tests.ts b/static/tests/motd-tests.ts new file mode 100644 index 000000000..6ce196c36 --- /dev/null +++ b/static/tests/motd-tests.ts @@ -0,0 +1,361 @@ +// Copyright (c) 2025, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; + +import {isValidAd} from '../../static/motd.js'; + +describe('MOTD Tests', () => { + describe('with fake timers', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should keep ad if now > from', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect(isValidAd({filter: [], html: '', valid_from: '2022-01-01T00:00:00+00:00'}, 'langForTest')).toBe( + true, + ); + }); + + it('should filter ad if now < from', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect(isValidAd({filter: [], html: '', valid_from: '2022-01-16T00:00:00+00:00'}, 'langForTest')).toBe( + false, + ); + }); + + it('should keep ad if now < until', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect(isValidAd({filter: [], html: '', valid_until: '2022-01-16T00:00:00+00:00'}, 'langForTest')).toBe( + true, + ); + }); + + it('should filter ad if now > until', () => { + vi.setSystemTime(new Date('2022-01-20T00:00:00+00:00')); + expect(isValidAd({filter: [], html: '', valid_until: '2022-01-16T00:00:00+00:00'}, 'langForTest')).toBe( + false, + ); + }); + + it('should keep ad if from < now < until', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: '2022-01-01T00:00:00+00:00', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should filter ad if now < from < until', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: '2022-01-10T00:00:00+00:00', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(false); + }); + + it('should filter ad if from < until < now', () => { + vi.setSystemTime(new Date('2022-01-20T00:00:00+00:00')); + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: '2022-01-10T00:00:00+00:00', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(false); + }); + + it('should filter ad if until < now < from', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: '2022-01-16T00:00:00+00:00', + valid_until: '2022-01-10T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(false); + }); + + it('should filter ad if from < now < until but filtered by lang', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: ['fakeLang'], + html: '', + valid_from: '2022-01-01T00:00:00+00:00', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(false); + }); + + it('should keep ad if from < now < until and not filtered by lang', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: ['langForTest'], + html: '', + valid_from: '2022-01-01T00:00:00+00:00', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad if from = now < until and not filtered by lang', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: ['langForTest'], + html: '', + valid_from: '2022-01-08T00:00:00+00:00', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad if from < now = until and not filtered by lang', () => { + vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00')); + expect( + isValidAd( + { + filter: ['langForTest'], + html: '', + valid_from: '2022-01-01T00:00:00+00:00', + valid_until: '2022-01-18T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad when now equals valid_until', () => { + vi.setSystemTime(new Date('2022-01-16T00:00:00+00:00')); + expect( + isValidAd( + { + filter: [], + html: '', + valid_until: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad when now equals valid_from', () => { + vi.setSystemTime(new Date('2022-01-16T00:00:00+00:00')); + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: '2022-01-16T00:00:00+00:00', + }, + 'langForTest', + ), + ).toBe(true); + }); + }); + + it('should keep ad if sublang is not set', () => { + expect(isValidAd({filter: [], html: ''}, 'fakeLang')).toBe(true); + }); + + it('should keep ad if sublang is not set even if filtering for lang', () => { + expect(isValidAd({filter: ['fakeLang'], html: ''}, 'langForTest')).toBe(false); + }); + + it('should keep ad if no lang is set', () => { + expect(isValidAd({filter: [], html: ''}, 'langForTest')).toBe(true); + }); + + it('should filter ad if not the correct language', () => { + expect(isValidAd({filter: ['anotherLang'], html: ''}, 'langForTest')).toBe(false); + }); + + it('should keep ad if the correct language is used', () => { + expect(isValidAd({filter: ['langForTest'], html: ''}, 'langForTest')).toBe(true); + }); + + it('should keep ad if valid_from has invalid date format', () => { + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: 'invalid-date-format', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad if valid_until has invalid date format', () => { + expect( + isValidAd( + { + filter: [], + html: '', + valid_until: 'not-a-date', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad if both dates have invalid format', () => { + expect( + isValidAd( + { + filter: [], + html: '', + valid_from: 'invalid-from', + valid_until: 'invalid-until', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should keep ad with empty string sublang', () => { + expect(isValidAd({filter: [], html: ''}, '')).toBe(true); + }); + + it('should keep ad with null sublang', () => { + expect(isValidAd({filter: [], html: ''}, null as any)).toBe(true); + }); + + it('should keep ad with undefined sublang', () => { + expect(isValidAd({filter: [], html: ''}, undefined as any)).toBe(true); + }); + + it('should keep ad if sublang matches one of multiple filter languages', () => { + expect( + isValidAd( + { + filter: ['lang1', 'lang2', 'langForTest', 'lang3'], + html: '', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should filter ad if sublang does not match any of multiple filter languages', () => { + expect( + isValidAd( + { + filter: ['lang1', 'lang2', 'lang3'], + html: '', + }, + 'langForTest', + ), + ).toBe(false); + }); + + it('should filter ad with invalid date and non-matching language filter', () => { + expect( + isValidAd( + { + filter: ['anotherLang'], + html: '', + valid_from: 'invalid-date', + }, + 'langForTest', + ), + ).toBe(false); + }); + + it('should keep ad with invalid date and matching language filter', () => { + expect( + isValidAd( + { + filter: ['langForTest'], + html: '', + valid_until: 'invalid-date', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should handle filter with empty strings', () => { + expect( + isValidAd( + { + filter: ['', 'langForTest', ''], + html: '', + }, + 'langForTest', + ), + ).toBe(true); + }); + + it('should handle filter with duplicate languages', () => { + expect( + isValidAd( + { + filter: ['langForTest', 'langForTest', 'langForTest'], + html: '', + }, + 'langForTest', + ), + ).toBe(true); + }); +}); diff --git a/static/tests/motd.ts b/static/tests/motd.ts deleted file mode 100644 index c14ac8d12..000000000 --- a/static/tests/motd.ts +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) 2022, Compiler Explorer Authors -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/// - -import {isValidAd} from '../motd.js'; -import {ITestable} from './frontend-testing.interfaces.js'; - -import {Ad} from '../motd.interfaces.js'; - -class MotdTests implements ITestable { - public readonly description: string = 'motd'; - - private static assertAd(ad: Ad, subLang: string, expected: boolean, message: string) { - if (isValidAd(ad, subLang) !== expected) { - throw new Error(message); - } - } - - private static assertAdWithDateNow(dateNow: number, ad: Ad, subLang: string, expected: boolean, message: string) { - const originalDateNow = Date.now; - Date.now = () => dateNow; - MotdTests.assertAd(ad, subLang, expected, message); - Date.now = originalDateNow; - } - - public async run() { - MotdTests.assertAd( - { - filter: [], - html: '', - }, - 'fakeLang', - true, - 'Keep ad if sublang is not set', - ); - - MotdTests.assertAd( - { - filter: ['fakeLang'], - html: '', - }, - 'langForTest', - false, - 'Keep ad if sublang is not set even if filtering for lang', - ); - - MotdTests.assertAd( - { - filter: [], - html: '', - }, - 'langForTest', - true, - 'Keep ad if no lang is set', - ); - - MotdTests.assertAd( - { - filter: ['anotherLang'], - html: '', - }, - 'langForTest', - false, - 'Filters ad if not the correct language', - ); - - MotdTests.assertAd( - { - filter: ['langForTest'], - html: '', - }, - 'langForTest', - true, - 'Keep ad if the correct language is used', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-01T00:00:00+00:00', - }, - 'langForTest', - true, - 'Keep ad if now > from', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - false, - 'Filter ad if now < from', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - true, - 'Keep ad if now < until', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - false, - 'Filter ad if now > until', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-01T00:00:00+00:00', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - true, - 'Keep ad if from < now < until', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-10T00:00:00+00:00', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - false, - 'Filter ad if now < from < until', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-20T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-10T00:00:00+00:00', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - false, - 'Filter ad if from < until < now', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: [], - html: '', - valid_from: '2022-01-16T00:00:00+00:00', - valid_until: '2022-01-10T00:00:00+00:00', - }, - 'langForTest', - false, - 'Filter ad if until < now < from', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: ['fakeLang'], - html: '', - valid_from: '2022-01-01T00:00:00+00:00', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - false, - 'Filter ad if from < now < until but filtered by lang', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: ['langForTest'], - html: '', - valid_from: '2022-01-01T00:00:00+00:00', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - true, - 'Keep ad if from < now < until and not filtered by lang', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: ['langForTest'], - html: '', - valid_from: '2022-01-08T00:00:00+00:00', - valid_until: '2022-01-16T00:00:00+00:00', - }, - 'langForTest', - true, - 'Keep ad if from = now < until and not filtered by lang', - ); - - MotdTests.assertAdWithDateNow( - Date.parse('2022-01-08T00:00:00+00:00'), - { - filter: ['langForTest'], - html: '', - valid_from: '2022-01-01T00:00:00+00:00', - valid_until: '2022-01-18T00:00:00+00:00', - }, - 'langForTest', - true, - 'Keep ad if from < now = until and not filtered by lang', - ); - } -} - -window.compilerExplorerFrontendTesting.add(new MotdTests()); diff --git a/static/tests/remote-id.ts b/static/tests/remote-id.ts deleted file mode 100644 index 3d86d6b78..000000000 --- a/static/tests/remote-id.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2025, Compiler Explorer Authors -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/// - -import {getRemoteId} from '../../shared/remote-utils.js'; - -import {UrlTestCases} from '../../shared/url-testcases.js'; - -import {ITestable} from './frontend-testing.interfaces.js'; - -class RemoteIdTests implements ITestable { - public readonly description: string = 'remoteId'; - - public async run() { - UrlTestCases.forEach(testCase => { - if (getRemoteId(testCase.remoteUrl, testCase.language) !== testCase.expectedId) { - throw new Error( - `Test case failed for language: ${testCase.language}, remoteUrl: ${testCase.remoteUrl}, expectedId: ${testCase.expectedId}`, - ); - } - }); - } -} - -window.compilerExplorerFrontendTesting.add(new RemoteIdTests()); diff --git a/static/tests/hello-world.ts b/static/tests/remote-utils-tests.ts similarity index 76% rename from static/tests/hello-world.ts rename to static/tests/remote-utils-tests.ts index 1dc1b5879..008efc2a3 100644 --- a/static/tests/hello-world.ts +++ b/static/tests/remote-utils-tests.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2022, Compiler Explorer Authors +// Copyright (c) 2025, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -22,15 +22,13 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -import {ITestable} from './frontend-testing.interfaces.js'; +import {getRemoteId} from '../../shared/remote-utils.js'; -class HelloWorldTests implements ITestable { - public readonly description: string = 'HelloWorld'; +import {describe, expect, it} from 'vitest'; +import {UrlTestCases} from '../../shared/url-testcases.js'; - public async run() { - const person = true; - if (!person) throw new Error('HelloWorldTests failed'); - } -} - -window.compilerExplorerFrontendTesting.add(new HelloWorldTests()); +describe('Remote ID Tests', () => { + it.each(UrlTestCases)('Check $remoteUrl $language', testCase => { + expect(getRemoteId(testCase.remoteUrl, testCase.language)).toBe(testCase.expectedId); + }); +}); diff --git a/test/static/utils-tests.ts b/static/tests/utils-tests.ts similarity index 100% rename from test/static/utils-tests.ts rename to static/tests/utils-tests.ts diff --git a/tsconfig.frontend.tests.json b/tsconfig.frontend.tests.json new file mode 100644 index 000000000..782d396ad --- /dev/null +++ b/tsconfig.frontend.tests.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.frontend.json", + "files": ["static/global.ts"], + "include": ["static/tests/**/*.ts"], +} diff --git a/vitest.config.ts b/vitest.config.ts index c18df9597..406815c15 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,8 +6,24 @@ export default defineConfig({ provider: 'v8', reporter: ['text', 'json', 'html'], }, - include: ['test/**/*.ts'], - exclude: ['test/_*.ts', 'test/utils.ts'], - setupFiles: ['/test/_setup-fake-aws.ts', '/test/_setup-log.ts'], + projects: [ + { + test: { + name: 'unit', + include: ['test/**/*.ts'], + exclude: ['test/_*.ts', 'test/utils.ts'], + setupFiles: ['test/_setup-fake-aws.ts', 'test/_setup-log.ts'], + }, + }, + { + test: { + name: 'frontend unit', + include: ['static/tests/**/*.ts'], + exclude: ['static/tests/_*.ts'], + setupFiles: ['static/tests/_setup-dom.ts'], + environment: 'happy-dom', + }, + }, + ], }, });