summaryrefslogtreecommitdiff
path: root/packages/integrations/web-vitals/test/basics.test.js
blob: 937619b483d4af47ef68cfbd91c8fbb5532f5cee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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(' ')
		);
	});
});