Adding E2E tests for client hydration and HMR (#3374)

* adding Tailwind E2E tests with Playwright

* package.json updates

* adding e2e tests to CI workflow

* using e2e for dev tests, mocha for build tests

* refactor: sharing test-utils helpers

* chore: update lockfile

* Adding contributing docs

* Revert "refactor: sharing test-utils helpers"

This reverts commit 48496f43bc99eab30747baf6e83041ba4932e786.

* refactor: simpler solution to resolving e2e test fixtures

* chore: updating lockfile

* refactor: cleaning up how URLs are resolved in e2e tests

* install playwright deps in CI

* ensure playwright deps are installed during CI

* adding a basic HMR test for tailwind styles

* using @e2e for playwright test packages

* adding react hydration and HMR tests

* adding hydration and HMR tests  for preact

* adding svelte hydration and HMR tests

* adding solid-js hydration and HMR tests

* adding solid hydration and HMR tests

* adding vue hydration and HMR tests

* adding client:media tests

* fixing Lit hydration and HMR tests

* fixing up the Vue e2e tests

* fixing up svelte tests

* chore: test cleanup

* chore: cleaning up test element IDs

* chore: updating lock file

* chore: update lockfile after merge

* TEMP: disabling React e2e tests

* Revert "TEMP: disabling React e2e tests"

This reverts commit ed1bad9cbc.

* updating to use the new editFiles helper

* chore: updating lock file

* updating lock file

* updating lockfile

* TEMP: watching for console logs

* TEMP: testing typescript tests

* updating test:e2e scripts for config file

* seems like it didn't find the config file?

* use a fresh dev server for each test

* removing Lit tests for now

* Revert "removing Lit tests for now"

This reverts commit 4970a8093e.

* updating test config for CI

* WIP: disabling HMR tests to track down why they're unreliable

* TEMP: logging to debug HMR test

* afterEach isn't a global in Playwright

* fix: the test's file reset helper was using a URL not filepath

* one last try, why is the HMR test hanging at cleanup?

* resetting files after tailwind HMR test

* create the onNextChange watcher before editFile is called

* moving the file changed sync into editFile()

* code refactor + Astro Component HMR test

* chore: lint fixes

* adding a test suite for the framework-multiple example app
This commit is contained in:
Tony Sullivan 2022-05-23 16:56:45 +00:00 committed by GitHub
parent 63c26c1b24
commit ff56f083bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 2183 additions and 105 deletions

View file

@ -6,5 +6,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@example/*", "@test/*"]
"ignore": ["@example/*", "@test/*", "@e2e/*"]
}

View file

@ -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 playwright install && pnpm run test:e2e",
"test:e2e:match": "cd packages/astro && pnpm playwright install && pnpm run test:e2e:match",
"benchmark": "turbo run benchmark --scope=astro",
"lint": "eslint \"packages/**/*.ts\"",
"format": "prettier -w .",

View file

@ -0,0 +1,42 @@
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/astro-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('Astro components', () => {
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const hero = page.locator('section');
await expect(hero, 'hero has background: white').toHaveCSS(
'background-color',
'rgb(255, 255, 255)'
);
await expect(hero, 'hero has color: black').toHaveCSS('color', 'rgb(0, 0, 0)');
// Edit the Hero component with a new background color
await astro.editFile('./src/components/Hero.astro', (content) =>
content.replace('background: white', 'background: rgb(230, 230, 230)')
);
await expect(hero, 'background color updated').toHaveCSS(
'background-color',
'rgb(230, 230, 230)'
);
});
});

View file

@ -0,0 +1,4 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({});

View file

@ -0,0 +1,8 @@
{
"name": "@e2e/astro-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -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;
}

View file

@ -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 (
<>
<div id={id} className="counter">
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,25 @@
---
export interface Props {
title: string;
}
const { title } = Astro.props as Props;
---
<section>
<h1>{title}</h1>
<slot />
</section>
<style>
section {
width: 100%;
height: 80vh;
background: white;
color: black;
}
h1 {
text-align: center;
}
</style>

View file

@ -0,0 +1,5 @@
import { h } from 'preact';
export default function({ id }) {
return <div id={id}>Preact client:only component</div>
}

View file

@ -0,0 +1,16 @@
---
import Hero from '../components/Hero.astro';
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<main>
<Hero title="Astro Components">
Lorem ipsum, dolor sit amet consectetur adipisicing elit.
</Hero>
</main>
</body>
</html>

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import lit from '@astrojs/lit';
// https://astro.build/config
export default defineConfig({
integrations: [lit()],
});

View file

@ -0,0 +1,11 @@
{
"name": "@e2e/lit-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/lit": "workspace:*",
"astro": "workspace:*",
"@webcomponents/template-shadowroot": "^0.1.0",
"lit": "^2.2.3"
}
}

View file

@ -0,0 +1,36 @@
import { LitElement, html } from 'lit';
export const tagName = 'my-counter';
class Counter extends LitElement {
static get properties() {
return {
count: {
type: Number,
},
};
}
constructor() {
super();
this.count = 0;
}
increment() {
this.count++;
}
render() {
return html`
<div>
<p>Count: ${this.count}</p>
<button type="button" @click=${this.increment}>Increment</button>
<slot />
</div>
`;
}
}
customElements.define(tagName, Counter);

View file

@ -0,0 +1,26 @@
---
import '../components/Counter.js';
const someProps = {
count: 0,
};
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<my-counter id="client-idle" {...someProps} client:idle>
<h1>Hello, client:idle!</h1>
</my-counter>
<my-counter id="client-load" {...someProps} client:load>
<h1>Hello, client:load!</h1>
</my-counter>
<my-counter id="client-visible" {...someProps} client:visible>
<h1>Hello, client:visible!</h1>
</my-counter>
</body>
</html>

View file

@ -0,0 +1,18 @@
---
import '../components/Counter.js';
const someProps = {
count: 0,
};
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<my-counter id="client-media" {...someProps} client:media="(max-width: 50em)">
<h1>Hello, client:media!</h1>
</my-counter>
</body>
</html>

View file

@ -0,0 +1,12 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';
import vue from '@astrojs/vue';
import solid from '@astrojs/solid-js';
// https://astro.build/config
export default defineConfig({
// Enable many frameworks to support all different kinds of components.
integrations: [preact(), react(), svelte(), vue(), solid()],
});

View file

@ -0,0 +1,24 @@
{
"name": "@e2e/multiple-frameworks",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@astrojs/lit": "^0.1.3",
"@astrojs/preact": "^0.1.2",
"@astrojs/react": "^0.1.2",
"@astrojs/solid-js": "^0.1.2",
"@astrojs/svelte": "^0.1.3",
"@astrojs/vue": "^0.1.4",
"astro": "^1.0.0-beta.31"
},
"dependencies": {
"@webcomponents/template-shadowroot": "^0.1.0",
"lit": "^2.2.4",
"preact": "^10.7.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"solid-js": "^1.4.2",
"svelte": "^3.48.0",
"vue": "^3.2.34"
}
}

