react-component.test.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import assert from 'node:assert/strict';
  2. import { describe, it, before, after } from 'node:test';
  3. import { load as cheerioLoad } from 'cheerio';
  4. import { isWindows, loadFixture } from '../../../astro/test/test-utils.js';
  5. let fixture;
  6. describe('React Components', () => {
  7. before(async () => {
  8. fixture = await loadFixture({
  9. root: new URL('./fixtures/react-component/', import.meta.url),
  10. });
  11. });
  12. describe('build', () => {
  13. before(async () => {
  14. await fixture.build();
  15. });
  16. it('Can load React', async () => {
  17. const html = await fixture.readFile('/index.html');
  18. const $ = cheerioLoad(html);
  19. // test 1: basic component renders
  20. assert.equal($('#react-static').text(), 'Hello static!');
  21. // test 2: no reactroot
  22. assert.equal($('#react-static').attr('data-reactroot'), undefined);
  23. // test 3: Can use function components
  24. assert.equal($('#arrow-fn-component').length, 1);
  25. // test 4: Can use spread for components
  26. assert.equal($('#component-spread-props').length, 1);
  27. // test 5: spread props renders
  28. assert.equal($('#component-spread-props').text(), 'Hello world!');
  29. // test 6: Can use TS components
  30. assert.equal($('.ts-component').length, 1);
  31. // test 7: Can use Pure components
  32. assert.equal($('#pure').length, 1);
  33. // test 8: Check number of islands
  34. assert.equal($('astro-island[uid]').length, 9);
  35. // test 9: Check island deduplication
  36. const uniqueRootUIDs = new Set($('astro-island').map((i, el) => $(el).attr('uid')));
  37. assert.equal(uniqueRootUIDs.size, 8);
  38. // test 10: Should properly render children passed as props
  39. const islandsWithChildren = $('.with-children');
  40. assert.equal(islandsWithChildren.length, 2);
  41. assert.equal(
  42. $(islandsWithChildren[0]).html(),
  43. $(islandsWithChildren[1]).find('astro-slot').html()
  44. );
  45. // test 11: Should generate unique React.useId per island
  46. const islandsWithId = $('.react-use-id');
  47. assert.equal(islandsWithId.length, 2);
  48. assert.notEqual($(islandsWithId[0]).attr('id'), $(islandsWithId[1]).attr('id'));
  49. });
  50. it('Can load Vue', async () => {
  51. const html = await fixture.readFile('/index.html');
  52. const $ = cheerioLoad(html);
  53. assert.equal($('#vue-h2').text(), 'Hasta la vista, baby');
  54. });
  55. it('Can use a pragma comment', async () => {
  56. const html = await fixture.readFile('/pragma-comment/index.html');
  57. const $ = cheerioLoad(html);
  58. // test 1: rendered the PragmaComment component
  59. assert.equal($('.pragma-comment').length, 2);
  60. });
  61. // TODO: is this still a relevant test?
  62. it.skip('Includes reactroot on hydrating components', async () => {
  63. const html = await fixture.readFile('/index.html');
  64. const $ = cheerioLoad(html);
  65. const div = $('#research');
  66. // test 1: has the hydration attr
  67. assert.ok(div.attr('data-reactroot'));
  68. // test 2: renders correctly
  69. assert.equal(div.html(), 'foo bar <!-- -->1');
  70. });
  71. it('Can load Suspense-using components', async () => {
  72. const html = await fixture.readFile('/suspense/index.html');
  73. const $ = cheerioLoad(html);
  74. assert.equal($('#client #lazy').length, 1);
  75. assert.equal($('#server #lazy').length, 1);
  76. });
  77. it('Can pass through props with cloneElement', async () => {
  78. const html = await fixture.readFile('/index.html');
  79. const $ = cheerioLoad(html);
  80. assert.equal($('#cloned').text(), 'Cloned With Props');
  81. });
  82. it('Children are parsed as React components, can be manipulated', async () => {
  83. const html = await fixture.readFile('/children/index.html');
  84. const $ = cheerioLoad(html);
  85. assert.equal($('#one .with-children-count').text(), '2');
  86. });
  87. it('Client children passes option to the client', async () => {
  88. const html = await fixture.readFile('/children/index.html');
  89. const $ = cheerioLoad(html);
  90. assert.equal($('[data-react-children]').length, 1);
  91. });
  92. });
  93. if (isWindows) return;
  94. describe('dev', () => {
  95. /** @type {import('../../../astro/test/test-utils.js').Fixture} */
  96. let devServer;
  97. before(async () => {
  98. devServer = await fixture.startDevServer();
  99. });
  100. after(async () => {
  101. await devServer.stop();
  102. });
  103. it('scripts proxy correctly', async () => {
  104. const html = await fixture.fetch('/').then((res) => res.text());
  105. const $ = cheerioLoad(html);
  106. for (const script of $('script').toArray()) {
  107. const { src } = script.attribs;
  108. if (!src) continue;
  109. assert.equal((await fixture.fetch(src)).status, 200, `404: ${src}`);
  110. }
  111. });
  112. // TODO: move this to separate dev test?
  113. it.skip('Throws helpful error message on window SSR', async () => {
  114. const html = await fixture.fetch('/window/index.html');
  115. assert.ok(
  116. (await html.text()).includes(
  117. `[/window]
  118. The window object is not available during server-side rendering (SSR).
  119. Try using \`import.meta.env.SSR\` to write SSR-friendly code.
  120. https://docs.astro.build/reference/api-reference/#importmeta`
  121. )
  122. );
  123. });
  124. // In moving over to Vite, the jsx-runtime import is now obscured. TODO: update the method of finding this.
  125. it.skip('uses the new JSX transform', async () => {
  126. const html = await fixture.fetch('/index.html');
  127. // Grab the imports
  128. const exp = /import\("(.+?)"\)/g;
  129. let match, componentUrl;
  130. while ((match = exp.exec(html))) {
  131. if (match[1].includes('Research.js')) {
  132. componentUrl = match[1];
  133. break;
  134. }
  135. }
  136. const component = await fixture.readFile(componentUrl);
  137. const jsxRuntime = component.imports.filter((i) => i.specifier.includes('jsx-runtime'));
  138. // test 1: react/jsx-runtime is used for the component
  139. assert.ok(jsxRuntime);
  140. });
  141. it('When a nested component throws it does not crash the server', async () => {
  142. const res = await fixture.fetch('/error-rendering');
  143. await res.arrayBuffer();
  144. });
  145. });
  146. });