Skip to content

Commit

Permalink
chore(request-nanostores): Test documented behavior (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fryuni authored Sep 28, 2024
1 parent b53264e commit 8ab5457
Show file tree
Hide file tree
Showing 22 changed files with 404 additions and 40 deletions.
9 changes: 5 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@
system: let
throwSystem = throw "Unsupported system: ${system}";
pkgs = nixpkgs.legacyPackages.${system};
node = pkgs.nodejs_22;

browsersInfo = builtins.fromJSON (builtins.readFile "${pkgs.playwright-driver}/browsers.json");
browsersInfo = builtins.fromJSON (builtins.readFile "${pkgs.playwright-driver}/browsers.json");
in {
devShells.default = pkgs.mkShell {
packages = [
pkgs.nodejs_20
pkgs.corepack_20
node
pkgs.corepack_22
pkgs.playwright
];

PLAYWRIGHT_NODEJS_PATH = "${pkgs.nodejs_20}/bin/node";
PLAYWRIGHT_NODEJS_PATH = "${node}/bin/node";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = 1;
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
Expand Down
39 changes: 39 additions & 0 deletions packages/request-nanostores/e2e/basic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { loadFixture, type PreviewServer } from '@inox-tools/astro-tests/astroFixture';
import { test, expect } from '@playwright/test';

const fixture = await loadFixture({
root: './fixture/basic',
});

let server: PreviewServer;

test.beforeAll(async () => {
delete process.env.INJECTED_STATE;
await fixture.build({});
process.env.INJECTED_STATE = JSON.stringify({
foo: 'bar',
});
server = await fixture.preview({});
});

test.afterAll(async () => {
await server?.stop();
delete process.env.INJECTED_STATE;
});

test('state is injected from the server on the client', async ({ page }) => {
await page.goto(fixture.resolveUrl('/'));

const pageState = JSON.parse(await page.locator('pre#injected-state').innerHTML());

expect(pageState).toStrictEqual({ foo: 'bar' });
});

test('state is available to UI frameworks in sync with server', async ({ page }) => {
await page.goto(fixture.resolveUrl('/islands'));

const serverState = JSON.parse(await page.locator('pre#server').innerHTML());
const clientState = JSON.parse(await page.locator('pre#client').innerHTML());

expect(serverState).toStrictEqual(clientState);
});
12 changes: 12 additions & 0 deletions packages/request-nanostores/e2e/fixture/basic/astro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import requestNanostores from '@inox-tools/request-nanostores';

import preact from '@astrojs/preact';

export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
integrations: [requestNanostores(), preact()],
devToolbar: { enabled: false },
});
14 changes: 14 additions & 0 deletions packages/request-nanostores/e2e/fixture/basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@request-nanostores/basic",
"private": true,
"type": "module",
"dependencies": {
"@astrojs/node": "catalog:",
"@astrojs/preact": "^3.5.3",
"@inox-tools/request-nanostores": "workspace:",
"@nanostores/preact": "^0.5.2",
"astro": "catalog:",
"nanostores": "catalog:",
"preact": "^10.24.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { FunctionComponent } from 'preact';
import { useStore } from '@nanostores/preact';
import { serializedState } from '../state.js';

export const Display: FunctionComponent<{ id: string }> = ({ id }) => {
const state = useStore(serializedState);

return <pre id={id}>{state}</pre>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
import { state } from '../state.js';
const injectedState = JSON.parse(process.env.INJECTED_STATE || '[123]');
state.set(injectedState);
---

<html>
<head>
<title>Basic example</title>
</head>
<body>
<pre id="injected-state"></pre>
<script>
import { serializedState } from '../state.js';

serializedState.subscribe((value) => {
document.getElementById('injected-state')!.innerHTML = value;
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
import { Display } from '../components/Display.tsx';
import { state } from '../state.js';
state.set({
obj: {
a: 1,
b: 2,
c: 3,
},
array: [1, 2, 3],
});
---

<html>
<head>
<title>Islands example</title>
</head>
<body>
<Display id="server" />
<Display id="client" client:only="preact" />
</body>
</html>
6 changes: 6 additions & 0 deletions packages/request-nanostores/e2e/fixture/basic/src/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { atom, computed } from 'nanostores';
import { shared } from '@it-astro:request-nanostores';

export const state = shared('fancy-state', atom<any>(null));

export const serializedState = computed(state, (state) => JSON.stringify(state));
9 changes: 9 additions & 0 deletions packages/request-nanostores/e2e/fixture/basic/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"lib": ["dom"],
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
12 changes: 7 additions & 5 deletions packages/request-nanostores/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@
"build": "tsup",
"dev": "tsup --watch",
"prepublish": "pnpm run build",
"test": "echo 'No tests for now :('",
"test:run": "vitest run --coverage",
"test:dev": "vitest --coverage.enabled=true"
"test": "vitest run",
"test:dev": "vitest",
"test:e2e": "playwright test",
"test:e2e:report": "playwright show-report"
},
"dependencies": {
"@inox-tools/request-state": "workspace:^",
"@inox-tools/utils": "workspace:^",
"astro-integration-kit": "catalog:"
},
"devDependencies": {
"@inox-tools/astro-tests": "workspace:",
"@playwright/test": "catalog:",
"@types/node": "catalog:",
"@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:",
"astro": "catalog:",
"jest-extended": "catalog:",
Expand All @@ -47,6 +49,6 @@
},
"peerDependencies": {
"astro": "catalog:lax",
"nanostores": "^0.11.2"
"nanostores": "catalog:"
}
}
78 changes: 78 additions & 0 deletions packages/request-nanostores/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
defineConfig,
devices,
type Project,
type PlaywrightWorkerOptions,
} from '@playwright/test';

type Proj = Project<{}, PlaywrightWorkerOptions>;

const browserOptions: Record<string, Proj['use']> = {
chromium: {
...devices['Desktop Chrome'],
launchOptions: {
executablePath: process.env.PLAYWRIGHT_CHROME_BIN,
},
},
firefox: {
...devices['Desktop Firefox'],
launchOptions: {
executablePath: process.env.PLAYWRIGHT_FIREFOX_BIN,
},
},
webkit: {
...devices['Desktop Safari'],
launchOptions: {
executablePath: process.env.PLAYWRIGHT_WEBKIT_BIN,
},
},
};

const browsers: Record<string, Proj> = Object.fromEntries(
Object.entries(browserOptions).map(([key, value]) => [
key,
{
name: key,
use: value,
},
])
);

const projects: Proj[] = [browsers.chromium];

if (process.env.CI) {
projects.push(browsers.firefox, browsers.webkit);
} else {
if (process.env.PLAYWRIGHT_FIREFOX_RUN) {
projects.push(browsers.firefox);
}

if (process.env.PLAYWRIGHT_WEBKIT_RUN) {
projects.push(browsers.webkit);
}
}

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only 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,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: projects,
});
1 change: 0 additions & 1 deletion packages/request-nanostores/tests/env.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import requestNanostores from '@inox-tools/request-nanostores';