View file

@ -0,0 +1,7 @@
---
const { id } = Astro.props
---
<div id={id} class="children">
<h1>Hello Astro (A)</h1>
</div>

View file

@ -0,0 +1,7 @@
---
const { id } = Astro.props
---
<div id={id} class="children">
<h1>Hello Astro (B)</h1>
</div>

View file

@ -0,0 +1,33 @@
import { LitElement, html } from 'lit';
export const tagName = 'my-counter';
class Counter extends LitElement {
static get properties() {
return {
count: {
type: Number,
},
};
}
constructor() {
super();
this.count = 0;
}
increment() {
this.count++;
}
render() {
return html`
<div>
<p>Count: ${this.count}</p>
<button type="button" @click=${this.increment}>Increment</button>
</div>
`;
}
}
customElements.define(tagName, Counter);

View file

@ -0,0 +1,19 @@
import { useState } from 'preact/hooks';
/** a counter written in Preact */
export function PreactCounter({ children, id }) {
const [count, setCount] = useState(0);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);
return (
<>
<div id={id} class="counter">
<button class="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button class="increment" onClick={add}>+</button>
</div>
<div class="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,19 @@
import { useState } from 'react';
/** a counter written in React */
export function Counter({ children, id }) {
const [count, setCount] = useState(0);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);
return (
<>
<div id={id} className="counter">
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,19 @@
import { createSignal } from 'solid-js';
/** a counter written with Solid */
export default function SolidCounter({ children, id }) {
const [count, setCount] = createSignal(0);
const add = () => setCount(count() + 1);
const subtract = () => setCount(count() - 1);
return (
<>
<div id={id} class="counter">
<button class="decrement" onClick={subtract}>-</button>
<pre>{count()}</pre>
<button class="increment" onClick={add}>+</button>
</div>
<div class="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,29 @@
<script>
export let id;
let children;
let count = 0;
function add() {
count += 1;
}
function subtract() {
count -= 1;
}
</script>
<div {id} class="counter">
<button class="decrement" on:click={subtract}>-</button>
<pre>{ count }</pre>
<button class="increment" on:click={add}>+</button>
</div>
<div class="counter-message">
<slot />
</div>
<style>
.counter {
background: white;
}
</style>

View file

@ -0,0 +1,34 @@
<template>
<div :id="id" class="counter">
<button class="decrement" @click="subtract()">-</button>
<pre>{{ count }}</pre>
<button class="increment" @click="add()">+</button>
</div>
<div class="counter-message">
<slot />
</div>
</template>
<script>
import { ref } from 'vue';
export default {
props: {
id: {
type: String,
required: true
}
},
setup(props) {
const count = ref(0);
const add = () => (count.value = count.value + 1);
const subtract = () => (count.value = count.value - 1);
return {
id: props.id,
count,
add,
subtract,
};
},
};
</script>

View file

@ -0,0 +1,2 @@
export { default as A } from './A.astro';
export { default as B } from './B.astro';

View file

@ -0,0 +1,50 @@
---
// Style Imports
import '../styles/global.css';
// Component Imports
import { A, B as Renamed } from '../components';
import * as react from '../components/ReactCounter.jsx';
import { PreactCounter } from '../components/PreactCounter.tsx';
import SolidCounter from '../components/SolidCounter.tsx';
import VueCounter from '../components/VueCounter.vue';
import SvelteCounter from '../components/SvelteCounter.svelte';
// Full Astro Component Syntax:
// https://docs.astro.build/core-concepts/astro-components/
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<main>
<react.Counter id="react-counter" client:idle>
<h1>Hello React!</h1>
<p>What's up?</p>
</react.Counter>
<PreactCounter id="preact-counter" client:idle>
<h1>Hello Preact!</h1>
</PreactCounter>
<SolidCounter id="solid-counter" client:idle>
<h1>Hello Solid!</h1>
</SolidCounter>
<VueCounter id="vue-counter" client:idle>
<h1>Hello Vue!</h1>
</VueCounter>
<SvelteCounter id="svelte-counter" client:idle>
<h1>Hello Svelte!</h1>
</SvelteCounter>
<A id="astro-a" />
<Renamed id="astro-b" />
</main>
</body>
</html>

View file

@ -0,0 +1,21 @@
html,
body {
font-family: system-ui;
margin: 0;
}
body {
padding: 2rem;
}
.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;
}

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
// https://astro.build/config
export default defineConfig({
integrations: [preact()],
});

View file

@ -0,0 +1,10 @@
{
"name": "@e2e/preact-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/preact": "workspace:*",
"astro": "workspace:*",
"preact": "^10.7.2"
}
}

View file

@ -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;
}

View file

@ -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 (
<>
<div id={id} className="counter">
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,5 @@
import { h } from 'preact';
export default function({ id }) {
return <div id={id}>Preact client:only component</div>
}

View file

@ -0,0 +1,37 @@
---
import Counter from '../components/Counter.jsx';
import PreactComponent from '../components/JSXComponent.jsx';
const someProps = {
count: 0,
};
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Counter id="server-only" {...someProps}>
<h1>Hello, server!</h1>
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
<h1>Hello, client:idle!</h1>
</Counter>
<Counter id="client-load" {...someProps} client:load>
<h1>Hello, client:load!</h1>
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
<h1>Hello, client:visible!</h1>
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
<h1>Hello, client:media!</h1>
</Counter>
<PreactComponent id="client-only" client:only="preact" />
</body>
</html>

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
integrations: [react()],
});

View file

@ -0,0 +1,11 @@
{
"name": "@e2e/react-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/react": "workspace:*",
"astro": "workspace:*",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}
}

View file

@ -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;
}

View file

@ -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 (
<>
<div id={id} className="counter">
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,5 @@
import React from 'react';
export default function({ id }) {
return <div id={id}>React client:only component</div>
}

View file

@ -0,0 +1,37 @@
---
import Counter from '../components/Counter.jsx';
import ReactComponent from '../components/JSXComponent.jsx';
const someProps = {
count: 0,
};
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Counter id="server-only" {...someProps}>
<h1>Hello, server!</h1>
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
<h1>Hello, client:idle!</h1>
</Counter>
<Counter id="client-load" {...someProps} client:load>
<h1>Hello, client:load!</h1>
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
<h1>Hello, client:visible!</h1>
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
<h1>Hello, client:media!</h1>
</Counter>
<ReactComponent id="client-only" client:only="react" />
</body>
</html>

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import solid from '@astrojs/solid-js';
// https://astro.build/config
export default defineConfig({
integrations: [solid()],
});

View file

@ -0,0 +1,12 @@
{
"name": "@e2e/solid-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/solid-js": "workspace:*",
"astro": "workspace:*"
},
"devDependencies": {
"solid-js": "^1.4.1"
}
}

