packages/react-dom/src/__tests__/ReactDOMFloat-test.js JAVASCRIPT 9,773 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 9,773.
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @emails react-core8 * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment9 */1011'use strict';12import {13  insertNodesAndExecuteScripts,14  mergeOptions,15} from '../test-utils/FizzTestUtils';1617let JSDOM;18let Stream;19let React;20let ReactDOM;21let ReactDOMClient;22let ReactDOMFizzServer;23let Suspense;24let SuspenseList;25let textCache;26let loadCache;27let writable;28let CSPnonce = null;29let container;30let buffer = '';31let hasErrored = false;32let fatalError = undefined;33let renderOptions;34let waitForAll;35let assertLog;36let Scheduler;37let clientAct;38let streamingContainer;39let assertConsoleErrorDev;4041describe('ReactDOMFloat', () => {42  beforeEach(() => {43    jest.resetModules();44    JSDOM = require('jsdom').JSDOM;4546    const jsdom = new JSDOM(47      '<!DOCTYPE html><html><head></head><body><div id="container">',48      {49        runScripts: 'dangerously',50      },51    );52    // We mock matchMedia. for simplicity it only matches 'all' or '' and misses everything else53    Object.defineProperty(jsdom.window, 'matchMedia', {54      writable: true,55      value: jest.fn().mockImplementation(query => ({56        matches: query === 'all' || query === '',57        media: query,58      })),59    });60    streamingContainer = null;61    global.window = jsdom.window;62    global.document = global.window.document;63    global.navigator = global.window.navigator;64    global.Node = global.window.Node;65    global.addEventListener = global.window.addEventListener;66    global.MutationObserver = global.window.MutationObserver;67    // The Fizz runtime assumes requestAnimationFrame exists so we need to polyfill it.68    global.requestAnimationFrame = global.window.requestAnimationFrame = cb =>69      setTimeout(cb);70    container = document.getElementById('container');7172    CSPnonce = null;73    React = require('react');74    ReactDOM = require('react-dom');75    ReactDOMClient = require('react-dom/client');76    ReactDOMFizzServer = require('react-dom/server');77    Stream = require('stream');78    Suspense = React.Suspense;79    SuspenseList = React.unstable_SuspenseList;80    Scheduler = require('scheduler/unstable_mock');8182    const InternalTestUtils = require('internal-test-utils');83    waitForAll = InternalTestUtils.waitForAll;84    assertLog = InternalTestUtils.assertLog;85    clientAct = InternalTestUtils.act;86    assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;8788    textCache = new Map();89    loadCache = new Set();9091    buffer = '';92    hasErrored = false;9394    writable = new Stream.PassThrough();95    writable.setEncoding('utf8');96    writable.on('data', chunk => {97      buffer += chunk;98    });99    writable.on('error', error => {100      hasErrored = true;101      fatalError = error;102    });103104    renderOptions = {};105    if (gate(flags => flags.shouldUseFizzExternalRuntime)) {106      renderOptions.unstable_externalRuntimeSrc =107        'react-dom/unstable_server-external-runtime';108    }109  });110111  const bodyStartMatch = /<body(?:>| .*?>)/;112  const headStartMatch = /<head(?:>| .*?>)/;113114  async function act(callback) {115    await callback();116    // Await one turn around the event loop.117    // This assumes that we'll flush everything we have so far.118    await new Promise(resolve => {119      setImmediate(resolve);120    });121    if (hasErrored) {122      throw fatalError;123    }124    // JSDOM doesn't support stream HTML parser so we need to give it a proper fragment.125    // We also want to execute any scripts that are embedded.126    // We assume that we have now received a proper fragment of HTML.127    let bufferedContent = buffer;128    buffer = '';129130    if (!bufferedContent) {131      jest.runAllTimers();132      return;133    }134135    const bodyMatch = bufferedContent.match(bodyStartMatch);136    const headMatch = bufferedContent.match(headStartMatch);137138    if (streamingContainer === null) {139      // This is the first streamed content. We decide here where to insert it. If we get <html>, <head>, or <body>140      // we abandon the pre-built document and start from scratch. If we get anything else we assume it goes into the141      // container. This is not really production behavior because you can't correctly stream into a deep div effectively142      // but it's pragmatic for tests.143144      if (145        bufferedContent.startsWith('<head>') ||146        bufferedContent.startsWith('<head ') ||147        bufferedContent.startsWith('<body>') ||148        bufferedContent.startsWith('<body ')149      ) {150        // wrap in doctype to normalize the parsing process151        bufferedContent = '<!DOCTYPE html><html>' + bufferedContent;152      } else if (153        bufferedContent.startsWith('<html>') ||154        bufferedContent.startsWith('<html ')155      ) {156        throw new Error(157          'Recieved <html> without a <!DOCTYPE html> which is almost certainly a bug in React',158        );159      }160161      if (bufferedContent.startsWith('<!DOCTYPE html>')) {162        // we can just use the whole document163        const tempDom = new JSDOM(bufferedContent);164165        // Wipe existing head and body content166        document.head.innerHTML = '';167        document.body.innerHTML = '';168169        // Copy the <html> attributes over170        const tempHtmlNode = tempDom.window.document.documentElement;171        for (let i = 0; i < tempHtmlNode.attributes.length; i++) {172          const attr = tempHtmlNode.attributes[i];173          document.documentElement.setAttribute(attr.name, attr.value);174        }175176        if (headMatch) {177          // We parsed a head open tag. we need to copy head attributes and insert future178          // content into <head>179          streamingContainer = document.head;180          const tempHeadNode = tempDom.window.document.head;181          for (let i = 0; i < tempHeadNode.attributes.length; i++) {182            const attr = tempHeadNode.attributes[i];183            document.head.setAttribute(attr.name, attr.value);184          }185          const source = document.createElement('head');186          source.innerHTML = tempHeadNode.innerHTML;187          await insertNodesAndExecuteScripts(source, document.head, CSPnonce);188        }189190        if (bodyMatch) {191          // We parsed a body open tag. we need to copy head attributes and insert future192          // content into <body>193          streamingContainer = document.body;194          const tempBodyNode = tempDom.window.document.body;195          for (let i = 0; i < tempBodyNode.attributes.length; i++) {196            const attr = tempBodyNode.attributes[i];197            document.body.setAttribute(attr.name, attr.value);198          }199          const source = document.createElement('body');200          source.innerHTML = tempBodyNode.innerHTML;201          await insertNodesAndExecuteScripts(source, document.body, CSPnonce);202        }203204        if (!headMatch && !bodyMatch) {205          throw new Error('expected <head> or <body> after <html>');206        }207      } else {208        // we assume we are streaming into the default container'209        streamingContainer = container;210        const div = document.createElement('div');211        div.innerHTML = bufferedContent;212        await insertNodesAndExecuteScripts(div, container, CSPnonce);213      }214    } else if (streamingContainer === document.head) {215      bufferedContent = '<!DOCTYPE html><html><head>' + bufferedContent;216      const tempDom = new JSDOM(bufferedContent);217218      const tempHeadNode = tempDom.window.document.head;219      const source = document.createElement('head');220      source.innerHTML = tempHeadNode.innerHTML;221      await insertNodesAndExecuteScripts(source, document.head, CSPnonce);222223      if (bodyMatch) {224        streamingContainer = document.body;225226        const tempBodyNode = tempDom.window.document.body;227        for (let i = 0; i < tempBodyNode.attributes.length; i++) {228          const attr = tempBodyNode.attributes[i];229          document.body.setAttribute(attr.name, attr.value);230        }231        const bodySource = document.createElement('body');232        bodySource.innerHTML = tempBodyNode.innerHTML;233        await insertNodesAndExecuteScripts(bodySource, document.body, CSPnonce);234      }235    } else {236      const div = document.createElement('div');237      div.innerHTML = bufferedContent;238      await insertNodesAndExecuteScripts(div, streamingContainer, CSPnonce);239    }240    await 0;241    // Let throttled boundaries reveal242    jest.runAllTimers();243  }244245  function getMeaningfulChildren(element) {246    const children = [];247    let node = element.firstChild;248    while (node) {249      if (node.nodeType === 1) {250        if (251          // some tags are ambiguous and might be hidden because they look like non-meaningful children252          // so we have a global override where if this data attribute is included we also include the node253          node.hasAttribute('data-meaningful') ||254          (node.tagName === 'SCRIPT' &&255            node.hasAttribute('src') &&256            node.getAttribute('src') !==257              renderOptions.unstable_externalRuntimeSrc &&258            node.hasAttribute('async')) ||259          (node.tagName !== 'SCRIPT' &&260            node.tagName !== 'TEMPLATE' &&261            node.tagName !== 'template' &&262            !node.hasAttribute('hidden') &&263            !node.hasAttribute('aria-hidden') &&264            // Ignore the render blocking expect265            (node.getAttribute('rel') !== 'expect' ||266              node.getAttribute('blocking') !== 'render'))267        ) {268          const props = {};269          const attributes = node.attributes;270          for (let i = 0; i < attributes.length; i++) {271            if (272              attributes[i].name === 'id' &&273              attributes[i].value.includes(':')274            ) {275              // We assume this is a React added ID that's a non-visual implementation detail.276              continue;277            }278            props[attributes[i].name] = attributes[i].value;279          }280          props.children = getMeaningfulChildren(node);281          children.push(React.createElement(node.tagName.toLowerCase(), props));282        }283      } else if (node.nodeType === 3) {284        children.push(node.data);285      }286      node = node.nextSibling;287    }288    return children.length === 0289      ? undefined290      : children.length === 1291        ? children[0]292        : children;293  }294295  function BlockedOn({value, children}) {296    readText(value);297    return children;298  }299300  function resolveText(text) {301    const record = textCache.get(text);302    if (record === undefined) {303      const newRecord = {304        status: 'resolved',305        value: text,306      };307      textCache.set(text, newRecord);308    } else if (record.status === 'pending') {309      const thenable = record.value;310      record.status = 'resolved';311      record.value = text;312      thenable.pings.forEach(t => t());313    }314  }315316  function readText(text) {317    const record = textCache.get(text);318    if (record !== undefined) {319      switch (record.status) {320        case 'pending':321          throw record.value;322        case 'rejected':323          throw record.value;324        case 'resolved':325          return record.value;326      }327    } else {328      const thenable = {329        pings: [],330        then(resolve) {331          if (newRecord.status === 'pending') {332            thenable.pings.push(resolve);333          } else {334            Promise.resolve().then(() => resolve(newRecord.value));335          }336        },337      };338339      const newRecord = {340        status: 'pending',341        value: thenable,342      };343      textCache.set(text, newRecord);344345      throw thenable;346    }347  }348349  function AsyncText({text}) {350    return readText(text);351  }352353  function renderToPipeableStream(jsx, options) {354    // Merge options with renderOptions, which may contain featureFlag specific behavior355    return ReactDOMFizzServer.renderToPipeableStream(356      jsx,357      mergeOptions(options, renderOptions),358    );359  }360361  function loadPreloads(hrefs) {362    const event = new window.Event('load');363    const nodes = document.querySelectorAll('link[rel="preload"]');364    resolveLoadables(hrefs, nodes, event, href =>365      Scheduler.log('load preload: ' + href),366    );367  }368369  function errorPreloads(hrefs) {370    const event = new window.Event('error');371    const nodes = document.querySelectorAll('link[rel="preload"]');372    resolveLoadables(hrefs, nodes, event, href =>373      Scheduler.log('error preload: ' + href),374    );375  }376377  function loadStylesheets(hrefs) {378    loadStylesheetsFrom(document, hrefs);379  }380381  function loadStylesheetsFrom(root, hrefs) {382    const event = new window.Event('load');383    const nodes = root.querySelectorAll('link[rel="stylesheet"]');384    resolveLoadables(hrefs, nodes, event, href => {385      Scheduler.log('load stylesheet: ' + href);386    });387  }388389  function errorStylesheets(hrefs) {390    const event = new window.Event('error');391    const nodes = document.querySelectorAll('link[rel="stylesheet"]');392    resolveLoadables(hrefs, nodes, event, href => {393      Scheduler.log('error stylesheet: ' + href);394    });395  }396397  function resolveLoadables(hrefs, nodes, event, onLoad) {398    const hrefSet = hrefs ? new Set(hrefs) : null;399    for (let i = 0; i < nodes.length; i++) {400      const node = nodes[i];401      if (loadCache.has(node)) {402        continue;403      }404      const href = node.getAttribute('href');405      if (!hrefSet || hrefSet.has(href)) {406        loadCache.add(node);407        onLoad(href);408        node.dispatchEvent(event);409      }410    }411  }412413  it('can render resources before singletons', async () => {414    const root = ReactDOMClient.createRoot(document);415    root.render(416      <>417        <title>foo</title>418        <html>419          <head>420            <link rel="foo" href="foo" />421          </head>422          <body>hello world</body>423        </html>424      </>,425    );426    try {427      await waitForAll([]);428    } catch (e) {429      // for DOMExceptions that happen when expecting this test to fail we need430      // to clear the scheduler first otherwise the expected failure will fail431      await waitForAll([]);432      throw e;433    }434    expect(getMeaningfulChildren(document)).toEqual(435      <html>436        <head>437          <title>foo</title>438          <link rel="foo" href="foo" />439        </head>440        <body>hello world</body>441      </html>,442    );443  });444445  it('can hydrate non Resources in head when Resources are also inserted there', async () => {446    await act(() => {447      const {pipe} = renderToPipeableStream(448        <html>449          <head>450            <meta property="foo" content="bar" />451            <link rel="foo" href="bar" onLoad={() => {}} />452            <title>foo</title>453            <noscript>454              <link rel="icon" href="icon" />455            </noscript>456            <base target="foo" href="bar" />457            <script async={true} src="foo" onLoad={() => {}} />458          </head>459          <body>foo</body>460        </html>,461      );462      pipe(writable);463    });464    expect(getMeaningfulChildren(document)).toEqual(465      <html>466        <head>467          <meta property="foo" content="bar" />468          <title>foo</title>469          <link rel="foo" href="bar" />470          <noscript>&lt;link rel="icon" href="icon"&gt;</noscript>471          <base target="foo" href="bar" />472          <script async="" src="foo" />473        </head>474        <body>foo</body>475      </html>,476    );477478    ReactDOMClient.hydrateRoot(479      document,480      <html>481        <head>482          <meta property="foo" content="bar" />483          <link rel="foo" href="bar" onLoad={() => {}} />484          <title>foo</title>485          <noscript>486            <link rel="icon" href="icon" />487          </noscript>488          <base target="foo" href="bar" />489          <script async={true} src="foo" onLoad={() => {}} />490        </head>491        <body>foo</body>492      </html>,493    );494    await waitForAll([]);495    expect(getMeaningfulChildren(document)).toEqual(496      <html>497        <head>498          <meta property="foo" content="bar" />499          <title>foo</title>500          <link rel="foo" href="bar" />501          <noscript>&lt;link rel="icon" href="icon"&gt;</noscript>502          <base target="foo" href="bar" />503          <script async="" src="foo" />504        </head>505        <body>foo</body>506      </html>,507    );508  });509510  it('warns if you render resource-like elements above <head> or <body>', async () => {511    const root = ReactDOMClient.createRoot(document);512513    root.render(514      <>515        <noscript>foo</noscript>516        <html>517          <body>foo</body>518        </html>519      </>,520    );521    await waitForAll([]);522    assertConsoleErrorDev([523      'Cannot render <noscript> outside the main document. Try moving it into the root <head> tag.',524    ]);525526    root.render(527      <html>528        <template>foo</template>529        <body>foo</body>530      </html>,531    );532    await waitForAll([]);533    assertConsoleErrorDev([534      'Cannot render <template> outside the main document. Try moving it into the root <head> tag.\n' +535        '    in html (at **)',536      'In HTML, <template> cannot be a child of <html>.\n' +537        'This will cause a hydration error.\n\n' +538        '> <html>\n' +539        '>   <template>\n' +540        '    ...\n' +541        '\n' +542        '    in template (at **)',543    ]);544545    root.render(546      <html>547        <body>foo</body>548        <style>foo</style>549      </html>,550    );551    await waitForAll([]);552    assertConsoleErrorDev([553      'Cannot render a <style> outside the main document without knowing its precedence ' +554        'and a unique href key. React can hoist and deduplicate <style> tags if you provide a ' +555        '`precedence` prop along with an `href` prop that does not conflict with the `href` ' +556        'values used in any other hoisted <style> or <link rel="stylesheet" ...> tags.  ' +557        'Note that hoisting <style> tags is considered an advanced feature that most will not use directly. ' +558        'Consider moving the <style> tag to the <head> or consider adding a `precedence="default"` ' +559        'and `href="some unique resource identifier"`.\n' +560        '    in html (at **)',561      'In HTML, <style> cannot be a child of <html>.\n' +562        'This will cause a hydration error.\n\n' +563        '> <html>\n' +564        '    <body>\n' +565        '>   <style>\n' +566        '\n' +567        '    in style (at **)',568    ]);569570    root.render(571      <>572        <html>573          <body>foo</body>574        </html>575        <link rel="stylesheet" href="foo" />576      </>,577    );578    await waitForAll([]);579    assertConsoleErrorDev([580      'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence. ' +581        'Consider adding precedence="default" or moving it into the root <head> tag.',582    ]);583584    root.render(585      <>586        <html>587          <body>foo</body>588          <script href="foo" />589        </html>590      </>,591    );592    await waitForAll([]);593    assertConsoleErrorDev([594      'Cannot render a sync or defer <script> outside the main document without knowing its order. ' +595        'Try adding async="" or moving it into the root <head> tag.\n' +596        '    in html (at **)',597      'In HTML, <script> cannot be a child of <html>.\n' +598        'This will cause a hydration error.\n' +599        '\n' +600        '> <html>\n' +601        '    <body>\n' +602        '>   <script href="foo">\n' +603        '\n' +604        '    in script (at **)',605      ...(gate('enableTrustedTypesIntegration')606        ? [607            'Encountered a script tag while rendering React component. ' +608              'Scripts inside React components are never executed when rendering on the client. ' +609              'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +610              '     in script (at **)',611          ]612        : []),613    ]);614615    root.render(616      <html>617        <script async={true} onLoad={() => {}} href="bar" />618        <body>foo</body>619      </html>,620    );621    await waitForAll([]);622    assertConsoleErrorDev([623      'Cannot render a <script> with onLoad or onError listeners outside the main document. ' +624        'Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or ' +625        'somewhere in the <body>.\n' +626        '    in html (at **)',627    ]);628629    root.render(630      <>631        <link rel="foo" onLoad={() => {}} href="bar" />632        <html>633          <body>foo</body>634        </html>635      </>,636    );637    await waitForAll([]);638    assertConsoleErrorDev([639      'Cannot render a <link> with onLoad or onError listeners outside the main document. ' +640        'Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or ' +641        'somewhere in the <body>.',642    ]);643    return;644  });645646  it('can acquire a resource after releasing it in the same commit', async () => {647    const root = ReactDOMClient.createRoot(container);648    root.render(649      <>650        <script async={true} src="foo" />651      </>,652    );653    await waitForAll([]);654    expect(getMeaningfulChildren(document)).toEqual(655      <html>656        <head>657          <script async="" src="foo" />658        </head>659        <body>660          <div id="container" />661        </body>662      </html>,663    );664665    root.render(666      <>667        {null}668        <script data-new="new" async={true} src="foo" />669      </>,670    );671    await waitForAll([]);672    // we don't see the attribute because the resource is the same and was not reconstructed673    expect(getMeaningfulChildren(document)).toEqual(674      <html>675        <head>676          <script async="" src="foo" />677        </head>678        <body>679          <div id="container" />680        </body>681      </html>,682    );683  });684685  it('emits an implicit <head> element to hold resources when none is rendered but an <html> is rendered', async () => {686    const chunks = [];687688    writable.on('data', chunk => {689      chunks.push(chunk);690    });691692    await act(() => {693      const {pipe} = renderToPipeableStream(694        <>695          <title>foo</title>696          <html>697            <body>bar</body>698          </html>699          <script async={true} src="foo" />700        </>,701      );702      pipe(writable);703    });704    expect(chunks).toEqual([705      '<!DOCTYPE html><html><head><script async="" src="foo"></script>' +706        (gate(flags => flags.shouldUseFizzExternalRuntime)707          ? '<script src="react-dom/unstable_server-external-runtime" async=""></script>'708          : '') +709        (gate(flags => flags.enableFizzBlockingRender)710          ? '<link rel="expect" href="#_R_" blocking="render"/>'711          : '') +712        '<title>foo</title></head>' +713        '<body>bar' +714        (gate(flags => flags.enableFizzBlockingRender)715          ? '<template id="_R_"></template>'716          : ''),717      '</body></html>',718    ]);719  });720721  it('dedupes if the external runtime is explicitly loaded using preinit', async () => {722    const unstable_externalRuntimeSrc = 'src-of-external-runtime';723    function App() {724      ReactDOM.preinit(unstable_externalRuntimeSrc, {as: 'script'});725      return (726        <div>727          <Suspense fallback={<h1>Loading...</h1>}>728            <AsyncText text="Hello" />729          </Suspense>730        </div>731      );732    }733734    await act(() => {735      const {pipe} = renderToPipeableStream(736        <html>737          <head />738          <body>739            <App />740          </body>741        </html>,742        {743          unstable_externalRuntimeSrc,744        },745      );746      pipe(writable);747    });748749    expect(750      Array.from(document.querySelectorAll('script[async]')).map(751        n => n.outerHTML,752      ),753    ).toEqual(['<script src="src-of-external-runtime" async=""></script>']);754  });755756  it('can send style insertion implementation independent of boundary commpletion instruction implementation', async () => {757    await act(() => {758      renderToPipeableStream(759        <html>760          <body>761            <Suspense fallback="loading foo...">762              <BlockedOn value="foo">foo</BlockedOn>763            </Suspense>764            <Suspense fallback="loading bar...">765              <BlockedOn value="bar">766                <link rel="stylesheet" href="bar" precedence="bar" />767                bar768              </BlockedOn>769            </Suspense>770          </body>771        </html>,772      ).pipe(writable);773    });774775    expect(getMeaningfulChildren(document)).toEqual(776      <html>777        <head />778        <body>779          {'loading foo...'}780          {'loading bar...'}781        </body>782      </html>,783    );784785    await act(() => {786      resolveText('foo');787    });788    expect(getMeaningfulChildren(document)).toEqual(789      <html>790        <head />791        <body>792          foo793          {'loading bar...'}794        </body>795      </html>,796    );797    await act(() => {798      resolveText('bar');799    });800    expect(getMeaningfulChildren(document)).toEqual(801      <html>802        <head>803          <link rel="stylesheet" href="bar" data-precedence="bar" />804        </head>805        <body>806          foo807          {'loading bar...'}808          <link rel="preload" href="bar" as="style" />809        </body>810      </html>,811    );812  });813814  it('can avoid inserting a late stylesheet if it already rendered on the client', async () => {815    await act(() => {816      renderToPipeableStream(817        <html>818          <body>819            <Suspense fallback="loading foo...">820              <BlockedOn value="foo">821                <link rel="stylesheet" href="foo" precedence="foo" />822                foo823              </BlockedOn>824            </Suspense>825            <Suspense fallback="loading bar...">826              <BlockedOn value="bar">827                <link rel="stylesheet" href="bar" precedence="bar" />828                bar829              </BlockedOn>830            </Suspense>831          </body>832        </html>,833      ).pipe(writable);834    });835836    expect(getMeaningfulChildren(document)).toEqual(837      <html>838        <head />839        <body>840          {'loading foo...'}841          {'loading bar...'}842        </body>843      </html>,844    );845846    ReactDOMClient.hydrateRoot(847      document,848      <html>849        <body>850          <link rel="stylesheet" href="foo" precedence="foo" />851          <Suspense fallback="loading foo...">852            <link rel="stylesheet" href="foo" precedence="foo" />853            foo854          </Suspense>855          <Suspense fallback="loading bar...">856            <link rel="stylesheet" href="bar" precedence="bar" />857            bar858          </Suspense>859        </body>860      </html>,861    );862    await waitForAll([]);863    loadPreloads();864    await assertLog(['load preload: foo']);865    expect(getMeaningfulChildren(document)).toEqual(866      <html>867        <head>868          <link rel="stylesheet" href="foo" data-precedence="foo" />869          <link as="style" href="foo" rel="preload" />870        </head>871        <body>872          {'loading foo...'}873          {'loading bar...'}874        </body>875      </html>,876    );877878    await act(() => {879      resolveText('bar');880    });881    await act(() => {882      loadStylesheets();883    });884    await assertLog(['load stylesheet: foo', 'load stylesheet: bar']);885    expect(getMeaningfulChildren(document)).toEqual(886      <html>887        <head>888          <link rel="stylesheet" href="foo" data-precedence="foo" />889          <link rel="stylesheet" href="bar" data-precedence="bar" />890          <link as="style" href="foo" rel="preload" />891        </head>892        <body>893          {'loading foo...'}894          {'bar'}895          <link as="style" href="bar" rel="preload" />896        </body>897      </html>,898    );899900    await act(() => {901      resolveText('foo');902    });903    await act(() => {904      loadStylesheets();905    });906    await assertLog([]);907    expect(getMeaningfulChildren(document)).toEqual(908      <html>909        <head>910          <link rel="stylesheet" href="foo" data-precedence="foo" />911          <link rel="stylesheet" href="bar" data-precedence="bar" />912          <link as="style" href="foo" rel="preload" />913        </head>914        <body>915          {'foo'}916          {'bar'}917          <link as="style" href="bar" rel="preload" />918          <link as="style" href="foo" rel="preload" />919        </body>920      </html>,921    );922  });923924  it('can hoist <link rel="stylesheet" .../> and <style /> tags together, respecting order of discovery', async () => {925    const css = `926body {927  background-color: red;928}`;929930    await act(() => {931      renderToPipeableStream(932        <html>933          <body>934            <link rel="stylesheet" href="one1" precedence="one" />935            <style href="two1" precedence="two">936              {css}937            </style>938            <link rel="stylesheet" href="three1" precedence="three" />939            <style href="four1" precedence="four">940              {css}941            </style>942            <Suspense>943              <BlockedOn value="block">944                <link rel="stylesheet" href="one2" precedence="one" />945                <link rel="stylesheet" href="two2" precedence="two" />946                <style href="three2" precedence="three">947                  {css}948                </style>949                <style href="four2" precedence="four">950                  {css}951                </style>952                <link rel="stylesheet" href="five1" precedence="five" />953              </BlockedOn>954            </Suspense>955            <Suspense>956              <BlockedOn value="block2">957                <style href="one3" precedence="one">958                  {css}959                </style>960                <style href="two3" precedence="two">961                  {css}962                </style>963                <link rel="stylesheet" href="three3" precedence="three" />964                <link rel="stylesheet" href="four3" precedence="four" />965                <style href="six1" precedence="six">966                  {css}967                </style>968              </BlockedOn>969            </Suspense>970            <Suspense>971              <BlockedOn value="block again">972                <link rel="stylesheet" href="one2" precedence="one" />973                <link rel="stylesheet" href="two2" precedence="two" />974                <style href="three2" precedence="three">975                  {css}976                </style>977                <style href="four2" precedence="four">978                  {css}979                </style>980                <link rel="stylesheet" href="five1" precedence="five" />981              </BlockedOn>982            </Suspense>983          </body>984        </html>,985      ).pipe(writable);986    });987988    expect(getMeaningfulChildren(document)).toEqual(989      <html>990        <head>991          <link rel="stylesheet" href="one1" data-precedence="one" />992          <style data-href="two1" data-precedence="two">993            {css}994          </style>995          <link rel="stylesheet" href="three1" data-precedence="three" />996          <style data-href="four1" data-precedence="four">997            {css}998          </style>999        </head>1000        <body />1001      </html>,1002    );10031004    await act(() => {1005      resolveText('block');1006    });10071008    expect(getMeaningfulChildren(document)).toEqual(1009      <html>1010        <head>1011          <link rel="stylesheet" href="one1" data-precedence="one" />1012          <link rel="stylesheet" href="one2" data-precedence="one" />1013          <style data-href="two1" data-precedence="two">1014            {css}1015          </style>1016          <link rel="stylesheet" href="two2" data-precedence="two" />1017          <link rel="stylesheet" href="three1" data-precedence="three" />1018          <style data-href="three2" data-precedence="three">1019            {css}1020          </style>1021          <style data-href="four1" data-precedence="four">1022            {css}1023          </style>1024          <style data-href="four2" data-precedence="four">1025            {css}1026          </style>1027          <link rel="stylesheet" href="five1" data-precedence="five" />1028        </head>1029        <body>1030          <link rel="preload" href="one2" as="style" />1031          <link rel="preload" href="two2" as="style" />1032          <link rel="preload" href="five1" as="style" />1033        </body>1034      </html>,1035    );10361037    await act(() => {1038      resolveText('block2');1039    });10401041    expect(getMeaningfulChildren(document)).toEqual(1042      <html>1043        <head>1044          <link rel="stylesheet" href="one1" data-precedence="one" />1045          <link rel="stylesheet" href="one2" data-precedence="one" />1046          <style data-href="one3" data-precedence="one">1047            {css}1048          </style>1049          <style data-href="two1" data-precedence="two">1050            {css}1051          </style>1052          <link rel="stylesheet" href="two2" data-precedence="two" />1053          <style data-href="two3" data-precedence="two">1054            {css}1055          </style>1056          <link rel="stylesheet" href="three1" data-precedence="three" />1057          <style data-href="three2" data-precedence="three">1058            {css}1059          </style>1060          <link rel="stylesheet" href="three3" data-precedence="three" />1061          <style data-href="four1" data-precedence="four">1062            {css}1063          </style>1064          <style data-href="four2" data-precedence="four">1065            {css}1066          </style>1067          <link rel="stylesheet" href="four3" data-precedence="four" />1068          <link rel="stylesheet" href="five1" data-precedence="five" />1069          <style data-href="six1" data-precedence="six">1070            {css}1071          </style>1072        </head>1073        <body>1074          <link rel="preload" href="one2" as="style" />1075          <link rel="preload" href="two2" as="style" />1076          <link rel="preload" href="five1" as="style" />1077          <link rel="preload" href="three3" as="style" />1078          <link rel="preload" href="four3" as="style" />1079        </body>1080      </html>,1081    );10821083    await act(() => {1084      resolveText('block again');1085    });10861087    expect(getMeaningfulChildren(document)).toEqual(1088      <html>1089        <head>1090          <link rel="stylesheet" href="one1" data-precedence="one" />1091          <link rel="stylesheet" href="one2" data-precedence="one" />1092          <style data-href="one3" data-precedence="one">1093            {css}1094          </style>1095          <style data-href="two1" data-precedence="two">1096            {css}1097          </style>1098          <link rel="stylesheet" href="two2" data-precedence="two" />1099          <style data-href="two3" data-precedence="two">1100            {css}1101          </style>1102          <link rel="stylesheet" href="three1" data-precedence="three" />1103          <style data-href="three2" data-precedence="three">1104            {css}1105          </style>1106          <link rel="stylesheet" href="three3" data-precedence="three" />1107          <style data-href="four1" data-precedence="four">1108            {css}1109          </style>1110          <style data-href="four2" data-precedence="four">1111            {css}1112          </style>1113          <link rel="stylesheet" href="four3" data-precedence="four" />1114          <link rel="stylesheet" href="five1" data-precedence="five" />1115          <style data-href="six1" data-precedence="six">1116            {css}1117          </style>1118        </head>1119        <body>1120          <link rel="preload" href="one2" as="style" />1121          <link rel="preload" href="two2" as="style" />1122          <link rel="preload" href="five1" as="style" />1123          <link rel="preload" href="three3" as="style" />1124          <link rel="preload" href="four3" as="style" />1125        </body>1126      </html>,1127    );11281129    ReactDOMClient.hydrateRoot(1130      document,1131      <html>1132        <body>1133          <link rel="stylesheet" href="one4" precedence="one" />1134          <style href="two4" precedence="two">1135            {css}1136          </style>1137          <link rel="stylesheet" href="three4" precedence="three" />1138          <style href="four4" precedence="four">1139            {css}1140          </style>1141          <link rel="stylesheet" href="seven1" precedence="seven" />1142          <style href="eight1" precedence="eight">1143            {css}1144          </style>1145        </body>1146      </html>,1147    );1148    await waitForAll([]);1149    await act(() => {1150      loadPreloads();1151      loadStylesheets();1152    });1153    await assertLog([1154      'load preload: one4',1155      'load preload: three4',1156      'load preload: seven1',1157      'load preload: one2',1158      'load preload: two2',1159      'load preload: five1',1160      'load preload: three3',1161      'load preload: four3',1162      'load stylesheet: one1',1163      'load stylesheet: one2',1164      'load stylesheet: one4',1165      'load stylesheet: two2',1166      'load stylesheet: three1',1167      'load stylesheet: three3',1168      'load stylesheet: three4',1169      'load stylesheet: four3',1170      'load stylesheet: five1',1171      'load stylesheet: seven1',1172    ]);11731174    expect(getMeaningfulChildren(document)).toEqual(1175      <html>1176        <head>1177          <link rel="stylesheet" href="one1" data-precedence="one" />1178          <link rel="stylesheet" href="one2" data-precedence="one" />1179          <style data-href="one3" data-precedence="one">1180            {css}1181          </style>1182          <link rel="stylesheet" href="one4" data-precedence="one" />1183          <style data-href="two1" data-precedence="two">1184            {css}1185          </style>1186          <link rel="stylesheet" href="two2" data-precedence="two" />1187          <style data-href="two3" data-precedence="two">1188            {css}1189          </style>1190          <style data-href="two4" data-precedence="two">1191            {css}1192          </style>1193          <link rel="stylesheet" href="three1" data-precedence="three" />1194          <style data-href="three2" data-precedence="three">1195            {css}1196          </style>1197          <link rel="stylesheet" href="three3" data-precedence="three" />1198          <link rel="stylesheet" href="three4" data-precedence="three" />1199          <style data-href="four1" data-precedence="four">1200            {css}1201          </style>1202          <style data-href="four2" data-precedence="four">1203            {css}1204          </style>1205          <link rel="stylesheet" href="four3" data-precedence="four" />1206          <style data-href="four4" data-precedence="four">1207            {css}1208          </style>1209          <link rel="stylesheet" href="five1" data-precedence="five" />1210          <style data-href="six1" data-precedence="six">1211            {css}1212          </style>1213          <link rel="stylesheet" href="seven1" data-precedence="seven" />1214          <style data-href="eight1" data-precedence="eight">1215            {css}1216          </style>1217          <link rel="preload" href="one4" as="style" />1218          <link rel="preload" href="three4" as="style" />1219          <link rel="preload" href="seven1" as="style" />1220        </head>1221        <body>1222          <link rel="preload" href="one2" as="style" />1223          <link rel="preload" href="two2" as="style" />1224          <link rel="preload" href="five1" as="style" />1225          <link rel="preload" href="three3" as="style" />1226          <link rel="preload" href="four3" as="style" />1227        </body>1228      </html>,1229    );1230  });12311232  it('client renders a boundary if a style Resource dependency fails to load', async () => {1233    function App() {1234      return (1235        <html>1236          <head />1237          <body>1238            <Suspense fallback="loading...">1239              <BlockedOn value="unblock">1240                <link rel="stylesheet" href="foo" precedence="arbitrary" />1241                <link rel="stylesheet" href="bar" precedence="arbitrary" />1242                Hello1243              </BlockedOn>1244            </Suspense>1245          </body>1246        </html>1247      );1248    }1249    await act(() => {1250      const {pipe} = renderToPipeableStream(<App />);1251      pipe(writable);1252    });12531254    expect(getMeaningfulChildren(document)).toEqual(1255      <html>1256        <head />1257        <body>loading...</body>1258      </html>,1259    );12601261    await act(() => {1262      resolveText('unblock');1263    });12641265    expect(getMeaningfulChildren(document)).toEqual(1266      <html>1267        <head>1268          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1269          <link rel="stylesheet" href="bar" data-precedence="arbitrary" />1270        </head>1271        <body>1272          loading...1273          <link rel="preload" href="foo" as="style" />1274          <link rel="preload" href="bar" as="style" />1275        </body>1276      </html>,1277    );12781279    errorStylesheets(['bar']);1280    assertLog(['error stylesheet: bar']);12811282    await waitForAll([]);12831284    const boundaryTemplateInstance = document.getElementById('B:0');1285    const suspenseInstance = boundaryTemplateInstance.previousSibling;12861287    expect(suspenseInstance.data).toEqual('$!');1288    expect(boundaryTemplateInstance.dataset.dgst).toBe('CSS failed to load');12891290    expect(getMeaningfulChildren(document)).toEqual(1291      <html>1292        <head>1293          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1294          <link rel="stylesheet" href="bar" data-precedence="arbitrary" />1295        </head>1296        <body>1297          loading...1298          <link rel="preload" href="foo" as="style" />1299          <link rel="preload" href="bar" as="style" />1300        </body>1301      </html>,1302    );13031304    const errors = [];1305    ReactDOMClient.hydrateRoot(document, <App />, {1306      onRecoverableError(err, errInfo) {1307        errors.push(err.message);1308        errors.push(err.digest);1309      },1310    });1311    await waitForAll([]);1312    // When binding a stylesheet that was SSR'd in a boundary reveal there is a loadingState promise1313    // We need to use that promise to resolve the suspended commit because we don't know if the load or error1314    // events have already fired. This requires the load to be awaited for the commit to have a chance to flush1315    // We could change this by tracking the loadingState's fulfilled status directly on the loadingState similar1316    // to thenables however this slightly increases the fizz runtime code size.1317    await clientAct(() => loadStylesheets());1318    assertLog(['load stylesheet: foo']);1319    expect(getMeaningfulChildren(document)).toEqual(1320      <html>1321        <head>1322          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1323          <link rel="stylesheet" href="bar" data-precedence="arbitrary" />1324        </head>1325        <body>1326          <link rel="preload" href="foo" as="style" />1327          <link rel="preload" href="bar" as="style" />1328          Hello1329        </body>1330      </html>,1331    );1332    expect(errors).toEqual([1333      'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',1334      'CSS failed to load',1335    ]);1336  });13371338  it('treats stylesheet links with a precedence as a resource', async () => {1339    await act(() => {1340      const {pipe} = renderToPipeableStream(1341        <html>1342          <head />1343          <body>1344            <link rel="stylesheet" href="foo" precedence="arbitrary" />1345            Hello1346          </body>1347        </html>,1348      );1349      pipe(writable);1350    });1351    expect(getMeaningfulChildren(document)).toEqual(1352      <html>1353        <head>1354          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1355        </head>1356        <body>Hello</body>1357      </html>,1358    );13591360    ReactDOMClient.hydrateRoot(1361      document,1362      <html>1363        <head />1364        <body>Hello</body>1365      </html>,1366    );1367    await waitForAll([]);1368    expect(getMeaningfulChildren(document)).toEqual(1369      <html>1370        <head>1371          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1372        </head>1373        <body>Hello</body>1374      </html>,1375    );1376  });13771378  it('inserts text separators following text when followed by an element that is converted to a resource and thus removed from the html inline', async () => {1379    // If you render many of these as siblings the values get emitted as a single text with no separator sometimes1380    // because the link gets elided as a resource1381    function AsyncTextWithResource({text, href, precedence}) {1382      const value = readText(text);1383      return (1384        <>1385          {value}1386          <link rel="stylesheet" href={href} precedence={precedence} />1387        </>1388      );1389    }13901391    await act(() => {1392      const {pipe} = renderToPipeableStream(1393        <html>1394          <head />1395          <body>1396            <AsyncTextWithResource text="foo" href="foo" precedence="one" />1397            <AsyncTextWithResource text="bar" href="bar" precedence="two" />1398            <AsyncTextWithResource text="baz" href="baz" precedence="three" />1399          </body>1400        </html>,1401      );1402      pipe(writable);1403      resolveText('foo');1404      resolveText('bar');1405      resolveText('baz');1406    });14071408    expect(getMeaningfulChildren(document)).toEqual(1409      <html>1410        <head>1411          <link rel="stylesheet" href="foo" data-precedence="one" />1412          <link rel="stylesheet" href="bar" data-precedence="two" />1413          <link rel="stylesheet" href="baz" data-precedence="three" />1414        </head>1415        <body>1416          {'foo'}1417          {'bar'}1418          {'baz'}1419        </body>1420      </html>,1421    );1422  });14231424  it('hoists late stylesheets the correct precedence', async () => {1425    function PresetPrecedence() {1426      ReactDOM.preinit('preset', {as: 'style', precedence: 'preset'});1427    }1428    await act(() => {1429      const {pipe} = renderToPipeableStream(1430        <html>1431          <head />1432          <body>1433            <link rel="stylesheet" href="initial" precedence="one" />1434            <PresetPrecedence />1435            <div>1436              <Suspense fallback="loading foo bar...">1437                <div>foo</div>1438                <link rel="stylesheet" href="foo" precedence="one" />1439                <BlockedOn value="bar">1440                  <div>bar</div>1441                  <link rel="stylesheet" href="bar" precedence="default" />1442                </BlockedOn>1443              </Suspense>1444            </div>1445            <div>1446              <Suspense fallback="loading bar baz qux...">1447                <BlockedOn value="bar">1448                  <div>bar</div>1449                  <link rel="stylesheet" href="bar" precedence="default" />1450                </BlockedOn>1451                <BlockedOn value="baz">1452                  <div>baz</div>1453                  <link rel="stylesheet" href="baz" precedence="two" />1454                </BlockedOn>1455                <BlockedOn value="qux">1456                  <div>qux</div>1457                  <link rel="stylesheet" href="qux" precedence="one" />1458                </BlockedOn>1459              </Suspense>1460            </div>1461            <div>1462              <Suspense fallback="loading bar baz qux...">1463                <BlockedOn value="unblock">1464                  <BlockedOn value="bar">1465                    <div>bar</div>1466                    <link rel="stylesheet" href="bar" precedence="default" />1467                  </BlockedOn>1468                  <BlockedOn value="baz">1469                    <div>baz</div>1470                    <link rel="stylesheet" href="baz" precedence="two" />1471                  </BlockedOn>1472                  <BlockedOn value="qux">1473                    <div>qux</div>1474                    <link rel="stylesheet" href="qux" precedence="one" />1475                  </BlockedOn>1476                </BlockedOn>1477              </Suspense>1478            </div>1479          </body>1480        </html>,1481      );1482      pipe(writable);1483    });14841485    expect(getMeaningfulChildren(document)).toEqual(1486      <html>1487        <head>1488          <link rel="stylesheet" href="initial" data-precedence="one" />1489          <link rel="stylesheet" href="foo" data-precedence="one" />1490          <link rel="stylesheet" href="preset" data-precedence="preset" />1491        </head>1492        <body>1493          <div>loading foo bar...</div>1494          <div>loading bar baz qux...</div>1495          <div>loading bar baz qux...</div>1496        </body>1497      </html>,1498    );14991500    await act(() => {1501      resolveText('foo');1502      resolveText('bar');1503    });15041505    expect(getMeaningfulChildren(document)).toEqual(1506      <html>1507        <head>1508          <link rel="stylesheet" href="initial" data-precedence="one" />1509          <link rel="stylesheet" href="foo" data-precedence="one" />1510          <link rel="stylesheet" href="preset" data-precedence="preset" />1511          <link rel="stylesheet" href="bar" data-precedence="default" />1512        </head>1513        <body>1514          <div>loading foo bar...</div>1515          <div>loading bar baz qux...</div>1516          <div>loading bar baz qux...</div>1517          <link rel="preload" href="bar" as="style" />1518        </body>1519      </html>,1520    );15211522    await act(() => {1523      const link = document.querySelector('link[rel="stylesheet"][href="foo"]');1524      const event = document.createEvent('Events');1525      event.initEvent('load', true, true);1526      link.dispatchEvent(event);1527    });15281529    expect(getMeaningfulChildren(document)).toEqual(1530      <html>1531        <head>1532          <link rel="stylesheet" href="initial" data-precedence="one" />1533          <link rel="stylesheet" href="foo" data-precedence="one" />1534          <link rel="stylesheet" href="preset" data-precedence="preset" />1535          <link rel="stylesheet" href="bar" data-precedence="default" />1536        </head>1537        <body>1538          <div>loading foo bar...</div>1539          <div>loading bar baz qux...</div>1540          <div>loading bar baz qux...</div>1541          <link rel="preload" href="bar" as="style" />1542        </body>1543      </html>,1544    );15451546    await act(() => {1547      const link = document.querySelector('link[rel="stylesheet"][href="bar"]');1548      const event = document.createEvent('Events');1549      event.initEvent('load', true, true);1550      link.dispatchEvent(event);1551    });15521553    expect(getMeaningfulChildren(document)).toEqual(1554      <html>1555        <head>1556          <link rel="stylesheet" href="initial" data-precedence="one" />1557          <link rel="stylesheet" href="foo" data-precedence="one" />1558          <link rel="stylesheet" href="preset" data-precedence="preset" />1559          <link rel="stylesheet" href="bar" data-precedence="default" />1560        </head>1561        <body>1562          <div>1563            <div>foo</div>1564            <div>bar</div>1565          </div>1566          <div>loading bar baz qux...</div>1567          <div>loading bar baz qux...</div>1568          <link rel="preload" href="bar" as="style" />1569        </body>1570      </html>,1571    );15721573    await act(() => {1574      resolveText('baz');1575    });15761577    expect(getMeaningfulChildren(document)).toEqual(1578      <html>1579        <head>1580          <link rel="stylesheet" href="initial" data-precedence="one" />1581          <link rel="stylesheet" href="foo" data-precedence="one" />1582          <link rel="stylesheet" href="preset" data-precedence="preset" />1583          <link rel="stylesheet" href="bar" data-precedence="default" />1584        </head>1585        <body>1586          <div>1587            <div>foo</div>1588            <div>bar</div>1589          </div>1590          <div>loading bar baz qux...</div>1591          <div>loading bar baz qux...</div>1592          <link rel="preload" as="style" href="bar" />1593          <link rel="preload" as="style" href="baz" />1594        </body>1595      </html>,1596    );15971598    await act(() => {1599      resolveText('qux');1600    });16011602    expect(getMeaningfulChildren(document)).toEqual(1603      <html>1604        <head>1605          <link rel="stylesheet" href="initial" data-precedence="one" />1606          <link rel="stylesheet" href="foo" data-precedence="one" />1607          <link rel="stylesheet" href="qux" data-precedence="one" />1608          <link rel="stylesheet" href="preset" data-precedence="preset" />1609          <link rel="stylesheet" href="bar" data-precedence="default" />1610          <link rel="stylesheet" href="baz" data-precedence="two" />1611        </head>1612        <body>1613          <div>1614            <div>foo</div>1615            <div>bar</div>1616          </div>1617          <div>loading bar baz qux...</div>1618          <div>loading bar baz qux...</div>1619          <link rel="preload" as="style" href="bar" />1620          <link rel="preload" as="style" href="baz" />1621          <link rel="preload" as="style" href="qux" />1622        </body>1623      </html>,1624    );16251626    await act(() => {1627      const bazlink = document.querySelector(1628        'link[rel="stylesheet"][href="baz"]',1629      );1630      const quxlink = document.querySelector(1631        'link[rel="stylesheet"][href="qux"]',1632      );1633      const presetLink = document.querySelector(1634        'link[rel="stylesheet"][href="preset"]',1635      );1636      const event = document.createEvent('Events');1637      event.initEvent('load', true, true);1638      bazlink.dispatchEvent(event);1639      quxlink.dispatchEvent(event);1640      presetLink.dispatchEvent(event);1641    });16421643    expect(getMeaningfulChildren(document)).toEqual(1644      <html>1645        <head>1646          <link rel="stylesheet" href="initial" data-precedence="one" />1647          <link rel="stylesheet" href="foo" data-precedence="one" />1648          <link rel="stylesheet" href="qux" data-precedence="one" />1649          <link rel="stylesheet" href="preset" data-precedence="preset" />1650          <link rel="stylesheet" href="bar" data-precedence="default" />1651          <link rel="stylesheet" href="baz" data-precedence="two" />1652        </head>1653        <body>1654          <div>1655            <div>foo</div>1656            <div>bar</div>1657          </div>1658          <div>1659            <div>bar</div>1660            <div>baz</div>1661            <div>qux</div>1662          </div>1663          <div>loading bar baz qux...</div>1664          <link rel="preload" as="style" href="bar" />1665          <link rel="preload" as="style" href="baz" />1666          <link rel="preload" as="style" href="qux" />1667        </body>1668      </html>,1669    );16701671    await act(() => {1672      resolveText('unblock');1673    });16741675    expect(getMeaningfulChildren(document)).toEqual(1676      <html>1677        <head>1678          <link rel="stylesheet" href="initial" data-precedence="one" />1679          <link rel="stylesheet" href="foo" data-precedence="one" />1680          <link rel="stylesheet" href="qux" data-precedence="one" />1681          <link rel="stylesheet" href="preset" data-precedence="preset" />1682          <link rel="stylesheet" href="bar" data-precedence="default" />1683          <link rel="stylesheet" href="baz" data-precedence="two" />1684        </head>1685        <body>1686          <div>1687            <div>foo</div>1688            <div>bar</div>1689          </div>1690          <div>1691            <div>bar</div>1692            <div>baz</div>1693            <div>qux</div>1694          </div>1695          <div>1696            <div>bar</div>1697            <div>baz</div>1698            <div>qux</div>1699          </div>1700          <link rel="preload" as="style" href="bar" />1701          <link rel="preload" as="style" href="baz" />1702          <link rel="preload" as="style" href="qux" />1703        </body>1704      </html>,1705    );1706  });17071708  it('normalizes stylesheet resource precedence for all boundaries inlined as part of the shell flush', async () => {1709    await act(() => {1710      const {pipe} = renderToPipeableStream(1711        <html>1712          <head />1713          <body>1714            <div>1715              outer1716              <link rel="stylesheet" href="1one" precedence="one" />1717              <link rel="stylesheet" href="1two" precedence="two" />1718              <link rel="stylesheet" href="1three" precedence="three" />1719              <link rel="stylesheet" href="1four" precedence="four" />1720              <Suspense fallback={null}>1721                <div>1722                  middle1723                  <link rel="stylesheet" href="2one" precedence="one" />1724                  <link rel="stylesheet" href="2two" precedence="two" />1725                  <link rel="stylesheet" href="2three" precedence="three" />1726                  <link rel="stylesheet" href="2four" precedence="four" />1727                  <Suspense fallback={null}>1728                    <div>1729                      inner1730                      <link rel="stylesheet" href="3five" precedence="five" />1731                      <link rel="stylesheet" href="3one" precedence="one" />1732                      <link rel="stylesheet" href="3two" precedence="two" />1733                      <link rel="stylesheet" href="3three" precedence="three" />1734                      <link rel="stylesheet" href="3four" precedence="four" />1735                    </div>1736                  </Suspense>1737                </div>1738              </Suspense>1739              <Suspense fallback={null}>1740                <div>middle</div>1741                <link rel="stylesheet" href="4one" precedence="one" />1742                <link rel="stylesheet" href="4two" precedence="two" />1743                <link rel="stylesheet" href="4three" precedence="three" />1744                <link rel="stylesheet" href="4four" precedence="four" />1745              </Suspense>1746            </div>1747          </body>1748        </html>,1749      );1750      pipe(writable);1751    });17521753    expect(getMeaningfulChildren(document)).toEqual(1754      <html>1755        <head>1756          <link rel="stylesheet" href="1one" data-precedence="one" />1757          <link rel="stylesheet" href="2one" data-precedence="one" />1758          <link rel="stylesheet" href="3one" data-precedence="one" />1759          <link rel="stylesheet" href="4one" data-precedence="one" />17601761          <link rel="stylesheet" href="1two" data-precedence="two" />1762          <link rel="stylesheet" href="2two" data-precedence="two" />1763          <link rel="stylesheet" href="3two" data-precedence="two" />1764          <link rel="stylesheet" href="4two" data-precedence="two" />17651766          <link rel="stylesheet" href="1three" data-precedence="three" />1767          <link rel="stylesheet" href="2three" data-precedence="three" />1768          <link rel="stylesheet" href="3three" data-precedence="three" />1769          <link rel="stylesheet" href="4three" data-precedence="three" />17701771          <link rel="stylesheet" href="1four" data-precedence="four" />1772          <link rel="stylesheet" href="2four" data-precedence="four" />1773          <link rel="stylesheet" href="3four" data-precedence="four" />1774          <link rel="stylesheet" href="4four" data-precedence="four" />17751776          <link rel="stylesheet" href="3five" data-precedence="five" />1777        </head>1778        <body>1779          <div>1780            outer1781            <div>1782              middle<div>inner</div>1783            </div>1784            <div>middle</div>1785          </div>1786        </body>1787      </html>,1788    );1789  });17901791  it('stylesheet resources are inserted according to precedence order on the client', async () => {1792    await act(() => {1793      const {pipe} = renderToPipeableStream(1794        <html>1795          <head />1796          <body>1797            <div>1798              <link rel="stylesheet" href="foo" precedence="one" />1799              <link rel="stylesheet" href="bar" precedence="two" />1800              Hello1801            </div>1802          </body>1803        </html>,1804      );1805      pipe(writable);1806    });18071808    expect(getMeaningfulChildren(document)).toEqual(1809      <html>1810        <head>1811          <link rel="stylesheet" href="foo" data-precedence="one" />1812          <link rel="stylesheet" href="bar" data-precedence="two" />1813        </head>1814        <body>1815          <div>Hello</div>1816        </body>1817      </html>,1818    );18191820    const root = ReactDOMClient.hydrateRoot(1821      document,1822      <html>1823        <head />1824        <body>1825          <div>1826            <link rel="stylesheet" href="foo" precedence="one" />1827            <link rel="stylesheet" href="bar" precedence="two" />1828            Hello1829          </div>1830        </body>1831      </html>,1832    );1833    await waitForAll([]);1834    expect(getMeaningfulChildren(document)).toEqual(1835      <html>1836        <head>1837          <link rel="stylesheet" href="foo" data-precedence="one" />1838          <link rel="stylesheet" href="bar" data-precedence="two" />1839        </head>1840        <body>1841          <div>Hello</div>1842        </body>1843      </html>,1844    );18451846    root.render(1847      <html>1848        <head />1849        <body>1850          <div>Goodbye</div>1851          <link rel="stylesheet" href="baz" precedence="one" />1852        </body>1853      </html>,1854    );1855    await waitForAll([]);1856    await act(() => {1857      loadPreloads();1858      loadStylesheets();1859    });1860    await assertLog([1861      'load preload: baz',1862      'load stylesheet: foo',1863      'load stylesheet: baz',1864      'load stylesheet: bar',1865    ]);1866    expect(getMeaningfulChildren(document)).toEqual(1867      <html>1868        <head>1869          <link rel="stylesheet" href="foo" data-precedence="one" />1870          <link rel="stylesheet" href="baz" data-precedence="one" />1871          <link rel="stylesheet" href="bar" data-precedence="two" />1872          <link rel="preload" as="style" href="baz" />1873        </head>1874        <body>1875          <div>Goodbye</div>1876        </body>1877      </html>,1878    );1879  });18801881  it('inserts preloads in render phase eagerly', async () => {1882    function Throw() {1883      throw new Error('Uh oh!');1884    }1885    class ErrorBoundary extends React.Component {1886      state = {hasError: false, error: null};1887      static getDerivedStateFromError(error) {1888        return {1889          hasError: true,1890          error,1891        };1892      }1893      render() {1894        if (this.state.hasError) {1895          return this.state.error.message;1896        }1897        return this.props.children;1898      }1899    }19001901    const root = ReactDOMClient.createRoot(container);1902    root.render(1903      <ErrorBoundary>1904        <link rel="stylesheet" href="foo" precedence="default" />1905        <div>foo</div>1906        <Throw />1907      </ErrorBoundary>,1908    );1909    await waitForAll([]);1910    expect(getMeaningfulChildren(document)).toEqual(1911      <html>1912        <head>1913          <link rel="preload" href="foo" as="style" />1914        </head>1915        <body>1916          <div id="container">Uh oh!</div>1917        </body>1918      </html>,1919    );1920  });19211922  it('will include child boundary stylesheet resources in the boundary reveal instruction', async () => {1923    await act(() => {1924      const {pipe} = renderToPipeableStream(1925        <html>1926          <head />1927          <body>1928            <div>1929              <Suspense fallback="loading foo...">1930                <BlockedOn value="foo">1931                  <div>foo</div>1932                  <link rel="stylesheet" href="foo" precedence="default" />1933                  <Suspense fallback="loading bar...">1934                    <BlockedOn value="bar">1935                      <div>bar</div>1936                      <link rel="stylesheet" href="bar" precedence="default" />1937                      <Suspense fallback="loading baz...">1938                        <BlockedOn value="baz">1939                          <div>baz</div>1940                          <link1941                            rel="stylesheet"1942                            href="baz"1943                            precedence="default"1944                          />1945                        </BlockedOn>1946                      </Suspense>1947                    </BlockedOn>1948                  </Suspense>1949                </BlockedOn>1950              </Suspense>1951            </div>1952          </body>1953        </html>,1954      );1955      pipe(writable);1956    });19571958    expect(getMeaningfulChildren(document)).toEqual(1959      <html>1960        <head />1961        <body>1962          <div>loading foo...</div>1963        </body>1964      </html>,1965    );19661967    await act(() => {1968      resolveText('bar');1969    });1970    expect(getMeaningfulChildren(document)).toEqual(1971      <html>1972        <head />1973        <body>1974          <div>loading foo...</div>1975        </body>1976      </html>,1977    );19781979    await act(() => {1980      resolveText('baz');1981    });1982    expect(getMeaningfulChildren(document)).toEqual(1983      <html>1984        <head />1985        <body>1986          <div>loading foo...</div>1987        </body>1988      </html>,1989    );19901991    await act(() => {1992      resolveText('foo');1993    });1994    expect(getMeaningfulChildren(document)).toEqual(1995      <html>1996        <head>1997          <link rel="stylesheet" href="foo" data-precedence="default" />1998          <link rel="stylesheet" href="bar" data-precedence="default" />1999          <link rel="stylesheet" href="baz" data-precedence="default" />2000        </head>

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.