summaryrefslogtreecommitdiff
path: root/packages/integrations/web-vitals/test
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/web-vitals/test')
-rw-r--r--packages/integrations/web-vitals/test/basics.test.js118
-rw-r--r--packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs14
-rw-r--r--packages/integrations/web-vitals/test/fixtures/basics/package.json16
-rw-r--r--packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro19
-rw-r--r--packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro11
-rw-r--r--packages/integrations/web-vitals/test/test-utils.js16
6 files changed, 194 insertions, 0 deletions
diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js
new file mode 100644
index 000000000..937619b48
--- /dev/null
+++ b/packages/integrations/web-vitals/test/basics.test.js
@@ -0,0 +1,118 @@
+// @ts-check
+
+import * as assert from 'node:assert/strict';
+import { after, before, beforeEach, describe, it } from 'node:test';
+import { parseHTML } from 'linkedom';
+import { loadFixture } from './test-utils.js';
+
+/**
+ * @template {Record<K, (...args: any[]) => void>} T
+ * @template {keyof T} K
+ */
+class MockFunction {
+ /** @type {Parameters<T[K]>[]} */
+ calls = [];
+
+ /**
+ * @param {T} object
+ * @param {K} property
+ */
+ constructor(object, property) {
+ this.object = object;
+ this.property = property;
+ this.original = object[property];
+ object[property] = /** @param {Parameters<T[K]>} args */ (...args) => {
+ this.calls.push(args);
+ };
+ }
+ restore() {
+ this.object[this.property] = this.original;
+ }
+ reset() {
+ this.calls = [];
+ }
+}
+
+describe('Web Vitals integration basics', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ /** @type {import('./test-utils').DevServer} */
+ let devServer;
+ /** @type {MockFunction<Console, 'error'>} */
+ let consoleErrorMock;
+
+ before(async () => {
+ consoleErrorMock = new MockFunction(console, 'error');
+ fixture = await loadFixture({ root: './fixtures/basics/' });
+ devServer = await fixture.startDevServer({});
+ });
+
+ after(async () => {
+ consoleErrorMock.restore();
+ await devServer.stop();
+ });
+
+ beforeEach(() => {
+ consoleErrorMock.reset();
+ });
+
+ it('adds a meta tag to the page', async () => {
+ const html = await fixture.fetch('/', {}).then((res) => res.text());
+ const { document } = parseHTML(html);
+ const meta = document.querySelector('head > meta[name="x-astro-vitals-route"]');
+ assert.ok(meta);
+ assert.equal(meta.getAttribute('content'), '/');
+ });
+
+ it('adds a meta tag using the route pattern to the page', async () => {
+ const html = await fixture.fetch('/test', {}).then((res) => res.text());
+ const { document } = parseHTML(html);
+ const meta = document.querySelector('head > meta[name="x-astro-vitals-route"]');
+ assert.ok(meta);
+ assert.equal(meta.getAttribute('content'), '/[dynamic]');
+ });
+
+ it('returns a 200 response even when bad data is sent to the injected endpoint', async () => {
+ {
+ // bad data
+ const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: 'garbage' });
+ assert.equal(res.status, 200);
+ }
+ {
+ // no data
+ const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: '[]' });
+ assert.equal(res.status, 200);
+ }
+ assert.equal(consoleErrorMock.calls.length, 2);
+ });
+
+ it('validates data sent to the injected endpoint with Zod', async () => {
+ const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: '[{}]' });
+ assert.equal(res.status, 200);
+ const call = consoleErrorMock.calls[0][0];
+ assert.ok(call instanceof Error);
+ assert.equal(call.name, 'ZodError');
+ });
+
+ it('inserts data via the injected endpoint', async () => {
+ const res = await fixture.fetch('/_web-vitals', {
+ method: 'POST',
+ body: JSON.stringify([
+ {
+ pathname: '/',
+ route: '/',
+ name: 'CLS',
+ id: 'v3-1711484350895-3748043125387',
+ value: 0,
+ rating: 'good',
+ },
+ ]),
+ });
+ assert.equal(res.status, 200);
+ assert.equal(
+ consoleErrorMock.calls.length,
+ 0,
+ 'Endpoint logged errors:\n' + consoleErrorMock.calls[0]?.join(' ')
+ );
+ });
+});
diff --git a/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs b/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs
new file mode 100644
index 000000000..42bfa6f66
--- /dev/null
+++ b/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs
@@ -0,0 +1,14 @@
+import db from '@astrojs/db';
+import node from '@astrojs/node';
+import webVitals from '@astrojs/web-vitals';
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [db(), webVitals()],
+ output: 'hybrid',
+ adapter: node({ mode: 'standalone' }),
+ devToolbar: {
+ enabled: false,
+ },
+});
diff --git a/packages/integrations/web-vitals/test/fixtures/basics/package.json b/packages/integrations/web-vitals/test/fixtures/basics/package.json
new file mode 100644
index 000000000..25ab0abc1
--- /dev/null
+++ b/packages/integrations/web-vitals/test/fixtures/basics/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@test/web-vitals",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview"
+ },
+ "dependencies": {
+ "@astrojs/db": "workspace:*",
+ "@astrojs/node": "workspace:*",
+ "@astrojs/web-vitals": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro
new file mode 100644
index 000000000..36c7c50e6
--- /dev/null
+++ b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro
@@ -0,0 +1,19 @@
+---
+import type { GetStaticPaths } from "astro";
+export const getStaticPaths = (() => {
+ return [{ params: { dynamic: 'test' } }];
+}) satisfies GetStaticPaths;
+---
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Web Vitals basics — dynamic route test</title>
+</head>
+<body>
+ <h1>Web Vitals basics</h1>
+ <p>Dynamic route test</p>
+</body>
+</html>
diff --git a/packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro
new file mode 100644
index 000000000..06ddd6565
--- /dev/null
+++ b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Web Vitals basics test</title>
+</head>
+<body>
+ <h1>Web Vitals basics test</h1>
+</body>
+</html>
diff --git a/packages/integrations/web-vitals/test/test-utils.js b/packages/integrations/web-vitals/test/test-utils.js
new file mode 100644
index 000000000..8dd4d970b
--- /dev/null
+++ b/packages/integrations/web-vitals/test/test-utils.js
@@ -0,0 +1,16 @@
+import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js';
+
+/** @typedef {import('../../../astro/test/test-utils').Fixture} Fixture */
+/** @typedef {import('../../../astro/test/test-utils').DevServer} DevServer */
+
+/** @type {typeof import('../../../astro/test/test-utils.js')['loadFixture']} */
+export function loadFixture(inlineConfig) {
+ if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }");
+
+ // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath
+ // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
+ return baseLoadFixture({
+ ...inlineConfig,
+ root: new URL(inlineConfig.root, import.meta.url).toString(),
+ });
+}