View file

@ -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;
}

View file

@ -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 (
<>
<div id={id} class="counter">
<button class="decrement" onClick={subtract}>-</button>
<pre>{count()}</pre>
<button class="increment" onClick={add}>+</button>
</div>
<div class="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,34 @@
---
import Counter from '../components/Counter.jsx';
const someProps = {
count: 0,
};
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Counter id="server-only" {...someProps}>
<h1>Hello, server!</h1>
</Counter>
<Counter id="client-idle" {...someProps} client:idle>
<h1>Hello, client:idle!</h1>
</Counter>
<Counter id="client-load" {...someProps} client:load>
<h1>Hello, client:load!</h1>
</Counter>
<Counter id="client-visible" {...someProps} client:visible>
<h1>Hello, client:visible!</h1>
</Counter>
<Counter id="client-media" {...someProps} client:media="(max-width: 50em)">
<h1>Hello, client:media!</h1>
</Counter>
</body>
</html>

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import svelte from '@astrojs/svelte';
// https://astro.build/config
export default defineConfig({
integrations: [svelte()],
});

View file

@ -0,0 +1,10 @@
{
"name": "@e2e/svelte-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
"svelte": "^3.48.0"
}
}

View file

@ -0,0 +1,35 @@
<script lang="ts">
export let id: string;
let count = 0;
function add() {
count += 1;
}
function subtract() {
count -= 1;
}
</script>
<div {id} class="counter">
<button class="decrement" on:click={subtract}>-</button>
<pre>{ count }</pre>
<button class="increment" on:click={add}>+</button>
</div>
<div id={`${id}-message`} class="message">
<slot />
</div>
<style>
.counter{
display: grid;
font-size: 2em;
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-top: 2em;
place-items: center;
}
.message {
text-align: center;
}
</style>

View file

@ -0,0 +1,34 @@
---
import Counter from '../components/Counter.svelte';
const someProps = {
count: 0,
};
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Counter id="server-only">
<h1>Hello, server!</h1>
</Counter>
<Counter id="client-idle" client:idle>
<h1>Hello, client:idle!</h1>
</Counter>
<Counter id="client-load" client:load>
<h1>Hello, client:load!</h1>
</Counter>
<Counter id="client-visible" client:visible>
<h1>Hello, client:visible!</h1>
</Counter>
<Counter id="client-media" client:media="(max-width: 50rem)">
<h1>Hello, client:media!</h1>
</Counter>
</body>
</html>

View file

@ -1,5 +1,5 @@
{
"name": "@test/e2e-tailwindcss",
"name": "@e2e/e2e-tailwindcss",
"version": "0.0.0",
"private": true,
"dependencies": {

View file

@ -0,0 +1,13 @@
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';
// https://astro.build/config
export default defineConfig({
integrations: [vue({
template: {
compilerOptions: {
isCustomElement: tag => tag.includes('my-button')
}
}
})],
});

View file

@ -0,0 +1,9 @@
{
"name": "@test/vue-component",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,47 @@
<template>
<div :id="id" class="counter">
<h1><slot /></h1>
<button class="decrement" @click="subtract()">-</button>
<Result :value="count" />
<button class="increment" @click="add()">+</button>
</div>
</template>
<script>
import { ref } from 'vue';
import Result from './Result.vue';
export default {
components: {
Result
},
props: {
id: {
type: String,
required: true
},
start: {
type: String,
required: true
},
stepSize: {
type: String,
default: "1"
}
},
setup(props) {
const id = props.id;
const count = ref(parseInt(props.start))
const stepSize = ref(parseInt(props.stepSize))
const add = () => (count.value = count.value + stepSize.value);
const subtract = () => (count.value = count.value - stepSize.value);
return {
count,
id,
add,
subtract,
};
},
};
</script>

View file

@ -0,0 +1,16 @@
<template>
<pre>{{ value }}</pre>
<my-button>Click Me</my-button>
</template>
<script>
export default {
props: {
value: {
type: Number,
required: true
}
}
}
</script>

View file

@ -0,0 +1,33 @@
---
import Counter from '../components/Counter.vue'
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width"
/>
<title>Vue component</title>
<style>
:global(:root) {
font-family: system-ui;
padding: 1em;
}
</style>
</head>
<body>
<main>
<Counter id="server-only" start="0">No Client</Counter>
<Counter id="client-load" start="0" client:load>Hello, client:load!</Counter>
<!-- Test island deduplication, i.e. same UID as the component above. -->
<Counter id="client-load-dup" start="0" client:load>Hello, client:load!</Counter>
<!-- Test island deduplication account for non-render affecting props. -->
<Counter id="client-load-step" start="0" step-size="2" client:load>Hello, client:load!</Counter>
<Counter id="client-idle" start="0" client:idle>Hello, client:idle!</Counter>
<!-- Test that two client:visibles have unique uids -->
<Counter id="client-visible" start="0" client:visible>Hello, client:visible!</Counter>
<Counter id="client-media" start="0" client:media="(max-width: 50rem)">Hello, client:media!</Counter>
</main>
</body>
</html>

View file

@ -0,0 +1,103 @@
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/lit-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
// TODO: configure playwright to handle web component APIs
// https://github.com/microsoft/playwright/issues/14241
test.describe.skip('Lit components', () => {
test('client:idle', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-idle');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
});
test('client:load', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-load');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
});
test('client:visible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Make sure the component is on screen to trigger hydration
const counter = page.locator('#client-visible');
await counter.scrollIntoViewIfNeeded();
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
});
test('client:media', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/media'));
const counter = page.locator('#client-media');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('Count: 0');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const label = page.locator('#client-idle h1');
await astro.editFile('./src/pages/index.astro', (original) =>
original.replace('Hello, client:idle!', 'Hello, updated client:idle!')
);
await expect(label, 'slot text updated').toHaveText('Hello, updated client:idle!');
});
});

View file