export default defineConfig({
output: 'server',
integrations: [requestNanostores()],
devToolbar: { enabled: false },
});
11 changes: 11 additions & 0 deletions packages/request-nanostores/tests/fixture/race/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@request-nanostores/race",
"private": true,
"type": "module",
"dependencies": {
"@astrojs/preact": "^3.5.3",
"@inox-tools/request-nanostores": "workspace:",
"astro": "catalog:",
"nanostores": "catalog:"
}
}
18 changes: 18 additions & 0 deletions packages/request-nanostores/tests/fixture/race/src/pages/race.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { APIRoute } from 'astro';
import { identifier } from '../state.js';
import * as timers from 'node:timers/promises';

let requestCount = 0;

export const GET: APIRoute = async (ctx) => {
const requestId = ++requestCount;
const delay = Number.parseInt(ctx.url.searchParams.get('delay') as string) || 1000;

identifier.set(requestId);

await timers.setTimeout(delay);

const afterDelay = identifier.get();

return Response.json({ requestId, afterDelay });
};
4 changes: 4 additions & 0 deletions packages/request-nanostores/tests/fixture/race/src/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { atom } from 'nanostores';
import { shared } from '@it-astro:request-nanostores';

export const identifier = shared('identifier', atom<number>(0));
9 changes: 9 additions & 0 deletions packages/request-nanostores/tests/fixture/race/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"lib": ["dom"],
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
49 changes: 49 additions & 0 deletions packages/request-nanostores/tests/race-protection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { loadFixture } from '@inox-tools/astro-tests/astroFixture';
import testAdapter from '@inox-tools/astro-tests/testAdapter';
import { test, expect } from 'vitest';

const fixture = await loadFixture({
root: './fixture/race',
adapter: testAdapter(),
});

await fixture.build({});
const app = await fixture.loadTestAdapterApp();

test('concurrent requests have independent state', async () => {
const responses = await Promise.all(
Array(5)
.fill(null)
.map(async (_, index) => {
const delay = 200 * (index + 1);
const url = new URL('https://example.com/race');
url.searchParams.set('delay', delay.toFixed(0));

const response = await app.render(new Request(url));
return response.json();
})
);

expect(responses).toEqual([
{
requestId: 1,
afterDelay: 1,
},
{
requestId: 2,
afterDelay: 2,
},
{
requestId: 3,
afterDelay: 3,
},
{
requestId: 4,
afterDelay: 4,
},
{
requestId: 5,
afterDelay: 5,
},
]);
});
2 changes: 2 additions & 0 deletions packages/request-nanostores/tests/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as matchers from 'jest-extended';
import { expect } from 'vitest';

process.setSourceMapsEnabled(true);

expect.extend(matchers);
Loading

0 comments on commit 8ab5457

Please sign in to comment.