From f9efabb7b9427a0467959ac922c65372e91cf5c6 Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Mon, 5 Feb 2024 13:35:05 -0500 Subject: [PATCH 1/7] Setup testing --- .dockerignore | 3 +- .github/workflows/test.yml | 38 + .gitignore | 5 +- README.md | 190 ++-- package-lock.json | 1070 ++++++++++++++++++++- package.json | 12 +- playwright-fetalmri.config.ts | 7 + playwright.config.ts | 76 ++ src/example.test.ts | 5 + tests/createAccount.spec.ts | 14 + tests/fetalmri.org/visualdatasets.spec.ts | 7 + vitest.config.ts | 17 + 12 files changed, 1344 insertions(+), 100 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 playwright-fetalmri.config.ts create mode 100644 playwright.config.ts create mode 100644 src/example.test.ts create mode 100644 tests/createAccount.spec.ts create mode 100644 tests/fetalmri.org/visualdatasets.spec.ts create mode 100644 vitest.config.ts diff --git a/.dockerignore b/.dockerignore index 7cb9ecd63..6cc15c438 100755 --- a/.dockerignore +++ b/.dockerignore @@ -7,4 +7,5 @@ node_modules npm-debug.log* yarn-debug.log* yarn-error.log* - +test-results +playwright-report diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..b4acde112 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: test + +on: + push: + branches: [ master ] + pull_request: + +jobs: + test: + name: Tests + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.7 + cache: 'npm' + + - name: Install dependencies + run: npm ci + - name: Unit tests + run: npm test + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: End-to-End tests + env: + TEST_SAFARI: yes + run: npm run test:e2e + - name: Upload E2E test coverage + if: ${{ always() }} + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage/clover.xml + flags: e2etests + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index 190631a17..591159e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ # testing /coverage +/test-results +/playwright-report # production /build @@ -32,5 +34,4 @@ yarn-error.log* .npm/ .cache/ .yarnrc -.github/ -dist/ \ No newline at end of file +dist/ diff --git a/README.md b/README.md index 30068b358..f8a7e6091 100644 --- a/README.md +++ b/README.md @@ -23,55 +23,47 @@ The *ChRIS_ui* is now running on http://localhost:3000/ ## Development -### [0] Preconditions - -1. **Install latest Docker for your platform.** - - Currently tested platforms - - Ubuntu 18.04+ (typically 20.04+, and Pop!_OS) - - Fedora 32+ - - Arch Linux - - macOS 11.X+ (Big Sur) - -2. **Get the backend services up so you can fully test the UI against actual data.** - * Install latest [``Docker Compose``](https://docs.docker.com/compose/) - * On a Linux machine make sure to add your computer user to the ``docker`` group - -3. **Open a terminal and start the backend services.** - ```bash - git clone https://github.com/FNNDSC/miniChRIS.git - cd miniChRIS - ./minichris.sh - ``` - -
- - - Alternatively, start the backend in development mode: - - - - ### Get the backend running from ChRIS_ultron_backEnd - - ```bash - $ git clone https://github.com/FNNDSC/ChRIS_ultron_backEnd.git - $ cd ChRIS_ultron_backEnd - $ ./make.sh -U -I -i - ``` - - ### Tearing down the ChRIS backend - - You can later remove all the backend containers and release storage volumes with: - ```bash - $ cd ChRIS_ultron_backEnd - $ sudo rm -r FS - $ ./unmake.sh - ``` -
- -See [FNNDSC/miniChRIS](https://github.com/FNNDSC/miniChRIS) for details. - -### [1] Configuring the backend URL +We support development on **Linux** only. + + +#### 1. Have the [_ChRIS_ backend](https://github.com/FNNDSC/ChRIS_ultron_backEnd) running. + +For local development, use [Docker Compose](https://docs.docker.com/compose/) and [miniChRIS-docker](https://github.com/FNNDSC/miniChRIS-docker). Open a terminal and run + +```shell +git clone https://github.com/FNNDSC/miniChRIS-docker.git +cd miniChRIS-docker +./minichris.sh +``` + +
+ + + Alternatively, start the backend in development mode (click to expand) + + + +##### Get the backend running from ChRIS_ultron_backEnd + +```bash +$ git clone https://github.com/FNNDSC/ChRIS_ultron_backEnd.git +$ cd ChRIS_ultron_backEnd +$ ./make.sh -U -I -i +``` + +##### Tearing down the ChRIS backend + +You can later remove all the backend containers and release storage volumes with: + +```bash +$ cd ChRIS_ultron_backEnd +$ sudo rm -r FS +$ ./unmake.sh +``` + +
+ +#### 2. Configuring the backend URL For development, it is recommended that you create either a `.env.local` or `.env.development.local` environment variables file in the root of the project. @@ -87,38 +79,43 @@ Copy the existing `.env` file to this new file. Changes to these files will be i For details on how to set up PFDCM, refer to the [PFDCM readme](https://github.com/FNNDSC/pfdcm). -### [2] Start UI development server +#### 3. Start UI development server + You can follow any of these steps to start UI development server -* #### Using ``node`` and ``yarn`` package manager directly on the metal +```shell +git clone https://github.com/FNNDSC/ChRIS_ui.git +cd ChRIS_ui +npm i +npm run dev +``` - Open a new terminal on your system and follow these steps: - ```bash - $ git clone https://github.com/FNNDSC/ChRIS_ui.git - $ cd ChRIS_ui - $ npm i - $ npm run dev - ``` +
+ + +Alternatively, using Docker (click to expand) + + - More details can be found in the - [wiki](https://github.com/FNNDSC/ChRIS_ui/wiki/Development-and-deployment-directly-on-the-metal). +These instructions are no longer supported. -* #### Using ``docker`` +Open a new terminal on your system and follow these steps: + +```bash +git clone https://github.com/FNNDSC/ChRIS_ui.git +cd ChRIS_ui +docker build -t localhost/fnndsc/chris_ui:dev -f Dockerfile_dev . +docker run --rm -it -v $PWD:/home/localuser -p 3000:3000 -u $(id -u):$(id -g) --userns=host --name chris_ui localhost/fnndsc/chris_ui:dev +``` - Open a new terminal on your system and follow these steps: - ```bash - $ git clone https://github.com/FNNDSC/ChRIS_ui.git - $ cd ChRIS_ui - $ docker build -t fnndsc/chris_ui:dev -f Dockerfile_dev . - $ docker run --rm -it -v $PWD:/home/localuser -p 3000:3000 -u $(id -u):$(id -g) --userns=host --name chris_ui fnndsc/chris_ui:dev - ``` - Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +
-## Build the ChRIS UI app for production +## Build for production [Source-to-image](https://github.com/openshift/source-to-image#readme) -can be used to build this project. +must be used to build this project for deployment. ```shell s2i build https://github.com/FNNDSC/ChRIS_ui quay.io/fedora/nodejs-20 s2ichrisui @@ -130,27 +127,54 @@ s2i build https://github.com/FNNDSC/ChRIS_ui quay.io/fedora/nodejs-20 s2ichrisui Set the environment variables `VITE_ACKEE_SERVER` and `VITE_ACKEE_DOMAIN_ID` to send analytics to an Ackee instance. -### E2E TESTS ARE RAN USING CYPRESS +## Testing -## Prerequisites: -- ChRIS_ultron_backend is running on `http://localhost:8000/api/v1/` -- ChRIS_ui is running on `http://localhost:3000/` -- You have Cypress installed using `npm install` -``` -- To run: -`$ npm run cypress:open` +_ChRIS_ui_ does unit tests using [vitest](https://vitest.dev/) and end-to-end (E2E) tests using [Playwright](https://playwright.dev). + +### Unit Tests + +Unit tests are defined in `*.test.ts` files inside `src`. + +It is recommended to leave this command running while developing _ChRIS_ui_. + +```shell +npm test ``` -This will open cypress in an interactive UI. -To run cypress in the terminal as a headless browser use: + +### End-to-End Tests + +E2E tests are located under `tests/`. Tests specific to http://fetalmri.org are found under `tests/fetalmri.org`. + +Playwright requires some system dependencies. On first run, you will be prompted to install these dependencies. +With Playwright installed, run + +```shell +npm run test:e2e ``` -`npm run cypress:run` + +Or + +```shell +npx playwright test -c playwright.config.ts --ui ``` -Running Cypress inside a container is not currently supported +### Writing Tests + +E2E tests can be recorded from user interactions. See https://playwright.dev/docs/codegen-intro +First, start the development server: +```shell +npm run dev +``` + +In another terminal, open the website and start recording tests: +```shell +npx playwright codegen http://localhost:5173 +``` + [license-badge]: https://img.shields.io/github/license/fnndsc/chris_ui.svg [last-commit-badge]: https://img.shields.io/github/last-commit/fnndsc/chris_ui.svg diff --git a/package-lock.json b/package-lock.json index 3076d5d8c..3977c64fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "vite": "^5.0.12" }, "devDependencies": { + "@playwright/test": "^1.41.2", "@types/d3-hierarchy": "^1.1.7", "@types/d3-selection": "^1.4.3", "@types/d3-shape": "^2.0.0", @@ -78,13 +79,19 @@ "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "@vitejs/plugin-react-swc": "^3.5.0", + "@vitest/coverage-v8": "^1.2.2", + "c8": "^9.1.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "happy-dom": "^13.3.8", + "playwright": "^1.41.2", + "playwright-test-coverage-native": "^0.2.0", "redux-devtools-extension": "^2.13.9", - "redux-logger": "^3.0.6" + "redux-logger": "^3.0.6", + "vitest": "^1.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -452,9 +459,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { "node": ">=6.9.0" } @@ -566,9 +573,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -629,11 +636,11 @@ } }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -641,6 +648,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@cornerstonejs/codec-charls": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@cornerstonejs/codec-charls/-/codec-charls-1.2.3.tgz", @@ -1161,6 +1174,18 @@ "node": ">=8" } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1390,6 +1415,21 @@ "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.1.2.tgz", "integrity": "sha512-hu/6kEEMnyDc4GiMiaEau3kYq0BZoB3X1tZLcNfg9zQZnOydUgaLcUgR8+IlMF/nVVIqNjZF2RA/5lmKAVz2cQ==" }, + "node_modules/@playwright/test": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", + "dev": true, + "dependencies": { + "playwright": "1.41.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.24", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", @@ -1770,6 +1810,12 @@ "win32" ] }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@swc/core": { "version": "1.3.104", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.104.tgz", @@ -2143,6 +2189,12 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2474,6 +2526,129 @@ "vite": "^4 || ^5" } }, + "node_modules/@vitest/coverage-v8": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.2.2.tgz", + "integrity": "sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "^1.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", + "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.2.2", + "@vitest/utils": "1.2.2", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", + "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.2.2", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", + "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", + "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", + "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/accessor-fn": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.0.tgz", @@ -2511,6 +2686,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2747,6 +2931,15 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", @@ -2906,6 +3099,40 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -2958,6 +3185,24 @@ "node": ">=12" } }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2974,6 +3219,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chris-utility": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/chris-utility/-/chris-utility-1.1.6.tgz", @@ -2989,6 +3246,20 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -3430,6 +3701,18 @@ "integrity": "sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug==", "dev": true }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3509,6 +3792,15 @@ "resolved": "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.21.tgz", "integrity": "sha512-lYCweHQDsC8UFpXErPlg86Px2A8bay0HiUY+wzoG3xv5GzgqVHU3lziwSc/Gzn7VV7y2KeP072SzCviuOoU02w==" }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3606,6 +3898,12 @@ "node": ">4.0" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -4083,6 +4381,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4092,6 +4399,29 @@ "node": ">=0.10.0" } }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4378,6 +4708,22 @@ "node": ">=12" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -4472,6 +4818,24 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -4495,6 +4859,18 @@ "node": ">=4" } }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -4619,6 +4995,29 @@ "node": ">=0.8.0" } }, + "node_modules/happy-dom": { + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-13.3.8.tgz", + "integrity": "sha512-RAbq4oYfJNkVan1m1F3jfA4YEyRY0/ASoNvZsNJbuX85jIypidmsz9jQZD7Tqz0VXA2MhAGfcsh5oshwmwNYSg==", + "dev": true, + "dependencies": { + "entities": "^4.5.0", + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/happy-dom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -4707,6 +5106,12 @@ "react-is": "^16.7.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", @@ -4725,6 +5130,15 @@ "entities": "^4.4.0" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -4951,6 +5365,15 @@ "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", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -5077,6 +5500,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -5198,6 +5633,47 @@ "semver": "bin/semver.js" } }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -5293,8 +5769,14 @@ "node": ">=6" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, @@ -5380,6 +5862,22 @@ "node": ">=6" } }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5437,6 +5935,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5445,6 +5952,44 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", + "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/magicast": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.3.tgz", + "integrity": "sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "source-map-js": "^1.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/marked": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", @@ -5469,6 +6014,12 @@ "source-map": "^0.6.1" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5510,6 +6061,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5527,6 +6090,18 @@ "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==" }, + "node_modules/mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -5612,6 +6187,33 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, + "node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5726,6 +6328,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -5851,6 +6468,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5868,11 +6500,75 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/platform": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" }, + "node_modules/playwright": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", + "dev": true, + "dependencies": { + "playwright-core": "1.41.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright-test-coverage-native": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/playwright-test-coverage-native/-/playwright-test-coverage-native-0.2.0.tgz", + "integrity": "sha512-MSM5FanaTig34TIoTwZoQsJ94L7x8KsMhwkoiFaPixxoqaJE/YdnkqT9lxDIO+Mvjzef0XdsG49PWvfxAb8SVA==", + "dev": true, + "peerDependencies": { + "@playwright/test": "^1.41.1" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -5909,6 +6605,38 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/preval.macro": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/preval.macro/-/preval.macro-5.0.0.tgz", @@ -6955,6 +7683,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -7280,6 +8017,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sirv": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", @@ -7339,11 +8094,37 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -7421,6 +8202,18 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -7433,6 +8226,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/stylis": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", @@ -7466,6 +8271,20 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/text-encoding-utf-8": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", @@ -7485,6 +8304,12 @@ "node": ">=12.22" } }, + "node_modules/tinybench": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", + "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", + "dev": true + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", @@ -7498,6 +8323,24 @@ "node": ">=4" } }, + "node_modules/tinypool": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", + "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -7565,6 +8408,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -7705,6 +8557,12 @@ "node": "*" } }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -7854,6 +8712,26 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/victory-area": { "version": "36.8.2", "resolved": "https://registry.npmjs.org/victory-area/-/victory-area-36.8.2.tgz", @@ -8288,6 +9166,94 @@ } } }, + "node_modules/vite-node": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", + "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", + "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.2.2", + "@vitest/runner": "1.2.2", + "@vitest/snapshot": "1.2.2", + "@vitest/spy": "1.2.2", + "@vitest/utils": "1.2.2", + "acorn-walk": "^8.3.2", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.2", + "vite": "^5.0.0", + "vite-node": "1.2.2", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "^1.0.0", + "@vitest/ui": "^1.0.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -8314,6 +9280,15 @@ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, + "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/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -8414,12 +9389,54 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -8433,6 +9450,33 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 640310f39..f816a9033 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "__start": "# npm start command is used by s2i in OpenShift as the deployment command", "dev": "vite", "build": "tsc && vite build", + "test": "vitest", + "test:e2e": "playwright test -c playwright.config.ts; rc=$?; c8 report -o coverage -r clover -r text && exit $rc", + "test:fetalmri": "playwright test -c playwright-fetalmri.config.ts", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "print-version": "./print_version.sh" @@ -69,6 +72,7 @@ "vite": "^5.0.12" }, "devDependencies": { + "@playwright/test": "^1.41.2", "@types/d3-hierarchy": "^1.1.7", "@types/d3-selection": "^1.4.3", "@types/d3-shape": "^2.0.0", @@ -82,12 +86,18 @@ "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "@vitejs/plugin-react-swc": "^3.5.0", + "@vitest/coverage-v8": "^1.2.2", + "c8": "^9.1.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "happy-dom": "^13.3.8", + "playwright": "^1.41.2", + "playwright-test-coverage-native": "^0.2.0", "redux-devtools-extension": "^2.13.9", - "redux-logger": "^3.0.6" + "redux-logger": "^3.0.6", + "vitest": "^1.2.2" } } diff --git a/playwright-fetalmri.config.ts b/playwright-fetalmri.config.ts new file mode 100644 index 000000000..575f5287e --- /dev/null +++ b/playwright-fetalmri.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "playwright-test-coverage-native"; +import baseConfig from "./playwright.config.ts"; + +export default defineConfig(baseConfig, { + testDir: "./tests/fetalmri.org", + testIgnore: undefined, +}); diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..5905524eb --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,76 @@ +import { defineConfig, devices, PlaywrightTestConfig } from "playwright-test-coverage-native"; + + +const SAFARI_BROWSERS: PlaywrightTestConfig["projects"] = []; + +if (process.env.TEST_SAFARI?.toLowerCase().startsWith('y')) { + SAFARI_BROWSERS.push( + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ); +} + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + testIgnore: "**/fetalmri.org/**", + /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ + snapshotDir: "./__snapshots__", + /* Maximum time one test can run for. */ + timeout: 10 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + baseURL: "http://localhost:5173", + }, + + /* Configure projects for major browsers */ + projects: [ + // NOTE: our goal here isn't to extensively test Niivue, all we need is a working WebGL2! + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + coverageDir: './coverage/tmp', + coverageSrc: './src', + coverageSourceMapHandler: 'localhosturl' + }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + + ...SAFARI_BROWSERS + ], + + webServer: { + command: "npm run dev", + url: "http://localhost:5173", + }, +}); diff --git a/src/example.test.ts b/src/example.test.ts new file mode 100644 index 000000000..f38c31d12 --- /dev/null +++ b/src/example.test.ts @@ -0,0 +1,5 @@ +import { test, expect } from "vitest"; + +test("example test, delete me when a real test is added", () => { + expect(1).toBe(1); +}); diff --git a/tests/createAccount.spec.ts b/tests/createAccount.spec.ts new file mode 100644 index 000000000..79a6e8ded --- /dev/null +++ b/tests/createAccount.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from "playwright-test-coverage-native"; + +test.describe("Create account page", () => { + test("has fields for username and password", async ({ page }) => { + await page.goto("/login"); + await page.goto('http://localhost:5173/'); + await page.getByRole('button', { name: 'Sign Up' }).click(); + await page.getByLabel('signupform').fill('test-user'); + await page.locator('#chris-email').fill('testuser@example.org'); + await page.locator('#chris-password').fill('testuser1234'); + await expect(page.getByRole('button', { name: 'Create Account' })).toBeInViewport(); + // we don't actually click the button, TODO figure out how to reuse this test account + }); +}); diff --git a/tests/fetalmri.org/visualdatasets.spec.ts b/tests/fetalmri.org/visualdatasets.spec.ts new file mode 100644 index 000000000..ba4103e2b --- /dev/null +++ b/tests/fetalmri.org/visualdatasets.spec.ts @@ -0,0 +1,7 @@ +import { test } from "playwright-test-coverage-native"; + +test.describe("Fetal MRI Viewer", () => { + test("exists", async ({ page }) => { + await page.goto("/niivue"); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..aa05a96f2 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; +import viteConfig from './vite.config'; + +export default mergeConfig(viteConfig, defineConfig({ + test: { + include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'], + environment: 'happy-dom', + restoreMocks: true, + + // coverage for unit testing not enabled, because we have none! + // coverage: { + // enabled: true, + // include: ["src/**"], + // reportsDirectory: "./coverage-vitest", + // }, + }, +})); From 41d2a67aaa274263a14e7355f996b08784fa92e8 Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Mon, 5 Feb 2024 13:53:10 -0500 Subject: [PATCH 2/7] Add tests/pluginCatalog.spec.ts --- tests/pluginCatalog.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/pluginCatalog.spec.ts diff --git a/tests/pluginCatalog.spec.ts b/tests/pluginCatalog.spec.ts new file mode 100644 index 000000000..9cbfbe8ad --- /dev/null +++ b/tests/pluginCatalog.spec.ts @@ -0,0 +1,7 @@ +import { test, expect } from "playwright-test-coverage-native"; + +test.describe("Plugin catalog page", () => { + test("The page renders", async ({ page }) => { + await page.goto('/catalog'); + await page.getByPlaceholder('Name').fill('hello world'); }); +}); From 28554300c693ac807116197c33255ecc5a48b08e Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Mon, 5 Feb 2024 13:55:22 -0500 Subject: [PATCH 3/7] Create npm scripts for ui and codegen --- README.md | 6 +++--- package.json | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8a7e6091..b653f09a0 100644 --- a/README.md +++ b/README.md @@ -152,10 +152,10 @@ With Playwright installed, run npm run test:e2e ``` -Or +Or, use a GUI to run tests one-by-one: ```shell -npx playwright test -c playwright.config.ts --ui +npm run test:ui ``` ### Writing Tests @@ -171,7 +171,7 @@ npm run dev In another terminal, open the website and start recording tests: ```shell -npx playwright codegen http://localhost:5173 +npm run test:codegen ``` diff --git a/package.json b/package.json index 351507e0a..918ab6e4e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "build": "tsc && vite build", "test": "vitest", "test:e2e": "playwright test -c playwright.config.ts; rc=$?; c8 report -o coverage -r clover -r text && exit $rc", + "test:ui": "playwright test -c playwright.config.ts --ui", + "test:codegen": "playwright codegen http://localhost:5173", "test:fetalmri": "playwright test -c playwright-fetalmri.config.ts", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", From c86f851f1160b011e92f4f39c908734dd25b791a Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Mon, 5 Feb 2024 13:57:07 -0500 Subject: [PATCH 4/7] Add codecov badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b653f09a0..c713de039 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![License][license-badge] ![Last Commit][last-commit-badge] ![Code Size][code-size] +[![codecov](https://codecov.io/gh/FNNDSC/ChRIS_ui/graph/badge.svg?token=J9PCSEQ5E5)](https://codecov.io/gh/FNNDSC/ChRIS_ui) This repository contains the reference UI for ChRIS, allowing users to create and interact with dynamic containerized workflows. The ChRIS UI is written primarily in [TypeScript](https://www.typescriptlang.org/) and [React](https://reactjs.org/), and uses the [PatternFly](https://github.com/patternfly/patternfly) React pattern library. From f06c15df756fb44e1863a8c3752b805fc0936e2c Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Mon, 5 Feb 2024 14:08:06 -0500 Subject: [PATCH 5/7] Bump actions --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4acde112..30c46c3d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20.7 cache: 'npm' @@ -30,7 +30,7 @@ jobs: run: npm run test:e2e - name: Upload E2E test coverage if: ${{ always() }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage/clover.xml From 7410fffd105bdb3d0b14edb2360f9a4533fda8e7 Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Tue, 6 Feb 2024 00:00:12 -0500 Subject: [PATCH 6/7] Use public CUBE for tests --- README.md | 79 +++++++++-------------- package-lock.json | 17 +++++ package.json | 8 ++- playwright-fetalmri.config.ts | 7 -- playwright.config.ts | 15 +++-- src/components/DragFileUpload/index.tsx | 3 + testingEnv/.env | 10 +++ testingEnv/README | 1 + tests/createAccount.spec.ts | 31 +++++---- tests/createFeed.spec.ts | 33 ++++++++++ tests/fetalmri.org/visualdatasets.spec.ts | 7 -- tests/fixtures/loggedIn.ts | 42 ++++++++++++ tests/fixtures/notLoggedIn.ts | 12 ++++ tests/helpers/clipboard.ts | 20 ++++++ tests/helpers/createAccount.ts | 15 +++++ tests/pluginCatalog.spec.ts | 22 ++++++- vite_publicCUBE.config.ts | 10 +++ 17 files changed, 245 insertions(+), 87 deletions(-) delete mode 100644 playwright-fetalmri.config.ts create mode 100644 testingEnv/.env create mode 100644 testingEnv/README create mode 100644 tests/createFeed.spec.ts delete mode 100644 tests/fetalmri.org/visualdatasets.spec.ts create mode 100644 tests/fixtures/loggedIn.ts create mode 100644 tests/fixtures/notLoggedIn.ts create mode 100644 tests/helpers/clipboard.ts create mode 100644 tests/helpers/createAccount.ts create mode 100644 vite_publicCUBE.config.ts diff --git a/README.md b/README.md index c713de039..8947c50be 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,18 @@ This repository contains the reference UI for ChRIS, allowing users to create an ## Quickstart -First, get the [ChRIS backend](https://github.com/FNNDSC/ChRIS_ultron_backEnd) -running. Assuming the backend is on `http://localhost:8000/api/v1/`: - ```shell -docker run --rm -d --name chris_ui -p 3000:5173 -e REACT_APP_CHRIS_UI_URL=http://localhost:8000/api/v1/ ghcr.io/fnndsc/chris_ui:latest +git clone https://github.com/FNNDSC/ChRIS_ui.git +cd ChRIS_ui +npm ci +npm run dev:public ``` -The *ChRIS_ui* is now running on http://localhost:3000/ - ## Development We support development on **Linux** only. - -#### 1. Have the [_ChRIS_ backend](https://github.com/FNNDSC/ChRIS_ultron_backEnd) running. +#### 0. Have the [_ChRIS_ backend](https://github.com/FNNDSC/ChRIS_ultron_backEnd) running (Optional) For local development, use [Docker Compose](https://docs.docker.com/compose/) and [miniChRIS-docker](https://github.com/FNNDSC/miniChRIS-docker). Open a terminal and run @@ -64,31 +61,17 @@ $ ./unmake.sh -#### 2. Configuring the backend URL - -For development, it is recommended that you create either a `.env.local` -or `.env.development.local` environment variables file in the root of the project. -Copy the existing `.env` file to this new file. Changes to these files will be ignored by git. - -**There are four (4) major environment variables that need to be set.** - -- Point `VITE_CHRIS_UI_URL` to your local backend instance. By default (or if you copied the `.env` file) this is set to `http://localhost:8000/api/v1/`. - -- Point `VITE_PFDCM_URL` to the URL of a running PFDCM instance. By default this is set to `http://localhost:4005/`. - -- Set `VITE_PFDCM_CUBEKEY` and `VITE_PFDCM_SWIFTKEY` to the aliases (or keys) given to CUBE and Swift while setting up PFDCM. By default these are both `local`. If you're unsure what to use, you can list CUBE and Swift keys using the PFDCM API, or ask for these keys. +If your backend is accessible from a host other than localhost, e.g. you are using a cloud or remote development +server, run `cp .env .env.development.local` then edit `.env.development.local` with your backend API URL. -For details on how to set up PFDCM, refer to the [PFDCM readme](https://github.com/FNNDSC/pfdcm). +#### 1. Install Dependencies -#### 3. Start UI development server - -You can follow any of these steps to start UI development server +You need Node version 20 or 21. ```shell git clone https://github.com/FNNDSC/ChRIS_ui.git cd ChRIS_ui npm i -npm run dev ```
@@ -113,6 +96,21 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+ +#### 2. Run the development server + +If you are running and have configured a backend in step 0, run + +```shell +npm run dev +``` + +Otherwise, you can use our public testing backend instance. Run + +```shell +npm run dev:public +``` + ## Build for production [Source-to-image](https://github.com/openshift/source-to-image#readme) @@ -144,7 +142,10 @@ npm test ### End-to-End Tests -E2E tests are located under `tests/`. Tests specific to http://fetalmri.org are found under `tests/fetalmri.org`. +> [!IMPORTANT] +> End-to-end (E2E) tests are configured to run against our public testing backend instance, which is prepopulated with data. + +E2E tests are located under `tests/`. Playwright requires some system dependencies. On first run, you will be prompted to install these dependencies. With Playwright installed, run @@ -153,27 +154,8 @@ With Playwright installed, run npm run test:e2e ``` -Or, use a GUI to run tests one-by-one: - -```shell -npm run test:ui -``` - -### Writing Tests - -E2E tests can be recorded from user interactions. See https://playwright.dev/docs/codegen-intro - -First, start the development server: - -```shell -npm run dev -``` - -In another terminal, open the website and start recording tests: - -```shell -npm run test:codegen -``` +For more information, consult the wiki: +https://github.com/FNNDSC/ChRIS_ui/wiki/E2E-Testing-with-Playwright @@ -181,4 +163,3 @@ npm run test:codegen [last-commit-badge]: https://img.shields.io/github/last-commit/fnndsc/chris_ui.svg [repo-link]: https://github.com/FNNDSC/ChRIS_ui [code-size]: https://img.shields.io/github/languages/code-size/FNNDSC/ChRIS_ui - diff --git a/package-lock.json b/package-lock.json index f01e556d1..968863543 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "vite": "^5.0.12" }, "devDependencies": { + "@faker-js/faker": "^8.4.0", "@playwright/test": "^1.41.2", "@types/d3-hierarchy": "^1.1.7", "@types/d3-selection": "^1.4.3", @@ -1116,6 +1117,22 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz", + "integrity": "sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@fnndsc/chrisapi": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@fnndsc/chrisapi/-/chrisapi-1.15.0.tgz", diff --git a/package.json b/package.json index 918ab6e4e..e269a75eb 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,14 @@ "start": "sirv dist --host --port ${PORT:=8080} --single --etag --quiet", "__start": "# npm start command is used by s2i in OpenShift as the deployment command", "dev": "vite", + "dev:public": "vite -c vite_publicCUBE.config.ts", "build": "tsc && vite build", "test": "vitest", - "test:e2e": "playwright test -c playwright.config.ts; rc=$?; c8 report -o coverage -r clover -r text && exit $rc", + "test:e2e": "playwright test -c playwright.config.ts; rc=$?; c8 report -o coverage -r clover && exit $rc", + "test:report": "c8 report -o coverage -r clover -r test", "test:ui": "playwright test -c playwright.config.ts --ui", - "test:codegen": "playwright codegen http://localhost:5173", + "test:codegen": "playwright codegen http://localhost:25173", + "test:codegen:mobile": "playwright codegen --device 'Pixel 5' http://localhost:25173", "test:fetalmri": "playwright test -c playwright-fetalmri.config.ts", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", @@ -75,6 +78,7 @@ "vite": "^5.0.12" }, "devDependencies": { + "@faker-js/faker": "^8.4.0", "@playwright/test": "^1.41.2", "@types/d3-hierarchy": "^1.1.7", "@types/d3-selection": "^1.4.3", diff --git a/playwright-fetalmri.config.ts b/playwright-fetalmri.config.ts deleted file mode 100644 index 575f5287e..000000000 --- a/playwright-fetalmri.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "playwright-test-coverage-native"; -import baseConfig from "./playwright.config.ts"; - -export default defineConfig(baseConfig, { - testDir: "./tests/fetalmri.org", - testIgnore: undefined, -}); diff --git a/playwright.config.ts b/playwright.config.ts index 5905524eb..78a4fd358 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -21,11 +21,10 @@ if (process.env.TEST_SAFARI?.toLowerCase().startsWith('y')) { */ export default defineConfig({ testDir: "./tests", - testIgnore: "**/fetalmri.org/**", /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ snapshotDir: "./__snapshots__", /* Maximum time one test can run for. */ - timeout: 10 * 1000, + timeout: 20_000, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -40,7 +39,7 @@ export default defineConfig({ use: { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", - baseURL: "http://localhost:5173", + baseURL: "http://localhost:25173", }, /* Configure projects for major browsers */ @@ -52,7 +51,7 @@ export default defineConfig({ ...devices["Desktop Chrome"], coverageDir: './coverage/tmp', coverageSrc: './src', - coverageSourceMapHandler: 'localhosturl' + coverageSourceMapHandler: 'localhosturl', }, }, @@ -63,14 +62,16 @@ export default defineConfig({ { name: 'Mobile Chrome', - use: { ...devices['Pixel 5'] }, + use: { + ...devices['Pixel 5'], + }, }, ...SAFARI_BROWSERS ], webServer: { - command: "npm run dev", - url: "http://localhost:5173", + command: "npm run dev:public", + url: "http://localhost:25173", }, }); diff --git a/src/components/DragFileUpload/index.tsx b/src/components/DragFileUpload/index.tsx index c440407f6..a9c2b07c5 100644 --- a/src/components/DragFileUpload/index.tsx +++ b/src/components/DragFileUpload/index.tsx @@ -59,6 +59,9 @@ const DragAndUpload = ({ isDragAccept, } = useDropzone({ onDrop, + // FS access API is not available on Firefox, and also causes trouble for testing. + // https://github.com/react-dropzone/react-dropzone/discussions/1339 + useFsAccessApi: false }); const { state, dispatch } = useContext(CreateFeedContext); diff --git a/testingEnv/.env b/testingEnv/.env new file mode 100644 index 000000000..4071da29b --- /dev/null +++ b/testingEnv/.env @@ -0,0 +1,10 @@ +VITE_CHRIS_UI_URL="https://publictesting-hosting-of-medical-image-analysis-platform-dcb83b.apps.shift.nerc.mghpcc.org/api/v1/" +VITE_CHRIS_UI_USERS_URL="https://publictesting-hosting-of-medical-image-analysis-platform-dcb83b.apps.shift.nerc.mghpcc.org/api/v1/users/" +VITE_CHRIS_UI_AUTH_URL="https://publictesting-hosting-of-medical-image-analysis-platform-dcb83b.apps.shift.nerc.mghpcc.org/api/v1/auth-token/" +VITE_ALPHA_FEATURES="production" + +VITE_PFDCM_URL="http://localhost:4005/" + +VITE_PFDCM_CUBEKEY="local" +VITE_PFDCM_SWIFTKEY="local" +VITE_SOURCEMAP="true" diff --git a/testingEnv/README b/testingEnv/README new file mode 100644 index 000000000..ea23d02f0 --- /dev/null +++ b/testingEnv/README @@ -0,0 +1 @@ +This directory contains a .env file specifying the CUBE to use for E2E testing. diff --git a/tests/createAccount.spec.ts b/tests/createAccount.spec.ts index 79a6e8ded..a1155c0f7 100644 --- a/tests/createAccount.spec.ts +++ b/tests/createAccount.spec.ts @@ -1,14 +1,21 @@ -import { test, expect } from "playwright-test-coverage-native"; +import { test, expect } from "./fixtures/notLoggedIn.ts"; +import createAccountHelper from "./helpers/createAccount.ts"; +import { faker } from "@faker-js/faker"; -test.describe("Create account page", () => { - test("has fields for username and password", async ({ page }) => { - await page.goto("/login"); - await page.goto('http://localhost:5173/'); - await page.getByRole('button', { name: 'Sign Up' }).click(); - await page.getByLabel('signupform').fill('test-user'); - await page.locator('#chris-email').fill('testuser@example.org'); - await page.locator('#chris-password').fill('testuser1234'); - await expect(page.getByRole('button', { name: 'Create Account' })).toBeInViewport(); - // we don't actually click the button, TODO figure out how to reuse this test account - }); +test("Can create new user accounts", async ({ page }) => { + + const username = faker.internet.userName(); + const email = faker.internet.email(); + const password = `testuser1234`; + await createAccountHelper('/', page, username, email, password); + + // account options menu should appear after being logged in + await page.getByRole('button', { name: username }).click(); + // account options menu should have a "Sign out" button + await page.getByRole('menuitem', { name: 'Sign out' }).click(); + await expect( + page.getByRole('button', { name: 'Login' }), + "login button should reappear after signing out" + ).toBeInViewport(); + // TODO make sure user's private data is no longer there }); diff --git a/tests/createFeed.spec.ts b/tests/createFeed.spec.ts new file mode 100644 index 000000000..2525faba4 --- /dev/null +++ b/tests/createFeed.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from "./fixtures/loggedIn.ts"; +import { faker } from "@faker-js/faker"; +import path from "path"; + +const SOME_FILE = path.join(__dirname, '..', 'package-lock.json'); + +test("Can create a feed", async ({ page }) => { + await page.goto('feeds?type=private'); + + const animal = faker.animal.type(); + const feedName = `A study on ${animal}`; + const feedNote = `The ${animal} is a very noteworthy animal.`; + + await page.getByRole('button', { name: 'Create Feed' }).click(); + await page.getByPlaceholder('e.g. Tractography Study').click(); + await page.getByPlaceholder('e.g. Tractography Study').fill(feedName); + await page.getByPlaceholder('Use this field to describe').click(); + await page.getByPlaceholder('Use this field to describe').fill(feedNote); + await page.getByRole('button', { name: 'Next' }).click(); + await page.getByText('Upload New Data').click(); + + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByText("Drag 'n' drop some files here").click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(SOME_FILE); + + await page.getByRole('button', { name: 'Next' }).click(); + await page.getByRole('button', { name: 'Next', exact: true }).click(); + await page.getByRole('button', { name: 'Create Analysis' }).click(); + + await expect(page.getByText('Feed Created Successfully')).toBeVisible(); + await expect(page.locator('tbody')).toContainText(feedName); +}); diff --git a/tests/fetalmri.org/visualdatasets.spec.ts b/tests/fetalmri.org/visualdatasets.spec.ts deleted file mode 100644 index ba4103e2b..000000000 --- a/tests/fetalmri.org/visualdatasets.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { test } from "playwright-test-coverage-native"; - -test.describe("Fetal MRI Viewer", () => { - test("exists", async ({ page }) => { - await page.goto("/niivue"); - }); -}); diff --git a/tests/fixtures/loggedIn.ts b/tests/fixtures/loggedIn.ts new file mode 100644 index 000000000..4f9be7220 --- /dev/null +++ b/tests/fixtures/loggedIn.ts @@ -0,0 +1,42 @@ +import { test as baseTest } from './notLoggedIn'; +import fs from 'fs'; +import path from 'path'; +import { faker } from '@faker-js/faker'; +import createAccountHelper from "../helpers/createAccount"; + +export * from '@playwright/test'; + +// create new user account for each worker +// https://playwright.dev/docs/auth#moderate-one-account-per-parallel-worker +export const test = baseTest.extend<{}, { workerStorageState: string }>({ + // Use the same storage state for all tests in this worker. + storageState: ({ workerStorageState }, use) => use(workerStorageState), + + + // Authenticate once per worker with a worker-scoped fixture. + workerStorageState: [async ({ browser }, use ) => { + // Use parallelIndex as a unique identifier for each worker. + const id = test.info().parallelIndex; + const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`); + + if (fs.existsSync(fileName)) { + // Reuse existing authentication state if any. + await use(fileName); + return; + } + + // Important: make sure we authenticate in a clean environment by unsetting storage state. + const page = await browser.newPage({ storageState: undefined }); + + // create a new user account + const username = faker.internet.userName(); + const email = faker.internet.email(); + const password = `testuser1234`; + const baseURL = test.info().project.use.baseURL as string; + await createAccountHelper(baseURL, page, username, email, password); + + await page.context().storageState({ path: fileName }); + await page.close(); + await use(fileName); + }, { scope: 'worker' }], +}); diff --git a/tests/fixtures/notLoggedIn.ts b/tests/fixtures/notLoggedIn.ts new file mode 100644 index 000000000..e7cac78fb --- /dev/null +++ b/tests/fixtures/notLoggedIn.ts @@ -0,0 +1,12 @@ +import { test as baseTest, expect } from 'playwright-test-coverage-native'; + +export * from '@playwright/test'; + +export const test = baseTest.extend({ + page: async ({ page }, use) => { + const errors: Error[] = []; + page.on("pageerror", (error) => errors.push(error)); + await use(page); + expect(errors).toHaveLength(0); + } +}); diff --git a/tests/helpers/clipboard.ts b/tests/helpers/clipboard.ts new file mode 100644 index 000000000..f612252ca --- /dev/null +++ b/tests/helpers/clipboard.ts @@ -0,0 +1,20 @@ +/** + * Helper functions for copy-paste. + * + * https://github.com/microsoft/playwright/issues/8114#issuecomment-1584033229 + */ + +import { Page } from "playwright"; +import * as os from "os"; + +export async function ctrlC(page: Page): Promise { + const isMac = os.platform() === "darwin"; + const modifier = isMac ? "Meta" : "Control"; + await page.keyboard.press(`${modifier}+KeyC`); +} + +export async function ctrlV(page: Page): Promise { + const isMac = os.platform() === "darwin"; + const modifier = isMac ? "Meta" : "Control"; + await page.keyboard.press(`${modifier}+KeyV`); +} diff --git a/tests/helpers/createAccount.ts b/tests/helpers/createAccount.ts new file mode 100644 index 000000000..a7959deee --- /dev/null +++ b/tests/helpers/createAccount.ts @@ -0,0 +1,15 @@ +import { Page } from "playwright"; + +async function createAccountHelper(url: string, page: Page, username: string, email: string, password: string) { + await page.goto(url); + await page.getByRole('button', { name: 'Sign Up' }).click(); + await page.locator('#chris-username').fill(username); + await page.locator('#chris-email').fill(email); + await page.locator('#chris-password').fill(password); + await page.getByRole('button', { name: 'Create Account' }).click(); + // wait for ChRIS_ui to redirect us back to the homepage + // after successful account creation + await page.waitForURL(url); +} + +export default createAccountHelper; diff --git a/tests/pluginCatalog.spec.ts b/tests/pluginCatalog.spec.ts index 9cbfbe8ad..72c0fafa4 100644 --- a/tests/pluginCatalog.spec.ts +++ b/tests/pluginCatalog.spec.ts @@ -1,7 +1,23 @@ -import { test, expect } from "playwright-test-coverage-native"; +import { test, expect } from "./fixtures/notLoggedIn.ts"; +import * as clipboard from "./helpers/clipboard.ts"; test.describe("Plugin catalog page", () => { - test("The page renders", async ({ page }) => { + test("Can copy plugin URL of pl-mri10yr06mo01da_normal", async ({ page, context, browserName }) => { + if (browserName === 'chromium') { + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + } + + await page.goto('/catalog'); + await page.getByPlaceholder('Name').fill('mri10yr'); + await page.getByText('pl-mri10yr06mo01da_normal').click(); + await page.getByRole('link', { name: 'Anonymized reference MRI' }).click(); + + await page.locator('.pf-v5-c-clipboard-copy__group .pf-v5-c-button').click({delay: 500}); + await expect(page.getByText('Copied')).toBeVisible(); + await page.goto('/catalog'); - await page.getByPlaceholder('Name').fill('hello world'); }); + await page.getByPlaceholder('Name').focus(); + await clipboard.ctrlV(page); + await expect(page.getByPlaceholder('Name')).toHaveValue(/https:\/\/.+\/api\/v1\/plugins\/\d+\//); + }); }); diff --git a/vite_publicCUBE.config.ts b/vite_publicCUBE.config.ts new file mode 100644 index 000000000..a75e1e707 --- /dev/null +++ b/vite_publicCUBE.config.ts @@ -0,0 +1,10 @@ +import { mergeConfig } from "vite"; +import viteConfig from './vite.config'; + +// use an existing CUBE running on NERC +export default mergeConfig(viteConfig, { + envDir: './testingEnv', + server: { + port: 25173 + } +}); From a5ebc10639e5de8d1e045ea91a993d51c78aecc6 Mon Sep 17 00:00:00 2001 From: Jennings Zhang Date: Tue, 6 Feb 2024 00:10:50 -0500 Subject: [PATCH 7/7] Skip clipboard paste for webkit --- tests/pluginCatalog.spec.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/pluginCatalog.spec.ts b/tests/pluginCatalog.spec.ts index 72c0fafa4..fd2904b9b 100644 --- a/tests/pluginCatalog.spec.ts +++ b/tests/pluginCatalog.spec.ts @@ -15,9 +15,12 @@ test.describe("Plugin catalog page", () => { await page.locator('.pf-v5-c-clipboard-copy__group .pf-v5-c-button').click({delay: 500}); await expect(page.getByText('Copied')).toBeVisible(); - await page.goto('/catalog'); - await page.getByPlaceholder('Name').focus(); - await clipboard.ctrlV(page); - await expect(page.getByPlaceholder('Name')).toHaveValue(/https:\/\/.+\/api\/v1\/plugins\/\d+\//); + // paste from clipboard doesn't seem to work on Safari + if (browserName !== 'webkit') { + await page.goto('/catalog'); + await page.getByPlaceholder('Name').focus(); + await clipboard.ctrlV(page); + await expect(page.getByPlaceholder('Name')).toHaveValue(/https:\/\/.+\/api\/v1\/plugins\/\d+\//); + } }); });