@ -0,0 +1,141 @@
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/multiple-frameworks/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('Multiple frameworks', () => {
test('React counter', async ({ astro, page }) => {
await page.goto('/');
const counter = await page.locator('#react-counter');
await expect(counter, 'component is visible').toBeVisible();
const count = await counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const increment = await counter.locator('.increment');
await increment.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('Preact counter', async ({ astro, page }) => {
await page.goto('/');
const counter = await page.locator('#preact-counter');
await expect(counter, 'component is visible').toBeVisible();
const count = await counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const increment = await counter.locator('.increment');
await increment.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('Solid counter', async ({ astro, page }) => {
await page.goto('/');
const counter = await page.locator('#solid-counter');
await expect(counter, 'component is visible').toBeVisible();
const count = await counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const increment = await counter.locator('.increment');
await increment.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('Vue counter', async ({ astro, page }) => {
await page.goto('/');
const counter = await page.locator('#vue-counter');
await expect(counter, 'component is visible').toBeVisible();
const count = await counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const increment = await counter.locator('.increment');
await increment.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('Svelte counter', async ({ astro, page }) => {
await page.goto('/');
const counter = await page.locator('#svelte-counter');
await expect(counter, 'component is visible').toBeVisible();
const count = await counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const increment = await counter.locator('.increment');
await increment.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('Astro components', async ({ astro, page }) => {
await page.goto('/');
const aComponent = await page.locator('#astro-a');
await expect(aComponent, 'component is visible').toBeVisible();
await expect(aComponent, 'component text is visible').toHaveText('Hello Astro (A)');
const bComponent = await page.locator('#astro-b');
await expect(bComponent, 'component is visible').toBeVisible();
await expect(bComponent, 'component text is visible').toHaveText('Hello Astro (B)');
});
test('HMR', async ({ astro, page }) => {
await page.goto('/');
// 1: updating the page template
const preactSlot = page.locator('#preact-counter + .counter-message');
await expect(preactSlot, 'initial slot content').toHaveText('Hello Preact!');
await astro.editFile('./src/pages/index.astro', (content) =>
content.replace('Hello Preact!', 'Hello Preact, updated!')
);
await expect(preactSlot, 'slot content updated').toHaveText('Hello Preact, updated!');
// Edit the react component
await astro.editFile('./src/components/ReactCounter.jsx', (content) =>
content.replace('useState(0)', 'useState(5)')
);
const reactCount = await page.locator('#react-counter pre');
await expect(reactCount, 'initial count updated to 5').toHaveText('5');
// Edit the svelte component's style
const svelteCounter = page.locator('#svelte-counter');
await expect(svelteCounter, 'initial background is white').toHaveCSS('background-color', 'rgb(255, 255, 255)');
await astro.editFile('./src/components/SvelteCounter.svelte', (content) =>
content.replace('background: white', 'background: rgb(230, 230, 230)')
);
await expect(svelteCounter, 'background color updated').toHaveCSS('background-color', 'rgb(230, 230, 230)');
});
});

View file

@ -10,18 +10,18 @@ const test = base.extend({
let devServer;
test.beforeAll(async ({ astro }) => {
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterAll(async ({ astro }) => {
test.afterEach(async () => {
await devServer.stop();
});
test('Loading styles that are nested', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
test.describe('Loading styles that are nested', () => {
test('header', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
await test.step('header', async () => {
const header = page.locator('header');
await expect(header, 'should have background color').toHaveCSS(

View file

@ -0,0 +1,143 @@
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/preact-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('Preact components', () => {
test('server only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#server-only');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('0');
});
test('client:idle', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-idle');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:load', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-load');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:visible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Make sure the component is on screen to trigger hydration
const counter = page.locator('#client-visible');
await counter.scrollIntoViewIfNeeded();
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:media', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-media');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('0');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const label = page.locator('#client-only');
await expect(label, 'component is visible').toBeVisible();
await expect(label, 'slot text is visible').toHaveText('Preact client:only component');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const count = page.locator('#client-idle pre');
await expect(count, 'initial count is 0').toHaveText('0');
// Edit the component's initial count prop
await astro.editFile('./src/pages/index.astro', (original) =>
original.replace('id="client-idle" {...someProps}', 'id="client-idle" count={5}')
);
await expect(count, 'count prop updated').toHaveText('5');
// Edit the component's slot text
await astro.editFile('./src/components/JSXComponent.jsx', (original) =>
original.replace('Preact client:only component', 'Updated preact client:only component')
);
const label = page.locator('#client-only');
await expect(label, 'client:only component is visible').toBeVisible();
await expect(label, 'client:only slot text is visible').toHaveText(
'Updated preact client:only component'
);
// Edit the imported CSS file
await astro.editFile('./src/components/Counter.css', (original) =>
original.replace('font-size: 2em;', 'font-size: 24px;')
);
await expect(count, 'imported styles updated').toHaveCSS('font-size', '24px');
});
});

View file

@ -0,0 +1,143 @@
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/react-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('React components', () => {
test('server only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#server-only');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated').toHaveText('0');
});
test('client:idle', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-idle');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:load', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-load');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:visible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Make sure the component is on screen to trigger hydration
const counter = page.locator('#client-visible');
await counter.scrollIntoViewIfNeeded();
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:media', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-media');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('0');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const label = page.locator('#client-only');
await expect(label, 'component is visible').toBeVisible();
await expect(label, 'slot text is visible').toHaveText('React client:only component');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const count = page.locator('#client-idle pre');
await expect(count, 'initial count is 0').toHaveText('0');
// Edit the component's initial count prop
await astro.editFile('./src/pages/index.astro', (original) =>
original.replace('id="client-idle" {...someProps}', 'id="client-idle" count={5}')
);
await expect(count, 'count prop updated').toHaveText('5');
// Edit the component's slot text
await astro.editFile('./src/components/JSXComponent.jsx', (original) =>
original.replace('React client:only component', 'Updated react client:only component')
);
const label = page.locator('#client-only');
await expect(label, 'client:only component is visible').toBeVisible();
await expect(label, 'client:only slot text is visible').toHaveText(
'Updated react client:only component'
);
// Edit the imported CSS file
await astro.editFile('./src/components/Counter.css', (original) =>
original.replace('font-size: 2em;', 'font-size: 24px;')
);
await expect(count, 'imported CSS updated').toHaveCSS('font-size', '24px');
});
});

View file

@ -0,0 +1,123 @@
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/solid-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('Solid components', () => {
test('server only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#server-only');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated').toHaveText('0');
});
test('client:idle', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-idle');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:load', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-load');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:visible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Make sure the component is on screen to trigger hydration
const counter = page.locator('#client-visible');
await counter.scrollIntoViewIfNeeded();
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:media', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-media');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('0');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const count = page.locator('#client-idle pre');
await expect(count, 'initial count is 0').toHaveText('0');
// Edit the component's initial count prop
await astro.editFile('./src/pages/index.astro', (original) =>
original.replace('id="client-idle" {...someProps}', 'id="client-idle" count={5}')
);
await expect(count, 'count prop updated').toHaveText('5');
// Edit the imported CSS
await astro.editFile('./src/components/Counter.css', (original) =>
original.replace('font-size: 2em;', 'font-size: 24px;')
);
await expect(count, 'imported CSS updated').toHaveCSS('font-size', '24px');
});
});

View file

@ -0,0 +1,114 @@
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/svelte-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('Svelte components', () => {
test('server only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#server-only');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated').toHaveText('0');
});
test('client:idle', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-idle');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:load', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-load');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:visible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Make sure the component is on screen to trigger hydration
const counter = page.locator('#client-visible');
await counter.scrollIntoViewIfNeeded();
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:media', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-media');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('0');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Edit the component's slot text
await astro.editFile('./src/pages/index.astro', (original) =>
original.replace('Hello, client:idle!', 'Hello, updated client:idle!')
);
const label = page.locator('#client-idle-message');
await expect(label, 'slot text updated').toHaveText('Hello, updated client:idle!');
});
});

View file

@ -10,18 +10,19 @@ const test = base.extend({
let devServer;
test.beforeAll(async ({ astro }) => {
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterAll(async ({ astro }) => {
test.afterEach(async ({ astro }) => {
await devServer.stop();
astro.resetAllFiles();
});
test('Tailwind CSS', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
test.describe('Tailwind CSS', () => {
test('body', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
await test.step('body', async () => {
const body = page.locator('body');
await expect(body, 'should have classes').toHaveClass('bg-dawn text-midnight');
@ -32,7 +33,9 @@ test('Tailwind CSS', async ({ page, astro }) => {
await expect(body, 'should have color').toHaveCSS('color', 'rgb(49, 39, 74)');
});
await test.step('button', async () => {
test('button', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const button = page.locator('button');
await expect(button, 'should have bg-purple-600').toHaveClass(/bg-purple-600/);
@ -48,4 +51,20 @@ 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');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
await astro.editFile('./src/components/Button.astro', (original) =>
original.replace('bg-purple-600', 'bg-purple-400')
);
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)'
);
});
});

View file

@ -0,0 +1,144 @@
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/vue-component/' });
await use(fixture);
},
});
let devServer;
test.beforeEach(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterEach(async () => {
await devServer.stop();
});
test.describe('Vue components', () => {
test('server only', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#server-only');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated').toHaveText('0');
});
test('client:idle', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-idle');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:load', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Multiple counters on the page to verify islands aren't sharing state
const counter = page.locator('#client-load');
const counterDup = page.locator('#client-load-dup');
const counterStep = page.locator('#client-load-step');
await expect(counter).toBeVisible();
await expect(counterDup).toBeVisible();
await expect(counterStep).toBeVisible();
const count = counter.locator('pre');
const countDup = counterDup.locator('pre');
const countStep = counterStep.locator('pre');
const countInc = counter.locator('.increment');
const countDupInc = counterDup.locator('.increment');
const countStepInc = counterStep.locator('.increment');
// Should only increment the first counter
await countInc.click();
await expect(count, 'intial count is 1').toHaveText('1');
await expect(countDup, 'initial count is 0').toHaveText('0');
await expect(countStep, 'initial count is 0').toHaveText('0');
// Should only increment the second counter
await countDupInc.click();
await expect(count, "count didn't change").toHaveText('1');
await expect(countDup, 'count incremented by 1').toHaveText('1');
await expect(countStep, "count didn't change").toHaveText('0');
// Should only increment the third counter
// Expecting an increase of 4 becasuse the component's
// step is set to 2
await countStepInc.click();
await countStepInc.click();
await expect(count, "count didn't change").toHaveText('1');
await expect(countDup, "count didn't change").toHaveText('1');
await expect(countStep, 'count incremented by 4').toHaveText('4');
});
test('client:visible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Make sure the component is on screen to trigger hydration
const counter = page.locator('#client-visible');
await counter.scrollIntoViewIfNeeded();
await expect(counter).toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('client:media', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#client-media');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('0');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
test('HMR', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
// Edit the component's slot text
await astro.editFile('./src/pages/index.astro', (original) =>
original.replace('Hello, client:visible!', 'Hello, updated client:visible!')
);
const label = page.locator('#client-visible h1');
await expect(label, 'slotted text updated').toHaveText('Hello, updated client:visible!');
});
});

View file

@ -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 --ignore **/errors.test.js && mocha --timeout 20000 **/lit-element.test.js && mocha --timeout 20000 **/errors.test.js",
"test:match": "mocha --timeout 20000 -g",
"test:e2e": "playwright test e2e"
"test:e2e": "playwright test",
"test:e2e:match": "playwright test -g"
},
"dependencies": {
"@astrojs/compiler": "^0.14.3",

View file

@ -0,0 +1,42 @@
import { devices } from '@playwright/test';
const config = {
testMatch: 'e2e/*.test.js',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* Fail the build on CI if you accidentally left test in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
projects: [
{
name: 'Chrome Stable',
use: {
browserName: 'chromium',
channel: 'chrome',
},
},
],
};
export default config;

View file

@ -31,7 +31,7 @@ if (import.meta.hot) {
}
}
if (hasAstroUpdate) {
return updatePage();
return await updatePage();
}
}
import.meta.hot.on('vite:beforeUpdate', async (event) => {

View file

@ -52,9 +52,7 @@ describe('Error display', () => {
expect($('.statusMessage').text()).to.equal('Internal Error');
// 2. Edit the file, fixing the error
let changeOccured = fixture.onNextChange();
await fixture.editFile('./src/components/SvelteSyntaxError.svelte', `<h1>No mismatch</h1>`);
await changeOccured;
// 3. Verify that the file is fixed.
html = await fixture.fetch('/svelte-syntax-error').then((res) => res.text());

View file

@ -28,6 +28,7 @@ polyfill(globalThis, {
* @property {(url: string) => string} resolveUrl
* @property {(url: string, opts: any) => Promise<Response>} fetch
* @property {(path: string) => Promise<string>} readFile
* @property {(path: string, updater: (content: string) => string) => Promise<void>} writeFile
* @property {(path: string) => Promise<string[]>} readdir
* @property {() => Promise<DevServer>} startDevServer
* @property {() => Promise<PreviewServer>} preview
@ -97,7 +98,7 @@ export async function loadFixture(inlineConfig) {
const resolveUrl = (url) =>
`http://${'127.0.0.1'}:${config.server.port}${url.replace(/^\/?/, '/')}`;
// A map of files that have been editted.
let fileEdits = new Map();
@ -108,6 +109,11 @@ export async function loadFixture(inlineConfig) {
fileEdits.clear();
};
const onNextChange = () =>
devServer
? new Promise((resolve) => devServer.watcher.once('change', resolve))
: Promise.reject(new Error('No dev server running'))
// After each test, reset each of the edits to their original contents.
if (typeof afterEach === 'function') {
afterEach(resetAllFiles);
@ -134,7 +140,9 @@ export async function loadFixture(inlineConfig) {
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 }),
clean: async () => {
await fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true });
},
loadTestAdapterApp: async () => {
const url = new URL('./server/entry.mjs', config.outDir);
const { createApp, manifest } = await import(url);
@ -142,22 +150,26 @@ export async function loadFixture(inlineConfig) {
app.manifest = manifest;
return app;
},
editFile: async (filePath, newContents) => {
editFile: async (filePath, newContentsOrCallback) => {
const fileUrl = new URL(filePath.replace(/^\//, ''), config.root);
const contents = await fs.promises.readFile(fileUrl, 'utf-8');
const reset = () => fs.writeFileSync(fileUrl, contents);
const reset = () => {
fs.writeFileSync(fileUrl, contents);
}
// Only save this reset if not already in the map, in case multiple edits happen
// to the same file.
if (!fileEdits.has(fileUrl.toString())) {
fileEdits.set(fileUrl.toString(), reset);
}
const newContents = typeof newContentsOrCallback === 'function'
? newContentsOrCallback(contents)
: newContentsOrCallback;
const nextChange = onNextChange();
await fs.promises.writeFile(fileUrl, newContents);
await nextChange;
return reset;
},
onNextChange: () =>
devServer
? new Promise((resolve) => devServer.watcher.once('change', resolve))
: Promise.reject(new Error('No dev server running')),
resetAllFiles
};
}

View file

@ -661,12 +661,108 @@ importers:
chai-as-promised: 7.1.1_chai@4.3.6
mocha: 9.2.2
packages/astro/e2e/fixtures/astro-component:
specifiers:
astro: workspace:*
dependencies:
astro: link:../../..
packages/astro/e2e/fixtures/lit-component:
specifiers:
'@astrojs/lit': workspace:*
'@webcomponents/template-shadowroot': ^0.1.0
astro: workspace:*
lit: ^2.2.3
dependencies:
'@astrojs/lit': link:../../../../integrations/lit
'@webcomponents/template-shadowroot': 0.1.0
astro: link:../../..
lit: 2.2.4
packages/astro/e2e/fixtures/multiple-frameworks:
specifiers:
'@astrojs/lit': ^0.1.3
'@astrojs/preact': ^0.1.2
'@astrojs/react': ^0.1.2
'@astrojs/solid-js': ^0.1.2
'@astrojs/svelte': ^0.1.3
'@astrojs/vue': ^0.1.4
'@webcomponents/template-shadowroot': ^0.1.0
astro: ^1.0.0-beta.31
lit: ^2.2.4
preact: ^10.7.2
react: ^18.1.0
react-dom: ^18.1.0
solid-js: ^1.4.2
svelte: ^3.48.0
vue: ^3.2.34
dependencies:
'@webcomponents/template-shadowroot': 0.1.0
lit: 2.2.4
preact: 10.7.2
react: 18.1.0
react-dom: 18.1.0_react@18.1.0
solid-js: 1.4.2
svelte: 3.48.0
vue: 3.2.34
devDependencies:
'@astrojs/lit': link:../../../../integrations/lit
'@astrojs/preact': link:../../../../integrations/preact
'@astrojs/react': link:../../../../integrations/react
'@astrojs/solid-js': link:../../../../integrations/solid
'@astrojs/svelte': link:../../../../integrations/svelte
'@astrojs/vue': link:../../../../integrations/vue
astro: link:../../..
packages/astro/e2e/fixtures/nested-styles:
specifiers:
astro: workspace:*
dependencies:
astro: link:../../..
packages/astro/e2e/fixtures/preact-component:
specifiers:
'@astrojs/preact': workspace:*
astro: workspace:*
preact: ^10.7.2
dependencies:
'@astrojs/preact': link:../../../../integrations/preact
astro: link:../../..
preact: 10.7.2
packages/astro/e2e/fixtures/react-component:
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/solid-component:
specifiers:
'@astrojs/solid-js': workspace:*
astro: workspace:*
solid-js: ^1.4.1
dependencies:
'@astrojs/solid-js': link:../../../../integrations/solid
astro: link:../../..
devDependencies:
solid-js: 1.4.2
packages/astro/e2e/fixtures/svelte-component:
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:*
@ -675,6 +771,14 @@ importers:
'@astrojs/tailwind': link:../../../../integrations/tailwind
astro: link:../../..
packages/astro/e2e/fixtures/vue-component:
specifiers:
'@astrojs/vue': workspace:*
astro: workspace:*
dependencies:
'@astrojs/vue': link:../../../../integrations/vue
astro: link:../../..
packages/astro/test/fixtures/0-css:
specifiers:
'@astrojs/react': workspace:*
@ -1972,7 +2076,7 @@ packages:
resolution: {integrity: sha512-rgi3g078uAxdb8jg1A5U8sNWMUQq7UXwHT7qmPiGOeB+h5p+tzUFy/Awq2suv99Tq8efpn3HrAGTuDvxyvbwfg==}
dependencies:
svelte: 3.48.0
svelte2tsx: 0.5.10_wwvk7nlptlrqo2czohjtk6eiqm
svelte2tsx: 0.5.9_wwvk7nlptlrqo2czohjtk6eiqm
transitivePeerDependencies:
- typescript
dev: false
@ -3725,7 +3829,7 @@ packages:
dependencies:
'@lit-labs/ssr-client': 1.0.1
'@lit/reactive-element': 1.3.2
'@types/node': 16.11.36
'@types/node': 16.11.34
lit: 2.2.4
lit-element: 3.2.0
lit-html: 2.2.4
@ -3748,7 +3852,7 @@ packages:
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
dependencies:
'@babel/runtime': 7.18.0
'@types/node': 12.20.52
'@types/node': 12.20.51
find-up: 4.1.0
fs-extra: 8.1.0
dev: true
@ -6032,7 +6136,7 @@ packages:
slash: 3.0.0
dev: true
/@rollup/plugin-babel/5.3.1_ykg7cmcqpmn5fbkb5gxs7i3du4:
/@rollup/plugin-babel/5.3.1_3gvlzenj6qraw2ojvkgevxalie:
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
engines: {node: '>= 10.0.0'}
peerDependencies:
@ -6047,8 +6151,8 @@ packages:
dependencies:
'@babel/core': 7.18.0
'@babel/helper-module-imports': 7.16.7
'@rollup/pluginutils': 3.1.0_rollup@2.74.1
rollup: 2.74.1
'@rollup/pluginutils': 3.1.0_rollup@2.72.1
rollup: 2.72.1
dev: true
/@rollup/plugin-inject/4.0.4_rollup@2.74.1:
@ -6062,19 +6166,19 @@ packages:
rollup: 2.74.1
dev: true
/@rollup/plugin-node-resolve/11.2.1_rollup@2.74.1:
/@rollup/plugin-node-resolve/11.2.1_rollup@2.72.1:
resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==}
engines: {node: '>= 10.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0
dependencies:
'@rollup/pluginutils': 3.1.0_rollup@2.74.1
'@rollup/pluginutils': 3.1.0_rollup@2.72.1
'@types/resolve': 1.17.1
builtin-modules: 3.3.0
deepmerge: 4.2.2
is-module: 1.0.0
resolve: 1.22.0
rollup: 2.74.1
rollup: 2.72.1
dev: true
/@rollup/plugin-node-resolve/13.3.0_rollup@2.74.1:
@ -6092,14 +6196,14 @@ packages:
rollup: 2.74.1
dev: true
/@rollup/plugin-replace/2.4.2_rollup@2.74.1:
/@rollup/plugin-replace/2.4.2_rollup@2.72.1:
resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==}
peerDependencies:
rollup: ^1.20.0 || ^2.0.0
dependencies:
'@rollup/pluginutils': 3.1.0_rollup@2.74.1
'@rollup/pluginutils': 3.1.0_rollup@2.72.1
magic-string: 0.25.9
rollup: 2.74.1
rollup: 2.72.1
dev: true
/@rollup/plugin-typescript/8.3.2_ewnwotriipvq7wps276zlnnxuy:
@ -6117,6 +6221,18 @@ packages:
typescript: 4.6.4
dev: true
/@rollup/pluginutils/3.1.0_rollup@2.72.1:
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
engines: {node: '>= 8.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0
dependencies:
'@types/estree': 0.0.39
estree-walker: 1.0.1
picomatch: 2.3.1
rollup: 2.72.1
dev: true
/@rollup/pluginutils/3.1.0_rollup@2.74.1:
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
engines: {node: '>= 8.0.0'}
@ -6255,7 +6371,7 @@ packages:
/@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: true
/@types/debug/4.1.7:
@ -6296,7 +6412,7 @@ packages:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies:
'@types/minimatch': 3.0.5
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: true
/@types/hast/2.3.4:
@ -6360,20 +6476,20 @@ packages:
'@types/unist': 2.0.6
dev: false
/@types/node/12.20.52:
resolution: {integrity: sha512-cfkwWw72849SNYp3Zx0IcIs25vABmFh73xicxhCkTcvtZQeIez15PpwQN8fY3RD7gv1Wrxlc9MEtfMORZDEsGw==}
/@types/node/12.20.51:
resolution: {integrity: sha512-anVDMfReTatfH8GVmHmaTZOL0jeTLNZ9wK9SSrQS3tMmn4vUc+9fVWlUzAieuQefWDyWUz4Z3aqXxDgO1VsYjg==}
dev: true
/@types/node/14.18.18:
resolution: {integrity: sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==}
dev: true
/@types/node/16.11.36:
resolution: {integrity: sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==}
/@types/node/16.11.34:
resolution: {integrity: sha512-UrWGDyLAlQ2Z8bNOGWTsqbP9ZcBeTYBVuTRNxXTztBy5KhWUFI3BaeDWoCP/CzV/EVGgO1NTYzv9ZytBI9GAEw==}
dev: false
/@types/node/17.0.35:
resolution: {integrity: sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==}
/@types/node/17.0.32:
resolution: {integrity: sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==}
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@ -6397,7 +6513,7 @@ packages:
/@types/prompts/2.0.14:
resolution: {integrity: sha512-HZBd99fKxRWpYCErtm2/yxUZv6/PBI9J7N4TNFffl5JbrYMHBwF25DjQGTW3b3jmXq+9P6/8fCIb2ee57BFfYA==}
dependencies:
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: false
/@types/prop-types/15.7.5:
@ -6437,7 +6553,7 @@ packages:
/@types/resolve/1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
'@types/node': 14.18.18
'@types/node': 17.0.32
dev: true
/@types/resolve/1.20.2:
@ -6447,19 +6563,19 @@ packages:
resolution: {integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==}
dependencies:
'@types/glob': 7.2.0
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: true
/@types/sass/1.43.1:
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
dependencies:
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: false
/@types/sax/1.2.4:
resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==}
dependencies:
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: false
/@types/scheduler/0.16.2:
@ -6473,7 +6589,7 @@ packages:
resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==}
dependencies:
'@types/mime': 1.3.2
'@types/node': 17.0.35
'@types/node': 17.0.32
dev: true
/@types/tailwindcss/3.0.10:
@ -6726,7 +6842,7 @@ packages:
acorn: 8.7.1
bindings: 1.5.0
estree-walker: 2.0.2
glob: 7.2.3
glob: 7.2.0
graceful-fs: 4.2.10
micromatch: 4.0.5
node-gyp-build: 4.4.0
@ -6974,13 +7090,8 @@ packages:
engines: {node: '>=6'}
dev: true
/ansi-colors/4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
dev: true
/ansi-regex/2.1.1:
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=}
engines: {node: '>=0.10.0'}
/ansi-regex/5.0.1:
@ -7064,7 +7175,7 @@ packages:
dev: false
/arrify/1.0.1:
resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==}
resolution: {integrity: sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=}
engines: {node: '>=0.10.0'}
dev: true
@ -7112,7 +7223,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.20.3
caniuse-lite: 1.0.30001341
caniuse-lite: 1.0.30001340
fraction.js: 4.2.0
normalize-range: 0.1.2
picocolors: 1.0.0
@ -7244,7 +7355,7 @@ packages:
dev: false
/boolbase/1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=}
/boxen/6.2.1:
resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==}
@ -7293,14 +7404,14 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001341
caniuse-lite: 1.0.30001340
electron-to-chromium: 1.4.137
escalade: 3.1.1
node-releases: 2.0.4
picocolors: 1.0.0
/buffer-crc32/0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
dev: false
/buffer-from/1.1.2:
@ -7369,8 +7480,8 @@ packages:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
/caniuse-lite/1.0.30001341:
resolution: {integrity: sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==}
/caniuse-lite/1.0.30001340:
resolution: {integrity: sha512-jUNz+a9blQTQVu4uFcn17uAD8IDizPzQkIKh3LCJfg9BkyIqExYYdyc/ZSlWUSKb8iYiXxKsxbv4zYSvkqjrxw==}
/canvas-confetti/1.5.1:
resolution: {integrity: sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==}
@ -7451,7 +7562,7 @@ packages:
dev: true
/check-error/1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=}
dev: true
/cheerio-select/1.6.0:
@ -8037,7 +8148,7 @@ packages:
resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
engines: {node: '>=8.6'}
dependencies:
ansi-colors: 4.1.3
ansi-colors: 4.1.1
dev: true
/entities/2.2.0:
@ -8682,10 +8793,8 @@ packages:
resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==}
dev: true
/for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
is-callable: 1.2.4
/foreach/2.0.6:
resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==}
dev: false
/formdata-polyfill/4.0.10:
@ -8883,17 +8992,6 @@ packages:
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/glob/7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
/globals/11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
@ -9563,14 +9661,14 @@ packages:
dependencies:
has-symbols: 1.0.3
/is-typed-array/1.1.9:
resolution: {integrity: sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==}
/is-typed-array/1.1.8:
resolution: {integrity: sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==}
engines: {node: '>= 0.4'}
dependencies:
available-typed-arrays: 1.0.5
call-bind: 1.0.2
es-abstract: 1.20.1
for-each: 0.3.3
foreach: 2.0.6
has-tostringtag: 1.0.0
dev: false
@ -9625,7 +9723,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 17.0.35
'@types/node': 17.0.32
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@ -11711,13 +11809,25 @@ packages:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.3
glob: 7.2.0
/rimraf/3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
dependencies:
glob: 7.2.3
glob: 7.2.0
/rollup-plugin-terser/7.0.2_rollup@2.72.1:
resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
peerDependencies:
rollup: ^2.0.0
dependencies:
'@babel/code-frame': 7.16.7
jest-worker: 26.6.2
rollup: 2.72.1
serialize-javascript: 4.0.0
terser: 5.13.1
dev: true
/rollup-plugin-terser/7.0.2_rollup@2.74.1:
resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
@ -11737,6 +11847,14 @@ packages:
estree-walker: 0.6.1
dev: false
/rollup/2.72.1:
resolution: {integrity: sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/rollup/2.74.1:
resolution: {integrity: sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==}
engines: {node: '>=10.0.0'}
@ -11955,7 +12073,7 @@ packages:
engines: {node: '>=12.0.0', npm: '>=5.6.0'}
hasBin: true
dependencies:
'@types/node': 17.0.35
'@types/node': 17.0.32
'@types/sax': 1.2.4
arg: 5.0.1
sax: 1.2.4
@ -12012,6 +12130,10 @@ packages:
smart-buffer: 4.2.0
dev: true
/solid-js/1.3.17:
resolution: {integrity: sha512-BFCosxa4hRm+LF7S+kBL5bNr4RtuZif6AaR5FdQkBpV1E6QNLAOFm4HWgEN8vL2aCWEKl384cT8Etw8ziW8aag==}
dev: false
/solid-js/1.4.2:
resolution: {integrity: sha512-IU5yKuT8P/n5F5g8j1rTXqxUdPYmoZDk/074TG94AEYf/nyXAeG82BSge4/lLIbCfUcnGUJ6DRdebIjujOAYyg==}
@ -12019,7 +12141,7 @@ packages:
resolution: {integrity: sha512-iwbgdBzQSxBKoxkzaZgC9MGGUsHWJ74at9i7FF0naoqtwGuKdLYOgOJ9QRlA353DHDS/ttH2e0SRS6s3gz8NLQ==}
dependencies:
nanostores: 0.5.12
solid-js: 1.4.2
solid-js: 1.3.17
dev: false
/sorcery/0.10.0:
@ -12364,8 +12486,8 @@ packages:
resolution: {integrity: sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==}
engines: {node: '>= 8'}
/svelte2tsx/0.5.10_wwvk7nlptlrqo2czohjtk6eiqm:
resolution: {integrity: sha512-nokQ0HTTWMcNX6tLrDLiOmJCuqjKZU9nCZ6/mVuCL3nusXdbp+9nv69VG2pCy7uQC66kV4Ls+j0WfvvJuGVnkg==}
/svelte2tsx/0.5.9_wwvk7nlptlrqo2czohjtk6eiqm:
resolution: {integrity: sha512-xTDASjlh+rKo4QRhTRYSH87sS7fRoyX67xhGIMPKa3FYqftRHRmMes6nVgEskiuhBovslNHYYpMMg5YM5n/STg==}
peerDependencies:
svelte: ^3.24
typescript: ^4.1.2
@ -12989,9 +13111,9 @@ packages:
inherits: 2.0.4
is-arguments: 1.1.1
is-generator-function: 1.0.10
is-typed-array: 1.1.9
is-typed-array: 1.1.8
safe-buffer: 5.2.1
which-typed-array: 1.1.8
which-typed-array: 1.1.7
dev: false
/uvu/0.5.3:
@ -13056,7 +13178,7 @@ packages:
debug: 4.3.4
fast-glob: 3.2.11
pretty-bytes: 5.6.0
rollup: 2.74.1
rollup: 2.72.1
workbox-build: 6.5.3
workbox-window: 6.5.3
transitivePeerDependencies:
@ -13266,16 +13388,16 @@ packages:
load-yaml-file: 0.2.0
path-exists: 4.0.0
/which-typed-array/1.1.8:
resolution: {integrity: sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==}
/which-typed-array/1.1.7:
resolution: {integrity: sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==}
engines: {node: '>= 0.4'}
dependencies:
available-typed-arrays: 1.0.5
call-bind: 1.0.2
es-abstract: 1.20.1
for-each: 0.3.3
foreach: 2.0.6
has-tostringtag: 1.0.0
is-typed-array: 1.1.9
is-typed-array: 1.1.8
dev: false
/which/1.3.1:
@ -13330,19 +13452,19 @@ packages:
'@babel/core': 7.18.0
'@babel/preset-env': 7.18.0_@babel+core@7.18.0
'@babel/runtime': 7.18.0
'@rollup/plugin-babel': 5.3.1_ykg7cmcqpmn5fbkb5gxs7i3du4
'@rollup/plugin-node-resolve': 11.2.1_rollup@2.74.1
'@rollup/plugin-replace': 2.4.2_rollup@2.74.1
'@rollup/plugin-babel': 5.3.1_3gvlzenj6qraw2ojvkgevxalie
'@rollup/plugin-node-resolve': 11.2.1_rollup@2.72.1
'@rollup/plugin-replace': 2.4.2_rollup@2.72.1
'@surma/rollup-plugin-off-main-thread': 2.2.3
ajv: 8.11.0
common-tags: 1.8.2
fast-json-stable-stringify: 2.1.0
fs-extra: 9.1.0
glob: 7.2.3
glob: 7.2.0
lodash: 4.17.21
pretty-bytes: 5.6.0
rollup: 2.74.1
rollup-plugin-terser: 7.0.2_rollup@2.74.1
rollup: 2.72.1
rollup-plugin-terser: 7.0.2_rollup@2.72.1
source-map: 0.8.0-beta.0
stringify-object: 3.3.0
strip-comments: 2.0.1