From 5b506b12608061bd5876ff3acd1122baba61ab08 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 11 May 2022 15:21:52 -0600 Subject: [PATCH 01/54] adding Tailwind E2E tests with Playwright --- .../e2e/fixtures/tailwindcss/astro.config.mjs | 12 ++ .../e2e/fixtures/tailwindcss/package.json | 9 + .../fixtures/tailwindcss/postcss.config.js | 9 + .../tailwindcss/src/components/Button.astro | 10 + .../tailwindcss/src/components/Complex.astro | 1 + .../tailwindcss/src/pages/index.astro | 18 ++ .../tailwindcss/src/pages/markdown-page.md | 11 + .../fixtures/tailwindcss/tailwind.config.js | 14 ++ packages/astro/e2e/tailwindcss.test.js | 98 +++++++++ packages/astro/e2e/test-utils.js | 202 ++++++++++++++++++ packages/astro/package.json | 6 +- 11 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 packages/astro/e2e/fixtures/tailwindcss/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/tailwindcss/package.json create mode 100644 packages/astro/e2e/fixtures/tailwindcss/postcss.config.js create mode 100644 packages/astro/e2e/fixtures/tailwindcss/src/components/Button.astro create mode 100644 packages/astro/e2e/fixtures/tailwindcss/src/components/Complex.astro create mode 100644 packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro create mode 100644 packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md create mode 100644 packages/astro/e2e/fixtures/tailwindcss/tailwind.config.js create mode 100644 packages/astro/e2e/tailwindcss.test.js create mode 100644 packages/astro/e2e/test-utils.js diff --git a/packages/astro/e2e/fixtures/tailwindcss/astro.config.mjs b/packages/astro/e2e/fixtures/tailwindcss/astro.config.mjs new file mode 100644 index 000000000000..473be9666e1e --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import tailwind from '@astrojs/tailwind'; + +// https://astro.build/config +export default defineConfig({ + integrations: [tailwind()], + vite: { + build: { + assetsInlineLimit: 0, + }, + }, +}); diff --git a/packages/astro/e2e/fixtures/tailwindcss/package.json b/packages/astro/e2e/fixtures/tailwindcss/package.json new file mode 100644 index 000000000000..4bcc5687273e --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/e2e-tailwindcss", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/tailwind": "workspace:*" + } +} diff --git a/packages/astro/e2e/fixtures/tailwindcss/postcss.config.js b/packages/astro/e2e/fixtures/tailwindcss/postcss.config.js new file mode 100644 index 000000000000..7df5ecb39683 --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/postcss.config.js @@ -0,0 +1,9 @@ +const path = require('path'); +module.exports = { + plugins: { + tailwindcss: { + config: path.join(__dirname, 'tailwind.config.js'), // update this if your path differs! + }, + autoprefixer: {} + }, +}; diff --git a/packages/astro/e2e/fixtures/tailwindcss/src/components/Button.astro b/packages/astro/e2e/fixtures/tailwindcss/src/components/Button.astro new file mode 100644 index 000000000000..6e0872173994 --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/src/components/Button.astro @@ -0,0 +1,10 @@ +--- +let { type = 'button' } = Astro.props; +--- + + diff --git a/packages/astro/e2e/fixtures/tailwindcss/src/components/Complex.astro b/packages/astro/e2e/fixtures/tailwindcss/src/components/Complex.astro new file mode 100644 index 000000000000..bd30373c8e09 --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/src/components/Complex.astro @@ -0,0 +1 @@ +
diff --git a/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro b/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro new file mode 100644 index 000000000000..d901b4233a9c --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro @@ -0,0 +1,18 @@ +--- +// Component Imports +import Button from '../components/Button.astro'; +import Complex from '../components/Complex.astro'; +--- + + + + + + Astro + TailwindCSS + + + + + + + diff --git a/packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md b/packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md new file mode 100644 index 000000000000..e4c6b6bc9d66 --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/src/pages/markdown-page.md @@ -0,0 +1,11 @@ +--- +title: "Markdown + Tailwind" +setup: | + import Button from '../components/Button.astro'; + import Complex from '../components/Complex.astro'; +--- + +
+ + +
\ No newline at end of file diff --git a/packages/astro/e2e/fixtures/tailwindcss/tailwind.config.js b/packages/astro/e2e/fixtures/tailwindcss/tailwind.config.js new file mode 100644 index 000000000000..7aeb483c1e36 --- /dev/null +++ b/packages/astro/e2e/fixtures/tailwindcss/tailwind.config.js @@ -0,0 +1,14 @@ +const path = require('path'); + +module.exports = { + content: [path.join(__dirname, 'src/**/*.{astro,html,js,jsx,md,svelte,ts,tsx,vue}')], + theme: { + extend: { + colors: { + dawn: '#f3e9fa', + dusk: '#514375', + midnight: '#31274a', + } + } + } +}; diff --git a/packages/astro/e2e/tailwindcss.test.js b/packages/astro/e2e/tailwindcss.test.js new file mode 100644 index 000000000000..bb2c7abba6f0 --- /dev/null +++ b/packages/astro/e2e/tailwindcss.test.js @@ -0,0 +1,98 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({ }, use) => { + const fixture = await loadFixture({ root: './fixtures/tailwindcss/' }); + await use(fixture); + }, +}); + +test.describe('dev', () => { + let devServer; + + test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); + }); + + test.afterAll(async ({ astro }) => { + await devServer.stop(); + }); + + test('Tailwind CSS', async ({ page }) => { + await page.goto(`localhost:${devServer.address.port}/`); + + await test.step('body', async () => { + const body = page.locator('body'); + + await expect(body, 'should have classes').toHaveClass('bg-dawn text-midnight'); + await expect(body, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(243, 233, 250)' + ); + await expect(body, 'should have color').toHaveCSS('color', 'rgb(49, 39, 74)'); + }); + + await test.step('button', async () => { + const button = page.locator('button'); + + await expect(button, 'should have bg-purple-600').toHaveClass(/bg-purple-600/); + await expect(button, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(147, 51, 234)' + ); + + await expect(button, 'should have lg:py-3').toHaveClass(/lg:py-3/); + await expect(button, 'should have padding bottom').toHaveCSS('padding-bottom', '12px'); + await expect(button, 'should have padding top').toHaveCSS('padding-top', '12px'); + + await expect(button, 'should have font-[900]').toHaveClass(/font-\[900\]/); + await expect(button, 'should have font weight').toHaveCSS('font-weight', '900'); + }); + }); +}); + +test.describe('build', () => { + let previewServer; + + test.beforeAll(async ({ astro }) => { + await astro.build(); + previewServer = await astro.preview(); + }); + + test.afterAll(async ({ astro }) => { + await previewServer.stop(); + }) + + test('Tailwind CSS', async ({ page }) => { + await page.goto(`localhost:3000/`); + + await test.step('body', async () => { + const body = page.locator('body'); + + await expect(body, 'should have classes').toHaveClass('bg-dawn text-midnight'); + await expect(body, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(243, 233, 250)' + ); + await expect(body, 'should have color').toHaveCSS('color', 'rgb(49, 39, 74)'); + }); + + await test.step('button', async () => { + const button = page.locator('button'); + + await expect(button, 'should have bg-purple-600').toHaveClass(/bg-purple-600/); + await expect(button, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(147, 51, 234)' + ); + + await expect(button, 'should have lg:py-3').toHaveClass(/lg:py-3/); + await expect(button, 'should have padding bottom').toHaveCSS('padding-bottom', '12px'); + await expect(button, 'should have padding top').toHaveCSS('padding-top', '12px'); + + await expect(button, 'should have font-[900]').toHaveClass(/font-\[900\]/); + await expect(button, 'should have font weight').toHaveCSS('font-weight', '900'); + }); + }); +}); diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js new file mode 100644 index 000000000000..460b3185f087 --- /dev/null +++ b/packages/astro/e2e/test-utils.js @@ -0,0 +1,202 @@ +import { execa } from 'execa'; +import { polyfill } from '@astrojs/webapi'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { resolveConfig, loadConfig } from '../dist/core/config.js'; +import dev from '../dist/core/dev/index.js'; +import build from '../dist/core/build/index.js'; +import preview from '../dist/core/preview/index.js'; +import { nodeLogDestination } from '../dist/core/logger/node.js'; +import os from 'os'; +import stripAnsi from 'strip-ansi'; + +// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16 +polyfill(globalThis, { + exclude: 'window document', +}); + +/** + * @typedef {import('node-fetch').Response} Response + * @typedef {import('../src/core/dev/index').DevServer} DevServer + * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig + * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer + * @typedef {import('../src/core/app/index').App} App + * + * + * @typedef {Object} Fixture + * @property {typeof build} build + * @property {(url: string, opts: any) => Promise} fetch + * @property {(path: string) => Promise} readFile + * @property {(path: string) => Promise} readdir + * @property {() => Promise} startDevServer + * @property {() => Promise} preview + * @property {() => Promise} clean + * @property {() => Promise} loadTestAdapterApp + */ + +/** + * Load Astro fixture + * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) + * @returns {Promise} The fixture. Has the following properties: + * .config - Returns the final config. Will be automatically passed to the methods below: + * + * Build + * .build() - Async. Builds into current folder (will erase previous build) + * .readFile(path) - Async. Read a file from the build. + * + * Dev + * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. + * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) + * + * Preview + * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit + * + * Clean-up + * .clean() - Async. Removes the project’s dist folder. + */ +export async function loadFixture(inlineConfig) { + if (!inlineConfig || !inlineConfig.root) + throw new Error("Must provide { root: './fixtures/...' }"); + + // load config + let cwd = inlineConfig.root; + delete inlineConfig.root; + if (typeof cwd === 'string') { + try { + cwd = new URL(cwd.replace(/\/?$/, '/')); + } catch (err1) { + cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); + } + } + // Load the config. + let config = await loadConfig({ cwd: fileURLToPath(cwd) }); + config = merge(config, { ...inlineConfig, root: cwd }); + + // Note: the inline config doesn't run through config validation where these normalizations usually occur + if (typeof inlineConfig.site === 'string') { + config.site = new URL(inlineConfig.site); + } + if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { + config.base = inlineConfig.base + '/'; + } + + /** @type {import('../src/core/logger/core').LogOptions} */ + const logging = { + dest: nodeLogDestination, + level: 'error', + }; + + /** @type {import('@astrojs/telemetry').AstroTelemetry} */ + const telemetry = { + record() { + return Promise.resolve(); + }, + }; + + return { + build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), + startDevServer: async (opts = {}) => { + const devResult = await dev(config, { logging, telemetry, ...opts }); + config.server.port = devResult.address.port; // update port + return devResult; + }, + config, + fetch: (url, init) => + fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), + preview: async (opts = {}) => { + const previewServer = await preview(config, { logging, telemetry, ...opts }); + return previewServer; + }, + readFile: (filePath) => + fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), + readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), + clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), + loadTestAdapterApp: async () => { + const url = new URL('./server/entry.mjs', config.outDir); + const { createApp } = await import(url); + return createApp(); + }, + }; +} + +/** + * Basic object merge utility. Returns new copy of merged Object. + * @param {Object} a + * @param {Object} b + * @returns {Object} + */ +function merge(a, b) { + const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); + const c = {}; + for (const k of allKeys) { + const needsObjectMerge = + typeof a[k] === 'object' && + typeof b[k] === 'object' && + (Object.keys(a[k]).length || Object.keys(b[k]).length) && + !Array.isArray(a[k]) && + !Array.isArray(b[k]); + if (needsObjectMerge) { + c[k] = merge(a[k] || {}, b[k] || {}); + continue; + } + c[k] = a[k]; + if (b[k] !== undefined) c[k] = b[k]; + } + return c; +} + +const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); + +/** Returns a process running the Astro CLI. */ +export function cli(/** @type {string[]} */ ...args) { + const spawned = execa('node', [cliPath, ...args]); + + spawned.stdout.setEncoding('utf8'); + + return spawned; +} + +export async function parseCliDevStart(proc) { + let stdout = ''; + let stderr = ''; + + for await (const chunk of proc.stdout) { + stdout += chunk; + if (chunk.includes('Local')) break; + } + if (!stdout) { + for await (const chunk of proc.stderr) { + stderr += chunk; + break; + } + } + + proc.kill(); + stdout = stripAnsi(stdout); + stderr = stripAnsi(stderr); + + if (stderr) { + throw new Error(stderr); + } + + const messages = stdout + .split('\n') + .filter((ln) => !!ln.trim()) + .map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim()); + + return { messages }; +} + +export async function cliServerLogSetup(flags = [], cmd = 'dev') { + const proc = cli(cmd, ...flags); + + const { messages } = await parseCliDevStart(proc); + + const local = messages.find((msg) => msg.includes('Local'))?.replace(/Local\s*/g, ''); + const network = messages.find((msg) => msg.includes('Network'))?.replace(/Network\s*/g, ''); + + return { local, network }; +} + +export const isWindows = os.platform() === 'win32'; diff --git a/packages/astro/package.json b/packages/astro/package.json index 86939565e7e0..c4d266a5d21c 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -72,7 +72,8 @@ "postbuild": "astro-scripts copy \"src/**/*.astro\"", "benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js", "test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js", - "test:match": "mocha --timeout 20000 -g" + "test:match": "mocha --timeout 20000 -g", + "test:e2e": "playwright test e2e" }, "dependencies": { "@astrojs/compiler": "^0.14.2", @@ -134,7 +135,8 @@ "zod": "^3.16.0" }, "devDependencies": { - "@babel/types": "^7.17.10", + "@babel/types": "^7.17.0", + "@playwright/test": "^1.21.1", "@types/babel__core": "^7.1.19", "@types/babel__generator": "^7.6.4", "@types/babel__traverse": "^7.17.1", From 4199c8a7b8662fac7897ce36f3a78e7e28b98abc Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 11 May 2022 15:22:01 -0600 Subject: [PATCH 02/54] package.json updates --- package.json | 1 + pnpm-lock.yaml | 332 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 331 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b1ca3d673bf5..9508c7b0b833 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "test:templates": "turbo run test --filter=create-astro --concurrency=1", "test:smoke": "node scripts/smoke/index.js", "test:vite-ci": "turbo run test --no-deps --scope=astro --concurrency=1", + "test:e2e": "cd packages/astro && pnpm run test:e2e", "benchmark": "turbo run benchmark --scope=astro", "lint": "eslint \"packages/**/*.ts\"", "format": "prettier -w .", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f81977c745a..b9b082a7f6e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,6 +659,14 @@ importers: chai-as-promised: 7.1.1_chai@4.3.6 mocha: 9.2.2 + packages/astro/e2e/fixtures/basic: + specifiers: + '@astrojs/tailwind': workspace:* + astro: workspace:* + dependencies: + '@astrojs/tailwind': link:../../../../integrations/tailwind + astro: link:../../.. + packages/astro/test/fixtures/0-css: specifiers: '@astrojs/react': workspace:* @@ -3160,6 +3168,23 @@ packages: esutils: 2.0.3 dev: true + /@babel/preset-typescript/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-validator-option': 7.16.7 + '@babel/plugin-transform-typescript': 7.16.8_@babel+core@7.16.12 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/runtime/7.17.9: resolution: {integrity: sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==} engines: {node: '>=6.9.0'} @@ -3771,6 +3796,51 @@ packages: '@octokit/openapi-types': 11.2.0 dev: true + /@playwright/test/1.21.1: + resolution: {integrity: sha512-XkkTXl5gvEm4fciqeHvY5IuSS/OfQef0MO6RpBNmtm6EuYSdtUvP/sDVuWRKsDqyVdB3WSA0az7iSw79f2//JQ==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@babel/code-frame': 7.16.7 + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-proposal-class-properties': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-dynamic-import': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-export-namespace-from': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-logical-assignment-operators': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-numeric-separator': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-optional-chaining': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-proposal-private-methods': 7.16.11_@babel+core@7.16.12 + '@babel/plugin-proposal-private-property-in-object': 7.16.7_@babel+core@7.16.12 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.16.12 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.16.12 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.16.12 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.16.12 + '@babel/plugin-transform-modules-commonjs': 7.16.8_@babel+core@7.16.12 + '@babel/preset-typescript': 7.16.7_@babel+core@7.16.12 + colors: 1.4.0 + commander: 8.3.0 + debug: 4.3.3 + expect: 27.2.5 + jest-matcher-utils: 27.2.5 + json5: 2.2.1 + mime: 3.0.0 + minimatch: 3.0.4 + ms: 2.1.3 + open: 8.4.0 + pirates: 4.0.4 + playwright-core: 1.21.1 + rimraf: 3.0.2 + source-map-support: 0.4.18 + stack-utils: 2.0.5 + yazl: 2.5.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /@polka/url/1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} @@ -4054,6 +4124,22 @@ packages: ci-info: 3.3.1 dev: true + /@types/istanbul-lib-coverage/2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: true + + /@types/istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + dev: true + + /@types/istanbul-reports/3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + dev: true + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -4216,6 +4302,10 @@ packages: '@types/node': 17.0.32 dev: true + /@types/stack-utils/2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: true + /@types/tailwindcss/3.0.10: resolution: {integrity: sha512-1UnZIHO0NOPyPlPFV0HuMjki2SHkvG9uBA1ZehWj/OQMSROk503nuNyyfmJSIT289yewxTbKoPG+KLxYRvfIIg==} dev: true @@ -4739,6 +4829,11 @@ packages: dependencies: color-convert: 2.0.1 + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /ansi-styles/6.1.0: resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} engines: {node: '>=12'} @@ -5036,7 +5131,6 @@ packages: /buffer-crc32/0.2.13: resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=} - dev: false /buffer-from/1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5328,6 +5422,11 @@ packages: resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==} dev: true + /colors/1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + dev: true + /comma-separated-tokens/2.0.2: resolution: {integrity: sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==} @@ -5335,6 +5434,11 @@ packages: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true + /commander/8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: true + /common-ancestor-path/1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} dev: false @@ -5574,6 +5678,11 @@ packages: dependencies: clone: 1.0.4 + /define-lazy-prop/2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: true + /define-properties/1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} engines: {node: '>= 0.4'} @@ -5659,6 +5768,11 @@ packages: /didyoumean/1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + /diff-sequences/27.5.1: + resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: true + /diff/5.0.0: resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} engines: {node: '>=0.3.1'} @@ -6023,6 +6137,11 @@ packages: resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + /escape-string-regexp/4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -6265,6 +6384,18 @@ packages: engines: {node: '>=6'} dev: true + /expect/27.2.5: + resolution: {integrity: sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + ansi-styles: 5.2.0 + jest-get-type: 27.5.1 + jest-matcher-utils: 27.2.5 + jest-message-util: 27.5.1 + jest-regex-util: 27.5.1 + dev: true + /extend-shallow/2.0.1: resolution: {integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=} engines: {node: '>=0.10.0'} @@ -6288,6 +6419,20 @@ packages: tmp: 0.0.33 dev: true + /extract-zip/2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.3.4 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.0 + transitivePeerDependencies: + - supports-color + dev: true + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -6326,6 +6471,12 @@ packages: dependencies: reusify: 1.0.4 + /fd-slicer/1.1.0: + resolution: {integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=} + dependencies: + pend: 1.2.0 + dev: true + /fetch-blob/3.1.5: resolution: {integrity: sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==} engines: {node: ^12.20 || >= 14.13} @@ -6550,6 +6701,13 @@ packages: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} dev: true + /get-stream/5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: true + /get-stream/6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -6912,6 +7070,16 @@ packages: - supports-color dev: true + /https-proxy-agent/5.0.0: + resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /https-proxy-agent/5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -7321,6 +7489,51 @@ packages: minimatch: 3.1.2 dev: true + /jest-diff/27.5.1: + resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + dev: true + + /jest-get-type/27.5.1: + resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: true + + /jest-matcher-utils/27.2.5: + resolution: {integrity: sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + dev: true + + /jest-message-util/27.5.1: + resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@babel/code-frame': 7.16.7 + '@jest/types': 27.5.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 27.5.1 + slash: 3.0.0 + stack-utils: 2.0.5 + dev: true + + /jest-regex-util/27.5.1: + resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: true + /jest-worker/26.6.2: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} @@ -7335,6 +7548,10 @@ packages: hasBin: true dev: true + /jpeg-js/0.4.3: + resolution: {integrity: sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==} + dev: true + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -8103,7 +8320,6 @@ packages: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} hasBin: true - dev: false /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -8122,6 +8338,12 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + /minimatch/3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + dependencies: + brace-expansion: 1.1.11 + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -8517,6 +8739,15 @@ packages: is-wsl: 2.2.0 dev: true + /open/8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + /optionator/0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} engines: {node: '>= 0.8.0'} @@ -8767,6 +8998,10 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true + /pend/1.2.0: + resolution: {integrity: sha1-elfrVQpng/kRUzH89GY9XI4AelA=} + dev: true + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -8778,6 +9013,18 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + /pirates/4.0.4: + resolution: {integrity: sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==} + engines: {node: '>= 6'} + dev: true + + /pixelmatch/5.2.1: + resolution: {integrity: sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==} + hasBin: true + dependencies: + pngjs: 4.0.1 + dev: true + /pkg-dir/4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -8936,6 +9183,15 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} dev: true + /pretty-format/27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + /pretty-format/3.8.0: resolution: {integrity: sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=} dev: false @@ -8947,6 +9203,11 @@ packages: /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + /progress/2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: true + /prompts/2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -8955,6 +9216,14 @@ packages: sisteransi: 1.0.5 dev: false + /proper-lockfile/4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + dependencies: + graceful-fs: 4.2.10 + retry: 0.12.0 + signal-exit: 3.0.7 + dev: true + /property-information/6.1.1: resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==} @@ -9050,6 +9319,10 @@ packages: react: 18.1.0 scheduler: 0.22.0 + /react-is/17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true + /react/17.0.2: resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} engines: {node: '>=0.10.0'} @@ -9367,6 +9640,11 @@ packages: unified: 10.1.2 dev: false + /retry/0.12.0: + resolution: {integrity: sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=} + engines: {node: '>= 4'} + dev: true + /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -9664,6 +9942,17 @@ packages: - supports-color dev: true + /socks-proxy-agent/6.1.1: + resolution: {integrity: sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.6.2 + transitivePeerDependencies: + - supports-color + dev: true + /socks/2.6.2: resolution: {integrity: sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==} engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} @@ -9696,6 +9985,12 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-support/0.4.18: + resolution: {integrity: sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==} + dependencies: + source-map: 0.5.7 + dev: true + /source-map-support/0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: @@ -9766,6 +10061,13 @@ packages: resolution: {integrity: sha512-JWp4cG2eybkvKA1QUHGoNK6JDEYcOnSuhzNGjZuYUPqXreDl/VkkvP2sZW7Rmh+icuCttrR9ccb2WPIazyM/Cw==} dev: true + /stack-utils/2.0.5: + resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + /statuses/2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -11149,6 +11451,19 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + /ws/8.4.2: + resolution: {integrity: sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /xregexp/2.0.0: resolution: {integrity: sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=} dev: true @@ -11238,6 +11553,19 @@ packages: yargs-parser: 20.2.4 dev: true + /yauzl/2.10.0: + resolution: {integrity: sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + + /yazl/2.5.1: + resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} + dependencies: + buffer-crc32: 0.2.13 + dev: true + /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} From 29cc61cb3c8f999b6b35140a70ad6d51b7a11364 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 11 May 2022 15:38:19 -0600 Subject: [PATCH 03/54] adding e2e tests to CI workflow --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb9d6cbd864c..16791b110060 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,6 +153,48 @@ jobs: - name: Test run: pnpm run test + e2e: + name: 'E2E: ${{ matrix.os }} (node@${{ matrix.node_version }})' + runs-on: ${{ matrix.os }} + env: + ASTRO_TELEMETRY_DISABLED: true + strategy: + matrix: + os: [ubuntu-latest] + node_version: [14, 16] + include: + - os: windows-latest + node_version: 16 + - os: macos-latest + node_version: 16 + fail-fast: false + needs: + - build + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PNPM + uses: pnpm/action-setup@v2.2.1 + + - name: Setup node@${{ matrix.node_version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node_version }} + cache: 'pnpm' + + - name: Download Build Artifacts + uses: actions/download-artifact@v3 + + - name: Extract Artifacts + run: ./.github/extract-artifacts.sh + + - name: Install dependencies + run: pnpm install + + - name: Test + run: pnpm run test:e2e + smoke: name: 'Test (Smoke) ${{ matrix.os }}' runs-on: ${{ matrix.os }} From e105bacea954582a1dfe518bafd17d2cd8add277 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 11 May 2022 15:39:53 -0600 Subject: [PATCH 04/54] using e2e for dev tests, mocha for build tests --- packages/astro/e2e/tailwindcss.test.js | 107 +++++++----------------- packages/astro/test/tailwindcss.test.js | 46 ---------- 2 files changed, 30 insertions(+), 123 deletions(-) diff --git a/packages/astro/e2e/tailwindcss.test.js b/packages/astro/e2e/tailwindcss.test.js index bb2c7abba6f0..8d643e7749ce 100644 --- a/packages/astro/e2e/tailwindcss.test.js +++ b/packages/astro/e2e/tailwindcss.test.js @@ -2,97 +2,50 @@ import { test as base, expect } from '@playwright/test'; import { loadFixture } from './test-utils.js'; const test = base.extend({ - astro: async ({ }, use) => { + astro: async ({}, use) => { const fixture = await loadFixture({ root: './fixtures/tailwindcss/' }); await use(fixture); }, }); -test.describe('dev', () => { - let devServer; +let devServer; - test.beforeAll(async ({ astro }) => { - devServer = await astro.startDevServer(); - }); - - test.afterAll(async ({ astro }) => { - await devServer.stop(); - }); - - test('Tailwind CSS', async ({ page }) => { - await page.goto(`localhost:${devServer.address.port}/`); - - await test.step('body', async () => { - const body = page.locator('body'); - - await expect(body, 'should have classes').toHaveClass('bg-dawn text-midnight'); - await expect(body, 'should have background color').toHaveCSS( - 'background-color', - 'rgb(243, 233, 250)' - ); - await expect(body, 'should have color').toHaveCSS('color', 'rgb(49, 39, 74)'); - }); - - await test.step('button', async () => { - const button = page.locator('button'); - - await expect(button, 'should have bg-purple-600').toHaveClass(/bg-purple-600/); - await expect(button, 'should have background color').toHaveCSS( - 'background-color', - 'rgb(147, 51, 234)' - ); - - await expect(button, 'should have lg:py-3').toHaveClass(/lg:py-3/); - await expect(button, 'should have padding bottom').toHaveCSS('padding-bottom', '12px'); - await expect(button, 'should have padding top').toHaveCSS('padding-top', '12px'); - - await expect(button, 'should have font-[900]').toHaveClass(/font-\[900\]/); - await expect(button, 'should have font weight').toHaveCSS('font-weight', '900'); - }); - }); +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); }); -test.describe('build', () => { - let previewServer; - - test.beforeAll(async ({ astro }) => { - await astro.build(); - previewServer = await astro.preview(); - }); - - test.afterAll(async ({ astro }) => { - await previewServer.stop(); - }) +test.afterAll(async ({ astro }) => { + await devServer.stop(); +}); - test('Tailwind CSS', async ({ page }) => { - await page.goto(`localhost:3000/`); +test('Tailwind CSS', async ({ page }) => { + await page.goto(`localhost:${devServer.address.port}/`); - await test.step('body', async () => { - const body = page.locator('body'); + await test.step('body', async () => { + const body = page.locator('body'); - await expect(body, 'should have classes').toHaveClass('bg-dawn text-midnight'); - await expect(body, 'should have background color').toHaveCSS( - 'background-color', - 'rgb(243, 233, 250)' - ); - await expect(body, 'should have color').toHaveCSS('color', 'rgb(49, 39, 74)'); - }); + await expect(body, 'should have classes').toHaveClass('bg-dawn text-midnight'); + await expect(body, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(243, 233, 250)' + ); + await expect(body, 'should have color').toHaveCSS('color', 'rgb(49, 39, 74)'); + }); - await test.step('button', async () => { - const button = page.locator('button'); + await test.step('button', async () => { + const button = page.locator('button'); - await expect(button, 'should have bg-purple-600').toHaveClass(/bg-purple-600/); - await expect(button, 'should have background color').toHaveCSS( - 'background-color', - 'rgb(147, 51, 234)' - ); + await expect(button, 'should have bg-purple-600').toHaveClass(/bg-purple-600/); + await expect(button, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(147, 51, 234)' + ); - await expect(button, 'should have lg:py-3').toHaveClass(/lg:py-3/); - await expect(button, 'should have padding bottom').toHaveCSS('padding-bottom', '12px'); - await expect(button, 'should have padding top').toHaveCSS('padding-top', '12px'); + await expect(button, 'should have lg:py-3').toHaveClass(/lg:py-3/); + await expect(button, 'should have padding bottom').toHaveCSS('padding-bottom', '12px'); + await expect(button, 'should have padding top').toHaveCSS('padding-top', '12px'); - await expect(button, 'should have font-[900]').toHaveClass(/font-\[900\]/); - await expect(button, 'should have font weight').toHaveCSS('font-weight', '900'); - }); + await expect(button, 'should have font-[900]').toHaveClass(/font-\[900\]/); + await expect(button, 'should have font weight').toHaveCSS('font-weight', '900'); }); }); diff --git a/packages/astro/test/tailwindcss.test.js b/packages/astro/test/tailwindcss.test.js index c8fad9f4ca37..2facca9e4c56 100644 --- a/packages/astro/test/tailwindcss.test.js +++ b/packages/astro/test/tailwindcss.test.js @@ -70,50 +70,4 @@ describe('Tailwind', () => { expect(bundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/); }); }); - - // with "build" handling CSS checking, the dev tests are mostly testing the paths resolve in dev - describe('dev', () => { - let devServer; - let $; - - before(async () => { - devServer = await fixture.startDevServer(); - const html = await fixture.fetch('/').then((res) => res.text()); - $ = cheerio.load(html); - }); - - after(async () => { - devServer && (await devServer.stop()); - }); - - it('resolves CSS in src/styles', async () => { - const bundledCSSHREF = $('link[rel=stylesheet]').attr('href'); - const res = await fixture.fetch(bundledCSSHREF); - expect(res.status).to.equal(200); - - const text = await res.text(); - expect(text, 'includes used component classes').to.match(/\.bg-purple-600/); - - // tests a random tailwind class that isn't used on the page - expect(text, 'purges unused classes').not.to.match(/\.bg-blue-600/); - - // tailwind escapes colons, `lg:py-3` compiles to `lg\:py-3` - expect(text, 'includes responsive classes').to.match(/\.lg\\\\:py-3/); - - // tailwind escapes brackets, `font-[900]` compiles to `font-\[900\]` - expect(text, 'supports arbitrary value classes').to.match(/.font-\\[900\\]/); - - // custom theme colors were included - expect(text, 'includes custom theme colors').to.match(/\.text-midnight/); - expect(text, 'includes custom theme colors').to.match(/\.bg-dawn/); - }); - - it('maintains classes in HTML', async () => { - const button = $('button'); - - expect(button.hasClass('text-white'), 'basic class').to.be.true; - expect(button.hasClass('lg:py-3'), 'responsive class').to.be.true; - expect(button.hasClass('font-[900]', 'arbitrary value')).to.be.true; - }); - }); }); From c854dffda706cc76c6628ee6ec8e52af1f1d8e51 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 11 May 2022 15:51:53 -0600 Subject: [PATCH 05/54] refactor: sharing test-utils helpers --- packages/astro/e2e/test-utils.js | 203 +---------------------------- packages/astro/test-utils.js | 204 ++++++++++++++++++++++++++++++ packages/astro/test/test-utils.js | 136 +------------------- pnpm-lock.yaml | 2 +- 4 files changed, 209 insertions(+), 336 deletions(-) create mode 100644 packages/astro/test-utils.js diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js index 460b3185f087..158a61d5dd3c 100644 --- a/packages/astro/e2e/test-utils.js +++ b/packages/astro/e2e/test-utils.js @@ -1,202 +1,3 @@ -import { execa } from 'execa'; -import { polyfill } from '@astrojs/webapi'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { resolveConfig, loadConfig } from '../dist/core/config.js'; -import dev from '../dist/core/dev/index.js'; -import build from '../dist/core/build/index.js'; -import preview from '../dist/core/preview/index.js'; -import { nodeLogDestination } from '../dist/core/logger/node.js'; -import os from 'os'; -import stripAnsi from 'strip-ansi'; +import { createFixtureLoader } from '../test-utils.js'; -// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16 -polyfill(globalThis, { - exclude: 'window document', -}); - -/** - * @typedef {import('node-fetch').Response} Response - * @typedef {import('../src/core/dev/index').DevServer} DevServer - * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig - * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer - * @typedef {import('../src/core/app/index').App} App - * - * - * @typedef {Object} Fixture - * @property {typeof build} build - * @property {(url: string, opts: any) => Promise} fetch - * @property {(path: string) => Promise} readFile - * @property {(path: string) => Promise} readdir - * @property {() => Promise} startDevServer - * @property {() => Promise} preview - * @property {() => Promise} clean - * @property {() => Promise} loadTestAdapterApp - */ - -/** - * Load Astro fixture - * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) - * @returns {Promise} The fixture. Has the following properties: - * .config - Returns the final config. Will be automatically passed to the methods below: - * - * Build - * .build() - Async. Builds into current folder (will erase previous build) - * .readFile(path) - Async. Read a file from the build. - * - * Dev - * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. - * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) - * - * Preview - * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit - * - * Clean-up - * .clean() - Async. Removes the project’s dist folder. - */ -export async function loadFixture(inlineConfig) { - if (!inlineConfig || !inlineConfig.root) - throw new Error("Must provide { root: './fixtures/...' }"); - - // load config - let cwd = inlineConfig.root; - delete inlineConfig.root; - if (typeof cwd === 'string') { - try { - cwd = new URL(cwd.replace(/\/?$/, '/')); - } catch (err1) { - cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); - } - } - // Load the config. - let config = await loadConfig({ cwd: fileURLToPath(cwd) }); - config = merge(config, { ...inlineConfig, root: cwd }); - - // Note: the inline config doesn't run through config validation where these normalizations usually occur - if (typeof inlineConfig.site === 'string') { - config.site = new URL(inlineConfig.site); - } - if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { - config.base = inlineConfig.base + '/'; - } - - /** @type {import('../src/core/logger/core').LogOptions} */ - const logging = { - dest: nodeLogDestination, - level: 'error', - }; - - /** @type {import('@astrojs/telemetry').AstroTelemetry} */ - const telemetry = { - record() { - return Promise.resolve(); - }, - }; - - return { - build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), - startDevServer: async (opts = {}) => { - const devResult = await dev(config, { logging, telemetry, ...opts }); - config.server.port = devResult.address.port; // update port - return devResult; - }, - config, - fetch: (url, init) => - fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), - preview: async (opts = {}) => { - const previewServer = await preview(config, { logging, telemetry, ...opts }); - return previewServer; - }, - readFile: (filePath) => - fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), - readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), - clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), - loadTestAdapterApp: async () => { - const url = new URL('./server/entry.mjs', config.outDir); - const { createApp } = await import(url); - return createApp(); - }, - }; -} - -/** - * Basic object merge utility. Returns new copy of merged Object. - * @param {Object} a - * @param {Object} b - * @returns {Object} - */ -function merge(a, b) { - const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); - const c = {}; - for (const k of allKeys) { - const needsObjectMerge = - typeof a[k] === 'object' && - typeof b[k] === 'object' && - (Object.keys(a[k]).length || Object.keys(b[k]).length) && - !Array.isArray(a[k]) && - !Array.isArray(b[k]); - if (needsObjectMerge) { - c[k] = merge(a[k] || {}, b[k] || {}); - continue; - } - c[k] = a[k]; - if (b[k] !== undefined) c[k] = b[k]; - } - return c; -} - -const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); - -/** Returns a process running the Astro CLI. */ -export function cli(/** @type {string[]} */ ...args) { - const spawned = execa('node', [cliPath, ...args]); - - spawned.stdout.setEncoding('utf8'); - - return spawned; -} - -export async function parseCliDevStart(proc) { - let stdout = ''; - let stderr = ''; - - for await (const chunk of proc.stdout) { - stdout += chunk; - if (chunk.includes('Local')) break; - } - if (!stdout) { - for await (const chunk of proc.stderr) { - stderr += chunk; - break; - } - } - - proc.kill(); - stdout = stripAnsi(stdout); - stderr = stripAnsi(stderr); - - if (stderr) { - throw new Error(stderr); - } - - const messages = stdout - .split('\n') - .filter((ln) => !!ln.trim()) - .map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim()); - - return { messages }; -} - -export async function cliServerLogSetup(flags = [], cmd = 'dev') { - const proc = cli(cmd, ...flags); - - const { messages } = await parseCliDevStart(proc); - - const local = messages.find((msg) => msg.includes('Local'))?.replace(/Local\s*/g, ''); - const network = messages.find((msg) => msg.includes('Network'))?.replace(/Network\s*/g, ''); - - return { local, network }; -} - -export const isWindows = os.platform() === 'win32'; +export const loadFixture = createFixtureLoader('e2e'); diff --git a/packages/astro/test-utils.js b/packages/astro/test-utils.js new file mode 100644 index 000000000000..0268925591c1 --- /dev/null +++ b/packages/astro/test-utils.js @@ -0,0 +1,204 @@ +import { execa } from 'execa'; +import { polyfill } from '@astrojs/webapi'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { resolveConfig, loadConfig } from './dist/core/config.js'; +import dev from './dist/core/dev/index.js'; +import build from './dist/core/build/index.js'; +import preview from './dist/core/preview/index.js'; +import { nodeLogDestination } from './dist/core/logger/node.js'; +import os from 'os'; +import stripAnsi from 'strip-ansi'; + +// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16 +polyfill(globalThis, { + exclude: 'window document', +}); + +/** + * @typedef {import('node-fetch').Response} Response + * @typedef {import('../src/core/dev/index').DevServer} DevServer + * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig + * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer + * @typedef {import('../src/core/app/index').App} App + * + * + * @typedef {Object} Fixture + * @property {typeof build} build + * @property {(url: string, opts: any) => Promise} fetch + * @property {(path: string) => Promise} readFile + * @property {(path: string) => Promise} readdir + * @property {() => Promise} startDevServer + * @property {() => Promise} preview + * @property {() => Promise} clean + * @property {() => Promise} loadTestAdapterApp + */ + +export function createFixtureLoader(root) { + /** + * Load Astro fixture + * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) + * @returns {Promise} The fixture. Has the following properties: + * .config - Returns the final config. Will be automatically passed to the methods below: + * + * Build + * .build() - Async. Builds into current folder (will erase previous build) + * .readFile(path) - Async. Read a file from the build. + * + * Dev + * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. + * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) + * + * Preview + * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit + * + * Clean-up + * .clean() - Async. Removes the project’s dist folder. + */ + return async function loadFixture(inlineConfig) { + if (!inlineConfig || !inlineConfig.root) + throw new Error("Must provide { root: './fixtures/...' }"); + + // load config + let cwd = path.join(root, inlineConfig.root); + delete inlineConfig.root; + if (typeof cwd === 'string') { + try { + cwd = new URL(cwd.replace(/\/?$/, '/')); + } catch (err1) { + cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); + } + } + // Load the config. + let config = await loadConfig({ cwd: fileURLToPath(cwd) }); + config = merge(config, { ...inlineConfig, root: cwd }); + + // Note: the inline config doesn't run through config validation where these normalizations usually occur + if (typeof inlineConfig.site === 'string') { + config.site = new URL(inlineConfig.site); + } + if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { + config.base = inlineConfig.base + '/'; + } + + /** @type {import('../src/core/logger/core').LogOptions} */ + const logging = { + dest: nodeLogDestination, + level: 'error', + }; + + /** @type {import('@astrojs/telemetry').AstroTelemetry} */ + const telemetry = { + record() { + return Promise.resolve(); + }, + }; + + return { + build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), + startDevServer: async (opts = {}) => { + const devResult = await dev(config, { logging, telemetry, ...opts }); + config.server.port = devResult.address.port; // update port + return devResult; + }, + config, + fetch: (url, init) => + fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), + preview: async (opts = {}) => { + const previewServer = await preview(config, { logging, telemetry, ...opts }); + return previewServer; + }, + readFile: (filePath) => + fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), + readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), + clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), + loadTestAdapterApp: async () => { + const url = new URL('./server/entry.mjs', config.outDir); + const { createApp } = await import(url); + return createApp(); + }, + }; + } +} + +/** + * Basic object merge utility. Returns new copy of merged Object. + * @param {Object} a + * @param {Object} b + * @returns {Object} + */ +function merge(a, b) { + const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); + const c = {}; + for (const k of allKeys) { + const needsObjectMerge = + typeof a[k] === 'object' && + typeof b[k] === 'object' && + (Object.keys(a[k]).length || Object.keys(b[k]).length) && + !Array.isArray(a[k]) && + !Array.isArray(b[k]); + if (needsObjectMerge) { + c[k] = merge(a[k] || {}, b[k] || {}); + continue; + } + c[k] = a[k]; + if (b[k] !== undefined) c[k] = b[k]; + } + return c; +} + +const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); + +/** Returns a process running the Astro CLI. */ +export function cli(/** @type {string[]} */ ...args) { + const spawned = execa('node', [cliPath, ...args]); + + spawned.stdout.setEncoding('utf8'); + + return spawned; +} + +export async function parseCliDevStart(proc) { + let stdout = ''; + let stderr = ''; + + for await (const chunk of proc.stdout) { + stdout += chunk; + if (chunk.includes('Local')) break; + } + if (!stdout) { + for await (const chunk of proc.stderr) { + stderr += chunk; + break; + } + } + + proc.kill(); + stdout = stripAnsi(stdout); + stderr = stripAnsi(stderr); + + if (stderr) { + throw new Error(stderr); + } + + const messages = stdout + .split('\n') + .filter((ln) => !!ln.trim()) + .map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim()); + + return { messages }; +} + +export async function cliServerLogSetup(flags = [], cmd = 'dev') { + const proc = cli(cmd, ...flags); + + const { messages } = await parseCliDevStart(proc); + + const local = messages.find((msg) => msg.includes('Local'))?.replace(/Local\s*/g, ''); + const network = messages.find((msg) => msg.includes('Network'))?.replace(/Network\s*/g, ''); + + return { local, network }; +} + +export const isWindows = os.platform() === 'win32'; diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 267e4039f62c..c3c5146a8aba 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -1,12 +1,8 @@ import { execa } from 'execa'; import { polyfill } from '@astrojs/webapi'; -import fs from 'fs'; import { fileURLToPath } from 'url'; -import { resolveConfig, loadConfig } from '../dist/core/config.js'; -import dev from '../dist/core/dev/index.js'; import build from '../dist/core/build/index.js'; -import preview from '../dist/core/preview/index.js'; -import { nodeLogDestination } from '../dist/core/logger/node.js'; +import { createFixtureLoader } from '../test-utils.js'; import os from 'os'; import stripAnsi from 'strip-ansi'; @@ -15,135 +11,7 @@ polyfill(globalThis, { exclude: 'window document', }); -/** - * @typedef {import('node-fetch').Response} Response - * @typedef {import('../src/core/dev/index').DevServer} DevServer - * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig - * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer - * @typedef {import('../src/core/app/index').App} App - * - * - * @typedef {Object} Fixture - * @property {typeof build} build - * @property {(url: string, opts: any) => Promise} fetch - * @property {(path: string) => Promise} readFile - * @property {(path: string) => Promise} readdir - * @property {() => Promise} startDevServer - * @property {() => Promise} preview - * @property {() => Promise} clean - * @property {() => Promise} loadTestAdapterApp - */ - -/** - * Load Astro fixture - * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) - * @returns {Promise} The fixture. Has the following properties: - * .config - Returns the final config. Will be automatically passed to the methods below: - * - * Build - * .build() - Async. Builds into current folder (will erase previous build) - * .readFile(path) - Async. Read a file from the build. - * - * Dev - * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. - * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) - * - * Preview - * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit - * - * Clean-up - * .clean() - Async. Removes the project’s dist folder. - */ -export async function loadFixture(inlineConfig) { - if (!inlineConfig || !inlineConfig.root) - throw new Error("Must provide { root: './fixtures/...' }"); - - // load config - let cwd = inlineConfig.root; - delete inlineConfig.root; - if (typeof cwd === 'string') { - try { - cwd = new URL(cwd.replace(/\/?$/, '/')); - } catch (err1) { - cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); - } - } - // Load the config. - let config = await loadConfig({ cwd: fileURLToPath(cwd) }); - config = merge(config, { ...inlineConfig, root: cwd }); - - // Note: the inline config doesn't run through config validation where these normalizations usually occur - if (typeof inlineConfig.site === 'string') { - config.site = new URL(inlineConfig.site); - } - if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { - config.base = inlineConfig.base + '/'; - } - - /** @type {import('../src/core/logger/core').LogOptions} */ - const logging = { - dest: nodeLogDestination, - level: 'error', - }; - - /** @type {import('@astrojs/telemetry').AstroTelemetry} */ - const telemetry = { - record() { - return Promise.resolve(); - }, - }; - - return { - build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), - startDevServer: async (opts = {}) => { - const devResult = await dev(config, { logging, telemetry, ...opts }); - config.server.port = devResult.address.port; // update port - return devResult; - }, - config, - fetch: (url, init) => - fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), - preview: async (opts = {}) => { - const previewServer = await preview(config, { logging, telemetry, ...opts }); - return previewServer; - }, - readFile: (filePath) => - fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), - readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), - clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), - loadTestAdapterApp: async () => { - const url = new URL('./server/entry.mjs', config.outDir); - const { createApp } = await import(url); - return createApp(); - }, - }; -} - -/** - * Basic object merge utility. Returns new copy of merged Object. - * @param {Object} a - * @param {Object} b - * @returns {Object} - */ -function merge(a, b) { - const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); - const c = {}; - for (const k of allKeys) { - const needsObjectMerge = - typeof a[k] === 'object' && - typeof b[k] === 'object' && - (Object.keys(a[k]).length || Object.keys(b[k]).length) && - !Array.isArray(a[k]) && - !Array.isArray(b[k]); - if (needsObjectMerge) { - c[k] = merge(a[k] || {}, b[k] || {}); - continue; - } - c[k] = a[k]; - if (b[k] !== undefined) c[k] = b[k]; - } - return c; -} +export const loadFixture = createFixtureLoader('test'); const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9b082a7f6e9..79c2f69f1f89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,7 +659,7 @@ importers: chai-as-promised: 7.1.1_chai@4.3.6 mocha: 9.2.2 - packages/astro/e2e/fixtures/basic: + packages/astro/e2e/fixtures/tailwindcss: specifiers: '@astrojs/tailwind': workspace:* astro: workspace:* From 400d1b8dc070b7e4fc17abf447f68172c38df4f1 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 11 May 2022 15:53:32 -0600 Subject: [PATCH 06/54] chore: update lockfile --- pnpm-lock.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79c2f69f1f89..7c45bb4d0e38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5595,6 +5595,18 @@ packages: ms: 2.1.3 dev: false + /debug/4.3.3: + resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + /debug/4.3.3_supports-color@8.1.1: resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} engines: {node: '>=6.0'} From 4512765213f57e0f39bb2e742eacd803d309835d Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 12 May 2022 14:36:12 -0600 Subject: [PATCH 07/54] Adding contributing docs --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ff3bae3a120..b325fc8d1e02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,6 +59,24 @@ pnpm run test pnpm run test:match "$STRING_MATCH" ``` +#### E2E tests + +Certain features, like HMR and client hydration, need end-to-end tests to verify functionality in the dev server. [Playwright](https://playwright.dev/) is used to test against the dev server. + +```shell +# run this in the top-level project root to run all E2E tests +pnpm run test:e2e +# run only a few tests, great for working on a single feature +# (example - `pnpm run test:e2e:match "Tailwind CSS" runs `tailwindcss.test.js`) +pnpm run test:e2e:match "$STRING_MATCH" +``` + +**When should you add E2E tests?** + +Any tests for `astro build` output should use the main `mocha` tests rather than E2E - these tests will run faster than having Playwright start the `astro preview` server. + +If a test needs to validate what happens on the page after it's loading in the browser, that's a perfect use for E2E dev server tests, i.e. to verify that hot-module reloading works in `astro dev` or that components were client hydrated and are interactive. + ### Other useful commands ```shell From 85172f7d5d2d3091b71707f4bdefe46389c61087 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 12 May 2022 14:36:34 -0600 Subject: [PATCH 08/54] Revert "refactor: sharing test-utils helpers" This reverts commit 48496f43bc99eab30747baf6e83041ba4932e786. --- packages/astro/e2e/test-utils.js | 203 ++++++++++++++++++++++++++++- packages/astro/test-utils.js | 204 ------------------------------ packages/astro/test/test-utils.js | 136 +++++++++++++++++++- pnpm-lock.yaml | 2 +- 4 files changed, 336 insertions(+), 209 deletions(-) delete mode 100644 packages/astro/test-utils.js diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js index 158a61d5dd3c..460b3185f087 100644 --- a/packages/astro/e2e/test-utils.js +++ b/packages/astro/e2e/test-utils.js @@ -1,3 +1,202 @@ -import { createFixtureLoader } from '../test-utils.js'; +import { execa } from 'execa'; +import { polyfill } from '@astrojs/webapi'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { resolveConfig, loadConfig } from '../dist/core/config.js'; +import dev from '../dist/core/dev/index.js'; +import build from '../dist/core/build/index.js'; +import preview from '../dist/core/preview/index.js'; +import { nodeLogDestination } from '../dist/core/logger/node.js'; +import os from 'os'; +import stripAnsi from 'strip-ansi'; -export const loadFixture = createFixtureLoader('e2e'); +// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16 +polyfill(globalThis, { + exclude: 'window document', +}); + +/** + * @typedef {import('node-fetch').Response} Response + * @typedef {import('../src/core/dev/index').DevServer} DevServer + * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig + * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer + * @typedef {import('../src/core/app/index').App} App + * + * + * @typedef {Object} Fixture + * @property {typeof build} build + * @property {(url: string, opts: any) => Promise} fetch + * @property {(path: string) => Promise} readFile + * @property {(path: string) => Promise} readdir + * @property {() => Promise} startDevServer + * @property {() => Promise} preview + * @property {() => Promise} clean + * @property {() => Promise} loadTestAdapterApp + */ + +/** + * Load Astro fixture + * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) + * @returns {Promise} The fixture. Has the following properties: + * .config - Returns the final config. Will be automatically passed to the methods below: + * + * Build + * .build() - Async. Builds into current folder (will erase previous build) + * .readFile(path) - Async. Read a file from the build. + * + * Dev + * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. + * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) + * + * Preview + * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit + * + * Clean-up + * .clean() - Async. Removes the project’s dist folder. + */ +export async function loadFixture(inlineConfig) { + if (!inlineConfig || !inlineConfig.root) + throw new Error("Must provide { root: './fixtures/...' }"); + + // load config + let cwd = inlineConfig.root; + delete inlineConfig.root; + if (typeof cwd === 'string') { + try { + cwd = new URL(cwd.replace(/\/?$/, '/')); + } catch (err1) { + cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); + } + } + // Load the config. + let config = await loadConfig({ cwd: fileURLToPath(cwd) }); + config = merge(config, { ...inlineConfig, root: cwd }); + + // Note: the inline config doesn't run through config validation where these normalizations usually occur + if (typeof inlineConfig.site === 'string') { + config.site = new URL(inlineConfig.site); + } + if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { + config.base = inlineConfig.base + '/'; + } + + /** @type {import('../src/core/logger/core').LogOptions} */ + const logging = { + dest: nodeLogDestination, + level: 'error', + }; + + /** @type {import('@astrojs/telemetry').AstroTelemetry} */ + const telemetry = { + record() { + return Promise.resolve(); + }, + }; + + return { + build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), + startDevServer: async (opts = {}) => { + const devResult = await dev(config, { logging, telemetry, ...opts }); + config.server.port = devResult.address.port; // update port + return devResult; + }, + config, + fetch: (url, init) => + fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), + preview: async (opts = {}) => { + const previewServer = await preview(config, { logging, telemetry, ...opts }); + return previewServer; + }, + readFile: (filePath) => + fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), + readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), + clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), + loadTestAdapterApp: async () => { + const url = new URL('./server/entry.mjs', config.outDir); + const { createApp } = await import(url); + return createApp(); + }, + }; +} + +/** + * Basic object merge utility. Returns new copy of merged Object. + * @param {Object} a + * @param {Object} b + * @returns {Object} + */ +function merge(a, b) { + const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); + const c = {}; + for (const k of allKeys) { + const needsObjectMerge = + typeof a[k] === 'object' && + typeof b[k] === 'object' && + (Object.keys(a[k]).length || Object.keys(b[k]).length) && + !Array.isArray(a[k]) && + !Array.isArray(b[k]); + if (needsObjectMerge) { + c[k] = merge(a[k] || {}, b[k] || {}); + continue; + } + c[k] = a[k]; + if (b[k] !== undefined) c[k] = b[k]; + } + return c; +} + +const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); + +/** Returns a process running the Astro CLI. */ +export function cli(/** @type {string[]} */ ...args) { + const spawned = execa('node', [cliPath, ...args]); + + spawned.stdout.setEncoding('utf8'); + + return spawned; +} + +export async function parseCliDevStart(proc) { + let stdout = ''; + let stderr = ''; + + for await (const chunk of proc.stdout) { + stdout += chunk; + if (chunk.includes('Local')) break; + } + if (!stdout) { + for await (const chunk of proc.stderr) { + stderr += chunk; + break; + } + } + + proc.kill(); + stdout = stripAnsi(stdout); + stderr = stripAnsi(stderr); + + if (stderr) { + throw new Error(stderr); + } + + const messages = stdout + .split('\n') + .filter((ln) => !!ln.trim()) + .map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim()); + + return { messages }; +} + +export async function cliServerLogSetup(flags = [], cmd = 'dev') { + const proc = cli(cmd, ...flags); + + const { messages } = await parseCliDevStart(proc); + + const local = messages.find((msg) => msg.includes('Local'))?.replace(/Local\s*/g, ''); + const network = messages.find((msg) => msg.includes('Network'))?.replace(/Network\s*/g, ''); + + return { local, network }; +} + +export const isWindows = os.platform() === 'win32'; diff --git a/packages/astro/test-utils.js b/packages/astro/test-utils.js deleted file mode 100644 index 0268925591c1..000000000000 --- a/packages/astro/test-utils.js +++ /dev/null @@ -1,204 +0,0 @@ -import { execa } from 'execa'; -import { polyfill } from '@astrojs/webapi'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { resolveConfig, loadConfig } from './dist/core/config.js'; -import dev from './dist/core/dev/index.js'; -import build from './dist/core/build/index.js'; -import preview from './dist/core/preview/index.js'; -import { nodeLogDestination } from './dist/core/logger/node.js'; -import os from 'os'; -import stripAnsi from 'strip-ansi'; - -// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16 -polyfill(globalThis, { - exclude: 'window document', -}); - -/** - * @typedef {import('node-fetch').Response} Response - * @typedef {import('../src/core/dev/index').DevServer} DevServer - * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig - * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer - * @typedef {import('../src/core/app/index').App} App - * - * - * @typedef {Object} Fixture - * @property {typeof build} build - * @property {(url: string, opts: any) => Promise} fetch - * @property {(path: string) => Promise} readFile - * @property {(path: string) => Promise} readdir - * @property {() => Promise} startDevServer - * @property {() => Promise} preview - * @property {() => Promise} clean - * @property {() => Promise} loadTestAdapterApp - */ - -export function createFixtureLoader(root) { - /** - * Load Astro fixture - * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) - * @returns {Promise} The fixture. Has the following properties: - * .config - Returns the final config. Will be automatically passed to the methods below: - * - * Build - * .build() - Async. Builds into current folder (will erase previous build) - * .readFile(path) - Async. Read a file from the build. - * - * Dev - * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. - * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) - * - * Preview - * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit - * - * Clean-up - * .clean() - Async. Removes the project’s dist folder. - */ - return async function loadFixture(inlineConfig) { - if (!inlineConfig || !inlineConfig.root) - throw new Error("Must provide { root: './fixtures/...' }"); - - // load config - let cwd = path.join(root, inlineConfig.root); - delete inlineConfig.root; - if (typeof cwd === 'string') { - try { - cwd = new URL(cwd.replace(/\/?$/, '/')); - } catch (err1) { - cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); - } - } - // Load the config. - let config = await loadConfig({ cwd: fileURLToPath(cwd) }); - config = merge(config, { ...inlineConfig, root: cwd }); - - // Note: the inline config doesn't run through config validation where these normalizations usually occur - if (typeof inlineConfig.site === 'string') { - config.site = new URL(inlineConfig.site); - } - if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { - config.base = inlineConfig.base + '/'; - } - - /** @type {import('../src/core/logger/core').LogOptions} */ - const logging = { - dest: nodeLogDestination, - level: 'error', - }; - - /** @type {import('@astrojs/telemetry').AstroTelemetry} */ - const telemetry = { - record() { - return Promise.resolve(); - }, - }; - - return { - build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), - startDevServer: async (opts = {}) => { - const devResult = await dev(config, { logging, telemetry, ...opts }); - config.server.port = devResult.address.port; // update port - return devResult; - }, - config, - fetch: (url, init) => - fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), - preview: async (opts = {}) => { - const previewServer = await preview(config, { logging, telemetry, ...opts }); - return previewServer; - }, - readFile: (filePath) => - fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), - readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), - clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), - loadTestAdapterApp: async () => { - const url = new URL('./server/entry.mjs', config.outDir); - const { createApp } = await import(url); - return createApp(); - }, - }; - } -} - -/** - * Basic object merge utility. Returns new copy of merged Object. - * @param {Object} a - * @param {Object} b - * @returns {Object} - */ -function merge(a, b) { - const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); - const c = {}; - for (const k of allKeys) { - const needsObjectMerge = - typeof a[k] === 'object' && - typeof b[k] === 'object' && - (Object.keys(a[k]).length || Object.keys(b[k]).length) && - !Array.isArray(a[k]) && - !Array.isArray(b[k]); - if (needsObjectMerge) { - c[k] = merge(a[k] || {}, b[k] || {}); - continue; - } - c[k] = a[k]; - if (b[k] !== undefined) c[k] = b[k]; - } - return c; -} - -const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); - -/** Returns a process running the Astro CLI. */ -export function cli(/** @type {string[]} */ ...args) { - const spawned = execa('node', [cliPath, ...args]); - - spawned.stdout.setEncoding('utf8'); - - return spawned; -} - -export async function parseCliDevStart(proc) { - let stdout = ''; - let stderr = ''; - - for await (const chunk of proc.stdout) { - stdout += chunk; - if (chunk.includes('Local')) break; - } - if (!stdout) { - for await (const chunk of proc.stderr) { - stderr += chunk; - break; - } - } - - proc.kill(); - stdout = stripAnsi(stdout); - stderr = stripAnsi(stderr); - - if (stderr) { - throw new Error(stderr); - } - - const messages = stdout - .split('\n') - .filter((ln) => !!ln.trim()) - .map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim()); - - return { messages }; -} - -export async function cliServerLogSetup(flags = [], cmd = 'dev') { - const proc = cli(cmd, ...flags); - - const { messages } = await parseCliDevStart(proc); - - const local = messages.find((msg) => msg.includes('Local'))?.replace(/Local\s*/g, ''); - const network = messages.find((msg) => msg.includes('Network'))?.replace(/Network\s*/g, ''); - - return { local, network }; -} - -export const isWindows = os.platform() === 'win32'; diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index c3c5146a8aba..267e4039f62c 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -1,8 +1,12 @@ import { execa } from 'execa'; import { polyfill } from '@astrojs/webapi'; +import fs from 'fs'; import { fileURLToPath } from 'url'; +import { resolveConfig, loadConfig } from '../dist/core/config.js'; +import dev from '../dist/core/dev/index.js'; import build from '../dist/core/build/index.js'; -import { createFixtureLoader } from '../test-utils.js'; +import preview from '../dist/core/preview/index.js'; +import { nodeLogDestination } from '../dist/core/logger/node.js'; import os from 'os'; import stripAnsi from 'strip-ansi'; @@ -11,7 +15,135 @@ polyfill(globalThis, { exclude: 'window document', }); -export const loadFixture = createFixtureLoader('test'); +/** + * @typedef {import('node-fetch').Response} Response + * @typedef {import('../src/core/dev/index').DevServer} DevServer + * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig + * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer + * @typedef {import('../src/core/app/index').App} App + * + * + * @typedef {Object} Fixture + * @property {typeof build} build + * @property {(url: string, opts: any) => Promise} fetch + * @property {(path: string) => Promise} readFile + * @property {(path: string) => Promise} readdir + * @property {() => Promise} startDevServer + * @property {() => Promise} preview + * @property {() => Promise} clean + * @property {() => Promise} loadTestAdapterApp + */ + +/** + * Load Astro fixture + * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) + * @returns {Promise} The fixture. Has the following properties: + * .config - Returns the final config. Will be automatically passed to the methods below: + * + * Build + * .build() - Async. Builds into current folder (will erase previous build) + * .readFile(path) - Async. Read a file from the build. + * + * Dev + * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. + * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) + * + * Preview + * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit + * + * Clean-up + * .clean() - Async. Removes the project’s dist folder. + */ +export async function loadFixture(inlineConfig) { + if (!inlineConfig || !inlineConfig.root) + throw new Error("Must provide { root: './fixtures/...' }"); + + // load config + let cwd = inlineConfig.root; + delete inlineConfig.root; + if (typeof cwd === 'string') { + try { + cwd = new URL(cwd.replace(/\/?$/, '/')); + } catch (err1) { + cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); + } + } + // Load the config. + let config = await loadConfig({ cwd: fileURLToPath(cwd) }); + config = merge(config, { ...inlineConfig, root: cwd }); + + // Note: the inline config doesn't run through config validation where these normalizations usually occur + if (typeof inlineConfig.site === 'string') { + config.site = new URL(inlineConfig.site); + } + if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { + config.base = inlineConfig.base + '/'; + } + + /** @type {import('../src/core/logger/core').LogOptions} */ + const logging = { + dest: nodeLogDestination, + level: 'error', + }; + + /** @type {import('@astrojs/telemetry').AstroTelemetry} */ + const telemetry = { + record() { + return Promise.resolve(); + }, + }; + + return { + build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), + startDevServer: async (opts = {}) => { + const devResult = await dev(config, { logging, telemetry, ...opts }); + config.server.port = devResult.address.port; // update port + return devResult; + }, + config, + fetch: (url, init) => + fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), + preview: async (opts = {}) => { + const previewServer = await preview(config, { logging, telemetry, ...opts }); + return previewServer; + }, + readFile: (filePath) => + fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), + readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), + clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), + loadTestAdapterApp: async () => { + const url = new URL('./server/entry.mjs', config.outDir); + const { createApp } = await import(url); + return createApp(); + }, + }; +} + +/** + * Basic object merge utility. Returns new copy of merged Object. + * @param {Object} a + * @param {Object} b + * @returns {Object} + */ +function merge(a, b) { + const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); + const c = {}; + for (const k of allKeys) { + const needsObjectMerge = + typeof a[k] === 'object' && + typeof b[k] === 'object' && + (Object.keys(a[k]).length || Object.keys(b[k]).length) && + !Array.isArray(a[k]) && + !Array.isArray(b[k]); + if (needsObjectMerge) { + c[k] = merge(a[k] || {}, b[k] || {}); + continue; + } + c[k] = a[k]; + if (b[k] !== undefined) c[k] = b[k]; + } + return c; +} const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c45bb4d0e38..462527438f77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,7 +659,7 @@ importers: chai-as-promised: 7.1.1_chai@4.3.6 mocha: 9.2.2 - packages/astro/e2e/fixtures/tailwindcss: + packages/astro/e2e/fixtures/basic: specifiers: '@astrojs/tailwind': workspace:* astro: workspace:* From 4ae372eeda94fc7441b1cc29ba9693a29c52a098 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 12 May 2022 14:42:49 -0600 Subject: [PATCH 09/54] refactor: simpler solution to resolving e2e test fixtures --- packages/astro/e2e/test-utils.js | 185 ++----------------------------- pnpm-lock.yaml | 2 +- 2 files changed, 9 insertions(+), 178 deletions(-) diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js index 460b3185f087..19a83b844462 100644 --- a/packages/astro/e2e/test-utils.js +++ b/packages/astro/e2e/test-utils.js @@ -1,39 +1,4 @@ -import { execa } from 'execa'; -import { polyfill } from '@astrojs/webapi'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { resolveConfig, loadConfig } from '../dist/core/config.js'; -import dev from '../dist/core/dev/index.js'; -import build from '../dist/core/build/index.js'; -import preview from '../dist/core/preview/index.js'; -import { nodeLogDestination } from '../dist/core/logger/node.js'; -import os from 'os'; -import stripAnsi from 'strip-ansi'; - -// polyfill WebAPIs to globalThis for Node v12, Node v14, and Node v16 -polyfill(globalThis, { - exclude: 'window document', -}); - -/** - * @typedef {import('node-fetch').Response} Response - * @typedef {import('../src/core/dev/index').DevServer} DevServer - * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig - * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer - * @typedef {import('../src/core/app/index').App} App - * - * - * @typedef {Object} Fixture - * @property {typeof build} build - * @property {(url: string, opts: any) => Promise} fetch - * @property {(path: string) => Promise} readFile - * @property {(path: string) => Promise} readdir - * @property {() => Promise} startDevServer - * @property {() => Promise} preview - * @property {() => Promise} clean - * @property {() => Promise} loadTestAdapterApp - */ +import { loadFixture as baseLoadFixture } from '../test/test-utils.js'; /** * Load Astro fixture @@ -55,148 +20,14 @@ polyfill(globalThis, { * Clean-up * .clean() - Async. Removes the project’s dist folder. */ -export async function loadFixture(inlineConfig) { +export function loadFixture(inlineConfig) { if (!inlineConfig || !inlineConfig.root) throw new Error("Must provide { root: './fixtures/...' }"); - // load config - let cwd = inlineConfig.root; - delete inlineConfig.root; - if (typeof cwd === 'string') { - try { - cwd = new URL(cwd.replace(/\/?$/, '/')); - } catch (err1) { - cwd = new URL(cwd.replace(/\/?$/, '/'), import.meta.url); - } - } - // Load the config. - let config = await loadConfig({ cwd: fileURLToPath(cwd) }); - config = merge(config, { ...inlineConfig, root: cwd }); - - // Note: the inline config doesn't run through config validation where these normalizations usually occur - if (typeof inlineConfig.site === 'string') { - config.site = new URL(inlineConfig.site); - } - if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { - config.base = inlineConfig.base + '/'; - } - - /** @type {import('../src/core/logger/core').LogOptions} */ - const logging = { - dest: nodeLogDestination, - level: 'error', - }; - - /** @type {import('@astrojs/telemetry').AstroTelemetry} */ - const telemetry = { - record() { - return Promise.resolve(); - }, - }; - - return { - build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), - startDevServer: async (opts = {}) => { - const devResult = await dev(config, { logging, telemetry, ...opts }); - config.server.port = devResult.address.port; // update port - return devResult; - }, - config, - fetch: (url, init) => - fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), - preview: async (opts = {}) => { - const previewServer = await preview(config, { logging, telemetry, ...opts }); - return previewServer; - }, - readFile: (filePath) => - fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), - readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), - clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), - loadTestAdapterApp: async () => { - const url = new URL('./server/entry.mjs', config.outDir); - const { createApp } = await import(url); - return createApp(); - }, - }; -} - -/** - * Basic object merge utility. Returns new copy of merged Object. - * @param {Object} a - * @param {Object} b - * @returns {Object} - */ -function merge(a, b) { - const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]); - const c = {}; - for (const k of allKeys) { - const needsObjectMerge = - typeof a[k] === 'object' && - typeof b[k] === 'object' && - (Object.keys(a[k]).length || Object.keys(b[k]).length) && - !Array.isArray(a[k]) && - !Array.isArray(b[k]); - if (needsObjectMerge) { - c[k] = merge(a[k] || {}, b[k] || {}); - continue; - } - c[k] = a[k]; - if (b[k] !== undefined) c[k] = b[k]; - } - return c; -} - -const cliPath = fileURLToPath(new URL('../astro.js', import.meta.url)); - -/** Returns a process running the Astro CLI. */ -export function cli(/** @type {string[]} */ ...args) { - const spawned = execa('node', [cliPath, ...args]); - - spawned.stdout.setEncoding('utf8'); - - return spawned; -} - -export async function parseCliDevStart(proc) { - let stdout = ''; - let stderr = ''; - - for await (const chunk of proc.stdout) { - stdout += chunk; - if (chunk.includes('Local')) break; - } - if (!stdout) { - for await (const chunk of proc.stderr) { - stderr += chunk; - break; - } - } - - proc.kill(); - stdout = stripAnsi(stdout); - stderr = stripAnsi(stderr); - - if (stderr) { - throw new Error(stderr); - } - - const messages = stdout - .split('\n') - .filter((ln) => !!ln.trim()) - .map((ln) => ln.replace(/[🚀┃]/g, '').replace(/\s+/g, ' ').trim()); - - return { messages }; -} - -export async function cliServerLogSetup(flags = [], cmd = 'dev') { - const proc = cli(cmd, ...flags); - - const { messages } = await parseCliDevStart(proc); - - const local = messages.find((msg) => msg.includes('Local'))?.replace(/Local\s*/g, ''); - const network = messages.find((msg) => msg.includes('Network'))?.replace(/Network\s*/g, ''); - - return { local, network }; + // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath + // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test` + return baseLoadFixture({ + ...inlineConfig, + root: new URL(inlineConfig.root, import.meta.url).toString() + }) } - -export const isWindows = os.platform() === 'win32'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 462527438f77..7c45bb4d0e38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,7 +659,7 @@ importers: chai-as-promised: 7.1.1_chai@4.3.6 mocha: 9.2.2 - packages/astro/e2e/fixtures/basic: + packages/astro/e2e/fixtures/tailwindcss: specifiers: '@astrojs/tailwind': workspace:* astro: workspace:* From aa2910030bafad9e0dfa08f37c65a8ec469359b7 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 12 May 2022 14:46:46 -0600 Subject: [PATCH 10/54] chore: updating lockfile --- pnpm-lock.yaml | 449 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 448 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c45bb4d0e38..497caf7305db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -469,7 +469,8 @@ importers: '@babel/generator': ^7.17.10 '@babel/parser': ^7.17.10 '@babel/traverse': ^7.17.10 - '@babel/types': ^7.17.10 + '@babel/types': ^7.17.0 + '@playwright/test': ^1.21.1 '@proload/core': ^0.3.2 '@proload/plugin-tsm': ^0.2.1 '@types/babel__core': ^7.1.19 @@ -603,6 +604,7 @@ importers: zod: 3.16.0 devDependencies: '@babel/types': 7.17.10 + '@playwright/test': 1.21.1 '@types/babel__core': 7.1.19 '@types/babel__generator': 7.6.4 '@types/babel__traverse': 7.17.1 @@ -1878,6 +1880,29 @@ packages: resolution: {integrity: sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==} engines: {node: '>=6.9.0'} + /@babel/core/7.16.12: + resolution: {integrity: sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.16.7 + '@babel/generator': 7.17.10 + '@babel/helper-compilation-targets': 7.17.10_@babel+core@7.16.12 + '@babel/helper-module-transforms': 7.17.7 + '@babel/helpers': 7.17.9 + '@babel/parser': 7.17.10 + '@babel/template': 7.16.7 + '@babel/traverse': 7.17.10 + '@babel/types': 7.17.10 + convert-source-map: 1.8.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.1 + semver: 6.3.0 + source-map: 0.5.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/core/7.17.10: resolution: {integrity: sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==} engines: {node: '>=6.9.0'} @@ -1922,6 +1947,22 @@ packages: '@babel/types': 7.17.10 dev: true + /@babel/helper-compilation-targets/7.17.10_@babel+core@7.16.12: + resolution: {integrity: sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/compat-data': 7.17.10 + '@babel/core': 7.16.12 + '@babel/helper-validator-option': 7.16.7 + browserslist: 4.20.3 + semver: 6.3.0 + dev: true + /@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.10: resolution: {integrity: sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==} engines: {node: '>=6.9.0'} @@ -1937,6 +1978,27 @@ packages: browserslist: 4.20.3 semver: 6.3.0 + /@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.16.12: + resolution: {integrity: sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-environment-visitor': 7.16.7 + '@babel/helper-function-name': 7.17.9 + '@babel/helper-member-expression-to-functions': 7.17.7 + '@babel/helper-optimise-call-expression': 7.16.7 + '@babel/helper-replace-supers': 7.16.7 + '@babel/helper-split-export-declaration': 7.16.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-create-class-features-plugin/7.17.9_@babel+core@7.17.10: resolution: {integrity: sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==} engines: {node: '>=6.9.0'} @@ -2198,6 +2260,22 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-class-properties/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-create-class-features-plugin': 7.17.9_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-class-properties/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==} engines: {node: '>=6.9.0'} @@ -2231,6 +2309,20 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.16.12 + dev: true + /@babel/plugin-proposal-dynamic-import/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==} engines: {node: '>=6.9.0'} @@ -2245,6 +2337,20 @@ packages: '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.17.10 dev: true + /@babel/plugin-proposal-export-namespace-from/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.16.12 + dev: true + /@babel/plugin-proposal-export-namespace-from/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==} engines: {node: '>=6.9.0'} @@ -2273,6 +2379,20 @@ packages: '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.17.10 dev: true + /@babel/plugin-proposal-logical-assignment-operators/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.16.12 + dev: true + /@babel/plugin-proposal-logical-assignment-operators/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==} engines: {node: '>=6.9.0'} @@ -2287,6 +2407,20 @@ packages: '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.17.10 dev: true + /@babel/plugin-proposal-nullish-coalescing-operator/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.16.12 + dev: true + /@babel/plugin-proposal-nullish-coalescing-operator/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==} engines: {node: '>=6.9.0'} @@ -2301,6 +2435,20 @@ packages: '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.17.10 dev: true + /@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.16.12 + dev: true + /@babel/plugin-proposal-numeric-separator/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==} engines: {node: '>=6.9.0'} @@ -2346,6 +2494,21 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.17.10 dev: true + /@babel/plugin-proposal-optional-chaining/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.16.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.16.12 + dev: true + /@babel/plugin-proposal-optional-chaining/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==} engines: {node: '>=6.9.0'} @@ -2361,6 +2524,22 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.17.10 dev: true + /@babel/plugin-proposal-private-methods/7.16.11_@babel+core@7.16.12: + resolution: {integrity: sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-create-class-features-plugin': 7.17.9_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-private-methods/7.16.11_@babel+core@7.17.10: resolution: {integrity: sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==} engines: {node: '>=6.9.0'} @@ -2377,6 +2556,24 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-private-property-in-object/7.16.7_@babel+core@7.16.12: + resolution: {integrity: sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-annotate-as-pure': 7.16.7 + '@babel/helper-create-class-features-plugin': 7.17.9_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.16.12 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-private-property-in-object/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==} engines: {node: '>=6.9.0'} @@ -2409,6 +2606,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.16.12: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.17.10: resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -2446,6 +2655,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: @@ -2458,6 +2679,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} peerDependencies: @@ -2470,6 +2703,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: @@ -2494,6 +2739,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: false + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.16.12: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.17.10: resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -2506,6 +2763,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: @@ -2518,6 +2787,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.16.12: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.17.10: resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: @@ -2530,6 +2811,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: @@ -2542,6 +2835,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: @@ -2554,6 +2859,18 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.16.12: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.10: resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: @@ -2566,6 +2883,19 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.16.12: + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.17.10: resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} @@ -2592,6 +2922,19 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-syntax-typescript/7.17.10_@babel+core@7.16.12: + resolution: {integrity: sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + dev: true + /@babel/plugin-transform-arrow-functions/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==} engines: {node: '>=6.9.0'} @@ -2808,6 +3151,24 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-commonjs/7.16.8_@babel+core@7.16.12: + resolution: {integrity: sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-module-transforms': 7.17.7 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/helper-simple-access': 7.17.7 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs/7.17.9_@babel+core@7.17.10: resolution: {integrity: sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw==} engines: {node: '>=6.9.0'} @@ -3037,6 +3398,23 @@ packages: '@babel/helper-plugin-utils': 7.16.7 dev: true + /@babel/plugin-transform-typescript/7.16.8_@babel+core@7.16.12: + resolution: {integrity: sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-create-class-features-plugin': 7.17.9_@babel+core@7.16.12 + '@babel/helper-plugin-utils': 7.16.7 + '@babel/plugin-syntax-typescript': 7.17.10_@babel+core@7.16.12 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-unicode-escapes/7.16.7_@babel+core@7.17.10: resolution: {integrity: sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==} engines: {node: '>=6.9.0'} @@ -3517,6 +3895,17 @@ packages: - supports-color dev: true + /@jest/types/27.5.1: + resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 17.0.32 + '@types/yargs': 16.0.4 + chalk: 4.1.2 + dev: true + /@jridgewell/gen-mapping/0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -4324,6 +4713,20 @@ packages: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true + /@types/yargs/16.0.4: + resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + + /@types/yauzl/2.10.0: + resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} + requiresBuild: true + dependencies: + '@types/node': 17.0.32 + dev: true + optional: true + /@typescript-eslint/eslint-plugin/5.23.0_c63nfttrfhylg3zmgcxfslaw44: resolution: {integrity: sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -9043,6 +9446,45 @@ packages: dependencies: find-up: 4.1.0 + /playwright-core/1.21.1: + resolution: {integrity: sha512-SbK5dEsai9ZUKlxcinqegorBq4GnftXd4/GfW+pLsdQIQWrLCM/JNh6YQ2Rf2enVykXCejtoXW8L5vJXBBVSJQ==} + engines: {node: '>=12'} + hasBin: true + dependencies: + colors: 1.4.0 + commander: 8.3.0 + debug: 4.3.3 + extract-zip: 2.0.1 + https-proxy-agent: 5.0.0 + jpeg-js: 0.4.3 + mime: 3.0.0 + pixelmatch: 5.2.1 + pngjs: 6.0.0 + progress: 2.0.3 + proper-lockfile: 4.1.2 + proxy-from-env: 1.1.0 + rimraf: 3.0.2 + socks-proxy-agent: 6.1.1 + stack-utils: 2.0.5 + ws: 8.4.2 + yauzl: 2.10.0 + yazl: 2.5.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /pngjs/4.0.1: + resolution: {integrity: sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==} + engines: {node: '>=8.0.0'} + dev: true + + /pngjs/6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + dev: true + /postcss-js/4.0.0_postcss@8.4.13: resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} engines: {node: ^12 || ^14 || >= 16} @@ -10010,6 +10452,11 @@ packages: source-map: 0.6.1 dev: true + /source-map/0.5.7: + resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} + engines: {node: '>=0.10.0'} + dev: true + /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} From b7db93ccc33866c44cc39d3999b53f8ba084f73c Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 12 May 2022 14:57:45 -0600 Subject: [PATCH 11/54] refactor: cleaning up how URLs are resolved in e2e tests --- packages/astro/e2e/tailwindcss.test.js | 4 ++-- packages/astro/e2e/test-utils.js | 20 -------------------- packages/astro/test/test-utils.js | 6 +++++- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/packages/astro/e2e/tailwindcss.test.js b/packages/astro/e2e/tailwindcss.test.js index 8d643e7749ce..e156a7be7f11 100644 --- a/packages/astro/e2e/tailwindcss.test.js +++ b/packages/astro/e2e/tailwindcss.test.js @@ -18,8 +18,8 @@ test.afterAll(async ({ astro }) => { await devServer.stop(); }); -test('Tailwind CSS', async ({ page }) => { - await page.goto(`localhost:${devServer.address.port}/`); +test('Tailwind CSS', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); await test.step('body', async () => { const body = page.locator('body'); diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js index 19a83b844462..e2552042c9ee 100644 --- a/packages/astro/e2e/test-utils.js +++ b/packages/astro/e2e/test-utils.js @@ -1,25 +1,5 @@ import { loadFixture as baseLoadFixture } from '../test/test-utils.js'; -/** - * Load Astro fixture - * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) - * @returns {Promise} The fixture. Has the following properties: - * .config - Returns the final config. Will be automatically passed to the methods below: - * - * Build - * .build() - Async. Builds into current folder (will erase previous build) - * .readFile(path) - Async. Read a file from the build. - * - * Dev - * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. - * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) - * - * Preview - * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit - * - * Clean-up - * .clean() - Async. Removes the project’s dist folder. - */ export function loadFixture(inlineConfig) { if (!inlineConfig || !inlineConfig.root) throw new Error("Must provide { root: './fixtures/...' }"); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 267e4039f62c..bc7ff3f0b2ac 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -25,6 +25,7 @@ polyfill(globalThis, { * * @typedef {Object} Fixture * @property {typeof build} build + * @property {(url: string) => string} resolveUrl * @property {(url: string, opts: any) => Promise} fetch * @property {(path: string) => Promise} readFile * @property {(path: string) => Promise} readdir @@ -93,6 +94,8 @@ export async function loadFixture(inlineConfig) { }, }; + const resolveUrl = (url) => `http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`; + return { build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), startDevServer: async (opts = {}) => { @@ -101,8 +104,9 @@ export async function loadFixture(inlineConfig) { return devResult; }, config, + resolveUrl, fetch: (url, init) => - fetch(`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`, init), + fetch(resolveUrl(url), init), preview: async (opts = {}) => { const previewServer = await preview(config, { logging, telemetry, ...opts }); return previewServer; From ee16c1469719f38e6466c86a55ee3f20a134c416 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 12 May 2022 15:17:02 -0600 Subject: [PATCH 12/54] install playwright deps in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16791b110060..a88a27121636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,7 +190,7 @@ jobs: run: ./.github/extract-artifacts.sh - name: Install dependencies - run: pnpm install + run: pnpm install && pnpx playwright install-deps - name: Test run: pnpm run test:e2e From 0b3c6c2a0e183f2deba684ad4c4f71cd7f260c6b Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 14:46:39 -0600 Subject: [PATCH 13/54] ensure playwright deps are installed during CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a88a27121636..2b5b1c79bbeb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,7 +190,7 @@ jobs: run: ./.github/extract-artifacts.sh - name: Install dependencies - run: pnpm install && pnpx playwright install-deps + run: pnpm install && pnpx playwright install - name: Test run: pnpm run test:e2e From c0bd9da29132a530de37d7113dca8c31fa09e3f4 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 12:08:47 -0600 Subject: [PATCH 14/54] adding a basic HMR test for tailwind styles --- packages/astro/e2e/tailwindcss.test.js | 25 +++++++++++++++++++++++- packages/astro/e2e/test-utils.js | 4 ++++ packages/astro/src/runtime/client/hmr.ts | 8 ++++++-- packages/astro/test/test-utils.js | 18 ++++++++++++++++- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/astro/e2e/tailwindcss.test.js b/packages/astro/e2e/tailwindcss.test.js index e156a7be7f11..0de09e855924 100644 --- a/packages/astro/e2e/tailwindcss.test.js +++ b/packages/astro/e2e/tailwindcss.test.js @@ -1,5 +1,5 @@ import { test as base, expect } from '@playwright/test'; -import { loadFixture } from './test-utils.js'; +import { loadFixture, onAfterHMR } from './test-utils.js'; const test = base.extend({ astro: async ({}, use) => { @@ -18,6 +18,10 @@ test.afterAll(async ({ astro }) => { await devServer.stop(); }); +test.afterEach(async ({ astro }) => { + astro.clean(); +}); + test('Tailwind CSS', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/')); @@ -48,4 +52,23 @@ test('Tailwind CSS', async ({ page, astro }) => { await expect(button, 'should have font-[900]').toHaveClass(/font-\[900\]/); await expect(button, 'should have font weight').toHaveCSS('font-weight', '900'); }); + + await test.step('HMR', async () => { + const afterHMR = onAfterHMR(page); + + await astro.writeFile( + 'src/components/Button.astro', + (original) => original.replace('bg-purple-600', 'bg-purple-400') + ); + + await afterHMR; + + const button = page.locator('button'); + + await expect(button, 'should have bg-purple-400').toHaveClass(/bg-purple-400/); + await expect(button, 'should have background color').toHaveCSS( + 'background-color', + 'rgb(192, 132, 252)' + ); + }); }); diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js index e2552042c9ee..614491401fb2 100644 --- a/packages/astro/e2e/test-utils.js +++ b/packages/astro/e2e/test-utils.js @@ -11,3 +11,7 @@ export function loadFixture(inlineConfig) { root: new URL(inlineConfig.root, import.meta.url).toString() }) } + +export function onAfterHMR(page) { + return page.waitForEvent('console', message => message.text() === 'astro:hmr:after') +} diff --git a/packages/astro/src/runtime/client/hmr.ts b/packages/astro/src/runtime/client/hmr.ts index 7cd7732569df..119ba8754623 100644 --- a/packages/astro/src/runtime/client/hmr.ts +++ b/packages/astro/src/runtime/client/hmr.ts @@ -14,7 +14,11 @@ if (import.meta.hot) { root.innerHTML = current?.innerHTML; } } - return diff(document, doc); + const result = diff(document, doc); + + // event used for synchronizing E2E tests + console.log('astro:hmr:after'); + return result; } async function updateAll(files: any[]) { let hasAstroUpdate = false; @@ -31,7 +35,7 @@ if (import.meta.hot) { } } if (hasAstroUpdate) { - return updatePage(); + return await updatePage(); } } import.meta.hot.on('vite:beforeUpdate', async (event) => { diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index bc7ff3f0b2ac..c351b0dfaaf1 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -28,6 +28,7 @@ polyfill(globalThis, { * @property {(url: string) => string} resolveUrl * @property {(url: string, opts: any) => Promise} fetch * @property {(path: string) => Promise} readFile + * @property {(path: string, updater: (content: string) => string) => Promise} writeFile * @property {(path: string) => Promise} readdir * @property {() => Promise} startDevServer * @property {() => Promise} preview @@ -96,6 +97,17 @@ export async function loadFixture(inlineConfig) { const resolveUrl = (url) => `http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`; + let cleanupCallbacks = []; + + async function writeFile(filePath, updater) { + const pathname = new URL(filePath.replace(/^\//, ''), config.root); + const initial = await fs.promises.readFile(pathname, 'utf8'); + + await fs.promises.writeFile(pathname, updater(initial), 'utf-8'); + + cleanupCallbacks.push(() => fs.promises.writeFile(pathname, initial, 'utf-8')); + } + return { build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }), startDevServer: async (opts = {}) => { @@ -113,8 +125,12 @@ export async function loadFixture(inlineConfig) { }, readFile: (filePath) => fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), + writeFile, readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)), - clean: () => fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }), + clean: async () => { + await fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true }); + await Promise.all(cleanupCallbacks.map(cb => cb())); + }, loadTestAdapterApp: async () => { const url = new URL('./server/entry.mjs', config.outDir); const { createApp } = await import(url); From fe5bb8c50c2513457110b7c5a28f54c3e1a17423 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 12:12:59 -0600 Subject: [PATCH 15/54] using @e2e for playwright test packages --- .changeset/config.json | 2 +- packages/astro/e2e/fixtures/tailwindcss/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 8292fbe07858..41ec3ec864fa 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -6,5 +6,5 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["@example/*", "@test/*"] + "ignore": ["@example/*", "@test/*", "@e2e/*"] } diff --git a/packages/astro/e2e/fixtures/tailwindcss/package.json b/packages/astro/e2e/fixtures/tailwindcss/package.json index 4bcc5687273e..f06e3ea91a86 100644 --- a/packages/astro/e2e/fixtures/tailwindcss/package.json +++ b/packages/astro/e2e/fixtures/tailwindcss/package.json @@ -1,5 +1,5 @@ { - "name": "@test/e2e-tailwindcss", + "name": "@e2e/e2e-tailwindcss", "version": "0.0.0", "private": true, "dependencies": { From 8e5f0d2bd146efa8769b4ec08929410b5774d59d Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 13:00:53 -0600 Subject: [PATCH 16/54] adding react hydration and HMR tests --- package.json | 1 + .../astro/e2e/fixtures/react/astro.config.mjs | 7 ++ .../astro/e2e/fixtures/react/package.json | 11 ++ .../fixtures/react/src/components/Counter.css | 11 ++ .../fixtures/react/src/components/Counter.jsx | 19 +++ .../react/src/components/JSXComponent.jsx | 5 + .../e2e/fixtures/react/src/pages/index.astro | 29 +++++ packages/astro/e2e/react.test.js | 109 ++++++++++++++++++ packages/astro/package.json | 3 +- pnpm-lock.yaml | 12 ++ 10 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 packages/astro/e2e/fixtures/react/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/react/package.json create mode 100644 packages/astro/e2e/fixtures/react/src/components/Counter.css create mode 100644 packages/astro/e2e/fixtures/react/src/components/Counter.jsx create mode 100644 packages/astro/e2e/fixtures/react/src/components/JSXComponent.jsx create mode 100644 packages/astro/e2e/fixtures/react/src/pages/index.astro create mode 100644 packages/astro/e2e/react.test.js diff --git a/package.json b/package.json index 9508c7b0b833..b143a6f0a92a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "test:smoke": "node scripts/smoke/index.js", "test:vite-ci": "turbo run test --no-deps --scope=astro --concurrency=1", "test:e2e": "cd packages/astro && pnpm run test:e2e", + "test:e2e:match": "cd packages/astro && pnpm run test:e2e:match", "benchmark": "turbo run benchmark --scope=astro", "lint": "eslint \"packages/**/*.ts\"", "format": "prettier -w .", diff --git a/packages/astro/e2e/fixtures/react/astro.config.mjs b/packages/astro/e2e/fixtures/react/astro.config.mjs new file mode 100644 index 000000000000..8a6f1951c9c2 --- /dev/null +++ b/packages/astro/e2e/fixtures/react/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import react from '@astrojs/react'; + +// https://astro.build/config +export default defineConfig({ + integrations: [react()], +}); diff --git a/packages/astro/e2e/fixtures/react/package.json b/packages/astro/e2e/fixtures/react/package.json new file mode 100644 index 000000000000..f76cc192d8d6 --- /dev/null +++ b/packages/astro/e2e/fixtures/react/package.json @@ -0,0 +1,11 @@ +{ + "name": "@e2e/react", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/react": "workspace:*", + "astro": "workspace:*", + "react": "^18.1.0", + "react-dom": "^18.1.0" + } +} diff --git a/packages/astro/e2e/fixtures/react/src/components/Counter.css b/packages/astro/e2e/fixtures/react/src/components/Counter.css new file mode 100644 index 000000000000..fb21044d78cc --- /dev/null +++ b/packages/astro/e2e/fixtures/react/src/components/Counter.css @@ -0,0 +1,11 @@ +.counter { + display: grid; + font-size: 2em; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 2em; + place-items: center; +} + +.counter-message { + text-align: center; +} diff --git a/packages/astro/e2e/fixtures/react/src/components/Counter.jsx b/packages/astro/e2e/fixtures/react/src/components/Counter.jsx new file mode 100644 index 000000000000..769e0cccf50c --- /dev/null +++ b/packages/astro/e2e/fixtures/react/src/components/Counter.jsx @@ -0,0 +1,19 @@ +import React, { useState } from 'react'; +import './Counter.css'; + +export default function Counter({ children, count: initialCount, id }) { + const [count, setCount] = useState(initialCount); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/react/src/components/JSXComponent.jsx b/packages/astro/e2e/fixtures/react/src/components/JSXComponent.jsx new file mode 100644 index 000000000000..90a4d7c4203f --- /dev/null +++ b/packages/astro/e2e/fixtures/react/src/components/JSXComponent.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function({ id }) { + return
React client:only component
+} diff --git a/packages/astro/e2e/fixtures/react/src/pages/index.astro b/packages/astro/e2e/fixtures/react/src/pages/index.astro new file mode 100644 index 000000000000..163618b13670 --- /dev/null +++ b/packages/astro/e2e/fixtures/react/src/pages/index.astro @@ -0,0 +1,29 @@ +--- +import Counter from '../components/Counter.jsx'; +import ReactComponent from '../components/JSXComponent.jsx'; + +const someProps = { + count: 0, +}; +--- + + + + + + + +

