diff --git a/e2e/client-only.test.js b/e2e/client-only.test.js new file mode 100644 index 000000000000..78a06fdbe31b --- /dev/null +++ b/e2e/client-only.test.js @@ -0,0 +1,111 @@ +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/client-only/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Client only', () => { + 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 children = await counter.locator('.children'); + await expect(children, 'children exist').toHaveText('react'); + + 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 children = await counter.locator('.children'); + await expect(children, 'children exist').toHaveText('preact'); + + 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 children = await counter.locator('.children'); + await expect(children, 'children exist').toHaveText('solid'); + + 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 children = await counter.locator('.children'); + await expect(children, 'children exist').toHaveText('vue'); + + 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 children = await counter.locator('.children'); + await expect(children, 'children exist').toHaveText('svelte'); + + const increment = await counter.locator('.increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/e2e/fixtures/client-only/astro.config.mjs b/e2e/fixtures/client-only/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/e2e/fixtures/client-only/astro.config.mjs @@ -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()], +}); diff --git a/e2e/fixtures/client-only/package.json b/e2e/fixtures/client-only/package.json new file mode 100644 index 000000000000..2ff99efb18e9 --- /dev/null +++ b/e2e/fixtures/client-only/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/client-only", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@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.32" + }, + "dependencies": { + "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.36" + } +} diff --git a/e2e/fixtures/client-only/src/components/PreactCounter.tsx b/e2e/fixtures/client-only/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..b0570046c4f8 --- /dev/null +++ b/e2e/fixtures/client-only/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/client-only/src/components/ReactCounter.jsx b/e2e/fixtures/client-only/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..30d804199bbd --- /dev/null +++ b/e2e/fixtures/client-only/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/client-only/src/components/SolidCounter.tsx b/e2e/fixtures/client-only/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..fbbb9850b5e4 --- /dev/null +++ b/e2e/fixtures/client-only/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/client-only/src/components/SvelteCounter.svelte b/e2e/fixtures/client-only/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..cc9fe8c93c60 --- /dev/null +++ b/e2e/fixtures/client-only/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/e2e/fixtures/client-only/src/components/VueCounter.vue b/e2e/fixtures/client-only/src/components/VueCounter.vue new file mode 100644 index 000000000000..906da19447c9 --- /dev/null +++ b/e2e/fixtures/client-only/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/fixtures/client-only/src/pages/index.astro b/e2e/fixtures/client-only/src/pages/index.astro new file mode 100644 index 000000000000..708ba1582dc9 --- /dev/null +++ b/e2e/fixtures/client-only/src/pages/index.astro @@ -0,0 +1,41 @@ +--- +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/ +--- + + + + + + + + +
+ +

react

+
+ + +

preact

+
+ + +

solid

+
+ + +

vue

+
+ + +

svelte

