diff --git a/.github/dependabot.yml b/.github/dependabot.yml index de6f757d8ace..bab46d79d261 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,14 +17,6 @@ updates: # We don't use storybook at the moment. - dependency-name: "@storybook/*" - # In https://github.com/mdn/yari/issues/3694 we discovered that - # installing puppeteer 9.1.0 seems to have made the tests flaky - # when jest starts up. See the issue for more detail. - # After this, we can keep an eye on puppeteer to see if the tests - # get better later with new versions. - - dependency-name: "puppeteer" - versions: ["9.1.x"] - - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 89795470a184..f12c2cd77af7 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -71,8 +71,6 @@ jobs: - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: yarn --frozen-lockfile - name: Install Python diff --git a/.github/workflows/developing.yml b/.github/workflows/developing.yml index 7af6a27a04d8..49191cb95d2d 100644 --- a/.github/workflows/developing.yml +++ b/.github/workflows/developing.yml @@ -19,6 +19,7 @@ jobs: uses: actions/setup-node@v2.3.2 with: node-version: "12" + cache: "yarn" - name: Cache node_modules uses: actions/cache@v2.1.6 @@ -26,14 +27,11 @@ jobs: with: path: | node_modules - key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('.github/workflows/developing.yml') }} - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 - run: | - yarn --frozen-lockfile + run: yarn --frozen-lockfile - name: Setup kernel for react native, increase watchers run: | @@ -72,11 +70,9 @@ jobs: # This will make sure the tests in `testing/tests/*.test.js` only run # if the development server is up and ready to be tested. TESTING_DEVELOPING: true - # Use local chrome installs since we skip downloading it as part - # of the yarn installs above - PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome + CONTENT_ROOT: mdn/content/files run: | - yarn test:testing developing + yarn test:developing - name: Debug server's stdout and stderr if tests failed if: failure() diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 8fc5f3d639df..03b1d1b582a5 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -35,8 +35,6 @@ jobs: - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: yarn --frozen-lockfile - name: Build the build diff --git a/.github/workflows/npm-published-simulation.yml b/.github/workflows/npm-published-simulation.yml index af1a5ff6cb2a..760a5057184c 100644 --- a/.github/workflows/npm-published-simulation.yml +++ b/.github/workflows/npm-published-simulation.yml @@ -24,6 +24,7 @@ jobs: uses: actions/setup-node@v2.3.2 with: node-version: "12" + cache: "yarn" - name: Cache node_modules uses: actions/cache@v2.1.6 @@ -31,12 +32,10 @@ jobs: with: path: | node_modules - key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('.github/workflows/npm-published-simulation.yml') }} - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: yarn --frozen-lockfile - name: Setup kernel for react native, increase watchers @@ -87,16 +86,19 @@ jobs: # This will make sure the tests in `testing/tests/*.test.js` only run # if the development server is up and ready to be tested. TESTING_DEVELOPING: true - # Use local chrome installs since we skip downloading it as part - # of the yarn installs above - PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome # When running Yari from within mdn/content it only starts 1 server; # the one on localhost:5000. No React dev server; the one # on localhost:3000. # Testing that dev server is not relevant or important in this context. DEVELOPING_SKIP_DEV_URL: true + CONTENT_ROOT: mdn/content/files + run: | + yarn test:developing + + - name: SSR build a page + working-directory: mdn/content run: | - yarn test:testing developing + yarn build files/en-us/mdn/kitchensink/index.html - name: Debug server's stdout and stderr if tests failed if: failure() @@ -106,8 +108,3 @@ jobs: echo "" echo "STDERR..................................................." cat /tmp/stderr.log - - - name: SSR build a page - working-directory: mdn/content - run: | - yarn build files/en-us/mdn/kitchensink/index.html diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index ff4dec09614b..6d7d272533c6 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -39,8 +39,6 @@ jobs: - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: | yarn --frozen-lockfile diff --git a/.github/workflows/pr-kumascript.yml b/.github/workflows/pr-kumascript.yml index f9bac236ad2d..7ee15b8bacc4 100644 --- a/.github/workflows/pr-kumascript.yml +++ b/.github/workflows/pr-kumascript.yml @@ -34,12 +34,10 @@ jobs: with: path: | node_modules - key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('.github/workflows/pr-kumascript.yml') }} - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: | yarn --frozen-lockfile diff --git a/.github/workflows/prod-build.yml b/.github/workflows/prod-build.yml index b1c6a462cf51..a8ccb49113f4 100644 --- a/.github/workflows/prod-build.yml +++ b/.github/workflows/prod-build.yml @@ -94,8 +94,6 @@ jobs: - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: yarn --frozen-lockfile - name: Install Python diff --git a/.github/workflows/stage-build.yml b/.github/workflows/stage-build.yml index b75abf1933f9..277bbc0271ca 100644 --- a/.github/workflows/stage-build.yml +++ b/.github/workflows/stage-build.yml @@ -94,8 +94,6 @@ jobs: - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: yarn --frozen-lockfile - name: Install Python diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index e0fa5b6cb7e1..325c048d5bce 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -20,6 +20,7 @@ jobs: uses: actions/setup-node@v2.3.2 with: node-version: "12" + cache: "yarn" - name: Cache node_modules uses: actions/cache@v2.1.6 @@ -27,12 +28,10 @@ jobs: with: path: | node_modules - key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('.github/workflows/testing.yml') }} - name: Install all yarn packages if: steps.cached-node_modules.outputs.cache-hit != 'true' - env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 run: | yarn --frozen-lockfile @@ -45,26 +44,27 @@ jobs: - name: Unit testing client run: yarn test:client - - name: Functional testing + - name: Build and start server env: - # Make this env var explicit for GitHub Actions because in local - # dev/debug you're encouraged to start it yourself in a separate - # terminal. - TESTING_START_SERVER: true - # Use local chrome installs since we skip downloading it as part - # of the yarn installs above - PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome + ENV_FILE: testing/.env run: | - # Needed for the puppeteer tests that start a static Express server, - # but it starts it from within the testing/ path. - echo "CONTENT_ROOT=content/files" >> .env + yarn prepare-build + yarn build + + yarn start:static-server > /tmp/stdout.log 2> /tmp/stderr.log & + sleep 1 + curl --retry-connrefused --retry 5 http://localhost:5000 > /dev/null - # In terms of the --maxWorkers option, it's not yet clear which - # is best. - # See https://jestjs.io/docs/en/troubleshooting#tests-are-extremely-slow-on-docker-andor-continuous-integration-ci-server - # and https://www.peterbe.com/plog/ideal-number-of-workers-in-jest-maxWorkers - # CI tends to have fewer CPUs than laptops so let's stay conservative - # for now. - # Also note that `--runInBand` is the same as `--maxWorkers=1` + - name: Functional testing + run: | + yarn test:testing + yarn test:headless - ./testing/scripts/functional-test.sh --runInBand + - name: Debug server's stdout and stderr if tests failed + if: failure() + run: | + echo "STDOUT..................................................." + cat /tmp/stdout.log + echo "" + echo "STDERR..................................................." + cat /tmp/stderr.log diff --git a/client/src/constants.ts b/client/src/constants.ts index ec3e9d259b94..6d41db7891bd 100644 --- a/client/src/constants.ts +++ b/client/src/constants.ts @@ -15,10 +15,6 @@ export const CRUD_MODE_HOSTNAMES = ( .map((x) => x.trim()) .filter(Boolean); -export const AUTOCOMPLETE_SEARCH_WIDGET = JSON.parse( - process.env.REACT_APP_AUTOCOMPLETE_SEARCH_WIDGET || JSON.stringify(CRUD_MODE) -); - // Remember to keep this in sync with the list inside the Node code. // E.g. libs/constants.js // Hardcoding the list in both places is most convenient and most performant. diff --git a/client/src/ui/organisms/header/index.scss b/client/src/ui/organisms/header/index.scss index 7a15ff5bcfc6..8bbd97bd163b 100644 --- a/client/src/ui/organisms/header/index.scss +++ b/client/src/ui/organisms/header/index.scss @@ -73,11 +73,12 @@ } @media #{$mq-large-desktop-and-up} { - grid-template-columns: 2fr 1fr; + grid-template-columns: 2fr 1fr 70px; } .main-nav, - .header-search { + .header-search, + .auth-container { grid-column: 1/2; } @@ -89,6 +90,10 @@ grid-row: 1/2; } + .auth-container { + grid-row: 3/4; + } + @media #{$mq-tablet-and-up} { .main-nav { grid-column: 1/3; @@ -99,11 +104,18 @@ grid-column: 1/2; grid-row: 2/3; } + + .auth-container { + grid-column: 2/3; + grid-row: 2/3; + justify-self: flex-end; + } } @media #{$mq-large-desktop-and-up} { .main-nav, - .header-search { + .header-search, + .auth-container { grid-row: 1/2; } @@ -114,6 +126,10 @@ .header-search { grid-column: 2/3; } + + .auth-container { + grid-column: 3/4; + } } } diff --git a/client/src/ui/organisms/header/index.tsx b/client/src/ui/organisms/header/index.tsx index c43ff26adbef..d739c2981e81 100644 --- a/client/src/ui/organisms/header/index.tsx +++ b/client/src/ui/organisms/header/index.tsx @@ -1,5 +1,6 @@ import { useRef, useState } from "react"; +import Login from "../../molecules/login"; import { Logo } from "../../atoms/logo"; import MainMenu from "../../molecules/main-menu"; import { Search } from "../../molecules/search"; @@ -95,6 +96,9 @@ export function Header() { toggleMainMenu(); }} /> +
+ +
); diff --git a/docs/envvars.md b/docs/envvars.md index 5c58ebd0dbe9..c57f478f4d93 100644 --- a/docs/envvars.md +++ b/docs/envvars.md @@ -205,31 +205,6 @@ This is the port for the WebSocket server, which is started when you run `yarn s If you want to serve static files some a completely different directory. -## Testing - -### `TESTING_OPEN_BROWSER` - -**Default: `false`** - -When running the `jest-puppeteer` test suites, if you set this to `true`, -it will open a browser on every page navigation. - -It might just flash by too quickly, so consider putting in -`await jestPuppeteer.debug()` inside the test function to slow it down. - -### `TESTING_START_SERVER` - -**Default: `false`** - -When `jest-puppeteer` starts the `jest` tests, if this variable is set -to `true` it will execute `node ../server/index.js` to start the `server` -on `localhost:5000`. - -In most cases, on your laptop it's better to start the server yourself -in a separate terminal and then run the headless tests in another. - -For more information, see the `testing/README.md`. - ## Client NOTE! Due to a quirk of how we build the client, anything `REACT_APP_*` environment diff --git a/package.json b/package.json index bc3cd2514a82..90449c9c45d9 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,20 @@ "test": "yarn prettier-check && yarn test:client && yarn test:kumascript && yarn test:content && yarn test:testing", "test:client": "cd client && tsc --noEmit && react-scripts test --env=jest-environment-jsdom-sixteen", "test:content": "jest content", + "test:headless": "playwright test headless", + "test:developing": "playwright test developing", "test:kumascript": "jest kumascript", - "test:testing": "cd testing && jest", + "test:testing": "jest --rootDir testing", "tool": "node tool/cli.js", "watch:ssr": "cd ssr && webpack --mode=production --watch", "prepare": "husky install" }, + "jest": { + "testPathIgnorePatterns": [ + "headless*", + "developing.spec.js" + ] + }, "resolutions": { "@typescript-eslint/typescript-estree": ">=4.15.2", "babel-loader": "8.1.0", @@ -108,6 +116,7 @@ "@babel/core": "^7.15.0", "@mdn/dinocons": "^0.3.4", "@mdn/minimalist": "^2.0.2", + "@playwright/test": "1.13.0", "@storybook/addon-a11y": "^6.3.4", "@storybook/addon-essentials": "^6.3.4", "@storybook/preset-create-react-app": "^3.2.0", @@ -140,14 +149,12 @@ "ignore-loader": "^0.1.2", "jest-environment-jsdom-sixteen": "^2.0.0", "jest-junit-reporter": "^1.1.0", - "jest-puppeteer": "5.0.4", "jsdom": "^16.7.0", "node-dev": "7.0.0", "pegjs": "^0.10.0", "prettier": "2.3.2", "prettier-plugin-packagejson": "^2.2.11", "pretty-quick": "3.1.1", - "puppeteer": "9.0.0", "react": "17.0.2", "react-dom": "17.0.2", "react-is": "^17.0.2", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 000000000000..a12f7d420513 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,10 @@ +const config = { + use: { + channel: "chrome", + // See more interesting options at https://playwright.dev/docs/test-configuration/ + // viewport: { width: 1280, height: 720 }, + // video: "retain-on-failure", + }, +}; + +module.exports = config; diff --git a/server/static.js b/server/static.js index 3960bba12c25..0f31352a6623 100644 --- a/server/static.js +++ b/server/static.js @@ -23,6 +23,10 @@ app.use((req, res, next) => { return next(); }); +app.get("/", async (req, res) => { + res.redirect(302, "/en-US/"); +}); + app.use(staticMiddlewares); app.use(cookieParser()); diff --git a/testing/README.md b/testing/README.md index 690cb8908b1d..d5038af0389e 100644 --- a/testing/README.md +++ b/testing/README.md @@ -1,192 +1,186 @@ -# Functional tests +# Testing -This is a module dedicated to simulating all of Yari but with a -fixed set of content files. +There are many kinds of testing in Yari. Each one serving a different purpose. +We try to test things as far away from the implementation details as possible. +In particular, we always try to favor an end-to-end test instead of a unit test. -Unlike testing the real content, this content "never changes". I.e. we -control it and know exactly what to expect to find. +In this directory (`/testing/`) we have the `jest` tests, +[`playwright`](https://playwright.dev/) +tests, and all the sample "fixture" content we use for testing. +There are actually +some tests that test with real content but mostly we try to control all +the content that we test against. -The general idea is that you run the same build commands as regular -Yari but you point it to a different "content root" and then we run -tests (using `jest`) on what got built. +## Functional tests -This way we can trigger specific `kumascript` macros and other effects -and know exactly what it should have become. +This is primarily to test the outcome of the Yari builder. You run `yarn build` +in a way so it builds based on the content here in `/testing/` and then, +using `jest`, we analyze the generated `index.json`, `index.html`, and +file attachments generated from that build. -## Local development +To run these tests, first run: -When hacking on the functional tests it's important that you can do -this _without_ continuous integration (CI). That way you don't need to -wait for CI to run your every commit in a pull request (PR). - -Also, you can start the full development environment with: - -```bash +```sh +export ENV_FILE=testing/.env +yarn prepare-build +yarn build yarn start:static-server ``` -That will start the `server` (Express -serve on ), and the client (`create-react-app` on -). -To test it go to: - -for example. +This will start a server on which serves the built content. +Now, in a separate terminal, you can run: -Now you should be able to make edits and notice automatic reloading. -But remember to unstage edits, otherwise it might break the automated -test suite. +```sh +yarn test:testing +``` -The first thing to do is to run the _whole_ test suite now: +The last command is just an alias for `jest` so you can run, for example: -```bash -./testing/scripts/functional-test.sh +```sh +# See jest help +yarn test:testing --help +# Interactive re-run every time a file is saved and exit on the first error +yarn test:testing --watch --bail +# Alias for searching by test file name. I.e. only run `index.test.js` +yarn test:testing index ``` -That includes all the important pre-build, build, and starting the `jest` -tests. But once you've run that once, you can "break it apart" and just -run the `jest` test suite and this you can run repeatedly: +## Headless tests -```bash -yarn test:testing +Headless tests are all about using a headless browser to browse the built +HTML files with `playwright`. It's based on the same steps as above, so first: + +```sh +export ENV_FILE=testing/.env +yarn prepare-build +yarn build +yarn start:static-server ``` -This assumes you've set the appropriate environment variables and built the -content. Alternatively, you can run `./testing/scripts/functional-test.sh` -which takes care of all of these things. +Now, to run the actual headless tests you run: -## Conditional testing in CI +```sh +yarn test:headless +``` -In GitHub Actions, instead of trying to optimize certain tests, just skip -them if none of the files that affect the tests have changed. +In CI automation it automatically picks up the browser binary according to the +setting `channel` in the file `/playwright.config.js`. For local development on +your laptop you might need to run: -One such example is the tests for the `kumascript` source code plus macros. -Use this technique heartily to speed up the continuous integration. +```sh +npx playwright install chrome +``` -## Caveats +(assuming `chrome` is what `channel` is set to in `playwright.config.js`) -- At the moment it might not work on Windows. It should. At least the "Local - development" -- We should put all known `kumascript` macros in some form. At least the ones - intend to support. -- There is no guidelines for how to add tests but feel free to pile on - sample pages. You should not be afraid to add more. +### Debugging `playwright` tests -## Writing headless tests +`playwright` has powerful debugging capabilities. Your best guide is +the [Debugging tools](https://playwright.dev/docs/debug) documentation. But +here are some quick tips to get you started. -Headless tests are when we use [`puppeteer`](https://pptr.dev/) to view pages -rendered from the functional tests. If it helps, the non-headless tests -use `fs.readFileSync()` and `cheerio` etc. to inspect the created files in -`client/build/**`. +```sh +# Just run the test by a test description string +yarn test:headless -g 'show your settings page' +# Make it NOT headless by making a browser pop up for each test +yarn test:headless --headed +# Exclusively run the tests in headless.sitesearch.spec.js only +playwright test headless.sitesearch +``` -We use [`jest-puppeteer`](https://github.com/smooth-code/jest-puppeteer) and -its README is very relevant to help you write tests. Here's the link to -the [document for `expect-puppeteer`](https://github.com/smooth-code/jest-puppeteer/blob/master/packages/expect-puppeteer/README.md#api) -which is your best friend when writing headless tests. +When you use `--headed` the browser will almost flash before your eyes +and close down before you get a chance to see what the browser is seeing. +What you can do is inject one line of `await page.pause();` anywhere inside +the test code. Now, next time you run, with `--headed`, a GUI should appear +that pauses and allows you to skip and resume tests. -To get started, open `testing/tests/headless.test.js` and make changes there. -If you need a new page to open, you need to add that to -`testing/content/files/...` first. Then it becomes possible to open it -based on the slug you typed. +## Headless tests of the development environment -Before running the tests, start the function dev server instance: +There are two kinds of headless tests that _don't_ use the `/testing/content/` +and `/testing/translated-content/` fixtures. The first one is testing what +Yari developers would see. To run these you first need to run, in one terminal: ```sh -yarn start:functional +yarn dev ``` -Before you proceed, appreciate that you can now open `http://localhost:5000` -and from there open any page (for example using the search) and what you -see in your browser is what you can expect to see in `jest-puppeteer` in -the tests. - -In a separate terminal, run all the tests: +And in another terminal, run: ```sh -./testing/scripts/functional-test.sh +export TESTING_DEVELOPING=true +yarn test:developing ``` -As you notice, that shell script actually does a lot. It prebuilds the -assets, builds the actual documents, and it runs _all_ `jest` tests. +**Note!** To avoid "cross-contamination" with the other fixture-based headless +tests, when doing this start a fresh new terminal so that previously set +environment variables don't interfere. -To just run all `jest` tests, just run the last command: +The other kind of headless tests is those that test how Yari would work from the +perspective of using the packaged `@mdn/yari` from within the `mdn/content` +repository. To run these you need to go into your `mdn/content` repo and there +first run in one terminal: ```sh -yarn test:testing +cd /where/is/mdn/content +yarn start ``` -Which is just an alias to start `jest` which means you can apply your own -parameters. For example, this starts the `jest` watcher: +Now, to run the tests in another terminal: ```sh -yarn test:testing --watch +cd /back/to/mdn/yari +export TESTING_DEVELOPING=true +export DEVELOPING_SKIP_DEV_URL=true +yarn test:developing ``` -Once the `jest` watcher has started press "p" and type `headless` -and now it only (re-runs) tests the headless tests. +**Note!** It's admittedly many permutations of testing and it's hard to +remember which is doing what. But as a tip, open the various files in +`.github/workflows/*.yml` and look through how they do it. -**Note!** that only in local development do you need to start the functional -server first. In GitHub Actions (CI), `jest-puppeteer` is instructed to start -the server as a setup (and teardown) step. +## Unit tests -## Debugging headless tests +There are currently 2 types of unit tests. The tests are located outside the +`/testing/` directory. -It's very likely that you'll want to see and test -what the headless browser sees. To help with that there are a couple of -useful tricks. - -The first trick is to set the `TESTING_OPEN_BROWSER=true` environment -variable. +First to unit test some React components. This tests the `client/src/**/*.test.tsx` +files: ```sh -TESTING_OPEN_BROWSER=true yarn test:testing --watch -``` - -Now, you'll see a browser window open and shut as the tests run. -It's unlikely that you're fast enough to see what it's in that browser -but what you can do is to "pause" the tests a little by injecting -this line (temporarily) into your test code: - -```javascript -await jestPuppeteer.debug(); -``` - -Note that when you use `await jestPuppeteer.debug()` the real browser window will -close as soon as the test failed with only a tiny timeout. To resolve that, add -a third option to the test with the number of seconds you want it to wait. E.g. - -```javascript -it("should show your settings page", async () => { - const url = testURL("/en-US/settings"); - await page.goto(url); - await jestPuppeteer.debug(); - await expect(page).toMatchElement("h1", { text: "Account settings" }); -}, 9999); +yarn test:client ``` -Another useful trick is to dump the DOM HTML on the console. You can -put this in anywhere: +Secondly, to unit test the `kumascript` tests. These tests are located in +`kumascript/tests/*.test.js`: -```javascript -console.log(await page.content()); +```sh +yarn test:kumascript ``` -## Headless tests should only test static server +In both of these cases, it's `jest` so you can do things like adding +`--watch --bail` for example to interactively test over and over. -To run the functional tests you need a server (on `localhost:5000`) and -it should just be a static file server. You _can_ use `yarn start:functional` -but that server has many tricks such as building on-the-fly. +### Unit test deployer Python tests -A better server to use is: +See the file `deployer/README.md` for instructions. -```sh -yarn start:static-server -``` +## Local development for debugging tests -Now you can run just the functional `jest` tests over and over: +Going back to testing the content in `/testing/content/files/` and +`/testing/translated-content/files/` you might find it fiddly to see what +you're testing. The `--headed` flag to `yarn test:headless` is good but it's +a bit hard to see what you're getting to get around that you can do the +following: ```sh -export TESTING_START_SERVER=false # should be false by default anyway -./testing/scripts/functional-test.sh +echo 'CONTENT_ROOT=testing/content/files' >> .env +echo 'CONTENT_TRANSLATED_ROOT=testing/translated-content/files' >> .env +yarn dev ``` -If in doubt, look at the file `.github/workflows/testing.yml` and what it does. +Now you can browse both and +to see what the content fixtures are. +For example, you can go to . +Again, remember to start with a fresh new terminal so that no other testing +related environment variables. And remember to undo these changes from +your personal `.env` when you're done. diff --git a/testing/jest-puppeteer.config.js b/testing/jest-puppeteer.config.js deleted file mode 100644 index dab63f05c6f5..000000000000 --- a/testing/jest-puppeteer.config.js +++ /dev/null @@ -1,20 +0,0 @@ -const serverExports = {}; -if (JSON.parse(process.env.TESTING_START_SERVER || "false")) { - serverExports.server = { - // This is the .env file here inside the 'testing/' directory. - // This is needed so that the server that gets started get the right - // environment variables specifically for the functional test suite. - command: "ENV_FILE=.env node ../server/static.js", - port: 5000, - host: "localhost", - debug: true, // XXX Not sure that the harm is of having this on - }; -} - -module.exports = { - ...serverExports, - launch: { - headless: !JSON.parse(process.env.TESTING_OPEN_BROWSER || "false"), - }, - setupFilesAfterEnv: ["expect-puppeteer"], -}; diff --git a/testing/jest.config.js b/testing/jest.config.js deleted file mode 100644 index d2c79a91a0d5..000000000000 --- a/testing/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const expectPuppeteer = require("expect-puppeteer"); - -expectPuppeteer.setDefaultOptions({ timeout: 3000 }); - -module.exports = { - preset: "jest-puppeteer", -}; diff --git a/testing/scripts/functional-test.sh b/testing/scripts/functional-test.sh index a2fe15f632cf..9e83eae8ebc0 100755 --- a/testing/scripts/functional-test.sh +++ b/testing/scripts/functional-test.sh @@ -3,9 +3,6 @@ set -e export ENV_FILE=testing/.env -# Temporary whilst only the functional tests use the autocomplete search widget. -export REACT_APP_AUTOCOMPLETE_SEARCH_WIDGET=true - yarn prepare-build yarn build diff --git a/testing/tests/destructive.test.js b/testing/tests/destructive.test.js index 8e2bdcb2f654..d53f3328868f 100644 --- a/testing/tests/destructive.test.js +++ b/testing/tests/destructive.test.js @@ -15,8 +15,8 @@ const { execSync } = require("child_process"); const tempy = require("tempy"); -const CONTENT_DIR = path.resolve(path.join("content")); -const BUILD_DIR = path.resolve(path.join("..", "client", "build")); +const CONTENT_DIR = path.resolve(path.join("testing", "content")); +const BUILD_DIR = path.resolve(path.join("client", "build")); function* walker(root) { const files = fs.readdirSync(root); @@ -34,7 +34,7 @@ function* walker(root) { describe("fixing flaws", () => { const pattern = path.join("web", "fixable_flaws"); - const baseDir = path.resolve(".."); + const baseDir = path.resolve("."); let tempdir; let tempBuildDir; diff --git a/testing/tests/developing.test.js b/testing/tests/developing.spec.js similarity index 58% rename from testing/tests/developing.test.js rename to testing/tests/developing.spec.js index cc8a8058c92a..183e86a5a543 100644 --- a/testing/tests/developing.test.js +++ b/testing/tests/developing.spec.js @@ -1,10 +1,5 @@ +const { test, expect } = require("@playwright/test"); const got = require("got"); -const { setDefaultOptions } = require("expect-puppeteer"); - -// The default it 500ms. Building and running these pages can be pretty slow -// since the rendering both involves create-react-app bundling the test page, -// and then the server building of the page can be pretty heavy. -setDefaultOptions({ timeout: 5000 }); const DEV_BASE_URL = process.env.DEVELOPING_DEV_BASE_URL || "http://localhost:3000"; @@ -15,54 +10,61 @@ function devURL(pathname = "/") { const SERVER_BASE_URL = process.env.DEVELOPING_SERVER_BASE_URL || "http://localhost:5000"; + function serverURL(pathname = "/") { return `${SERVER_BASE_URL}${pathname}`; } -const SKIP_DEV_URL = JSON.parse(process.env.DEVELOPING_SKIP_DEV_URL || "false"); - -// This "trick" is to force every test to be skipped if the environment -// variable hasn't been set. This way, when you run `jest ...`, and it finds -// all `**/*.test.js` it doesn't actually run these tests unless explicitly -// prepared to do so. -// The source of this idea comes from https://github.com/facebook/jest/issues/7245 -const isTesting = JSON.parse(process.env.TESTING_DEVELOPING || "false"); -const withDeveloping = isTesting ? it : it.skip; -// If the test suite runs in a way that there's no separate dev server, -// don't bother using the `DEV_BASE_URL`. -// For example, when it tests the `npm pack` tarball, it's starting only -// the one server (on `localhost:5000`) that suite will set the `DEV_BASE_URL` -// to be the same as `SAME_BASE_URL`. -// In conclusion, if there's only 1 base URL to test again; don't test both. -const withCrud = isTesting && !SKIP_DEV_URL ? it : it.skip; - -describe("Testing the kitchensink page", () => { - withCrud("open the page", async () => { +function withCrud() { + return ( + withDevelop() || JSON.parse(process.env.DEVELOPING_SKIP_DEV_URL || "false") + ); +} + +function withDevelop() { + return !JSON.parse(process.env.TESTING_DEVELOPING || "false"); +} + +test.describe("Testing the kitchensink page", () => { + test("open the page", async ({ page }) => { + test.skip(withCrud()); + await page.goto(devURL("/en-US/docs/MDN/Kitchensink")); - await expect(page).toMatch("The MDN Content Kitchensink"); - await expect(page).toMatch("No known flaws at the moment"); + await page.waitForLoadState("networkidle"); + expect(await page.title()).toContain("The MDN Content Kitchensink"); + expect( + await page.isVisible("text=The MDN Content Kitchensink") + ).toBeTruthy(); + expect( + await page.isVisible("text=No known flaws at the moment") + ).toBeTruthy(); }); - withCrud("open a file attachement directly in the dev URL", async () => { + test("open a file attachement directly in the dev URL", async ({ page }) => { + test.skip(withCrud()); + await page.goto(devURL("/en-US/docs/MDN/Kitchensink/iceberg.jpg")); // This is how Chromium makes a document title when viewing an image. expect(await page.title()).toBe("iceberg.jpg (1400×1050)"); - // Unfortunately, there's no API in puppeteer to test that the image URL - // you opened of correct type or file size or even HTTP status code. + // TODO: It would be nice to know what you opened is of correct type + // or file size. expect(page.url()).toBe(devURL("/en-US/docs/MDN/Kitchensink/iceberg.jpg")); }); - withDeveloping("server-side render HTML", async () => { + test("server-side render HTML", async ({ page }) => { + test.skip(withDevelop()); + // You can go to the page directly via the server - await page.goto(serverURL("/en-US/docs/MDN/Kitchensink"), { - // This is necessary because the page contains lazy loading iframes - // to external domains. - waitUntil: "networkidle0", - }); - await expect(page).toMatch("The MDN Content Kitchensink"); + await page.goto(serverURL("/en-US/docs/MDN/Kitchensink")); + await page.waitForLoadState("networkidle"); + expect( + await page.isVisible("text=The MDN Content Kitchensink") + ).toBeTruthy(); }); - withDeveloping("server-side render HTML", async () => { + test("server-side render JSON", async () => { + test.skip(withDevelop()); + // Loading the index.json doesn't require a headless browser const { doc } = await got( serverURL("/en-US/docs/MDN/Kitchensink/index.json") @@ -77,8 +79,10 @@ describe("Testing the kitchensink page", () => { // Note, some of these tests cover some of the core code that we use in // the Lambda@Edge origin-request handler. -describe("Testing the Express server", () => { - withDeveloping("redirect without any useful headers", async () => { +test.describe("Testing the Express server", () => { + test("redirect without any useful headers", async () => { + test.skip(withDevelop()); + let response = await got(serverURL("/"), { followRedirect: false }); expect(response.statusCode).toBe(302); expect(response.headers.location).toBe("/en-US/"); @@ -97,7 +101,9 @@ describe("Testing the Express server", () => { expect(response.headers.location).toBe("/en-US/docs/Web"); }); - withDeveloping("redirect based on _redirects.txt", async () => { + test("redirect based on _redirects.txt", async () => { + test.skip(withDevelop()); + // Yes, this is a bit delicate since it depends on non-fixtures, but // it's realistic and it's a good end-to-end test. // See mdn/content/files/en-us/_redirects.txt @@ -127,7 +133,9 @@ describe("Testing the Express server", () => { ); }); - withDeveloping("redirect by preferred locale cookie", async () => { + test("redirect by preferred locale cookie", async () => { + test.skip(withDevelop()); + let response = await got(serverURL("/"), { followRedirect: false, headers: { @@ -149,7 +157,9 @@ describe("Testing the Express server", () => { expect(response.headers.location).toBe("/en-US/"); }); - withDeveloping("redirect by 'Accept-Language' header", async () => { + test("redirect by 'Accept-Language' header", async () => { + test.skip(withDevelop()); + let response = await got(serverURL("/"), { followRedirect: false, headers: { @@ -171,7 +181,9 @@ describe("Testing the Express server", () => { expect(response.headers.location).toBe("/en-US/"); }); - withDeveloping("redirect by cookie trumps", async () => { + test("redirect by cookie trumps", async () => { + test.skip(withDevelop()); + const response = await got(serverURL("/"), { followRedirect: false, headers: { @@ -184,26 +196,37 @@ describe("Testing the Express server", () => { }); }); -describe("Testing the CRUD apps", () => { - withCrud("open the writer's home page", async () => { +test.describe("Testing the CRUD apps", () => { + test("open the writer's home page", async ({ page }) => { + test.skip(withCrud()); + await page.goto(devURL("/")); - await expect(page).toMatch("Writer's home page"); - await expect(page).toMatchElement("a", { text: "Flaws Dashboard" }); + expect(await page.title()).toContain("MDN Web Docs"); + expect(await page.isVisible("text=Writer's home page")).toBeTruthy(); + expect(await page.isVisible('a:has-text("Flaws Dashboard")')).toBeTruthy(); }); - withCrud("open the Flaws Dashboard", async () => { + test("open the Flaws Dashboard", async ({ page }) => { + test.skip(withCrud()); + await page.goto(devURL("/")); - await expect(page).toClick("a", { text: "Flaws Dashboard" }); - await expect(page).toMatch("Documents with flaws found (0)"); + await page.click('a:has-text("Flaws Dashboard")'); + await page.waitForLoadState("networkidle"); + expect( + await page.isVisible("text=Documents with flaws found (0)") + ).toBeTruthy(); }); - withCrud("open the sitemap app", async () => { + test("open the sitemap app", async ({ page }) => { + test.skip(withCrud()); + await page.goto(devURL("/")); - await expect(page).toMatch("Writer's home page"); - await expect(page).toClick("a", { text: "Sitemap" }); - await expect(page).toMatchElement("a", { text: "Web" }); - await expect(page).toMatchElement("a", { text: "Learn" }); - await expect(page).toClick("a", { text: "Glossary" }); - await expect(page).toMatchElement("a", { text: "Glossary/PNG" }); + expect(await page.isVisible("text=Writer's home page")).toBeTruthy(); + await page.click('a:has-text("Sitemap")'); + await page.waitForLoadState("networkidle"); + expect(await page.isVisible('a:has-text("/Web")')).toBeTruthy(); + expect(await page.isVisible('a:has-text("/Learn")')).toBeTruthy(); + await page.click('a:has-text("/Glossary")'); + expect(await page.isVisible('a:has-text("Glossary/PNG")')).toBeTruthy(); }); }); diff --git a/testing/tests/headless.auth.spec.js b/testing/tests/headless.auth.spec.js new file mode 100644 index 000000000000..afbd29ad0ac2 --- /dev/null +++ b/testing/tests/headless.auth.spec.js @@ -0,0 +1,144 @@ +const { test, expect } = require("@playwright/test"); + +function testURL(pathname = "/") { + return `http://localhost:5000${pathname}`; +} + +test.describe("Visiting pages related and requiring authentication", () => { + test.beforeEach(async ({ context }) => { + // Necessary hack to make sure any existing 'sessionid' cookies don't + // interfere on the re-used `page` across tests. + await context.clearCookies(); + }); + + test("clicking 'Sign in' should offer links to all identity providers", async ({ + page, + }) => { + await page.goto(testURL("/en-US/docs/Web/Foo")); + await page.click("text=Sign in"); + expect(await page.innerText("h1")).toContain("Sign in"); + expect(page.url()).toContain( + testURL( + `/en-US/signin?${new URLSearchParams({ + next: "/en-US/docs/Web/Foo", + }).toString()}` + ) + ); + expect(await page.isVisible('a:has-text("GitHub")')).toBeTruthy(); + expect(await page.isVisible('a:has-text("Google")')).toBeTruthy(); + }); + + test("going to 'Sign up' page without query string", async ({ page }) => { + await page.goto(testURL("/en-US/signup")); + + expect(await page.innerText("h1")).toContain("Sign in to MDN Web Docs"); + expect(await page.isVisible("text=Invalid URL")).toBeTruthy(); + expect( + await page.isVisible("text=Please retry the sign-in process") + ).toBeTruthy(); + }); + + test("going to 'Sign up' page with realistic (fake) query string", async ({ + page, + }) => { + const sp = new URLSearchParams(); + sp.set("csrfmiddlewaretoken", "abc"); + sp.set("provider", "github"); + sp.set( + "user_details", + JSON.stringify({ + name: "Peter B", + }) + ); + await page.goto(testURL(`/en-US/signup?${sp.toString()}`)); + expect(await page.innerText("h1")).toContain("Sign in to MDN Web Docs"); + expect(await page.isVisible("text=Invalid URL")).toBeFalsy(); + expect( + await page.isVisible( + "text=You are signing in to MDN Web Docs with GitHub as Peter B." + ) + ).toBeTruthy(); + expect( + await page.isVisible( + "text=I agree to Mozilla's Terms and Privacy Notice." + ) + ).toBeTruthy(); + expect( + await page.isVisible('button:has-text("Complete sign-in")') + ).toBeTruthy(); + expect( + await page.isVisible('button[disabled]:has-text("Complete sign-in")') + ).toBeTruthy(); + await page.check('input[name="terms"]'); + expect( + await page.isVisible('button[disabled]:has-text("Complete sign-in")') + ).toBeFalsy(); + expect( + await page.isVisible('button:has-text("Complete sign-in")') + ).toBeTruthy(); + await page.click('button:has-text("Complete sign-in")'); + }); + + test("show your settings page", async ({ page }) => { + await page.goto(testURL("/en-US/settings")); + expect(await page.innerText("h1")).toBe("Account settings"); + expect(await page.isVisible("text=You have not signed in")).toBeTruthy(); + expect(await page.isVisible("text=Sign in")).toBeTruthy(); + + // First sign in with GitHub (happy path) + await page.goto(testURL("/en-US/signin")); + expect(await page.isVisible('a:has-text("GitHub")')).toBeTruthy(); + await page.click('a:has-text("GitHub")'); + expect(page.url()).toMatch(testURL("/en-US/")); + // This is important otherwise it won't wait for the XHR where the + // cookie gets set! + await page.waitForLoadState("networkidle"); + await page.goto(testURL("/en-US/settings")); + expect(await page.innerText("h1")).toBe("Account settings"); + expect( + await page.isVisible('button:has-text("Close account")') + ).toBeTruthy(); + // Change locale to French + await page.selectOption('select[name="locale"]', { + label: "French", + }); + await page.click('button:has-text("Update language")'); + await page.waitForLoadState("networkidle"); + expect( + await page.isVisible("text=Updated settings successfully") + ).toBeTruthy(); + }); + // This test has turned out to be fragile. It's failing sporadically and + // we're not sure why or how. We've seen several times where the PR (on + // something entirely unrelated) passes but when tested on the main branch + // it then fails. + // Example: + // https://github.com/mdn/yari/actions/runs/1005504856 + // Clearly, there's something fragile about it. + // But as of July 6 2021, there's an offline discussion that we might + // revamp how auth works, so instead of trying to unbreak this fragile + // test, let's comment it out. At least it'll unbreak our sporadically + // failing CI but we can keep it around in case we really do need it + // and find the time to work on fixing what's fragile about it. + // test("should ask you to checkbox to sign up with Google", async ({page}) => { + // const url = testURL("/en-US/"); + // await page.goto(url); + // // Wait for it to figure out that you're not signed in. + // await expect(page).toClick("a", { text: /Sign in/ }); + // await page.waitForNavigation({ waitUntil: "networkidle2" }); + // await expect(page.url()).toMatch(testURL("/en-US/signin")); + // await expect(page).toMatch("Sign in with Google"); + // await expect(page).toClick("a", { + // text: /Sign in with Google/, + // }); + // await expect(page.url()).toMatch(testURL("/en-US/signin")); + // await page.waitForNavigation({ waitUntil: "networkidle2" }); + // const checkbox = await page.$('input[type="checkbox"]'); + // await checkbox.click(); + // await expect(page).toClick("button", { + // text: /Complete sign-in/, + // }); + // await expect(page.url()).toMatch(testURL("/en-US/")); + // await expect(page).toMatch("Googler-username"); + // }); +}); diff --git a/testing/tests/headless.auth.test.js b/testing/tests/headless.auth.test.js deleted file mode 100644 index cfb02f5dc89d..000000000000 --- a/testing/tests/headless.auth.test.js +++ /dev/null @@ -1,90 +0,0 @@ -const { setDefaultOptions } = require("expect-puppeteer"); - -// The default it 500ms. It has happened and it can happen again, that sometimes -// it just takes a little longer than 500ms. Give it a healthy margin of a -// timeout so as to reduce the risk of it failing when there's nothing wrong. -setDefaultOptions({ timeout: 1500 }); - -function testURL(pathname = "/") { - return `http://localhost:5000${pathname}`; -} - -describe("Visiting pages related and requiring authentication", () => { - beforeEach(async () => { - // Necessary hack to make sure any existing 'sessionid' cookies don't - // interfere on the re-used `page` across tests. - await page.deleteCookie({ name: "sessionid", url: testURL() }); - }); - - it("going to 'Sign up' page without query string", async () => { - await page.goto(testURL("/en-US/signup")); - await expect(page).toMatchElement("h1", { - text: "Sign in to MDN Web Docs", - }); - await expect(page).toMatch("Invalid URL"); - await expect(page).toMatchElement("a", { - text: "Please retry the sign-in process", - }); - }); - - it("going to 'Sign up' page with realistic (fake) query string", async () => { - const sp = new URLSearchParams(); - sp.set("csrfmiddlewaretoken", "abc"); - sp.set("provider", "github"); - sp.set( - "user_details", - JSON.stringify({ - name: "Peter B", - }) - ); - - await page.goto(testURL(`/en-US/signup?${sp.toString()}`)); - await expect(page).toMatchElement("h1", { - text: "Sign in to MDN Web Docs", - }); - await expect(page).not.toMatch("Invalid URL"); - await expect(page).toMatch( - "You are signing in to MDN Web Docs with GitHub as Peter B." - ); - await expect(page).toMatch( - "I agree to Mozilla's Terms and Privacy Notice." - ); - await expect(page).toMatchElement("button", { text: "Complete sign-in" }); - }); - - // This test has turned out to be fragile. It's failing sporadically and - // we're not sure why or how. We've seen several times where the PR (on - // something entirely unrelated) passes but when tested on the main branch - // it then fails. - // Example: - // https://github.com/mdn/yari/actions/runs/1005504856 - // Clearly, there's something fragile about it. - // But as of July 6 2021, there's an offline discussion that we might - // revamp how auth works, so instead of trying to unbreak this fragile - // test, let's comment it out. At least it'll unbreak our sporadically - // failing CI but we can keep it around in case we really do need it - // and find the time to work on fixing what's fragile about it. - // it("should ask you to checkbox to sign up with Google", async () => { - // const url = testURL("/en-US/"); - // await page.goto(url); - // // Wait for it to figure out that you're not signed in. - // await expect(page).toClick("a", { text: /Sign in/ }); - // await page.waitForNavigation({ waitUntil: "networkidle2" }); - // await expect(page.url()).toMatch(testURL("/en-US/signin")); - - // await expect(page).toMatch("Sign in with Google"); - // await expect(page).toClick("a", { - // text: /Sign in with Google/, - // }); - // await expect(page.url()).toMatch(testURL("/en-US/signin")); - // await page.waitForNavigation({ waitUntil: "networkidle2" }); - // const checkbox = await page.$('input[type="checkbox"]'); - // await checkbox.click(); - - // await expect(page).toClick("button", { - // text: /Complete sign-in/, - // }); - // await expect(page.url()).toMatch(testURL("/en-US/")); - // await expect(page).toMatch("Googler-username"); - // }); -}); diff --git a/testing/tests/headless.index.spec.js b/testing/tests/headless.index.spec.js new file mode 100644 index 000000000000..4ca7791a2639 --- /dev/null +++ b/testing/tests/headless.index.spec.js @@ -0,0 +1,293 @@ +const { test, expect } = require("@playwright/test"); + +function testURL(pathname = "/") { + return `http://localhost:5000${pathname}`; +} + +test.describe("Basic viewing of functional pages", () => { + test("open the temporary home page", async ({ page }) => { + await page.goto(testURL("/")); + expect(await page.title()).toContain("MDN Web Docs"); + expect(await page.innerText("h1")).toBe( + "Resources for developers, by developers." + ); + }); + + test("open the /en-US/docs/Web/Foo page", async ({ page }) => { + await page.goto(testURL("/en-US/docs/Web/Foo")); + expect(await page.title()).toContain(": A test tag"); + expect(await page.innerText("h1")).toBe(": A test tag"); + expect(await page.isVisible(".metadata time")).toBeTruthy(); + }); + + test("open the French /fr/docs/Web/Foo page and navigate to English", async ({ + page, + }) => { + await page.goto(testURL("/fr/docs/Web/Foo")); + expect(await page.innerText("h1")).toBe(": Une page de test"); + await page.click("text=View in English"); + expect(await page.innerText("h1")).toBe(": A test tag"); + // Should have been redirected too... + expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo/")); + }); + + test("open the /en-US/docs/Web/InteractiveExample page", async ({ page }) => { + await page.goto(testURL("/en-US/docs/Web/InteractiveExample")); + expect( + await page.isVisible("text=I Have an Interactive Example") + ).toBeTruthy(); + }); + + test("open the /en-US/docs/Learn/CSS/CSS_layout/Introduction page", async ({ + page, + }) => { + const uri = "/en-US/docs/Learn/CSS/CSS_layout/Introduction"; + const flexSample1Uri = `${uri}/Flex/_sample_.Flex_1.html`; + const flexSample2Uri = `${uri}/Flex/_sample_.Flex_2.html`; + const gridSample1Uri = `${uri}/Grid/_sample_.Grid_1.html`; + const gridSample2Uri = `${uri}/_sample_.Grid_2.html`; + await page.goto(testURL(uri)); + expect(await page.title()).toContain("A Test Introduction to CSS layout"); + expect(await page.innerText("h1")).toBe( + "A Test Introduction to CSS layout" + ); + expect(await page.innerText("#flexbox")).toBe("Flexbox"); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${flexSample1Uri}"]`) + ).toBeTruthy(); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${flexSample2Uri}"]`) + ).toBeTruthy(); + expect(await page.innerText("#grid_layout")).toBe("Grid Layout"); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${gridSample1Uri}"]`) + ).toBeTruthy(); + expect(await page.innerText("#Grid_2 pre.css.notranslate")).toMatch( + /\.wrapper\s*\{\s*display:\s*grid;/ + ); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${gridSample2Uri}"]`) + ).toBeTruthy(); + + // Ensure that the live-sample pages were built. + for (const sampleUri of [ + flexSample1Uri, + flexSample2Uri, + gridSample1Uri, + gridSample2Uri, + ]) { + await page.goto(testURL(sampleUri)); + expect(await page.innerText("body > div.wrapper > div.box1")).toBe("One"); + expect(await page.innerText("body > div.wrapper > div.box2")).toBe("Two"); + expect(await page.innerText("body > div.wrapper > div.box3")).toBe( + "Three" + ); + } + }); + + test("open the /en-US/docs/Learn/CSS/CSS_layout/Introduction/Flex page", async ({ + page, + }) => { + const uri = "/en-US/docs/Learn/CSS/CSS_layout/Introduction/Flex"; + const flexSample1Uri = `${uri}/_sample_.Flex_1.html`; + const flexSample2Uri = `${uri}/_sample_.Flex_2.html`; + await page.goto(testURL(uri)); + expect(await page.title()).toContain( + "A Test Introduction to CSS Flexbox Layout" + ); + expect(await page.innerText("h1")).toBe( + "A Test Introduction to CSS Flexbox Layout" + ); + expect(await page.innerText("#flexbox")).toBe("Flexbox"); + + expect(await page.innerText("#Flex_1 pre.css.notranslate")).toMatch( + /\.wrapper\s*\{\s*display:\s*flex;\s*\}/ + ); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${flexSample1Uri}"]`) + ).toBeTruthy(); + + expect(await page.innerText("#Flex_2 pre.css.notranslate")).toMatch( + /\.wrapper {\s*display: flex;\s*\}\s*\.wrapper > div \{\s*flex: 1;\s*\}/ + ); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${flexSample2Uri}"]`) + ).toBeTruthy(); + }); + + test("open the /en-US/docs/Learn/CSS/CSS_layout/Introduction/Grid page", async ({ + page, + }) => { + const uri = "/en-US/docs/Learn/CSS/CSS_layout/Introduction/Grid"; + const gridSample1Uri = `${uri}/_sample_.Grid_1.html`; + const gridSample2Uri = `${uri}/_sample_.Grid_2.html`; + await page.goto(testURL(uri)); + expect(await page.title()).toContain( + "A Test Introduction to CSS Grid Layout" + ); + expect(await page.innerText("h1")).toBe( + "A Test Introduction to CSS Grid Layout" + ); + expect(await page.innerText("#grid_layout")).toBe("Grid Layout"); + expect(await page.innerText("#Grid_1 pre.css.notranslate")).toMatch( + /\.wrapper\s*\{\s*display:\s*grid;/ + ); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${gridSample1Uri}"]`) + ).toBeTruthy(); + + expect(await page.innerText("#Grid_2 pre.css.notranslate")).toMatch( + /grid-template-columns: 1fr 1fr 1fr;/ + ); + expect( + await page.isVisible(`iframe.sample-code-frame[src$="${gridSample2Uri}"]`) + ).toBeTruthy(); + + // Ensure that the live-sample page "gridSample2Uri" was built. + await page.goto(testURL(gridSample2Uri)); + expect(await page.innerText("body > div.wrapper > div.box1")).toBe("One"); + expect(await page.innerText("body > div.wrapper > div.box2")).toBe("Two"); + expect(await page.innerText("body > div.wrapper > div.box3")).toBe("Three"); + }); + + test("return to previous page on back-button press", async ({ page }) => { + await page.goto(testURL("/en-US/docs/Web/Foo")); + expect(await page.title()).toContain(": A test tag"); + expect(await page.innerText("h1")).toBe(": A test tag"); + // Click the parent page in the breadcrumbs + await page.click(".breadcrumbs-container a"); + expect(await page.innerText("h1")).toBe("Web technology for developers"); + expect(page.url()).toBe(testURL("/en-US/docs/Web")); + await page.goBack(); + expect(await page.innerText("h1")).toBe(": A test tag"); + }); + + test("have a semantically valid breadcrumb trail", async ({ page }) => { + await page.goto(testURL("/en-US/docs/Web/Foo")); + // Let's not get too technical about the name of the selectors and + // stuff but do note that the page you're on is always a valid link + expect( + await page.innerText( + "nav a.breadcrumb-penultimate[property=item][typeof=WebPage]" + ) + ).toBe( + // You gotta know your fixture documents + "Web technology for developers" + ); + expect( + await page.innerText( + "nav a.breadcrumb-current-page[property=item][typeof=WebPage]" + ) + ).toBe( + // Always includes a link to "self" + ": A test tag" + ); + }); + + test("say which page was not found", async ({ page }) => { + await page.goto(testURL("/en-US/docs/Doesnot/exist")); + expect(await page.isVisible("text=Page not found")).toBeTruthy(); + expect(await page.isVisible("text=could not be found")).toBeTruthy(); + }); + + test("suggest the en-US equivalent on non-en-US pages not found", async ({ + page, + }) => { + await page.goto(testURL("/ja/docs/Web/foo")); + expect(await page.isVisible("text=Page not found")).toBeTruthy(); + expect(await page.isVisible("text=could not be found")).toBeTruthy(); + // Wait for XHR loading of the whole document + await page.waitForLoadState("networkidle"); + // Simply by swapping the "ja" for "en-US" it's able to find the index.json + // for that slug and present a link to it. + expect(await page.isVisible("text=Good news!")).toBeTruthy(); + expect(await page.getAttribute(".fallback-link a", "href")).toBe( + "/en-US/docs/Web/Foo" + ); + }); + + test("give the home page and see Hacks blog posts", async ({ page }) => { + await page.goto(testURL("/en-US/")); + expect( + await page.isVisible("text=Resources for developers, by developers.") + ).toBeTruthy(); + expect(await page.isVisible("text=Hacks Blog")).toBeTruthy(); + }); +}); + +test.describe("changing language", () => { + test("from French to English, set a cookie, and back again", async ({ + page, + }) => { + await page.goto(testURL("/fr/docs/Web/Foo")); + expect(await page.isVisible("text=: Une page de test")).toBeTruthy(); + await page.selectOption('select[name="language"]', { + label: "English (US)", + }); + + await page.click('button:has-text("Change language")'); + // Wait for XHR loading of the whole document + await page.waitForLoadState("networkidle"); + expect(await page.isVisible("text=: A test tag")).toBeTruthy(); + expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo/")); + + // And change back to French + await page.selectOption('select[name="language"]', { + label: "Français", + }); + await page.click('button:has-text("Change language")'); + // Wait for XHR loading of the whole document + await page.waitForLoadState("networkidle"); + expect(await page.isVisible("text=: Une page de test")).toBeTruthy(); + expect(page.url()).toBe(testURL("/fr/docs/Web/Foo/")); + }); +}); + +test.describe("viewing retired locales", () => { + test("redirect retired locale to English (document)", async ({ page }) => { + await page.goto(testURL("/ar/docs/Web/Foo")); + expect(page.url()).toMatch( + testURL("/en-US/docs/Web/Foo/?retiredLocale=ar") + ); + expect(await page.innerText("h1")).toBe(": A test tag"); + }); + + test("redirect retired locale to English (index.json)", async ({ page }) => { + await page.goto(testURL("/ar/docs/Web/Foo/index.json")); + expect(page.url()).toMatch( + testURL("/en-US/docs/Web/Foo/index.json?retiredLocale=ar") + ); + expect(await page.isVisible("text=: A test tag")).toBeTruthy(); + }); + + test("redirect retired locale to English (search with query string)", async ({ + page, + }) => { + await page.goto(testURL("/ar/search?q=video")); + expect(page.url()).toMatch( + testURL("/en-US/search/?q=video&retiredLocale=ar") + ); + expect(await page.isVisible("text=Search results for: video")).toBeTruthy(); + }); + + test("say the locale was retired", async ({ page }) => { + await page.goto(testURL("/en-US/docs/Web/Foo/?retiredLocale=ar")); + expect( + await page.isVisible("text=The page you requested has been retired") + ).toBeTruthy(); + // sanity check that it goes away + await page.goto(testURL("/en-US/docs/Web/Foo/")); + expect( + await page.isVisible("text=The page you requested has been retired") + ).toBeFalsy(); + }); + + test("not say the locale was retired if viewing a translated page", async ({ + page, + }) => { + await page.goto(testURL("/fr/docs/Web/Foo/?retiredLocale=sv-SE")); + expect( + await page.isVisible("text=The page you requested has been retired") + ).toBeFalsy(); + }); +}); diff --git a/testing/tests/headless.search.spec.js b/testing/tests/headless.search.spec.js new file mode 100644 index 000000000000..5aead32f8bc9 --- /dev/null +++ b/testing/tests/headless.search.spec.js @@ -0,0 +1,99 @@ +const { test, expect } = require("@playwright/test"); + +function testURL(pathname = "/") { + return "http://localhost:5000" + pathname; +} + +test.describe("Autocomplete search", () => { + const SEARCH_SELECTOR = 'form input[type="search"]'; + + test("find Foo page by title search", async ({ page }) => { + // Yes, this is a cheeky implementation testing detail but it's a nice + // sanity check that the server can respond with *something* that's + // sensible. This "test" also helps make sense of other potentially + // very confusing errors within the important tests themselves. + await page.goto(testURL("/en-US/search-index.json")); + // It's JSON but this asserts that page will be findable + expect(await page.isVisible("text=: A test tag")).toBeTruthy(); + + await page.goto(testURL("/")); + + // This will activate the fancy autocomplete search and it should start + // a download of the `/en-US/search-index.json` too. + await page.focus(SEARCH_SELECTOR); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.waitForLoadState("networkidle"); + + await page.fill(SEARCH_SELECTOR, "foo"); + expect(await page.isVisible("text=: A test tag")).toBeTruthy(); + // There's only 1 and this clicks on the first one anyway. + await page.click("div.result-item"); + await page.waitForLoadState("networkidle"); + expect(await page.innerText("h1")).toBe(": A test tag"); + // Should have been redirected too... + expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo")); + }); + + test("find nothing by title search", async ({ page }) => { + await page.goto(testURL("/")); + + // This will activate the fancy autocomplete search and it should start + // a download of the `/en-US/search-index.json` too. + await page.focus(SEARCH_SELECTOR); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.waitForLoadState("networkidle"); + + await page.fill(SEARCH_SELECTOR, "gooblyg00k"); + expect(await page.innerText(".nothing-found")).toContain( + "No document titles found" + ); + }); + + test("find Foo page by fuzzy-search", async ({ page }) => { + await page.goto(testURL("/")); + + // This will activate the fancy autocomplete search and it should start + // a download of the `/en-US/search-index.json` too. + await page.focus(SEARCH_SELECTOR); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.waitForLoadState("networkidle"); + + await page.fill(SEARCH_SELECTOR, "/"); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + expect(await page.isVisible("text=Fuzzy searching by URI")).toBeTruthy(); + expect(await page.isVisible("text=No document titles found")).toBeFalsy(); + await page.fill(SEARCH_SELECTOR, "/wboo"); + expect(await page.isVisible("text=: A test tag")).toBeTruthy(); + + await page.click("div.result-item"); + await page.waitForLoadState("networkidle"); + expect(await page.innerText("h1")).toBe(": A test tag"); + // Should have been redirected too... + expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo")); + }); + + test("find nothing by fuzzy-search", async ({ page }) => { + await page.goto(testURL("/")); + + // This will activate the fancy autocomplete search and it should start + // a download of the `/en-US/search-index.json` too. + await page.focus(SEARCH_SELECTOR); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.waitForLoadState("networkidle"); + + await page.fill(SEARCH_SELECTOR, "/gooblygook"); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + expect(await page.isVisible("text=No document titles found")).toBeTruthy(); + }); + + test("input placeholder changes when focused", async ({ page }) => { + await page.goto(testURL("/")); + expect(await page.getAttribute(SEARCH_SELECTOR, "placeholder")).toMatch( + /Site search/ + ); + await page.focus(SEARCH_SELECTOR); + expect(await page.getAttribute(SEARCH_SELECTOR, "placeholder")).toMatch( + /Go ahead/ + ); + }); +}); diff --git a/testing/tests/headless.search.test.js b/testing/tests/headless.search.test.js deleted file mode 100644 index 8bcf30c6b5fd..000000000000 --- a/testing/tests/headless.search.test.js +++ /dev/null @@ -1,72 +0,0 @@ -function testURL(pathname = "/") { - return `http://localhost:5000${pathname}`; -} - -describe("Autocomplete search", () => { - const SEARCH_SELECTOR = 'form input[type="search"]'; - - beforeAll(async () => { - // Yes, this is a cheeky implementation testing detail but it's a nice - // sanity check that the server can respond with *something* that's - // sensible. This "test" also helps make sense of other potentially - // very confusing errors within the important tests themselves. - await page.goto(testURL("/en-US/search-index.json")); - // It's JSON but this asserts that page will be findable - await expect(page).toMatch(": A test tag"); - }); - - test("find Foo page by title search", async () => { - await page.goto(testURL("/")); - await expect(page).toFill(SEARCH_SELECTOR, "foo"); - await expect(page).toMatch(": A test tag"); - // There's only 1 and this clicks on the first one anyway. - await expect(page).toClick("div.result-item"); - await page.waitForNavigation(); - await expect(page).toMatchElement("h1", { text: ": A test tag" }); - // Should have been redirected too... - // Note! It's important that this happens *after* the `.toMatchElement` - // on the line above because expect-puppeteer doesn't have a wait to - // properly wait for the (pushState) URL to have changed. - expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo")); - }); - - test("find nothing by title search", async () => { - await page.goto(testURL("/")); - await expect(page).toFill(SEARCH_SELECTOR, "gooblyg00k"); - await expect(page).toMatchElement(".nothing-found", { - text: "No document titles found", - }); - }); - - test("find Foo page by fuzzy-search", async () => { - await page.goto(testURL("/")); - await expect(page).toFill(SEARCH_SELECTOR, "/"); - await expect(page).toMatch("Fuzzy searching by URI"); - await expect(page).not.toMatchElement(".nothing-found", { - text: "No document titles found", - }); - await expect(page).toFill(SEARCH_SELECTOR, "/wboo"); - await expect(page).toMatch(": A test tag"); - await expect(page).toClick("div.result-item"); - await page.waitForNavigation(); - await expect(page).toMatchElement("h1", { text: ": A test tag" }); - }); - - test("find nothing by fuzzy-search", async () => { - await page.goto(testURL("/")); - await expect(page).toFill(SEARCH_SELECTOR, "/gooblygook"); - await expect(page).toMatchElement(".nothing-found", { - text: "No document titles found", - }); - }); - - test("input placeholder changes when focused", async () => { - await expect(page).toMatchElement(SEARCH_SELECTOR, { - placeholder: /Site search/, - }); - await expect(page).toClick(SEARCH_SELECTOR); - await expect(page).toMatchElement(SEARCH_SELECTOR, { - placeholder: /Go ahead/, - }); - }); -}); diff --git a/testing/tests/headless.sitesearch.spec.js b/testing/tests/headless.sitesearch.spec.js new file mode 100644 index 000000000000..6177556ed620 --- /dev/null +++ b/testing/tests/headless.sitesearch.spec.js @@ -0,0 +1,81 @@ +const { test, expect } = require("@playwright/test"); + +function testURL(pathname = "/") { + return "http://localhost:5000" + pathname; +} + +test.describe("Site search", () => { + const SEARCH_SELECTOR = 'form input[type="search"]'; + + test("submit the autocomplete search form will redirect to site search", async ({ + page, + }) => { + await page.goto(testURL("/en-US/search/")); + await page.fill(SEARCH_SELECTOR, "foo"); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.$eval('form[role="search"]', (form) => form.submit()); + // Force a wait for the lazy-loading + await page.waitForLoadState("networkidle"); + // Force a wait for the search results + await page.waitForSelector("div.search-results"); + expect(await page.isVisible("text=Search results for:")).toBeTruthy(); + expect(page.url()).toBe(testURL("/en-US/search/?q=foo")); + }); + + test("go to site-search page without query", async ({ page }) => { + await page.goto(testURL("/en-US/search/")); + expect(await page.isVisible("text=No query, no results")).toBeTruthy(); + // See server/static.js for how fixtures are hardcoded + await page.fill(SEARCH_SELECTOR, "FOO"); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.$eval('form[role="search"]', (form) => form.submit()); + // Force a wait for the lazy-loading + await page.waitForLoadState("networkidle"); + expect(page.url()).toBe(testURL("/en-US/search/?q=FOO")); + expect(await page.isVisible("text=Search results for: FOO")).toBeTruthy(); + expect(await page.isVisible("text=Found 1 match")).toBeTruthy(); + }); + + test("search and find nothing", async ({ page }) => { + await page.goto(testURL("/en-US/search/")); + expect(await page.isVisible("text=No query, no results")).toBeTruthy(); + // See server/static.js for how fixtures are hardcoded + await page.fill(SEARCH_SELECTOR, "NOTHING"); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.$eval('form[role="search"]', (form) => form.submit()); + await page.waitForLoadState("networkidle"); + expect(page.url()).toBe(testURL("/en-US/search/?q=NOTHING")); + expect( + await page.isVisible("text=Search results for: NOTHING") + ).toBeTruthy(); + expect(await page.isVisible("text=Found 0 matches")).toBeTruthy(); + }); + + test("search and go to page 2", async ({ page }) => { + await page.goto(testURL("/en-US/search/")); + // See server/static.js for how fixtures are hardcoded + await page.fill(SEARCH_SELECTOR, "SERIAL(20)"); + await page.waitForSelector("#nav-main-search"); // autocomplete search form + await page.$eval('form[role="search"]', (form) => form.submit()); + await page.waitForLoadState("networkidle"); + expect( + await page.isVisible("text=Search results for: SERIAL(20)") + ).toBeTruthy(); + expect(page.url()).toBe(testURL("/en-US/search/?q=SERIAL%2820%29")); + expect( + await page.isVisible("text=Found 20 matches in 0.1 milliseconds") + ).toBeTruthy(); + expect(await page.isVisible("text=Serial 0")).toBeTruthy(); + expect(await page.isVisible("text=Serial 9")).toBeTruthy(); + expect(await page.isVisible('a:has-text("Next")')).toBeTruthy(); + expect(await page.isVisible("text=Serial 9")).toBeTruthy(); + expect(await page.isVisible('a:has-text("Previous")')).toBeFalsy(); + await page.click('a:has-text("Next")'); + expect(await page.isVisible("text=(page 2)")).toBeTruthy(); + expect(await page.isVisible("text=Serial 10")).toBeTruthy(); + expect(await page.isVisible("text=Serial 19")).toBeTruthy(); + expect(await page.isVisible('a:has-text("Previous")')).toBeTruthy(); + expect(await page.isVisible('a:has-text("Next")')).toBeFalsy(); + expect(page.url()).toBe(testURL("/en-US/search?q=SERIAL%2820%29&page=2")); + }); +}); diff --git a/testing/tests/headless.test.js b/testing/tests/headless.test.js deleted file mode 100644 index bd9e156e9de0..000000000000 --- a/testing/tests/headless.test.js +++ /dev/null @@ -1,294 +0,0 @@ -const { setDefaultOptions } = require("expect-puppeteer"); - -// The default it 500ms. It has happened and it can happen again, that sometimes -// it just takes a little longer than 500ms. Give it a healthy margin of a -// timeout so as to reduce the risk of it failing when there's nothing wrong. -setDefaultOptions({ timeout: 1500 }); - -function testURL(pathname = "/") { - return `http://localhost:5000${pathname}`; -} - -describe("Basic viewing of functional pages", () => { - it("open the temporary home page", async () => { - await page.goto(testURL("/")); - await expect(page).toMatch("MDN Web Docs"); - await expect(page).toMatchElement("title", { text: /MDN Web Docs/ }); - }); - - it("open the /en-US/docs/Web/Foo page", async () => { - await page.goto(testURL("/en-US/docs/Web/Foo")); - await expect(page).toMatch(": A test tag"); - await expect(page).toMatchElement(".metadata time", { - visible: true, - }); - }); - - it("open the French /fr/docs/Web/Foo page and navigate to English", async () => { - await page.goto(testURL("/fr/docs/Web/Foo")); - await expect(page).toMatchElement("h1", { - text: ": Une page de test", - }); - await expect(page).toClick("a.view-in-english", { - text: "View in English", - }); - await expect(page).toMatchElement("h1", { text: ": A test tag" }); - // Should have been redirected too... - // Note! It's important that this happens *after* the `.toMatchElement` - // on the line above because expect-puppeteer doesn't have a wait to - // properly wait for the (pushState) URL to have changed. - expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo/")); - }); - - it("open the /en-US/docs/Web/InteractiveExample page", async () => { - await page.goto(testURL("/en-US/docs/Web/InteractiveExample"), { - // Be a bit less patient with this particular page because it contains - // an iframe, on an external URL, which we're not particularly - // interested in waiting for. - waitUntil: "domcontentloaded", - }); - await expect(page).toMatch("I Have an Interactive Example"); - }); - - it("open the /en-US/docs/Learn/CSS/CSS_layout/Introduction page", async () => { - const uri = "/en-US/docs/Learn/CSS/CSS_layout/Introduction"; - const flexSample1Uri = `${uri}/Flex/_sample_.Flex_1.html`; - const flexSample2Uri = `${uri}/Flex/_sample_.Flex_2.html`; - const gridSample1Uri = `${uri}/Grid/_sample_.Grid_1.html`; - const gridSample2Uri = `${uri}/_sample_.Grid_2.html`; - await page.goto(testURL(uri)); - await expect(page).toMatch("A Test Introduction to CSS layout"); - await expect(page).toMatchElement("h1", { - text: "A Test Introduction to CSS layout", - }); - await expect(page).toMatchElement("#flexbox", { - text: "Flexbox", - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${flexSample1Uri}"]` - ); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${flexSample2Uri}"]` - ); - await expect(page).toMatchElement("#grid_layout", { - text: "Grid Layout", - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${gridSample1Uri}"]` - ); - await expect(page).toMatchElement("#Grid_2 pre.css.notranslate", { - text: /\.wrapper\s*\{\s*display:\s*grid;/, - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${gridSample2Uri}"]` - ); - // Ensure that the live-sample pages were built. - for (const sampleUri of [ - flexSample1Uri, - flexSample2Uri, - gridSample1Uri, - gridSample2Uri, - ]) { - await page.goto(testURL(sampleUri)); - await expect(page).toMatchElement("body > div.wrapper > div.box1", { - text: "One", - }); - await expect(page).toMatchElement("body > div.wrapper > div.box2", { - text: "Two", - }); - await expect(page).toMatchElement("body > div.wrapper > div.box3", { - text: "Three", - }); - } - }); - - it("open the /en-US/docs/Learn/CSS/CSS_layout/Introduction/Flex page", async () => { - const uri = "/en-US/docs/Learn/CSS/CSS_layout/Introduction/Flex"; - const flexSample1Uri = `${uri}/_sample_.Flex_1.html`; - const flexSample2Uri = `${uri}/_sample_.Flex_2.html`; - await page.goto(testURL(uri)); - await expect(page).toMatch("A Test Introduction to CSS Flexbox Layout"); - await expect(page).toMatchElement("h1", { - text: "A Test Introduction to CSS Flexbox Layout", - }); - await expect(page).toMatchElement("#flexbox", { - text: "Flexbox", - }); - await expect(page).toMatchElement("#Flex_1 pre.css.notranslate", { - text: /\.wrapper\s*\{\s*display:\s*flex;\s*\}/, - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${flexSample1Uri}"]` - ); - await expect(page).toMatchElement("#Flex_2 pre.css.notranslate", { - text: /\.wrapper\s*\{\s*display:\s*flex;\s*\}.+flex:\s*1;/, - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${flexSample2Uri}"]` - ); - }); - - it("open the /en-US/docs/Learn/CSS/CSS_layout/Introduction/Grid page", async () => { - const uri = "/en-US/docs/Learn/CSS/CSS_layout/Introduction/Grid"; - const gridSample1Uri = `${uri}/_sample_.Grid_1.html`; - const gridSample2Uri = `${uri}/_sample_.Grid_2.html`; - await page.goto(testURL(uri)); - await expect(page).toMatch("A Test Introduction to CSS Grid Layout"); - await expect(page).toMatchElement("h1", { - text: "A Test Introduction to CSS Grid Layout", - }); - await expect(page).toMatchElement("#grid_layout", { - text: "Grid Layout", - }); - await expect(page).toMatchElement("#Grid_1 pre.css.notranslate", { - text: /\.wrapper\s*\{\s*display:\s*grid;/, - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${gridSample1Uri}"]` - ); - await expect(page).toMatchElement("#Grid_2 pre.css.notranslate", { - text: /\.wrapper\s*\{\s*display:\s*grid;.+\.box1\s*\{/, - }); - await expect(page).toMatchElement( - `iframe.sample-code-frame[src$="${gridSample2Uri}"]` - ); - // Ensure that the live-sample page "gridSample2Uri" was built. - await page.goto(testURL(gridSample2Uri)); - await expect(page).toMatchElement("body > div.wrapper > div.box1", { - text: "One", - }); - await expect(page).toMatchElement("body > div.wrapper > div.box2", { - text: "Two", - }); - await expect(page).toMatchElement("body > div.wrapper > div.box3", { - text: "Three", - }); - }); - - it("should return to previous page on back-button press", async () => { - await page.goto(testURL("/en-US/docs/Web/Foo")); - await expect(page).toMatch(": A test tag"); - await expect(page).toMatchElement("h1", { - text: ": A test tag", - }); - // Click the parent page in the breadcrumbs - // BUT due to some bug somewhere, you can't do - // - // await expect(page).toClick("nav.breadcrumbs a") - // - // ...because you keep getting: - // - // "Node is either not visible or not an HTMLElement" - // - // So, because of that, let's just do it "the pure puppeteer way." - // - // For more information, see - // https://github.com/puppeteer/puppeteer/issues/2977#issuecomment-412807613 - await page.evaluate(() => { - document.querySelector(".breadcrumbs-container a").click(); - }); - await expect(page).toMatchElement("h1", { - text: "Web technology for developers", - }); - expect(page.url()).toBe(testURL("/en-US/docs/Web")); - await page.goBack(); - await expect(page).toMatchElement("h1", { - text: ": A test tag", - }); - }); - - it("should have a semantically valid breadcrumb trail", async () => { - await page.goto(testURL("/en-US/docs/Web/Foo")); - // Let's not get too technical about the name of the selectors and - // stuff but do note that the page you're on is always a valid link - await expect(page).toMatchElement("nav a[property=item][typeof=WebPage]", { - // Always includes a link to "self" - text: ": A test tag", - }); - await expect(page).toMatchElement("nav a[property=item][typeof=WebPage]", { - // You gotta know your fixture documents - text: "Web technology for developers", - }); - }); - - it("should say which page was not found", async () => { - await page.goto(testURL("/en-US/docs/Doesnot/exist")); - await expect(page).toMatch("Page not found"); - await expect(page).toMatch("/en-US/docs/Doesnot/exist could not be found"); - }); - - it("should suggest the en-US equivalent on non-en-US pages not found", async () => { - await page.goto(testURL("/ja/docs/Web/foo")); - await expect(page).toMatch("Page not found"); - await expect(page).toMatch("/ja/docs/Web/foo could not be found"); - // Simply by swapping the "ja" for "en-US" it's able to find the index.json - // for that slug and present a link to it. - await expect(page).toMatch("Good news!"); - await expect(page).toMatchElement("a", { - text: ": A test tag", - href: "/en-US/docs/Web/Foo", - }); - }); - - it("should give the home page and see Hacks blog posts", async () => { - await page.goto(testURL("/en-US/")); - await expect(page).toMatch("Resources for developers, by developers."); - await expect(page).toMatch("Hacks Blog"); - - // One home page for every built locale - await page.goto(testURL("/fr/")); - await expect(page).toMatch("Resources for developers, by developers."); - }); - - it("should be able to switch from French to English, set a cookie, and back again", async () => { - await page.goto(testURL("/fr/docs/Web/Foo")); - await expect(page).toMatch(": Une page de test"); - await expect(page).toSelect('select[name="language"]', "English (US)"); - await expect(page).toClick("button", { text: "Change language" }); - await expect(page).toMatch(": A test tag"); - expect(page.url()).toBe(testURL("/en-US/docs/Web/Foo/")); - - // And change back to French - await expect(page).toSelect('select[name="language"]', "Français"); - await expect(page).toClick("button", { text: "Change language" }); - await expect(page).toMatch(": Une page de test"); - expect(page.url()).toBe(testURL("/fr/docs/Web/Foo/")); - }); - - it("should redirect retired locale to English (document)", async () => { - await page.goto(testURL("/ar/docs/Web/Foo")); - await expect(page.url()).toMatch( - testURL("/en-US/docs/Web/Foo/?retiredLocale=ar") - ); - await expect(page).toMatch(": A test tag"); - }); - - it("should redirect retired locale to English (index.json)", async () => { - await page.goto(testURL("/ar/docs/Web/Foo/index.json")); - await expect(page.url()).toMatch( - testURL("/en-US/docs/Web/Foo/index.json?retiredLocale=ar") - ); - await expect(page).toMatch(": A test tag"); - }); - - it("should redirect retired locale to English (search with query string)", async () => { - await page.goto(testURL("/ar/search?q=video")); - await expect(page.url()).toMatch( - testURL("/en-US/search/?q=video&retiredLocale=ar") - ); - await expect(page).toMatch("Search results for: video"); - }); - - it("should say the locale was retired", async () => { - await page.goto(testURL("/en-US/docs/Web/Foo/?retiredLocale=ar")); - await expect(page).toMatch("The page you requested has been retired"); - // sanity check that it goes away - await page.goto(testURL("/en-US/docs/Web/Foo/")); - await expect(page).not.toMatch("The page you requested has been retired"); - }); - - it("should not say the locale was retired if viewing a translated page", async () => { - await page.goto(testURL("/fr/docs/Web/Foo/?retiredLocale=sv-SE")); - await expect(page).not.toMatch("The page you requested has been retired"); - }); -}); diff --git a/testing/tests/index.test.js b/testing/tests/index.test.js index 1ebfa8e0df9f..7a86441e010a 100644 --- a/testing/tests/index.test.js +++ b/testing/tests/index.test.js @@ -5,7 +5,7 @@ const cheerio = require("cheerio"); const glob = require("glob"); const sizeOf = require("image-size"); -const buildRoot = path.join("..", "client", "build"); +const buildRoot = path.join("client", "build"); test("all favicons on the home page", () => { // The home page SPA is built, in terms of the index.html template, diff --git a/testing/tests/sitesearch.test.js b/testing/tests/sitesearch.test.js deleted file mode 100644 index eef148e61007..000000000000 --- a/testing/tests/sitesearch.test.js +++ /dev/null @@ -1,61 +0,0 @@ -function testURL(pathname = "/") { - return "http://localhost:5000" + pathname; -} - -describe("Site search", () => { - const SEARCH_SELECTOR = 'form input[type="search"]'; - - test("Submit the autocomplete search form will redirect to site search", async () => { - await page.goto(testURL("/")); - await expect(page).toFill(SEARCH_SELECTOR, "foo"); - await page.$eval('form[role="search"]', (form) => form.submit()); - // Force a wait for the lazy-loading - await page.waitForNavigation({ waitUntil: "networkidle2" }); - expect(page.url()).toBe(testURL("/en-US/search/?q=foo")); - }); - - test("Go to site-search page without query", async () => { - await page.goto(testURL("/en-us/search/")); - await expect(page).toMatch("No query, no results"); - // See server/static.js for how fixtures are hardcoded - await expect(page).toFill(SEARCH_SELECTOR, "FOO"); - await page.$eval('form[role="search"]', (form) => form.submit()); - // Force a wait for the lazy-loading - await page.waitForNavigation({ waitUntil: "networkidle2" }); - await expect(page).toMatch("Search results for: FOO"); - await expect(page).toMatch("Found 1 match"); - }); - - test("Search and find nothing", async () => { - await page.goto(testURL("/en-us/search/")); - await expect(page).toMatch("No query, no results"); - // See server/static.js for how fixtures are hardcoded - await expect(page).toFill(SEARCH_SELECTOR, "NOTHING"); - await page.$eval('form[role="search"]', (form) => form.submit()); - await page.waitForNavigation({ waitUntil: "networkidle2" }); - await expect(page).toMatch("Search results for: NOTHING"); - await expect(page).toMatch("Found 0 matches"); - }); - - test("Search and go to page 2", async () => { - await page.goto(testURL("/en-US/search/")); - // See server/static.js for how fixtures are hardcoded - await expect(page).toFill(SEARCH_SELECTOR, "SERIAL(20)"); - await page.$eval('form[role="search"]', (form) => form.submit()); - await page.waitForNavigation({ waitUntil: "networkidle2" }); - await expect(page).toMatch("Search results for: SERIAL(20)"); - expect(page.url()).toBe(testURL("/en-US/search/?q=SERIAL%2820%29")); - await expect(page).toMatch("Found 20 matches in 0.1 milliseconds"); - await expect(page).toMatch("Serial 0"); - await expect(page).toMatch("Serial 9"); - await expect(page).toMatchElement("a", { text: "Next" }); - await expect(page).not.toMatchElement("a", { text: "Previous" }); - await expect(page).toClick(".pagination a"); - await expect(page).toMatch("(page 2)"); - await expect(page).toMatch("Serial 10"); - await expect(page).toMatch("Serial 19"); - await expect(page).toMatchElement("a", { text: "Previous" }); - await expect(page).not.toMatchElement("a", { text: "Next" }); - expect(page.url()).toBe(testURL("/en-US/search?q=SERIAL%2820%29&page=2")); - }); -}); diff --git a/yarn.lock b/yarn.lock index e75a366079aa..b3d26e74fcd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -96,7 +96,28 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.12.1", "@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.15.0": +"@babel/core@^7.14.0": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" + integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.8" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-module-transforms" "^7.14.8" + "@babel/helpers" "^7.14.8" + "@babel/parser" "^7.14.8" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.12.1", "@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.14.8", "@babel/generator@^7.15.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15" integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ== @@ -119,6 +140,13 @@ dependencies: "@babel/types" "^7.12.13" +"@babel/helper-annotate-as-pure@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61" + integrity sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -152,7 +180,7 @@ "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-compilation-targets@^7.12.1", "@babel/helper-compilation-targets@^7.12.5", "@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.13", "@babel/helper-compilation-targets@^7.13.8", "@babel/helper-compilation-targets@^7.15.0": +"@babel/helper-compilation-targets@^7.12.1", "@babel/helper-compilation-targets@^7.12.5", "@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.13", "@babel/helper-compilation-targets@^7.13.8", "@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.15.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818" integrity sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A== @@ -184,6 +212,18 @@ "@babel/helper-replace-supers" "^7.13.0" "@babel/helper-split-export-declaration" "^7.12.13" +"@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.14.6": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.8.tgz#a6f8c3de208b1e5629424a9a63567f56501955fc" + integrity sha512-bpYvH8zJBWzeqi1o+co8qOrw+EXzQ/0c74gVmY205AWXy9nifHrOg77y+1zwxX5lXE7Icq4sPlSQ4O2kWBrteQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-member-expression-to-functions" "^7.14.7" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -345,6 +385,13 @@ dependencies: "@babel/types" "^7.13.12" +"@babel/helper-member-expression-to-functions@^7.14.7": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz#97e56244beb94211fe277bd818e3a329c66f7970" + integrity sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-member-expression-to-functions@^7.15.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b" @@ -394,7 +441,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.15.0": +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.14.8", "@babel/helper-module-transforms@^7.15.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz#679275581ea056373eddbe360e1419ef23783b08" integrity sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg== @@ -408,6 +455,20 @@ "@babel/traverse" "^7.15.0" "@babel/types" "^7.15.0" +"@babel/helper-module-transforms@^7.14.5": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz#d4279f7e3fd5f4d5d342d833af36d4dd87d7dc49" + integrity sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA== + dependencies: + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-simple-access" "^7.14.8" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.8" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" + "@babel/helper-optimise-call-expression@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" @@ -444,6 +505,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== +"@babel/helper-plugin-utils@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + "@babel/helper-regex@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" @@ -489,7 +555,7 @@ "@babel/traverse" "^7.13.0" "@babel/types" "^7.13.12" -"@babel/helper-replace-supers@^7.15.0": +"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4" integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA== @@ -513,7 +579,7 @@ dependencies: "@babel/types" "^7.13.12" -"@babel/helper-simple-access@^7.14.8": +"@babel/helper-simple-access@^7.14.5", "@babel/helper-simple-access@^7.14.8": version "7.14.8" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== @@ -563,7 +629,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== -"@babel/helper-validator-identifier@^7.14.9": +"@babel/helper-validator-identifier@^7.14.8", "@babel/helper-validator-identifier@^7.14.9": version "7.14.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== @@ -630,7 +696,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.3", "@babel/parser@^7.12.7", "@babel/parser@^7.14.5", "@babel/parser@^7.15.0", "@babel/parser@^7.7.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.3", "@babel/parser@^7.12.7", "@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.15.0", "@babel/parser@^7.7.0": version "7.15.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.0.tgz#b6d6e29058ca369127b0eeca2a1c4b5794f1b6b9" integrity sha512-0v7oNOjr6YT9Z2RAOTv4T9aP+ubfx4Q/OhVtAet7PFDt0t9Oy6Jn+/rfC6b8HJ5zEqrQCiMxJfgtHpmIminmJQ== @@ -887,6 +953,16 @@ "@babel/helper-create-class-features-plugin" "^7.13.0" "@babel/helper-plugin-utils" "^7.13.0" +"@babel/plugin-proposal-private-property-in-object@^7.14.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz#9f65a4d0493a940b4c01f8aa9d3f1894a587f636" + integrity sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-unicode-property-regex@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz#2a183958d417765b9eae334f47758e5d6a82e072" @@ -1051,6 +1127,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-top-level-await@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" @@ -1086,6 +1169,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/plugin-syntax-typescript@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz#b82c6ce471b165b5ce420cf92914d6fb46225716" + integrity sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-transform-arrow-functions@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3" @@ -1359,6 +1449,16 @@ "@babel/helper-simple-access" "^7.12.13" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-commonjs@^7.14.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz#7aaee0ea98283de94da98b28f8c35701429dad97" + integrity sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A== + dependencies: + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-simple-access" "^7.14.5" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-systemjs@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz#663fea620d593c93f214a464cd399bf6dc683086" @@ -1695,6 +1795,15 @@ "@babel/helper-plugin-utils" "^7.13.0" "@babel/plugin-syntax-typescript" "^7.12.13" +"@babel/plugin-transform-typescript@^7.14.5": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.14.6.tgz#6e9c2d98da2507ebe0a883b100cde3c7279df36c" + integrity sha512-XlTdBq7Awr4FYIzqhmYY80WN0V0azF74DMPyFqVHBvf81ZUgc4X7ZOpx6O8eLDK6iM5cCQzeyJw0ynTaefixRA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.14.6" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-typescript" "^7.14.5" + "@babel/plugin-transform-unicode-escapes@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709" @@ -2027,6 +2136,15 @@ "@babel/helper-validator-option" "^7.12.17" "@babel/plugin-transform-typescript" "^7.13.0" +"@babel/preset-typescript@^7.13.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.14.5.tgz#aa98de119cf9852b79511f19e7f44a2d379bcce0" + integrity sha512-u4zO6CdbRKbS9TypMqrlGH7sd2TAJppZwn3c/ZRLeO/wGsbddxgbPDUZVNrie3JWYLQ9vpineKlsrWFvO6Pwkw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-validator-option" "^7.14.5" + "@babel/plugin-transform-typescript" "^7.14.5" + "@babel/register@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.12.1.tgz#cdb087bdfc4f7241c03231f22e15d211acf21438" @@ -2290,11 +2408,6 @@ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== -"@hapi/hoek@^9.0.0": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" - integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== - "@hapi/joi@^15.1.0": version "15.1.1" resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" @@ -2312,13 +2425,6 @@ dependencies: "@hapi/hoek" "^8.3.0" -"@hapi/topo@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" - integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== - dependencies: - "@hapi/hoek" "^9.0.0" - "@html-validate/stylish@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@html-validate/stylish/-/stylish-1.0.0.tgz#f8700775d8863e7de50564f8d3e6908a5f40ab9d" @@ -2400,16 +2506,6 @@ "@types/node" "*" jest-mock "^26.6.2" -"@jest/environment@^27.0.1": - version "27.0.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.0.1.tgz#27ed89bf8179c0a030690f063d922d6da7a519ac" - integrity sha512-nG+r3uSs2pOTsdhgt6lUm4ZGJLRcTc6HZIkrFsVpPcdSqEpJehEny9r9y2Bmhkn8fKXWdGCYJKF3i4nKO0HSmA== - dependencies: - "@jest/fake-timers" "^27.0.1" - "@jest/types" "^27.0.1" - "@types/node" "*" - jest-mock "^27.0.1" - "@jest/fake-timers@^25.1.0": version "25.5.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185" @@ -2433,18 +2529,6 @@ jest-mock "^26.6.2" jest-util "^26.6.2" -"@jest/fake-timers@^27.0.1": - version "27.0.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.0.1.tgz#6987a596b0bcf8c07653086076c17058b4c77b5c" - integrity sha512-3CyLJQnHzKI4TCJSCo+I9TzIHjSK4RrNEk93jFM6Q9+9WlSJ3mpMq/p2YuKMe0SiHKbmZOd5G/Ll5ofF9Xkw9g== - dependencies: - "@jest/types" "^27.0.1" - "@sinonjs/fake-timers" "^7.0.2" - "@types/node" "*" - jest-message-util "^27.0.1" - jest-mock "^27.0.1" - jest-util "^27.0.1" - "@jest/globals@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" @@ -2569,17 +2653,6 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jest/types@^27.0.1": - version "27.0.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.1.tgz#631738c942e70045ebbf42a3f9b433036d3845e4" - integrity sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - "@jest/types@^27.0.2": version "27.0.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.2.tgz#e153d6c46bda0f2589f0702b071f9898c7bbd37e" @@ -2691,6 +2764,50 @@ dependencies: mkdirp "^1.0.4" +"@playwright/test@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.13.0.tgz#d22fa9f02c17758f7cb6178be3e22462c4357582" + integrity sha512-pT9GTQ1eaxdxycnGg4ZANxGZsu4bzN6M8eRzcDxTprJUWZDBqZknGImOFKMRkHB+YhS8UJRuJ6YpPfZaNL2RLw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/core" "^7.14.0" + "@babel/plugin-proposal-class-properties" "^7.13.0" + "@babel/plugin-proposal-dynamic-import" "^7.13.8" + "@babel/plugin-proposal-export-namespace-from" "^7.12.13" + "@babel/plugin-proposal-logical-assignment-operators" "^7.13.8" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" + "@babel/plugin-proposal-numeric-separator" "^7.12.13" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + "@babel/plugin-proposal-private-methods" "^7.13.0" + "@babel/plugin-proposal-private-property-in-object" "^7.14.0" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-transform-modules-commonjs" "^7.14.0" + "@babel/preset-typescript" "^7.13.0" + colors "^1.4.0" + commander "^6.1.0" + debug "^4.1.1" + expect "^26.4.2" + extract-zip "^2.0.1" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.2" + mime "^2.4.6" + minimatch "^3.0.3" + ms "^2.1.2" + pirates "^4.0.1" + pixelmatch "^5.2.1" + pngjs "^5.0.0" + progress "^2.0.3" + proper-lockfile "^4.1.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + source-map-support "^0.4.18" + stack-utils "^2.0.3" + ws "^7.4.6" + yazl "^2.5.1" + "@pmmmwh/react-refresh-webpack-plugin@0.4.3", "@pmmmwh/react-refresh-webpack-plugin@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz#1eec460596d200c0236bf195b078a5d1df89b766" @@ -2751,23 +2868,6 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@sideway/address@^4.1.0": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.1.tgz#9e321e74310963fdf8eebfbee09c7bd69972de4d" - integrity sha512-+I5aaQr3m0OAmMr7RQ3fR9zx55sejEYR2BFJaxL+zT3VM2611X0SHvPWIbAUBZVTn/YzYKbV8gJ2oT/QELknfQ== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sidvind/better-ajv-errors@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@sidvind/better-ajv-errors/-/better-ajv-errors-0.9.0.tgz#fcc84df0d2a68b100a4d6b9b2cc7066525c42dee" @@ -2803,13 +2903,6 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@sinonjs/fake-timers@^7.0.2": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.0.tgz#8f13af27d842cbf51ad4502e05562fe9391d084e" - integrity sha512-hAEzXi6Wbvlb67NnGMGSNOeAflLVnMa4yliPU/ty1qjgW/vAletH15/v/esJwASSIA0YlIyjnloenFbEZc9q9A== - dependencies: - "@sinonjs/commons" "^1.7.0" - "@storybook/addon-a11y@^6.3.4": version "6.3.4" resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-6.3.4.tgz#056c3c0e3b2d66d8aa2f02dc84374c948d07df91" @@ -5079,13 +5172,6 @@ axe-core@^4.2.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.1.tgz#0c6a076e4a1c3e0544ba6a9479158f9be7a7928e" integrity sha512-3WVgVPs/7OnKU3s+lqMtkv3wQlg3WxK1YifmpJSDO0E1aPBrZWlrrTO6cxRqCXLuX2aYgCljqXIQd0VnRidV0g== -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== - dependencies: - follow-redirects "^1.10.0" - axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -5497,7 +5583,7 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@^4.0.3, bl@^4.1.0: +bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -6195,11 +6281,6 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.1.1.tgz#9a32fcefdf7bcdb6f0a7e1c0f8098ec57897b80a" - integrity sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ== - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -6319,17 +6400,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -clone-deep@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" - integrity sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY= - dependencies: - for-own "^0.1.3" - is-plain-object "^2.0.1" - kind-of "^3.0.2" - lazy-cache "^1.0.3" - shallow-clone "^0.1.2" - clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -6458,7 +6528,7 @@ colornames@^1.1.1: resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= -colors@^1.1.2, colors@^1.2.1: +colors@^1.1.2, colors@^1.2.1, colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -6488,22 +6558,12 @@ commander@^2.15.1, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" - integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== - commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@^6.2.1: +commander@^6.1.0, commander@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== @@ -7206,14 +7266,6 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -cwd@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567" - integrity sha1-FyQAaUBXwioTsM8WFix+S3p/5Wc= - dependencies: - find-pkg "^0.1.2" - fs-exists-sync "^0.1.0" - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -7577,11 +7629,6 @@ detect-port@^1.3.0: address "^1.0.1" debug "^2.6.0" -devtools-protocol@0.0.869402: - version "0.0.869402" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.869402.tgz#03ade701761742e43ae4de5dc188bcd80f156d8d" - integrity sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA== - diagnostics@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" @@ -8019,7 +8066,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -8680,19 +8727,7 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-tilde@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk= - dependencies: - os-homedir "^1.0.1" - -expect-puppeteer@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/expect-puppeteer/-/expect-puppeteer-5.0.4.tgz#54bfdecabb2acb3e3f0d0292cd3dab2dd8ff5a81" - integrity sha512-NV7jSiKhK+byocxg9A+0av+Q2RSCP9bcLVRz7zhHaESeCOkuomMvl9oD+uo1K+NdqRCXhNkQlUGWlmtbrpR1qw== - -expect@^26.6.0, expect@^26.6.2: +expect@^26.4.2, expect@^26.6.0, expect@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== @@ -8805,7 +8840,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@^2.0.0: +extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -9114,30 +9149,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-file-up@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/find-file-up/-/find-file-up-0.1.3.tgz#cf68091bcf9f300a40da411b37da5cce5a2fbea0" - integrity sha1-z2gJG8+fMApA2kEbN9pczlovvqA= - dependencies: - fs-exists-sync "^0.1.0" - resolve-dir "^0.1.0" - -find-pkg@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/find-pkg/-/find-pkg-0.1.2.tgz#1bdc22c06e36365532e2a248046854b9788da557" - integrity sha1-G9wiwG42NlUy4qJIBGhUuXiNpVc= - dependencies: - find-file-up "^0.1.2" - -find-process@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/find-process/-/find-process-1.4.4.tgz#52820561162fda0d1feef9aed5d56b3787f0fd6e" - integrity sha512-rRSuT1LE4b+BFK588D2V8/VG9liW0Ark1XJgroxZXI0LtwmQJOb490DvDYvbm+Hek9ETFzTutGfJ90gumITPhQ== - dependencies: - chalk "^4.0.0" - commander "^5.1.0" - debug "^4.1.1" - find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -9224,28 +9235,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -follow-redirects@^1.10.0: - version "1.13.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" - integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== - -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" - foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -9343,11 +9332,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - fs-extra@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" @@ -9674,24 +9658,6 @@ global-modules@2.0.0, global-modules@^2.0.0: dependencies: global-prefix "^3.0.0" -global-modules@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" - integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0= - dependencies: - global-prefix "^0.1.4" - is-windows "^0.2.0" - -global-prefix@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" - integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948= - dependencies: - homedir-polyfill "^1.0.0" - ini "^1.3.4" - is-windows "^0.2.0" - which "^1.2.12" - global-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" @@ -10271,13 +10237,6 @@ hoist-non-react-statics@^3.3.0: dependencies: react-is "^16.7.0" -homedir-polyfill@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - hoopy@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" @@ -10968,7 +10927,7 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.5: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -11000,13 +10959,6 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-ci@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" - integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== - dependencies: - ci-info "^3.1.1" - is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" @@ -11286,7 +11238,7 @@ is-plain-object@3.0.1: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== -is-plain-object@^2.0.1, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -11444,11 +11396,6 @@ is-window@^1.0.2: resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" integrity sha1-LIlspT25feRdPDMTOmXYyfVjSA0= -is-windows@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw= - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -11654,19 +11601,6 @@ jest-config@^26.6.3: micromatch "^4.0.2" pretty-format "^26.6.2" -jest-dev-server@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/jest-dev-server/-/jest-dev-server-5.0.3.tgz#324bf6426477450ec3dae349ee9223d43f8be368" - integrity sha512-aJR3a5KdY18Lsz+VbREKwx2HM3iukiui+J9rlv9o6iYTwZCSsJazSTStcD9K1q0AIF3oA+FqLOKDyo/sc7+fJw== - dependencies: - chalk "^4.1.1" - cwd "^0.10.0" - find-process "^1.4.4" - prompts "^2.4.1" - spawnd "^5.0.0" - tree-kill "^1.2.2" - wait-on "^5.3.0" - jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" @@ -11730,29 +11664,6 @@ jest-environment-node@^26.6.2: jest-mock "^26.6.2" jest-util "^26.6.2" -jest-environment-node@^27.0.1: - version "27.0.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.0.1.tgz#7d7df7ae191477a823ffb4fcc0772b4c23ec5c87" - integrity sha512-/p94lo0hx+hbKUw1opnRFUPPsjncRBEUU+2Dh7BuxX8Nr4rRiTivLYgXzo79FhaeMYV0uiV5WAbHBq6xC11JJg== - dependencies: - "@jest/environment" "^27.0.1" - "@jest/fake-timers" "^27.0.1" - "@jest/types" "^27.0.1" - "@types/node" "*" - jest-mock "^27.0.1" - jest-util "^27.0.1" - -jest-environment-puppeteer@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/jest-environment-puppeteer/-/jest-environment-puppeteer-5.0.4.tgz#ed64689bf200923828ca98761b4da36eb8ce31bc" - integrity sha512-wd4EDOD4QRi11QZ1IV8WsL1wlnnMUtcqtU0BNm+REzRtg78K2XHn3jS6YxGeXIOnsgrJeHxsD7DlRZ/GkFteLg== - dependencies: - chalk "^4.1.1" - cwd "^0.10.0" - jest-dev-server "^5.0.3" - jest-environment-node "^27.0.1" - merge-deep "^3.0.3" - jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" @@ -11857,21 +11768,6 @@ jest-message-util@^26.6.0, jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-message-util@^27.0.1: - version "27.0.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.0.1.tgz#382b7c55d8e0b1aba9eeb41d3cfdd34e451210ed" - integrity sha512-w8BfON2GwWORkos8BsxcwwQrLkV2s1ENxSRXK43+6yuquDE2hVxES/jrFqOArpP1ETVqqMmktU6iGkG8ncVzeA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.0.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.4" - pretty-format "^27.0.1" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-mock@^25.1.0, jest-mock@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a" @@ -11887,27 +11783,11 @@ jest-mock@^26.6.2: "@jest/types" "^26.6.2" "@types/node" "*" -jest-mock@^27.0.1: - version "27.0.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.0.1.tgz#8394e297bc3dfed980961622cb51fd042b4acf5a" - integrity sha512-fXCSZQDT5hUcAUy8OBnB018x7JFOMQnz4XfpSKEbfpWzL6o5qaLRhgf2Qg2NPuVKmC/fgOf33Edj8wjF4I24CQ== - dependencies: - "@jest/types" "^27.0.1" - "@types/node" "*" - jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-puppeteer@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/jest-puppeteer/-/jest-puppeteer-5.0.4.tgz#c52e3379c11425ce974d025c1a8bf9f599da4b3f" - integrity sha512-IUOVKgHEaKsLqahZy/J/DvXB59SQx4AVpZKTRDvJzCdkvdGc3NVsNwUhovr6SK+HOK1TOiqAiXPTAPiIq3mkrg== - dependencies: - expect-puppeteer "^5.0.4" - jest-environment-puppeteer "^5.0.4" - jest-regex-util@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" @@ -12062,18 +11942,6 @@ jest-util@^26.6.0, jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" -jest-util@^27.0.1: - version "27.0.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.1.tgz#324ed9879d129c1e64f9169a739d6d50d7928769" - integrity sha512-lEw3waSmEOO4ZkwkUlFSvg4es1+8+LIkSGxp/kF60K0+vMR3Dv3O2HMZhcln9NHqSQzpVbsDT6OeMzUPW7DfRg== - dependencies: - "@jest/types" "^27.0.1" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^3.0.0" - picomatch "^2.2.3" - jest-validate@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" @@ -12138,16 +12006,10 @@ jest@26.6.0: import-local "^3.0.2" jest-cli "^26.6.0" -joi@^17.3.0: - version "17.4.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.0.tgz#b5c2277c8519e016316e49ababd41a1908d9ef20" - integrity sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.0" - "@sideway/formula" "^3.0.0" - "@sideway/pinpoint" "^2.0.0" +jpeg-js@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== js-string-escape@^1.0.1: version "1.0.1" @@ -12341,13 +12203,6 @@ killable@^1.0.1: resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== -kind-of@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" - integrity sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU= - dependencies: - is-buffer "^1.0.2" - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -12426,16 +12281,6 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" -lazy-cache@^0.2.3: - version "0.2.7" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" - integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U= - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - lazy-universal-dotenv@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz#a6c8938414bca426ab8c9463940da451a911db38" @@ -13140,15 +12985,6 @@ meow@^9.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-deep@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.3.tgz#1a2b2ae926da8b2ae93a0ac15d90cd1922766003" - integrity sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA== - dependencies: - arr-union "^3.1.0" - clone-deep "^0.2.4" - kind-of "^3.0.2" - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -13292,6 +13128,11 @@ mime@^2.4.4: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== +mime@^2.4.6: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -13344,7 +13185,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -13422,19 +13263,6 @@ mixin-deep@>=1.3.2, mixin-deep@^1.2.0: resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-2.0.1.tgz#9a6946bef4a368401b784970ae3caaaa6bab02fa" integrity sha512-imbHQNRglyaplMmjBLL3V5R6Bfq5oM+ivds3SKgc6oRtzErEnBUUc5No11Z2pilkUvl42gJvi285xTNswcKCMA== -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -13488,6 +13316,11 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -14110,11 +13943,6 @@ os-filter-obj@^2.0.0: dependencies: arch "^2.1.0" -os-homedir@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -14412,11 +14240,6 @@ parse-json@^5.0.0: json-parse-better-errors "^1.0.1" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - parse5-htmlparser2-tree-adapter@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" @@ -14607,6 +14430,13 @@ pirates@^4.0.0, pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" +pixelmatch@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" + integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ== + dependencies: + pngjs "^4.0.1" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -14647,6 +14477,16 @@ platform@^1.3.3: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== +pngjs@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" + integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== + +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + pngquant-bin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/pngquant-bin/-/pngquant-bin-6.0.0.tgz#aff0d7e61095feb96ced379ad8c7294ad3dd1712" @@ -15501,7 +15341,7 @@ pretty-format@^26.0.0, pretty-format@^26.6.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^27.0.1, pretty-format@^27.0.2: +pretty-format@^27.0.2: version "27.0.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.0.2.tgz#9283ff8c4f581b186b2d4da461617143dca478a4" integrity sha512-mXKbbBPnYTG7Yra9qFBtqj+IXcsvxsvOBco3QHxtxTl+hHKq6QdzMZ+q0CtL4ORHZgwGImRr2XZUX2EWzORxig== @@ -15550,7 +15390,7 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.0, progress@^2.0.1: +progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -15595,7 +15435,7 @@ prompts@2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prompts@^2.0.0, prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.1: +prompts@^2.0.0, prompts@^2.0.1, prompts@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== @@ -15612,6 +15452,15 @@ prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, object-assign "^4.1.1" react-is "^16.8.1" +proper-lockfile@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + property-information@^5.0.0, property-information@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943" @@ -15704,24 +15553,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-9.0.0.tgz#b476e17ceb3e33a6667bf682d66dde9898f9c031" - integrity sha512-Avu8SKWQRC1JKNMgfpH7d4KzzHOL/A65jRYrjNU46hxnOYGwqe4zZp/JW8qulaH0Pnbm5qyO3EbSKvqBUlfvkg== - dependencies: - debug "^4.1.0" - devtools-protocol "0.0.869402" - extract-zip "^2.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - pkg-dir "^4.2.0" - progress "^2.0.1" - proxy-from-env "^1.1.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" - q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -16660,14 +16491,6 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" -resolve-dir@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" - integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4= - dependencies: - expand-tilde "^1.2.2" - global-modules "^0.2.3" - resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -16872,7 +16695,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.4.0, rxjs@^6.6.3: +rxjs@^6.4.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -17186,16 +17009,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-clone@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" - integrity sha1-WQnodLp3EG1zrEFM/sH/yofZcGA= - dependencies: - is-extendable "^0.1.1" - kind-of "^2.0.1" - lazy-cache "^0.2.3" - mixin-object "^2.0.1" - shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -17425,6 +17238,13 @@ source-map-support@0.5.19, source-map-support@^0.5.16, source-map-support@^0.5.1 buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.4.18: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + dependencies: + source-map "^0.5.6" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -17455,16 +17275,6 @@ space-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== -spawnd@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/spawnd/-/spawnd-5.0.0.tgz#ea72200bdc468998e84e1c3e7b914ce85fc1c32c" - integrity sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA== - dependencies: - exit "^0.1.2" - signal-exit "^3.0.3" - tree-kill "^1.2.2" - wait-port "^0.2.9" - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -18157,16 +17967,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" @@ -18180,17 +17980,6 @@ tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - tar@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" @@ -18478,11 +18267,6 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -18722,7 +18506,7 @@ typescript@^4.3.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== -unbzip2-stream@^1.0.9, unbzip2-stream@^1.3.3: +unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== @@ -19193,26 +18977,6 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" -wait-on@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.3.0.tgz#584e17d4b3fe7b46ac2b9f8e5e102c005c2776c7" - integrity sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg== - dependencies: - axios "^0.21.1" - joi "^17.3.0" - lodash "^4.17.21" - minimist "^1.2.5" - rxjs "^6.6.3" - -wait-port@^0.2.9: - version "0.2.9" - resolved "https://registry.yarnpkg.com/wait-port/-/wait-port-0.2.9.tgz#3905cf271b5dbe37a85c03b85b418b81cb24ee55" - integrity sha512-hQ/cVKsNqGZ/UbZB/oakOGFqic00YAMM5/PEj3Bt4vKarv2jWIWzDbqlwT94qMs/exAQAsvMOq99sZblV92zxQ== - dependencies: - chalk "^2.4.2" - commander "^3.0.2" - debug "^4.1.1" - walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -19560,7 +19324,7 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@^1.2.12, which@^1.2.9, which@^1.3.1: +which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -19837,7 +19601,7 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.2.3, ws@^7.4.6: +ws@^7.4.6: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== @@ -19956,6 +19720,13 @@ yauzl@^2.10.0, yauzl@^2.4.2: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" +yazl@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"