aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/react/test/react-component.test.js
blob: 44dbb138fa4ea95b1316620098cf82e903d4612f (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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import { load as cheerioLoad } from 'cheerio';
import { isWindows, loadFixture } from '../../../astro/test/test-utils.js';

let fixture;

describe('React Components', () => {
	before(async () => {
		fixture = await loadFixture({
			root: new URL('./fixtures/react-component/', import.meta.url),
		});
	});

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

		it('Can load React', async () => {
			const html = await fixture.readFile('/index.html');
			const $ = cheerioLoad(html);

			// test 1: basic component renders
			assert.equal($('#react-static').text(), 'Hello static!');

			// test 2: no reactroot
			assert.equal($('#react-static').attr('data-reactroot'), undefined);

			// test 3: Can use function components
			assert.equal($('#arrow-fn-component').length, 1);

			// test 4: Can use spread for components
			assert.equal($('#component-spread-props').length, 1);

			// test 5: spread props renders
			assert.equal($('#component-spread-props').text(), 'Hello world!');

			// test 6: Can use TS components
			assert.equal($('.ts-component').length, 1);

			// test 7: Can use Pure components
			assert.equal($('#pure').length, 1);

			// test 8: Check number of islands
			assert.equal($('astro-island[uid]').length, 9);

			// test 9: Check island deduplication
			const uniqueRootUIDs = new Set($('astro-island').map((i, el) => $(el).attr('uid')));
			assert.equal(uniqueRootUIDs.size, 8);

			// test 10: Should properly render children passed as props
			const islandsWithChildren = $('.with-children');
			assert.equal(islandsWithChildren.length, 2);
			assert.equal(
				$(islandsWithChildren[0]).html(),
				$(islandsWithChildren[1]).find('astro-slot').html()
			);

			// test 11: Should generate unique React.useId per island
			const islandsWithId = $('.react-use-id');
			assert.equal(islandsWithId.length, 2);
			assert.notEqual($(islandsWithId[0]).attr('id'), $(islandsWithId[1]).attr('id'));
		});

		it('Can load Vue', async () => {
			const html = await fixture.readFile('/index.html');
			const $ = cheerioLoad(html);
			assert.equal($('#vue-h2').text(), 'Hasta la vista, baby');
		});

		it('Can use a pragma comment', async () => {
			const html = await fixture.readFile('/pragma-comment/index.html');
			const $ = cheerioLoad(html);

			// test 1: rendered the PragmaComment component
			assert.equal($('.pragma-comment').length, 2);
		});

		// TODO: is this still a relevant test?
		it.skip('Includes reactroot on hydrating components', async () => {
			const html = await fixture.readFile('/index.html');
			const $ = cheerioLoad(html);

			const div = $('#research');

			// test 1: has the hydration attr
			assert.ok(div.attr('data-reactroot'));

			// test 2: renders correctly
			assert.equal(div.html(), 'foo bar <!-- -->1');
		});

		it('Can load Suspense-using components', async () => {
			const html = await fixture.readFile('/suspense/index.html');
			const $ = cheerioLoad(html);
			assert.equal($('#client #lazy').length, 1);
			assert.equal($('#server #lazy').length, 1);
		});

		it('Can pass through props with cloneElement', async () => {
			const html = await fixture.readFile('/index.html');
			const $ = cheerioLoad(html);
			assert.equal($('#cloned').text(), 'Cloned With Props');
		});

		it('Children are parsed as React components, can be manipulated', async () => {
			const html = await fixture.readFile('/children/index.html');
			const $ = cheerioLoad(html);
			assert.equal($('#one .with-children-count').text(), '2');
		});

		it('Client children passes option to the client', async () => {
			const html = await fixture.readFile('/children/index.html');
			const $ = cheerioLoad(html);
			assert.equal($('[data-react-children]').length, 1);
		});
	});

	if (isWindows) return;

	describe('dev', () => {
		/** @type {import('../../../astro/test/test-utils.js').Fixture} */
		let devServer;

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

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

		it('scripts proxy correctly', async () => {
			const html = await fixture.fetch('/').then((res) => res.text());
			const $ = cheerioLoad(html);

			for (const script of $('script').toArray()) {
				const { src } = script.attribs;
				if (!src) continue;
				assert.equal((await fixture.fetch(src)).status, 200, `404: ${src}`);
			}
		});

		// TODO: move this to separate dev test?
		it.skip('Throws helpful error message on window SSR', async () => {
			const html = await fixture.fetch('/window/index.html');
			assert.ok(
				(await html.text()).includes(
					`[/window]
			The window object is not available during server-side rendering (SSR).
			Try using \`import.meta.env.SSR\` to write SSR-friendly code.
			https://docs.astro.build/reference/api-reference/#importmeta`
				)
			);
		});

		// In moving over to Vite, the jsx-runtime import is now obscured. TODO: update the method of finding this.
		it.skip('uses the new JSX transform', async () => {
			const html = await fixture.fetch('/index.html');

			// Grab the imports
			const exp = /import\("(.+?)"\)/g;
			let match, componentUrl;
			while ((match = exp.exec(html))) {
				if (match[1].includes('Research.js')) {
					componentUrl = match[1];
					break;
				}
			}
			const component = await fixture.readFile(componentUrl);
			const jsxRuntime = component.imports.filter((i) => i.specifier.includes('jsx-runtime'));

			// test 1: react/jsx-runtime is used for the component
			assert.ok(jsxRuntime);
		});

		it('When a nested component throws it does not crash the server', async () => {
			const res = await fixture.fetch('/error-rendering');
			await res.arrayBuffer();
		});
	});
});