Skip to content

Commit

Permalink
Server islands tests (#11405)
Browse files Browse the repository at this point in the history
* Add support for the build to Server Islands

* Use command instead

* editor tips

* Add comment about defaultRoutes

* Use renderChunk instead of generateBundle

* Adds tests for server islands

* linting
  • Loading branch information
matthewp authored Jul 8, 2024
1 parent 9eaff96 commit 5c8ee23
Show file tree
Hide file tree
Showing 17 changed files with 343 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/astro/e2e/fixtures/server-islands/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import mdx from '@astrojs/mdx';
import react from '@astrojs/react';
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';

// https://astro.build/config
export default defineConfig({
output: 'hybrid',
adapter: nodejs({ mode: 'standalone' }),
integrations: [react(), mdx()],
});
16 changes: 16 additions & 0 deletions packages/astro/e2e/fixtures/server-islands/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@e2e/server-islands",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "astro dev"
},
"dependencies": {
"@astrojs/react": "workspace:*",
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*",
"@astrojs/node": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
---
<h2 id="island">I am an island</h2>
12 changes: 12 additions & 0 deletions packages/astro/e2e/fixtures/server-islands/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
import Island from '../components/Island.astro';
---

<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Island server:defer />
</body>
</html>
3 changes: 3 additions & 0 deletions packages/astro/e2e/fixtures/server-islands/src/pages/mdx.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Island from '../components/Island.astro';

<Island server:defer />
59 changes: 59 additions & 0 deletions packages/astro/e2e/server-islands.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';

const test = testFactory({ root: './fixtures/server-islands/' });

test.describe('Server islands', () => {
test.describe('Development', () => {
let devServer;

test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer();
});

test.afterAll(async () => {
await devServer.stop();
});

test('Load content from the server', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
let el = page.locator('#island');

await expect(el, 'element rendered').toBeVisible();
await expect(el, 'should have content').toHaveText('I am an island');
});

test('Can be in an MDX file', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/mdx'));
let el = page.locator('#island');

await expect(el, 'element rendered').toBeVisible();
await expect(el, 'should have content').toHaveText('I am an island');
});
});

test.describe('Production', () => {
let previewServer;

test.beforeAll(async ({ astro }) => {
// Playwright's Node version doesn't have these functions, so stub them.
process.stdout.clearLine = () => {};
process.stdout.cursorTo = () => {};
await astro.build();
previewServer = await astro.preview();
});

test.afterAll(async () => {
await previewServer.stop();
});

test('Only one component in prod', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));

let el = page.locator('#island');

await expect(el, 'element rendered').toBeVisible();
await expect(el, 'should have content').toHaveText('I am an island');
});
});
});
2 changes: 2 additions & 0 deletions packages/astro/src/core/server-islands/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function ensureServerIslandRoute(manifest: ManifestData) {
[{ content: '_server-islands', dynamic: false, spread: false }],
[{ content: 'name', dynamic: true, spread: false }]
],
// eslint-disable-next-line
pattern: /^\/_server-islands\/([^/]+?)$/,
prerender: false,
isIndex: false,
Expand Down Expand Up @@ -69,6 +70,7 @@ export function createEndpoint(manifest: SSRManifest) {

const instance: ComponentInstance = {
default: page,
partial: true,
};

return instance;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';

export default defineConfig({
output: 'hybrid',
integrations: [
svelte()
]
});

10 changes: 10 additions & 0 deletions packages/astro/test/fixtures/server-islands/hybrid/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@test/server-islands-hybrid",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
"svelte": "^4.2.18"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---
<h2 id="island">I'm an island</h2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
import Island from '../components/Island.astro';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>Testing</h1>
<Island server:defer />
</body>
</html>
10 changes: 10 additions & 0 deletions packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';

export default defineConfig({
output: 'server',
integrations: [
svelte()
]
});

10 changes: 10 additions & 0 deletions packages/astro/test/fixtures/server-islands/ssr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@test/server-islands-ssr",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
"svelte": "^4.2.18"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---
<h2 id="island">I'm an island</h2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
import Island from '../components/Island.astro';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>Testing</h1>
<Island server:defer />
</body>
</html>
120 changes: 120 additions & 0 deletions packages/astro/test/server-islands.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import * as cheerio from 'cheerio';
import testAdapter from './test-adapter.js';
import { loadFixture } from './test-utils.js';

describe('Server islands', () => {
describe('SSR', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/server-islands/ssr',
adapter: testAdapter(),
});
});

describe('dev', () => {
let devServer;

before(async () => {
devServer = await fixture.startDevServer();
});

after(async () => {
await devServer.stop();
});

it('omits the islands HTML', async () => {
const res = await fixture.fetch('/');
assert.equal(res.status, 200);
const html = await res.text();
const $ = cheerio.load(html);
const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 0);
});
});

describe('prod', () => {
before(async () => {
await fixture.build();
});

it('omits the islands HTML', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();

const $ = cheerio.load(html);
const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 0);

const serverIslandScript = $('script[data-island-id]');
assert.equal(serverIslandScript.length, 1, 'has the island script');
});
});
});

describe('Hybrid mode', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/server-islands/hybrid',
adapter: testAdapter(),
});
});

describe('build', () => {
before(async () => {
await fixture.build();
});

it('Omits the island HTML from the static HTML', async () => {
let html = await fixture.readFile('/client/index.html');

const $ = cheerio.load(html);
const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 0);

const serverIslandScript = $('script[data-island-id]');
assert.equal(serverIslandScript.length, 1, 'has the island script');
});

describe('prod', () => {
async function fetchIsland() {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/_server-islands/Island', {
method: 'POST',
body: JSON.stringify({
componentExport: 'default',
props: {},
slots: {},
})
});
return app.render(request);
}

it('Island returns its HTML', async () => {
const response = await fetchIsland();
const html = await response.text();
const $ = cheerio.load(html);

const serverIslandEl = $('h2#island');
assert.equal(serverIslandEl.length, 1);
});

it('Island does not include the doctype', async () => {
const response = await fetchIsland();
const html = await response.text();
console.log(html);

assert.ok(!/doctype/i.test(html), 'html does not include doctype');
});
});
});
});
});
45 changes: 45 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5c8ee23

Please sign in to comment.