+
+
+ + diff --git a/e2e/fixtures/nested-in-preact/astro.config.mjs b/e2e/fixtures/nested-in-preact/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/e2e/fixtures/nested-in-preact/astro.config.mjs @@ -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()], +}); diff --git a/e2e/fixtures/nested-in-preact/package.json b/e2e/fixtures/nested-in-preact/package.json new file mode 100644 index 000000000000..c09c7838cddd --- /dev/null +++ b/e2e/fixtures/nested-in-preact/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-preact", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@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.32" + }, + "dependencies": { + "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.36" + } +} diff --git a/e2e/fixtures/nested-in-preact/src/components/PreactCounter.tsx b/e2e/fixtures/nested-in-preact/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/e2e/fixtures/nested-in-preact/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-preact/src/components/ReactCounter.jsx b/e2e/fixtures/nested-in-preact/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..0dc0deb47b38 --- /dev/null +++ b/e2e/fixtures/nested-in-preact/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export default function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-preact/src/components/SolidCounter.tsx b/e2e/fixtures/nested-in-preact/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/e2e/fixtures/nested-in-preact/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-preact/src/components/SvelteCounter.svelte b/e2e/fixtures/nested-in-preact/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/e2e/fixtures/nested-in-preact/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/e2e/fixtures/nested-in-preact/src/components/VueCounter.vue b/e2e/fixtures/nested-in-preact/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/e2e/fixtures/nested-in-preact/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/fixtures/nested-in-preact/src/pages/index.astro b/e2e/fixtures/nested-in-preact/src/pages/index.astro new file mode 100644 index 000000000000..619e8cccd0ae --- /dev/null +++ b/e2e/fixtures/nested-in-preact/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import ReactCounter 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/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/e2e/fixtures/nested-in-react/astro.config.mjs b/e2e/fixtures/nested-in-react/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/e2e/fixtures/nested-in-react/astro.config.mjs @@ -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()], +}); diff --git a/e2e/fixtures/nested-in-react/package.json b/e2e/fixtures/nested-in-react/package.json new file mode 100644 index 000000000000..fae7b3e2b8c1 --- /dev/null +++ b/e2e/fixtures/nested-in-react/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-react", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@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.32" + }, + "dependencies": { + "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.36" + } +} diff --git a/e2e/fixtures/nested-in-react/src/components/PreactCounter.tsx b/e2e/fixtures/nested-in-react/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/e2e/fixtures/nested-in-react/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-react/src/components/ReactCounter.jsx b/e2e/fixtures/nested-in-react/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..0dc0deb47b38 --- /dev/null +++ b/e2e/fixtures/nested-in-react/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export default function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-react/src/components/SolidCounter.tsx b/e2e/fixtures/nested-in-react/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/e2e/fixtures/nested-in-react/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-react/src/components/SvelteCounter.svelte b/e2e/fixtures/nested-in-react/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/e2e/fixtures/nested-in-react/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/e2e/fixtures/nested-in-react/src/components/VueCounter.vue b/e2e/fixtures/nested-in-react/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/e2e/fixtures/nested-in-react/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/fixtures/nested-in-react/src/pages/index.astro b/e2e/fixtures/nested-in-react/src/pages/index.astro new file mode 100644 index 000000000000..0b3b23d9d8e8 --- /dev/null +++ b/e2e/fixtures/nested-in-react/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import ReactCounter 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/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/e2e/fixtures/nested-in-solid/astro.config.mjs b/e2e/fixtures/nested-in-solid/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/e2e/fixtures/nested-in-solid/astro.config.mjs @@ -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()], +}); diff --git a/e2e/fixtures/nested-in-solid/package.json b/e2e/fixtures/nested-in-solid/package.json new file mode 100644 index 000000000000..412561a6de94 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-solid", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@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.32" + }, + "dependencies": { + "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.36" + } +} diff --git a/e2e/fixtures/nested-in-solid/src/components/PreactCounter.tsx b/e2e/fixtures/nested-in-solid/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-solid/src/components/ReactCounter.jsx b/e2e/fixtures/nested-in-solid/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..c7197a072b85 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-solid/src/components/SolidCounter.tsx b/e2e/fixtures/nested-in-solid/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-solid/src/components/SvelteCounter.svelte b/e2e/fixtures/nested-in-solid/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/e2e/fixtures/nested-in-solid/src/components/VueCounter.vue b/e2e/fixtures/nested-in-solid/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/fixtures/nested-in-solid/src/pages/index.astro b/e2e/fixtures/nested-in-solid/src/pages/index.astro new file mode 100644 index 000000000000..0feb5ba60035 --- /dev/null +++ b/e2e/fixtures/nested-in-solid/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import { Counter as ReactCounter } 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/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/e2e/fixtures/nested-in-svelte/astro.config.mjs b/e2e/fixtures/nested-in-svelte/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/astro.config.mjs @@ -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()], +}); diff --git a/e2e/fixtures/nested-in-svelte/package.json b/e2e/fixtures/nested-in-svelte/package.json new file mode 100644 index 000000000000..44c3018cf0ed --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-svelte", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@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.32" + }, + "dependencies": { + "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.36" + } +} diff --git a/e2e/fixtures/nested-in-svelte/src/components/PreactCounter.tsx b/e2e/fixtures/nested-in-svelte/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-svelte/src/components/ReactCounter.jsx b/e2e/fixtures/nested-in-svelte/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..c7197a072b85 --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-svelte/src/components/SolidCounter.tsx b/e2e/fixtures/nested-in-svelte/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-svelte/src/components/SvelteCounter.svelte b/e2e/fixtures/nested-in-svelte/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/e2e/fixtures/nested-in-svelte/src/components/VueCounter.vue b/e2e/fixtures/nested-in-svelte/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/fixtures/nested-in-svelte/src/pages/index.astro b/e2e/fixtures/nested-in-svelte/src/pages/index.astro new file mode 100644 index 000000000000..764ebfaf702e --- /dev/null +++ b/e2e/fixtures/nested-in-svelte/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import { Counter as ReactCounter } 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/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/e2e/fixtures/nested-in-vue/astro.config.mjs b/e2e/fixtures/nested-in-vue/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/e2e/fixtures/nested-in-vue/astro.config.mjs @@ -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()], +}); diff --git a/e2e/fixtures/nested-in-vue/package.json b/e2e/fixtures/nested-in-vue/package.json new file mode 100644 index 000000000000..b25b3e0b1dc7 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-vue", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@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.32" + }, + "dependencies": { + "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.36" + } +} diff --git a/e2e/fixtures/nested-in-vue/src/components/PreactCounter.tsx b/e2e/fixtures/nested-in-vue/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-vue/src/components/ReactCounter.jsx b/e2e/fixtures/nested-in-vue/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..c7197a072b85 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-vue/src/components/SolidCounter.tsx b/e2e/fixtures/nested-in-vue/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +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 ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/e2e/fixtures/nested-in-vue/src/components/SvelteCounter.svelte b/e2e/fixtures/nested-in-vue/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/e2e/fixtures/nested-in-vue/src/components/VueCounter.vue b/e2e/fixtures/nested-in-vue/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/e2e/fixtures/nested-in-vue/src/pages/index.astro b/e2e/fixtures/nested-in-vue/src/pages/index.astro new file mode 100644 index 000000000000..5e4d47d768b3 --- /dev/null +++ b/e2e/fixtures/nested-in-vue/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import { Counter as ReactCounter } 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/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/e2e/nested-in-preact.test.js b/e2e/nested-in-preact.test.js new file mode 100644 index 000000000000..ab4d3c6ba298 --- /dev/null +++ b/e2e/nested-in-preact.test.js @@ -0,0 +1,96 @@ +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/nested-in-preact/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Preact', () => { + 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('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-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('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-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('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-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('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-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('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/e2e/nested-in-react.test.js b/e2e/nested-in-react.test.js new file mode 100644 index 000000000000..5b7a0d18b01e --- /dev/null +++ b/e2e/nested-in-react.test.js @@ -0,0 +1,96 @@ +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/nested-in-react/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in React', () => { + 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('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-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('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-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('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-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('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-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('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/e2e/nested-in-solid.test.js b/e2e/nested-in-solid.test.js new file mode 100644 index 000000000000..11f97f9d8f0c --- /dev/null +++ b/e2e/nested-in-solid.test.js @@ -0,0 +1,97 @@ +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/nested-in-solid/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Solid', () => { + 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('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-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('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-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('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-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('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-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('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); + diff --git a/e2e/nested-in-svelte.test.js b/e2e/nested-in-svelte.test.js new file mode 100644 index 000000000000..f951854c7f43 --- /dev/null +++ b/e2e/nested-in-svelte.test.js @@ -0,0 +1,96 @@ +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/nested-in-svelte/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Svelte', () => { + 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('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-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('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-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('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-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('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-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('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/e2e/nested-in-vue.test.js b/e2e/nested-in-vue.test.js new file mode 100644 index 000000000000..c3fa9a20379c --- /dev/null +++ b/e2e/nested-in-vue.test.js @@ -0,0 +1,96 @@ +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/nested-in-vue/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Vue', () => { + 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('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-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('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-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('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-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('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-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('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/src/@types/astro.ts b/src/@types/astro.ts index 14dc852d3b5c..b64edbb2bd2f 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -767,7 +767,7 @@ export interface MarkdownInstance> { } export type GetHydrateCallback = () => Promise< - (element: Element, innerHTML: string | null) => void + (element: Element, innerHTML: string | null) => void | Promise >; /** diff --git a/src/runtime/client/events.ts b/src/runtime/client/events.ts new file mode 100644 index 000000000000..aa8e92736350 --- /dev/null +++ b/src/runtime/client/events.ts @@ -0,0 +1,24 @@ +const HYDRATE_KEY = `astro:hydrate`; +function debounce any>(cb: T, wait = 20) { + let h = 0; + let callable = (...args: any) => { + clearTimeout(h); + h = setTimeout(() => cb(...args), wait) as unknown as number; + }; + return callable as T; +} + +export const notify = debounce(() => { + if (document.querySelector('astro-root[ssr]')) { + window.dispatchEvent(new CustomEvent(HYDRATE_KEY)); + } +}); + +export const listen = (cb: (...args: any[]) => any) => window.addEventListener(HYDRATE_KEY, cb, { once: true }); + +if (!(window as any)[HYDRATE_KEY]) { + if ('MutationObserver' in window) { + new MutationObserver(notify).observe(document.body, { subtree: true, childList: true }); + } + (window as any)[HYDRATE_KEY] = true; +} diff --git a/src/runtime/client/idle.ts b/src/runtime/client/idle.ts index e1e1c5b2f7ab..ff37585e6aa9 100644 --- a/src/runtime/client/idle.ts +++ b/src/runtime/client/idle.ts @@ -1,7 +1,8 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component as soon as the main thread is free! + * Hydrate this component as soon as the main thread is free * (or after a short delay, if `requestIdleCallback`) isn't supported */ export default async function onIdle( @@ -9,35 +10,44 @@ export default async function onIdle( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const cb = async () => { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } + let innerHTML: string | null = null; + let hydrate: Awaited>; - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + async function idle() { + listen(idle) + const cb = async () => { + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - const hydrate = await getHydrateCallback(); + if (!hydrate) { + hydrate = await getHydrateCallback(); + } + for (const root of roots) { + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + notify(); + }; - for (const root of roots) { - hydrate(root, innerHTML); + if ('requestIdleCallback' in window) { + (window as any).requestIdleCallback(cb); + } else { + setTimeout(cb, 200); } - }; - - if ('requestIdleCallback' in window) { - (window as any).requestIdleCallback(cb); - } else { - setTimeout(cb, 200); } + idle(); } diff --git a/src/runtime/client/load.ts b/src/runtime/client/load.ts index d969b4061b6e..80a1f4d51534 100644 --- a/src/runtime/client/load.ts +++ b/src/runtime/client/load.ts @@ -1,36 +1,44 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component immediately! + * Hydrate this component immediately */ export default async function onLoad( astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); - } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } + let hydrate: Awaited>; - //const innerHTML = roots[0].querySelector(`astro-fragment`)?.innerHTML ?? null; - const hydrate = await getHydrateCallback(); - - for (const root of roots) { - hydrate(root, innerHTML); + async function load() { + listen(load); + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } + } + if (!hydrate) { + hydrate = await getHydrateCallback(); + } + for (const root of roots) { + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + notify(); } + load(); } diff --git a/src/runtime/client/media.ts b/src/runtime/client/media.ts index edaa9a433864..cd44158790ab 100644 --- a/src/runtime/client/media.ts +++ b/src/runtime/client/media.ts @@ -1,45 +1,55 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component when a matching media query is found! + * Hydrate this component when a matching media query is found */ export default async function onMedia( astroId: string, options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); - } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } + let hydrate: Awaited>; - const cb = async () => { - const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); - } - }; + async function media() { + listen(media) + const cb = async () => { + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } + } + if (!hydrate) { + hydrate = await getHydrateCallback(); + } + for (const root of roots) { + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + notify(); + }; - if (options.value) { - const mql = matchMedia(options.value); - if (mql.matches) { - cb(); - } else { - mql.addEventListener('change', cb, { once: true }); + if (options.value) { + const mql = matchMedia(options.value); + if (mql.matches) { + cb(); + } else { + mql.addEventListener('change', cb, { once: true }); + } } } + media(); } diff --git a/src/runtime/client/only.ts b/src/runtime/client/only.ts index 04937c6084fd..65ea02bd7649 100644 --- a/src/runtime/client/only.ts +++ b/src/runtime/client/only.ts @@ -1,4 +1,5 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { listen, notify } from './events'; /** * Hydrate this component only on the client @@ -8,27 +9,36 @@ export default async function onOnly( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); - } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - const hydrate = await getHydrateCallback(); + let hydrate: Awaited>; - for (const root of roots) { - hydrate(root, innerHTML); + async function only() { + listen(only); + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } + } + if (!hydrate) { + hydrate = await getHydrateCallback(); + } + for (const root of roots) { + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + notify(); } + only(); } diff --git a/src/runtime/client/visible.ts b/src/runtime/client/visible.ts index e9c3e3310963..9202d8c7260f 100644 --- a/src/runtime/client/visible.ts +++ b/src/runtime/client/visible.ts @@ -1,7 +1,8 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component when one of it's children becomes visible! + * Hydrate this component when one of it's children becomes visible * We target the children because `astro-root` is set to `display: contents` * which doesn't work with IntersectionObserver */ @@ -10,46 +11,61 @@ export default async function onVisible( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - + let io: IntersectionObserver; let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); - } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } + let hydrate: Awaited>; - const cb = async () => { - const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); - } - }; + async function visible() { + listen(visible) + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + const cb = async () => { + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } + } + if (!hydrate) { + hydrate = await getHydrateCallback(); + } + for (const root of roots) { + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + notify(); + }; - const io = new IntersectionObserver((entries) => { - for (const entry of entries) { - if (!entry.isIntersecting) continue; - // As soon as we hydrate, disconnect this IntersectionObserver for every `astro-root` + if (io) { io.disconnect(); - cb(); - break; // break loop on first match } - }); - for (const root of roots) { - for (let i = 0; i < root.children.length; i++) { - const child = root.children[i]; - io.observe(child); + io = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (!entry.isIntersecting) continue; + // As soon as we hydrate, disconnect this IntersectionObserver for every `astro-root` + io.disconnect(); + cb(); + break; // break loop on first match + } + }); + + for (const root of roots) { + for (let i = 0; i < root.children.length; i++) { + const child = root.children[i]; + io.observe(child); + } } } + + visible(); } diff --git a/src/runtime/server/index.ts b/src/runtime/server/index.ts index 762e764ce7de..e3c2806422dc 100644 --- a/src/runtime/server/index.ts +++ b/src/runtime/server/index.ts @@ -330,7 +330,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr const template = needsAstroTemplate ? `` : ''; return markHTMLString( - `${ + `${ html ?? '' }${template}` );