Hello, client:idle!

+
+ + +

Hello, client:load!

+
+ + +

Hello, client:visible!

+
+ + + + diff --git a/packages/astro/e2e/react.test.js b/packages/astro/e2e/react.test.js new file mode 100644 index 000000000000..0d6bc225b86e --- /dev/null +++ b/packages/astro/e2e/react.test.js @@ -0,0 +1,109 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture, onAfterHMR } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/react/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterAll(async ({ astro }) => { + await devServer.stop(); +}); + +test.afterEach(async ({ astro }) => { + astro.clean(); +}); + +test.only('React', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + + await test.step('client:idle', async () => { + const counter = page.locator('#counter-idle'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:load', async () => { + const counter = page.locator('#counter-load'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:visible', async () => { + const counter = page.locator('#counter-visible'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:only', async () => { + const label = page.locator('#client-only'); + await expect(label).toBeVisible(); + + await expect(label).toHaveText('React client:only component'); + }); + + await test.step('HMR', async () => { + const afterHMR = onAfterHMR(page); + + // test 1: updating the page component + await astro.writeFile( + 'src/pages/index.astro', + (original) => original.replace('id="counter-idle" {...someProps}', 'id="counter-idle" count={5}') + ); + + await afterHMR; + + const count = page.locator('#counter-idle pre'); + await expect(count).toHaveText('5'); + + // test 2: updating the react component + await astro.writeFile( + 'src/components/JSXComponent.jsx', + (original) => original.replace('React client:only component', 'Updated react client:only component') + ); + + await afterHMR; + + const label = page.locator('#client-only'); + await expect(label).toBeVisible(); + + await expect(label).toHaveText('Updated react client:only component'); + + // test 3: updating imported CSS + await astro.writeFile( + 'src/components/Counter.css', + (original) => original.replace('font-size: 2em;', 'font-size: 24px;') + ); + + await expect(count).toHaveCSS('font-size', '24px'); + }); +}); diff --git a/packages/astro/package.json b/packages/astro/package.json index c4d266a5d21c..bffad56637d7 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -73,7 +73,8 @@ "benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js", "test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js", "test:match": "mocha --timeout 20000 -g", - "test:e2e": "playwright test e2e" + "test:e2e": "playwright test e2e", + "test:e2e:match": "playwright test e2e -g" }, "dependencies": { "@astrojs/compiler": "^0.14.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 497caf7305db..7a1d78e6db3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -661,6 +661,18 @@ importers: chai-as-promised: 7.1.1_chai@4.3.6 mocha: 9.2.2 + packages/astro/e2e/fixtures/react: + specifiers: + '@astrojs/react': workspace:* + astro: workspace:* + react: ^18.1.0 + react-dom: ^18.1.0 + dependencies: + '@astrojs/react': link:../../../../integrations/react + astro: link:../../.. + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + packages/astro/e2e/fixtures/tailwindcss: specifiers: '@astrojs/tailwind': workspace:* From 91d802e73b78d743dba7b4a994013cd73b7dbb3a Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 13:03:58 -0600 Subject: [PATCH 17/54] adding hydration and HMR tests for preact --- .../e2e/fixtures/preact/astro.config.mjs | 7 ++ .../astro/e2e/fixtures/preact/package.json | 11 ++ .../preact/src/components/Counter.css | 11 ++ .../preact/src/components/Counter.jsx | 20 ++++ .../preact/src/components/JSXComponent.jsx | 5 + .../e2e/fixtures/preact/src/pages/index.astro | 29 +++++ packages/astro/e2e/preact.test.js | 109 ++++++++++++++++++ pnpm-lock.yaml | 12 ++ 8 files changed, 204 insertions(+) create mode 100644 packages/astro/e2e/fixtures/preact/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/preact/package.json create mode 100644 packages/astro/e2e/fixtures/preact/src/components/Counter.css create mode 100644 packages/astro/e2e/fixtures/preact/src/components/Counter.jsx create mode 100644 packages/astro/e2e/fixtures/preact/src/components/JSXComponent.jsx create mode 100644 packages/astro/e2e/fixtures/preact/src/pages/index.astro create mode 100644 packages/astro/e2e/preact.test.js diff --git a/packages/astro/e2e/fixtures/preact/astro.config.mjs b/packages/astro/e2e/fixtures/preact/astro.config.mjs new file mode 100644 index 000000000000..08916b1fea78 --- /dev/null +++ b/packages/astro/e2e/fixtures/preact/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; + +// https://astro.build/config +export default defineConfig({ + integrations: [preact()], +}); diff --git a/packages/astro/e2e/fixtures/preact/package.json b/packages/astro/e2e/fixtures/preact/package.json new file mode 100644 index 000000000000..07bb82299725 --- /dev/null +++ b/packages/astro/e2e/fixtures/preact/package.json @@ -0,0 +1,11 @@ +{ + "name": "@e2e/react", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/preact": "workspace:*", + "astro": "workspace:*", + "react": "^18.1.0", + "react-dom": "^18.1.0" + } +} diff --git a/packages/astro/e2e/fixtures/preact/src/components/Counter.css b/packages/astro/e2e/fixtures/preact/src/components/Counter.css new file mode 100644 index 000000000000..fb21044d78cc --- /dev/null +++ b/packages/astro/e2e/fixtures/preact/src/components/Counter.css @@ -0,0 +1,11 @@ +.counter { + display: grid; + font-size: 2em; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 2em; + place-items: center; +} + +.counter-message { + text-align: center; +} diff --git a/packages/astro/e2e/fixtures/preact/src/components/Counter.jsx b/packages/astro/e2e/fixtures/preact/src/components/Counter.jsx new file mode 100644 index 000000000000..526f269639b5 --- /dev/null +++ b/packages/astro/e2e/fixtures/preact/src/components/Counter.jsx @@ -0,0 +1,20 @@ +import { h, Fragment } from 'preact'; +import { useState } from 'preact/hooks'; +import './Counter.css'; + +export default function Counter({ children, count: initialCount, id }) { + const [count, setCount] = useState(initialCount); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/preact/src/components/JSXComponent.jsx b/packages/astro/e2e/fixtures/preact/src/components/JSXComponent.jsx new file mode 100644 index 000000000000..6cc7b7858c58 --- /dev/null +++ b/packages/astro/e2e/fixtures/preact/src/components/JSXComponent.jsx @@ -0,0 +1,5 @@ +import { h } from 'preact'; + +export default function({ id }) { + return
Preact client:only component
+} diff --git a/packages/astro/e2e/fixtures/preact/src/pages/index.astro b/packages/astro/e2e/fixtures/preact/src/pages/index.astro new file mode 100644 index 000000000000..e9c582ba5e66 --- /dev/null +++ b/packages/astro/e2e/fixtures/preact/src/pages/index.astro @@ -0,0 +1,29 @@ +--- +import Counter from '../components/Counter.jsx'; +import PreactComponent from '../components/JSXComponent.jsx'; + +const someProps = { + count: 0, +}; +--- + + + + + + + +

Hello, client:idle!

+
+ + +

Hello, client:load!

+
+ + +

Hello, client:visible!

+
+ + + + diff --git a/packages/astro/e2e/preact.test.js b/packages/astro/e2e/preact.test.js new file mode 100644 index 000000000000..c64f4feefafe --- /dev/null +++ b/packages/astro/e2e/preact.test.js @@ -0,0 +1,109 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture, onAfterHMR } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/preact/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterAll(async ({ astro }) => { + await devServer.stop(); +}); + +test.afterEach(async ({ astro }) => { + astro.clean(); +}); + +test.only('Preact', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + + await test.step('client:idle', async () => { + const counter = page.locator('#counter-idle'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:load', async () => { + const counter = page.locator('#counter-load'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:visible', async () => { + const counter = page.locator('#counter-visible'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:only', async () => { + const label = page.locator('#client-only'); + await expect(label).toBeVisible(); + + await expect(label).toHaveText('Preact client:only component'); + }); + + await test.step('HMR', async () => { + const afterHMR = onAfterHMR(page); + + // test 1: updating the page component + await astro.writeFile( + 'src/pages/index.astro', + (original) => original.replace('id="counter-idle" {...someProps}', 'id="counter-idle" count={5}') + ); + + await afterHMR; + + const count = page.locator('#counter-idle pre'); + await expect(count).toHaveText('5'); + + // test 2: updating the react component + await astro.writeFile( + 'src/components/JSXComponent.jsx', + (original) => original.replace('Preact client:only component', 'Updated preact client:only component') + ); + + await afterHMR; + + const label = page.locator('#client-only'); + await expect(label).toBeVisible(); + + await expect(label).toHaveText('Updated preact client:only component'); + + // test 3: updating imported CSS + await astro.writeFile( + 'src/components/Counter.css', + (original) => original.replace('font-size: 2em;', 'font-size: 24px;') + ); + + await expect(count).toHaveCSS('font-size', '24px'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a1d78e6db3c..8fd816659c71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -661,6 +661,18 @@ importers: chai-as-promised: 7.1.1_chai@4.3.6 mocha: 9.2.2 + packages/astro/e2e/fixtures/preact: + specifiers: + '@astrojs/preact': workspace:* + astro: workspace:* + react: ^18.1.0 + react-dom: ^18.1.0 + dependencies: + '@astrojs/preact': link:../../../../integrations/preact + astro: link:../../.. + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + packages/astro/e2e/fixtures/react: specifiers: '@astrojs/react': workspace:* From 08ae1c90db53cb3189ffad862c947bde8d935a50 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 13:16:15 -0600 Subject: [PATCH 18/54] adding svelte hydration and HMR tests --- .../astro/e2e/fixtures/preact/package.json | 5 +- .../e2e/fixtures/svelte/astro.config.mjs | 7 ++ .../astro/e2e/fixtures/svelte/package.json | 10 +++ .../svelte/src/components/Counter.svelte | 34 ++++++++ .../e2e/fixtures/svelte/src/pages/index.astro | 26 ++++++ packages/astro/e2e/preact.test.js | 2 +- packages/astro/e2e/svelte.test.js | 81 +++++++++++++++++++ pnpm-lock.yaml | 16 +++- 8 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 packages/astro/e2e/fixtures/svelte/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/svelte/package.json create mode 100644 packages/astro/e2e/fixtures/svelte/src/components/Counter.svelte create mode 100644 packages/astro/e2e/fixtures/svelte/src/pages/index.astro create mode 100644 packages/astro/e2e/svelte.test.js diff --git a/packages/astro/e2e/fixtures/preact/package.json b/packages/astro/e2e/fixtures/preact/package.json index 07bb82299725..64658bf777b5 100644 --- a/packages/astro/e2e/fixtures/preact/package.json +++ b/packages/astro/e2e/fixtures/preact/package.json @@ -1,11 +1,10 @@ { - "name": "@e2e/react", + "name": "@e2e/preact", "version": "0.0.0", "private": true, "dependencies": { "@astrojs/preact": "workspace:*", "astro": "workspace:*", - "react": "^18.1.0", - "react-dom": "^18.1.0" + "preact": "^10.7.2" } } diff --git a/packages/astro/e2e/fixtures/svelte/astro.config.mjs b/packages/astro/e2e/fixtures/svelte/astro.config.mjs new file mode 100644 index 000000000000..77fdcd1b9e48 --- /dev/null +++ b/packages/astro/e2e/fixtures/svelte/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import svelte from '@astrojs/svelte'; + +// https://astro.build/config +export default defineConfig({ + integrations: [svelte()], +}); diff --git a/packages/astro/e2e/fixtures/svelte/package.json b/packages/astro/e2e/fixtures/svelte/package.json new file mode 100644 index 000000000000..81ff2584431d --- /dev/null +++ b/packages/astro/e2e/fixtures/svelte/package.json @@ -0,0 +1,10 @@ +{ + "name": "@e2e/react", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/svelte": "workspace:*", + "astro": "workspace:*", + "svelte": "^3.48.0" + } +} diff --git a/packages/astro/e2e/fixtures/svelte/src/components/Counter.svelte b/packages/astro/e2e/fixtures/svelte/src/components/Counter.svelte new file mode 100644 index 000000000000..fb7e2aafcfb6 --- /dev/null +++ b/packages/astro/e2e/fixtures/svelte/src/components/Counter.svelte @@ -0,0 +1,34 @@ + + +
+ +
{ count }
+ +
+
+ +
+ + diff --git a/packages/astro/e2e/fixtures/svelte/src/pages/index.astro b/packages/astro/e2e/fixtures/svelte/src/pages/index.astro new file mode 100644 index 000000000000..35a13e7f376a --- /dev/null +++ b/packages/astro/e2e/fixtures/svelte/src/pages/index.astro @@ -0,0 +1,26 @@ +--- +import Counter from '../components/Counter.svelte'; + +const someProps = { + count: 0, +}; +--- + + + + + + + +

Hello, client:idle!

+
+ + +

Hello, client:load!

+
+ + +

Hello, client:visible!

+
+ + diff --git a/packages/astro/e2e/preact.test.js b/packages/astro/e2e/preact.test.js index c64f4feefafe..a7db134526e9 100644 --- a/packages/astro/e2e/preact.test.js +++ b/packages/astro/e2e/preact.test.js @@ -85,7 +85,7 @@ test.only('Preact', async ({ page, astro }) => { const count = page.locator('#counter-idle pre'); await expect(count).toHaveText('5'); - // test 2: updating the react component + // test 2: updating the preact component await astro.writeFile( 'src/components/JSXComponent.jsx', (original) => original.replace('Preact client:only component', 'Updated preact client:only component') diff --git a/packages/astro/e2e/svelte.test.js b/packages/astro/e2e/svelte.test.js new file mode 100644 index 000000000000..6732c363616a --- /dev/null +++ b/packages/astro/e2e/svelte.test.js @@ -0,0 +1,81 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture, onAfterHMR } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/svelte/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterAll(async ({ astro }) => { + await devServer.stop(); +}); + +test.afterEach(async ({ astro }) => { + astro.clean(); +}); + +test.only('Svelte', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + + await test.step('client:idle', async () => { + const counter = page.locator('#counter-idle'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:load', async () => { + const counter = page.locator('#counter-load'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:visible', async () => { + const counter = page.locator('#counter-visible'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('HMR', async () => { + const afterHMR = onAfterHMR(page); + + // test 1: updating the page component + await astro.writeFile( + 'src/pages/index.astro', + (original) => original.replace('id="counter-idle" {...someProps}', 'id="counter-idle" count={5}') + ); + + await afterHMR; + + const count = page.locator('#counter-idle pre'); + await expect(count).toHaveText('5'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fd816659c71..b24296f10520 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -665,13 +665,11 @@ importers: specifiers: '@astrojs/preact': workspace:* astro: workspace:* - react: ^18.1.0 - react-dom: ^18.1.0 + preact: ^10.7.2 dependencies: '@astrojs/preact': link:../../../../integrations/preact astro: link:../../.. - react: 18.1.0 - react-dom: 18.1.0_react@18.1.0 + preact: 10.7.2 packages/astro/e2e/fixtures/react: specifiers: @@ -685,6 +683,16 @@ importers: react: 18.1.0 react-dom: 18.1.0_react@18.1.0 + packages/astro/e2e/fixtures/svelte: + specifiers: + '@astrojs/svelte': workspace:* + astro: workspace:* + svelte: ^3.48.0 + dependencies: + '@astrojs/svelte': link:../../../../integrations/svelte + astro: link:../../.. + svelte: 3.48.0 + packages/astro/e2e/fixtures/tailwindcss: specifiers: '@astrojs/tailwind': workspace:* From 2481a5a389029f07f61eb5a22aaa0567e7711b26 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 13:21:23 -0600 Subject: [PATCH 19/54] adding solid-js hydration and HMR tests --- .../astro/e2e/fixtures/solid/astro.config.mjs | 7 ++ .../astro/e2e/fixtures/solid/package.json | 10 +++ .../fixtures/solid/src/components/Counter.css | 11 +++ .../fixtures/solid/src/components/Counter.jsx | 19 ++++ .../e2e/fixtures/solid/src/pages/index.astro | 26 ++++++ .../astro/e2e/fixtures/svelte/package.json | 2 +- packages/astro/e2e/solid.test.js | 89 +++++++++++++++++++ pnpm-lock.yaml | 10 +++ 8 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 packages/astro/e2e/fixtures/solid/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/solid/package.json create mode 100644 packages/astro/e2e/fixtures/solid/src/components/Counter.css create mode 100644 packages/astro/e2e/fixtures/solid/src/components/Counter.jsx create mode 100644 packages/astro/e2e/fixtures/solid/src/pages/index.astro create mode 100644 packages/astro/e2e/solid.test.js diff --git a/packages/astro/e2e/fixtures/solid/astro.config.mjs b/packages/astro/e2e/fixtures/solid/astro.config.mjs new file mode 100644 index 000000000000..a6c39b8535d2 --- /dev/null +++ b/packages/astro/e2e/fixtures/solid/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import solid from '@astrojs/solid-js'; + +// https://astro.build/config +export default defineConfig({ + integrations: [solid()], +}); diff --git a/packages/astro/e2e/fixtures/solid/package.json b/packages/astro/e2e/fixtures/solid/package.json new file mode 100644 index 000000000000..d95d4629f681 --- /dev/null +++ b/packages/astro/e2e/fixtures/solid/package.json @@ -0,0 +1,10 @@ +{ + "name": "@e2e/solid", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/solid-js": "workspace:*", + "astro": "workspace:*", + "solid-js": "^1.3.17" + } +} diff --git a/packages/astro/e2e/fixtures/solid/src/components/Counter.css b/packages/astro/e2e/fixtures/solid/src/components/Counter.css new file mode 100644 index 000000000000..cffdbda7b65e --- /dev/null +++ b/packages/astro/e2e/fixtures/solid/src/components/Counter.css @@ -0,0 +1,11 @@ +.counter { + display: grid; + font-size: 2em; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 3em; + place-items: center; +} + +.counter-message { + text-align: center; +} diff --git a/packages/astro/e2e/fixtures/solid/src/components/Counter.jsx b/packages/astro/e2e/fixtures/solid/src/components/Counter.jsx new file mode 100644 index 000000000000..e315033d3fe9 --- /dev/null +++ b/packages/astro/e2e/fixtures/solid/src/components/Counter.jsx @@ -0,0 +1,19 @@ +import { createSignal } from 'solid-js'; +import './Counter.css'; + +export default function Counter({ children, id, count: initialCount = 0 }) { + const [count, setCount] = createSignal(initialCount); + const add = () => setCount(count() + 1); + const subtract = () => setCount(count() - 1); + + return ( + <> +
+ +
{count()}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/solid/src/pages/index.astro b/packages/astro/e2e/fixtures/solid/src/pages/index.astro new file mode 100644 index 000000000000..a49d4db255e1 --- /dev/null +++ b/packages/astro/e2e/fixtures/solid/src/pages/index.astro @@ -0,0 +1,26 @@ +--- +import Counter from '../components/Counter.jsx'; + +const someProps = { + count: 0, +}; +--- + + + + + + + +

Hello, client:idle!

+
+ + +

Hello, client:load!

+
+ + +

Hello, client:visible!

+
+ + diff --git a/packages/astro/e2e/fixtures/svelte/package.json b/packages/astro/e2e/fixtures/svelte/package.json index 81ff2584431d..80e0661f88e4 100644 --- a/packages/astro/e2e/fixtures/svelte/package.json +++ b/packages/astro/e2e/fixtures/svelte/package.json @@ -1,5 +1,5 @@ { - "name": "@e2e/react", + "name": "@e2e/svelte", "version": "0.0.0", "private": true, "dependencies": { diff --git a/packages/astro/e2e/solid.test.js b/packages/astro/e2e/solid.test.js new file mode 100644 index 000000000000..0ce0731457a2 --- /dev/null +++ b/packages/astro/e2e/solid.test.js @@ -0,0 +1,89 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture, onAfterHMR } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/solid/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterAll(async ({ astro }) => { + await devServer.stop(); +}); + +test.afterEach(async ({ astro }) => { + astro.clean(); +}); + +test.only('Solid', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + + await test.step('client:idle', async () => { + const counter = page.locator('#counter-idle'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:load', async () => { + const counter = page.locator('#counter-load'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('client:visible', async () => { + const counter = page.locator('#counter-visible'); + await expect(counter).toBeVisible(); + + const count = counter.locator('pre'); + await expect(count).toHaveText('0'); + + const inc = counter.locator('.increment'); + await inc.click(); + + await expect(count).toHaveText('1'); + }); + + await test.step('HMR', async () => { + const afterHMR = onAfterHMR(page); + + // test 1: updating the page component + await astro.writeFile( + 'src/pages/index.astro', + (original) => original.replace('id="counter-idle" {...someProps}', 'id="counter-idle" count={5}') + ); + + await afterHMR; + + const count = page.locator('#counter-idle pre'); + await expect(count).toHaveText('5'); + + // test 2: updating imported CSS + await astro.writeFile( + 'src/components/Counter.css', + (original) => original.replace('font-size: 2em;', 'font-size: 24px;') + ); + + await expect(count).toHaveCSS('font-size', '24px'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b24296f10520..f0a004249af9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -683,6 +683,16 @@ importers: react: 18.1.0 react-dom: 18.1.0_react@18.1.0 + packages/astro/e2e/fixtures/solid: + specifiers: + '@astrojs/solid-js': workspace:* + astro: workspace:* + solid-js: ^1.3.17 + dependencies: + '@astrojs/solid-js': link:../../../../integrations/solid + astro: link:../../.. + solid-js: 1.3.17 + packages/astro/e2e/fixtures/svelte: specifiers: '@astrojs/svelte': workspace:* From 54e4ee2cd9a9f725f9e7ab96404bfe879eba4a25 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 14:00:56 -0600 Subject: [PATCH 20/54] adding solid hydration and HMR tests --- .../astro/e2e/fixtures/vue/astro.config.mjs | 7 ++++ packages/astro/e2e/fixtures/vue/package.json | 10 +++++ .../fixtures/vue/src/components/Counter.vue | 40 +++++++++++++++++++ .../e2e/fixtures/vue/src/pages/index.astro | 26 ++++++++++++ pnpm-lock.yaml | 10 +++++ 5 files changed, 93 insertions(+) create mode 100644 packages/astro/e2e/fixtures/vue/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/vue/package.json create mode 100644 packages/astro/e2e/fixtures/vue/src/components/Counter.vue create mode 100644 packages/astro/e2e/fixtures/vue/src/pages/index.astro diff --git a/packages/astro/e2e/fixtures/vue/astro.config.mjs b/packages/astro/e2e/fixtures/vue/astro.config.mjs new file mode 100644 index 000000000000..88193061246b --- /dev/null +++ b/packages/astro/e2e/fixtures/vue/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; + +// https://astro.build/config +export default defineConfig({ + integrations: [vue()], +}); diff --git a/packages/astro/e2e/fixtures/vue/package.json b/packages/astro/e2e/fixtures/vue/package.json new file mode 100644 index 000000000000..86b0c06731f3 --- /dev/null +++ b/packages/astro/e2e/fixtures/vue/package.json @@ -0,0 +1,10 @@ +{ + "name": "@e2e/vue", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/vue": "workspace:*", + "astro": "workspace:*", + "vue": "^3.2.33" + } +} diff --git a/packages/astro/e2e/fixtures/vue/src/components/Counter.vue b/packages/astro/e2e/fixtures/vue/src/components/Counter.vue new file mode 100644 index 000000000000..d5d5215f721f --- /dev/null +++ b/packages/astro/e2e/fixtures/vue/src/components/Counter.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/packages/astro/e2e/fixtures/vue/src/pages/index.astro b/packages/astro/e2e/fixtures/vue/src/pages/index.astro new file mode 100644 index 000000000000..77327247b80d --- /dev/null +++ b/packages/astro/e2e/fixtures/vue/src/pages/index.astro @@ -0,0 +1,26 @@ +--- +import Counter from '../components/Counter.vue'; + +const someProps = { + count: 0, +}; +--- + + + + + + + +

Hello, client:idle!

+
+ + +

Hello, client:load!

+
+ + +

Hello, client:visible!

+
+ + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0a004249af9..422238ffb8ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -711,6 +711,16 @@ importers: '@astrojs/tailwind': link:../../../../integrations/tailwind astro: link:../../.. + packages/astro/e2e/fixtures/vue: + specifiers: + '@astrojs/vue': workspace:* + astro: workspace:* + vue: ^3.2.33 + dependencies: + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + vue: 3.2.33 + packages/astro/test/fixtures/0-css: specifiers: '@astrojs/react': workspace:* From a934059a6e6c5e7daa9adfe1eb4212bcdafe69e5 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Sun, 15 May 2022 14:24:01 -0600 Subject: [PATCH 21/54] adding vue hydration and HMR tests --- .../astro/e2e/fixtures/lit/astro.config.mjs | 7 ++ packages/astro/e2e/fixtures/lit/package.json | 11 +++ .../fixtures/lit/src/components/Counter.js | 43 +++++++++ .../e2e/fixtures/lit/src/pages/index.astro | 26 ++++++ .../fixtures/vue/src/components/Counter.vue | 4 +- packages/astro/e2e/lit.test.js | 88 +++++++++++++++++++ packages/astro/e2e/vue.test.js | 81 +++++++++++++++++ pnpm-lock.yaml | 12 +++ 8 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 packages/astro/e2e/fixtures/lit/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/lit/package.json create mode 100644 packages/astro/e2e/fixtures/lit/src/components/Counter.js create mode 100644 packages/astro/e2e/fixtures/lit/src/pages/index.astro create mode 100644 packages/astro/e2e/lit.test.js create mode 100644 packages/astro/e2e/vue.test.js diff --git a/packages/astro/e2e/fixtures/lit/astro.config.mjs b/packages/astro/e2e/fixtures/lit/astro.config.mjs new file mode 100644 index 000000000000..1eab8f9ab8be --- /dev/null +++ b/packages/astro/e2e/fixtures/lit/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import lit from '@astrojs/lit'; + +// https://astro.build/config +export default defineConfig({ + integrations: [lit()], +}); diff --git a/packages/astro/e2e/fixtures/lit/package.json b/packages/astro/e2e/fixtures/lit/package.json new file mode 100644 index 000000000000..6c2d78baf4c8 --- /dev/null +++ b/packages/astro/e2e/fixtures/lit/package.json @@ -0,0 +1,11 @@ +{ + "name": "@e2e/svelte", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/lit": "workspace:*", + "astro": "workspace:*", + "@webcomponents/template-shadowroot": "^0.1.0", + "lit": "^2.2.3" + } +} diff --git a/packages/astro/e2e/fixtures/lit/src/components/Counter.js b/packages/astro/e2e/fixtures/lit/src/components/Counter.js new file mode 100644 index 000000000000..1bc78c244854 --- /dev/null +++ b/packages/astro/e2e/fixtures/lit/src/components/Counter.js @@ -0,0 +1,43 @@ +import { LitElement, html } from 'lit'; + +export const tagName = 'my-counter'; + +class Counter extends LitElement { + static get properties() { + return { + count: { + type: Number, + }, + id: { + type: String, + } + }; + } + + constructor() { + super(); + this.count = this.count || 0; + } + + decrement() { + this.count--; + } + + increment() { + this.count++; + } + + render() { + return html` +
+ + +

Count: ${this.count}

+ + +
+ `; + } +} + +customElements.define(tagName, Counter); diff --git a/packages/astro/e2e/fixtures/lit/src/pages/index.astro b/packages/astro/e2e/fixtures/lit/src/pages/index.astro new file mode 100644 index 000000000000..6bc21750539d --- /dev/null +++ b/packages/astro/e2e/fixtures/lit/src/pages/index.astro @@ -0,0 +1,26 @@ +--- +import '../components/Counter.js'; + +const someProps = { + count: 0, +}; +--- + + + + + + + +

Hello, client:idle!

+
+ + +

Hello, client:load!

+
+ + +

Hello, client:visible!

+
+ + diff --git a/packages/astro/e2e/fixtures/vue/src/components/Counter.vue b/packages/astro/e2e/fixtures/vue/src/components/Counter.vue index d5d5215f721f..e07f8f181340 100644 --- a/packages/astro/e2e/fixtures/vue/src/components/Counter.vue +++ b/packages/astro/e2e/fixtures/vue/src/components/Counter.vue @@ -1,8 +1,8 @@