packages/react-dom/src/__tests__/ReactDOMFloat-test.js JAVASCRIPT 10,357 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 10,357.
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      'Encountered a script tag while rendering React component. ' +606        'Scripts inside React components are never executed when rendering on the client. ' +607        'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +608        '     in script (at **)',609    ]);610611    root.render(612      <html>613        <script async={true} onLoad={() => {}} href="bar" />614        <body>foo</body>615      </html>,616    );617    await waitForAll([]);618    assertConsoleErrorDev([619      'Cannot render a <script> with onLoad or onError listeners outside the main document. ' +620        'Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or ' +621        'somewhere in the <body>.\n' +622        '    in html (at **)',623    ]);624625    root.render(626      <>627        <link rel="foo" onLoad={() => {}} href="bar" />628        <html>629          <body>foo</body>630        </html>631      </>,632    );633    await waitForAll([]);634    assertConsoleErrorDev([635      'Cannot render a <link> with onLoad or onError listeners outside the main document. ' +636        'Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or ' +637        'somewhere in the <body>.',638    ]);639    return;640  });641642  it('can acquire a resource after releasing it in the same commit', async () => {643    const root = ReactDOMClient.createRoot(container);644    root.render(645      <>646        <script async={true} src="foo" />647      </>,648    );649    await waitForAll([]);650    expect(getMeaningfulChildren(document)).toEqual(651      <html>652        <head>653          <script async="" src="foo" />654        </head>655        <body>656          <div id="container" />657        </body>658      </html>,659    );660661    root.render(662      <>663        {null}664        <script data-new="new" async={true} src="foo" />665      </>,666    );667    await waitForAll([]);668    // we don't see the attribute because the resource is the same and was not reconstructed669    expect(getMeaningfulChildren(document)).toEqual(670      <html>671        <head>672          <script async="" src="foo" />673        </head>674        <body>675          <div id="container" />676        </body>677      </html>,678    );679  });680681  it('emits an implicit <head> element to hold resources when none is rendered but an <html> is rendered', async () => {682    const chunks = [];683684    writable.on('data', chunk => {685      chunks.push(chunk);686    });687688    await act(() => {689      const {pipe} = renderToPipeableStream(690        <>691          <title>foo</title>692          <html>693            <body>bar</body>694          </html>695          <script async={true} src="foo" />696        </>,697      );698      pipe(writable);699    });700    expect(chunks).toEqual([701      '<!DOCTYPE html><html><head><script async="" src="foo"></script>' +702        (gate(flags => flags.shouldUseFizzExternalRuntime)703          ? '<script src="react-dom/unstable_server-external-runtime" async=""></script>'704          : '') +705        (gate(flags => flags.enableFizzBlockingRender)706          ? '<link rel="expect" href="#_R_" blocking="render"/>'707          : '') +708        '<title>foo</title></head>' +709        '<body>bar' +710        (gate(flags => flags.enableFizzBlockingRender)711          ? '<template id="_R_"></template>'712          : ''),713      '</body></html>',714    ]);715  });716717  it('dedupes if the external runtime is explicitly loaded using preinit', async () => {718    const unstable_externalRuntimeSrc = 'src-of-external-runtime';719    function App() {720      ReactDOM.preinit(unstable_externalRuntimeSrc, {as: 'script'});721      return (722        <div>723          <Suspense fallback={<h1>Loading...</h1>}>724            <AsyncText text="Hello" />725          </Suspense>726        </div>727      );728    }729730    await act(() => {731      const {pipe} = renderToPipeableStream(732        <html>733          <head />734          <body>735            <App />736          </body>737        </html>,738        {739          unstable_externalRuntimeSrc,740        },741      );742      pipe(writable);743    });744745    expect(746      Array.from(document.querySelectorAll('script[async]')).map(747        n => n.outerHTML,748      ),749    ).toEqual(['<script src="src-of-external-runtime" async=""></script>']);750  });751752  it('can send style insertion implementation independent of boundary commpletion instruction implementation', async () => {753    await act(() => {754      renderToPipeableStream(755        <html>756          <body>757            <Suspense fallback="loading foo...">758              <BlockedOn value="foo">foo</BlockedOn>759            </Suspense>760            <Suspense fallback="loading bar...">761              <BlockedOn value="bar">762                <link rel="stylesheet" href="bar" precedence="bar" />763                bar764              </BlockedOn>765            </Suspense>766          </body>767        </html>,768      ).pipe(writable);769    });770771    expect(getMeaningfulChildren(document)).toEqual(772      <html>773        <head />774        <body>775          {'loading foo...'}776          {'loading bar...'}777        </body>778      </html>,779    );780781    await act(() => {782      resolveText('foo');783    });784    expect(getMeaningfulChildren(document)).toEqual(785      <html>786        <head />787        <body>788          foo789          {'loading bar...'}790        </body>791      </html>,792    );793    await act(() => {794      resolveText('bar');795    });796    expect(getMeaningfulChildren(document)).toEqual(797      <html>798        <head>799          <link rel="stylesheet" href="bar" data-precedence="bar" />800        </head>801        <body>802          foo803          {'loading bar...'}804          <link rel="preload" href="bar" as="style" />805        </body>806      </html>,807    );808  });809810  it('can avoid inserting a late stylesheet if it already rendered on the client', async () => {811    await act(() => {812      renderToPipeableStream(813        <html>814          <body>815            <Suspense fallback="loading foo...">816              <BlockedOn value="foo">817                <link rel="stylesheet" href="foo" precedence="foo" />818                foo819              </BlockedOn>820            </Suspense>821            <Suspense fallback="loading bar...">822              <BlockedOn value="bar">823                <link rel="stylesheet" href="bar" precedence="bar" />824                bar825              </BlockedOn>826            </Suspense>827          </body>828        </html>,829      ).pipe(writable);830    });831832    expect(getMeaningfulChildren(document)).toEqual(833      <html>834        <head />835        <body>836          {'loading foo...'}837          {'loading bar...'}838        </body>839      </html>,840    );841842    ReactDOMClient.hydrateRoot(843      document,844      <html>845        <body>846          <link rel="stylesheet" href="foo" precedence="foo" />847          <Suspense fallback="loading foo...">848            <link rel="stylesheet" href="foo" precedence="foo" />849            foo850          </Suspense>851          <Suspense fallback="loading bar...">852            <link rel="stylesheet" href="bar" precedence="bar" />853            bar854          </Suspense>855        </body>856      </html>,857    );858    await waitForAll([]);859    loadPreloads();860    await assertLog(['load preload: foo']);861    expect(getMeaningfulChildren(document)).toEqual(862      <html>863        <head>864          <link rel="stylesheet" href="foo" data-precedence="foo" />865          <link as="style" href="foo" rel="preload" />866        </head>867        <body>868          {'loading foo...'}869          {'loading bar...'}870        </body>871      </html>,872    );873874    await act(() => {875      resolveText('bar');876    });877    await act(() => {878      loadStylesheets();879    });880    await assertLog(['load stylesheet: foo', 'load stylesheet: bar']);881    expect(getMeaningfulChildren(document)).toEqual(882      <html>883        <head>884          <link rel="stylesheet" href="foo" data-precedence="foo" />885          <link rel="stylesheet" href="bar" data-precedence="bar" />886          <link as="style" href="foo" rel="preload" />887        </head>888        <body>889          {'loading foo...'}890          {'bar'}891          <link as="style" href="bar" rel="preload" />892        </body>893      </html>,894    );895896    await act(() => {897      resolveText('foo');898    });899    await act(() => {900      loadStylesheets();901    });902    await assertLog([]);903    expect(getMeaningfulChildren(document)).toEqual(904      <html>905        <head>906          <link rel="stylesheet" href="foo" data-precedence="foo" />907          <link rel="stylesheet" href="bar" data-precedence="bar" />908          <link as="style" href="foo" rel="preload" />909        </head>910        <body>911          {'foo'}912          {'bar'}913          <link as="style" href="bar" rel="preload" />914          <link as="style" href="foo" rel="preload" />915        </body>916      </html>,917    );918  });919920  it('can hoist <link rel="stylesheet" .../> and <style /> tags together, respecting order of discovery', async () => {921    const css = `922body {923  background-color: red;924}`;925926    await act(() => {927      renderToPipeableStream(928        <html>929          <body>930            <link rel="stylesheet" href="one1" precedence="one" />931            <style href="two1" precedence="two">932              {css}933            </style>934            <link rel="stylesheet" href="three1" precedence="three" />935            <style href="four1" precedence="four">936              {css}937            </style>938            <Suspense>939              <BlockedOn value="block">940                <link rel="stylesheet" href="one2" precedence="one" />941                <link rel="stylesheet" href="two2" precedence="two" />942                <style href="three2" precedence="three">943                  {css}944                </style>945                <style href="four2" precedence="four">946                  {css}947                </style>948                <link rel="stylesheet" href="five1" precedence="five" />949              </BlockedOn>950            </Suspense>951            <Suspense>952              <BlockedOn value="block2">953                <style href="one3" precedence="one">954                  {css}955                </style>956                <style href="two3" precedence="two">957                  {css}958                </style>959                <link rel="stylesheet" href="three3" precedence="three" />960                <link rel="stylesheet" href="four3" precedence="four" />961                <style href="six1" precedence="six">962                  {css}963                </style>964              </BlockedOn>965            </Suspense>966            <Suspense>967              <BlockedOn value="block again">968                <link rel="stylesheet" href="one2" precedence="one" />969                <link rel="stylesheet" href="two2" precedence="two" />970                <style href="three2" precedence="three">971                  {css}972                </style>973                <style href="four2" precedence="four">974                  {css}975                </style>976                <link rel="stylesheet" href="five1" precedence="five" />977              </BlockedOn>978            </Suspense>979          </body>980        </html>,981      ).pipe(writable);982    });983984    expect(getMeaningfulChildren(document)).toEqual(985      <html>986        <head>987          <link rel="stylesheet" href="one1" data-precedence="one" />988          <style data-href="two1" data-precedence="two">989            {css}990          </style>991          <link rel="stylesheet" href="three1" data-precedence="three" />992          <style data-href="four1" data-precedence="four">993            {css}994          </style>995        </head>996        <body />997      </html>,998    );9991000    await act(() => {1001      resolveText('block');1002    });10031004    expect(getMeaningfulChildren(document)).toEqual(1005      <html>1006        <head>1007          <link rel="stylesheet" href="one1" data-precedence="one" />1008          <link rel="stylesheet" href="one2" data-precedence="one" />1009          <style data-href="two1" data-precedence="two">1010            {css}1011          </style>1012          <link rel="stylesheet" href="two2" data-precedence="two" />1013          <link rel="stylesheet" href="three1" data-precedence="three" />1014          <style data-href="three2" data-precedence="three">1015            {css}1016          </style>1017          <style data-href="four1" data-precedence="four">1018            {css}1019          </style>1020          <style data-href="four2" data-precedence="four">1021            {css}1022          </style>1023          <link rel="stylesheet" href="five1" data-precedence="five" />1024        </head>1025        <body>1026          <link rel="preload" href="one2" as="style" />1027          <link rel="preload" href="two2" as="style" />1028          <link rel="preload" href="five1" as="style" />1029        </body>1030      </html>,1031    );10321033    await act(() => {1034      resolveText('block2');1035    });10361037    expect(getMeaningfulChildren(document)).toEqual(1038      <html>1039        <head>1040          <link rel="stylesheet" href="one1" data-precedence="one" />1041          <link rel="stylesheet" href="one2" data-precedence="one" />1042          <style data-href="one3" data-precedence="one">1043            {css}1044          </style>1045          <style data-href="two1" data-precedence="two">1046            {css}1047          </style>1048          <link rel="stylesheet" href="two2" data-precedence="two" />1049          <style data-href="two3" data-precedence="two">1050            {css}1051          </style>1052          <link rel="stylesheet" href="three1" data-precedence="three" />1053          <style data-href="three2" data-precedence="three">1054            {css}1055          </style>1056          <link rel="stylesheet" href="three3" data-precedence="three" />1057          <style data-href="four1" data-precedence="four">1058            {css}1059          </style>1060          <style data-href="four2" data-precedence="four">1061            {css}1062          </style>1063          <link rel="stylesheet" href="four3" data-precedence="four" />1064          <link rel="stylesheet" href="five1" data-precedence="five" />1065          <style data-href="six1" data-precedence="six">1066            {css}1067          </style>1068        </head>1069        <body>1070          <link rel="preload" href="one2" as="style" />1071          <link rel="preload" href="two2" as="style" />1072          <link rel="preload" href="five1" as="style" />1073          <link rel="preload" href="three3" as="style" />1074          <link rel="preload" href="four3" as="style" />1075        </body>1076      </html>,1077    );10781079    await act(() => {1080      resolveText('block again');1081    });10821083    expect(getMeaningfulChildren(document)).toEqual(1084      <html>1085        <head>1086          <link rel="stylesheet" href="one1" data-precedence="one" />1087          <link rel="stylesheet" href="one2" data-precedence="one" />1088          <style data-href="one3" data-precedence="one">1089            {css}1090          </style>1091          <style data-href="two1" data-precedence="two">1092            {css}1093          </style>1094          <link rel="stylesheet" href="two2" data-precedence="two" />1095          <style data-href="two3" data-precedence="two">1096            {css}1097          </style>1098          <link rel="stylesheet" href="three1" data-precedence="three" />1099          <style data-href="three2" data-precedence="three">1100            {css}1101          </style>1102          <link rel="stylesheet" href="three3" data-precedence="three" />1103          <style data-href="four1" data-precedence="four">1104            {css}1105          </style>1106          <style data-href="four2" data-precedence="four">1107            {css}1108          </style>1109          <link rel="stylesheet" href="four3" data-precedence="four" />1110          <link rel="stylesheet" href="five1" data-precedence="five" />1111          <style data-href="six1" data-precedence="six">1112            {css}1113          </style>1114        </head>1115        <body>1116          <link rel="preload" href="one2" as="style" />1117          <link rel="preload" href="two2" as="style" />1118          <link rel="preload" href="five1" as="style" />1119          <link rel="preload" href="three3" as="style" />1120          <link rel="preload" href="four3" as="style" />1121        </body>1122      </html>,1123    );11241125    ReactDOMClient.hydrateRoot(1126      document,1127      <html>1128        <body>1129          <link rel="stylesheet" href="one4" precedence="one" />1130          <style href="two4" precedence="two">1131            {css}1132          </style>1133          <link rel="stylesheet" href="three4" precedence="three" />1134          <style href="four4" precedence="four">1135            {css}1136          </style>1137          <link rel="stylesheet" href="seven1" precedence="seven" />1138          <style href="eight1" precedence="eight">1139            {css}1140          </style>1141        </body>1142      </html>,1143    );1144    await waitForAll([]);1145    await act(() => {1146      loadPreloads();1147      loadStylesheets();1148    });1149    await assertLog([1150      'load preload: one4',1151      'load preload: three4',1152      'load preload: seven1',1153      'load preload: one2',1154      'load preload: two2',1155      'load preload: five1',1156      'load preload: three3',1157      'load preload: four3',1158      'load stylesheet: one1',1159      'load stylesheet: one2',1160      'load stylesheet: one4',1161      'load stylesheet: two2',1162      'load stylesheet: three1',1163      'load stylesheet: three3',1164      'load stylesheet: three4',1165      'load stylesheet: four3',1166      'load stylesheet: five1',1167      'load stylesheet: seven1',1168    ]);11691170    expect(getMeaningfulChildren(document)).toEqual(1171      <html>1172        <head>1173          <link rel="stylesheet" href="one1" data-precedence="one" />1174          <link rel="stylesheet" href="one2" data-precedence="one" />1175          <style data-href="one3" data-precedence="one">1176            {css}1177          </style>1178          <link rel="stylesheet" href="one4" data-precedence="one" />1179          <style data-href="two1" data-precedence="two">1180            {css}1181          </style>1182          <link rel="stylesheet" href="two2" data-precedence="two" />1183          <style data-href="two3" data-precedence="two">1184            {css}1185          </style>1186          <style data-href="two4" data-precedence="two">1187            {css}1188          </style>1189          <link rel="stylesheet" href="three1" data-precedence="three" />1190          <style data-href="three2" data-precedence="three">1191            {css}1192          </style>1193          <link rel="stylesheet" href="three3" data-precedence="three" />1194          <link rel="stylesheet" href="three4" data-precedence="three" />1195          <style data-href="four1" data-precedence="four">1196            {css}1197          </style>1198          <style data-href="four2" data-precedence="four">1199            {css}1200          </style>1201          <link rel="stylesheet" href="four3" data-precedence="four" />1202          <style data-href="four4" data-precedence="four">1203            {css}1204          </style>1205          <link rel="stylesheet" href="five1" data-precedence="five" />1206          <style data-href="six1" data-precedence="six">1207            {css}1208          </style>1209          <link rel="stylesheet" href="seven1" data-precedence="seven" />1210          <style data-href="eight1" data-precedence="eight">1211            {css}1212          </style>1213          <link rel="preload" href="one4" as="style" />1214          <link rel="preload" href="three4" as="style" />1215          <link rel="preload" href="seven1" as="style" />1216        </head>1217        <body>1218          <link rel="preload" href="one2" as="style" />1219          <link rel="preload" href="two2" as="style" />1220          <link rel="preload" href="five1" as="style" />1221          <link rel="preload" href="three3" as="style" />1222          <link rel="preload" href="four3" as="style" />1223        </body>1224      </html>,1225    );1226  });12271228  it('client renders a boundary if a style Resource dependency fails to load', async () => {1229    function App() {1230      return (1231        <html>1232          <head />1233          <body>1234            <Suspense fallback="loading...">1235              <BlockedOn value="unblock">1236                <link rel="stylesheet" href="foo" precedence="arbitrary" />1237                <link rel="stylesheet" href="bar" precedence="arbitrary" />1238                Hello1239              </BlockedOn>1240            </Suspense>1241          </body>1242        </html>1243      );1244    }1245    await act(() => {1246      const {pipe} = renderToPipeableStream(<App />);1247      pipe(writable);1248    });12491250    expect(getMeaningfulChildren(document)).toEqual(1251      <html>1252        <head />1253        <body>loading...</body>1254      </html>,1255    );12561257    await act(() => {1258      resolveText('unblock');1259    });12601261    expect(getMeaningfulChildren(document)).toEqual(1262      <html>1263        <head>1264          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1265          <link rel="stylesheet" href="bar" data-precedence="arbitrary" />1266        </head>1267        <body>1268          loading...1269          <link rel="preload" href="foo" as="style" />1270          <link rel="preload" href="bar" as="style" />1271        </body>1272      </html>,1273    );12741275    errorStylesheets(['bar']);1276    assertLog(['error stylesheet: bar']);12771278    await waitForAll([]);12791280    const boundaryTemplateInstance = document.getElementById('B:0');1281    const suspenseInstance = boundaryTemplateInstance.previousSibling;12821283    expect(suspenseInstance.data).toEqual('$!');1284    expect(boundaryTemplateInstance.dataset.dgst).toBe('CSS failed to load');12851286    expect(getMeaningfulChildren(document)).toEqual(1287      <html>1288        <head>1289          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1290          <link rel="stylesheet" href="bar" data-precedence="arbitrary" />1291        </head>1292        <body>1293          loading...1294          <link rel="preload" href="foo" as="style" />1295          <link rel="preload" href="bar" as="style" />1296        </body>1297      </html>,1298    );12991300    const errors = [];1301    ReactDOMClient.hydrateRoot(document, <App />, {1302      onRecoverableError(err, errInfo) {1303        errors.push(err.message);1304        errors.push(err.digest);1305      },1306    });1307    await waitForAll([]);1308    // When binding a stylesheet that was SSR'd in a boundary reveal there is a loadingState promise1309    // We need to use that promise to resolve the suspended commit because we don't know if the load or error1310    // events have already fired. This requires the load to be awaited for the commit to have a chance to flush1311    // We could change this by tracking the loadingState's fulfilled status directly on the loadingState similar1312    // to thenables however this slightly increases the fizz runtime code size.1313    await clientAct(() => loadStylesheets());1314    assertLog(['load stylesheet: foo']);1315    expect(getMeaningfulChildren(document)).toEqual(1316      <html>1317        <head>1318          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1319          <link rel="stylesheet" href="bar" data-precedence="arbitrary" />1320        </head>1321        <body>1322          <link rel="preload" href="foo" as="style" />1323          <link rel="preload" href="bar" as="style" />1324          Hello1325        </body>1326      </html>,1327    );1328    expect(errors).toEqual([1329      'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.',1330      'CSS failed to load',1331    ]);1332  });13331334  it('treats stylesheet links with a precedence as a resource', async () => {1335    await act(() => {1336      const {pipe} = renderToPipeableStream(1337        <html>1338          <head />1339          <body>1340            <link rel="stylesheet" href="foo" precedence="arbitrary" />1341            Hello1342          </body>1343        </html>,1344      );1345      pipe(writable);1346    });1347    expect(getMeaningfulChildren(document)).toEqual(1348      <html>1349        <head>1350          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1351        </head>1352        <body>Hello</body>1353      </html>,1354    );13551356    ReactDOMClient.hydrateRoot(1357      document,1358      <html>1359        <head />1360        <body>Hello</body>1361      </html>,1362    );1363    await waitForAll([]);1364    expect(getMeaningfulChildren(document)).toEqual(1365      <html>1366        <head>1367          <link rel="stylesheet" href="foo" data-precedence="arbitrary" />1368        </head>1369        <body>Hello</body>1370      </html>,1371    );1372  });13731374  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 () => {1375    // If you render many of these as siblings the values get emitted as a single text with no separator sometimes1376    // because the link gets elided as a resource1377    function AsyncTextWithResource({text, href, precedence}) {1378      const value = readText(text);1379      return (1380        <>1381          {value}1382          <link rel="stylesheet" href={href} precedence={precedence} />1383        </>1384      );1385    }13861387    await act(() => {1388      const {pipe} = renderToPipeableStream(1389        <html>1390          <head />1391          <body>1392            <AsyncTextWithResource text="foo" href="foo" precedence="one" />1393            <AsyncTextWithResource text="bar" href="bar" precedence="two" />1394            <AsyncTextWithResource text="baz" href="baz" precedence="three" />1395          </body>1396        </html>,1397      );1398      pipe(writable);1399      resolveText('foo');1400      resolveText('bar');1401      resolveText('baz');1402    });14031404    expect(getMeaningfulChildren(document)).toEqual(1405      <html>1406        <head>1407          <link rel="stylesheet" href="foo" data-precedence="one" />1408          <link rel="stylesheet" href="bar" data-precedence="two" />1409          <link rel="stylesheet" href="baz" data-precedence="three" />1410        </head>1411        <body>1412          {'foo'}1413          {'bar'}1414          {'baz'}1415        </body>1416      </html>,1417    );1418  });14191420  it('hoists late stylesheets the correct precedence', async () => {1421    function PresetPrecedence() {1422      ReactDOM.preinit('preset', {as: 'style', precedence: 'preset'});1423    }1424    await act(() => {1425      const {pipe} = renderToPipeableStream(1426        <html>1427          <head />1428          <body>1429            <link rel="stylesheet" href="initial" precedence="one" />1430            <PresetPrecedence />1431            <div>1432              <Suspense fallback="loading foo bar...">1433                <div>foo</div>1434                <link rel="stylesheet" href="foo" precedence="one" />1435                <BlockedOn value="bar">1436                  <div>bar</div>1437                  <link rel="stylesheet" href="bar" precedence="default" />1438                </BlockedOn>1439              </Suspense>1440            </div>1441            <div>1442              <Suspense fallback="loading bar baz qux...">1443                <BlockedOn value="bar">1444                  <div>bar</div>1445                  <link rel="stylesheet" href="bar" precedence="default" />1446                </BlockedOn>1447                <BlockedOn value="baz">1448                  <div>baz</div>1449                  <link rel="stylesheet" href="baz" precedence="two" />1450                </BlockedOn>1451                <BlockedOn value="qux">1452                  <div>qux</div>1453                  <link rel="stylesheet" href="qux" precedence="one" />1454                </BlockedOn>1455              </Suspense>1456            </div>1457            <div>1458              <Suspense fallback="loading bar baz qux...">1459                <BlockedOn value="unblock">1460                  <BlockedOn value="bar">1461                    <div>bar</div>1462                    <link rel="stylesheet" href="bar" precedence="default" />1463                  </BlockedOn>1464                  <BlockedOn value="baz">1465                    <div>baz</div>1466                    <link rel="stylesheet" href="baz" precedence="two" />1467                  </BlockedOn>1468                  <BlockedOn value="qux">1469                    <div>qux</div>1470                    <link rel="stylesheet" href="qux" precedence="one" />1471                  </BlockedOn>1472                </BlockedOn>1473              </Suspense>1474            </div>1475          </body>1476        </html>,1477      );1478      pipe(writable);1479    });14801481    expect(getMeaningfulChildren(document)).toEqual(1482      <html>1483        <head>1484          <link rel="stylesheet" href="initial" data-precedence="one" />1485          <link rel="stylesheet" href="foo" data-precedence="one" />1486          <link rel="stylesheet" href="preset" data-precedence="preset" />1487        </head>1488        <body>1489          <div>loading foo bar...</div>1490          <div>loading bar baz qux...</div>1491          <div>loading bar baz qux...</div>1492        </body>1493      </html>,1494    );14951496    await act(() => {1497      resolveText('foo');1498      resolveText('bar');1499    });15001501    expect(getMeaningfulChildren(document)).toEqual(1502      <html>1503        <head>1504          <link rel="stylesheet" href="initial" data-precedence="one" />1505          <link rel="stylesheet" href="foo" data-precedence="one" />1506          <link rel="stylesheet" href="preset" data-precedence="preset" />1507          <link rel="stylesheet" href="bar" data-precedence="default" />1508        </head>1509        <body>1510          <div>loading foo bar...</div>1511          <div>loading bar baz qux...</div>1512          <div>loading bar baz qux...</div>1513          <link rel="preload" href="bar" as="style" />1514        </body>1515      </html>,1516    );15171518    await act(() => {1519      const link = document.querySelector('link[rel="stylesheet"][href="foo"]');1520      const event = document.createEvent('Events');1521      event.initEvent('load', true, true);1522      link.dispatchEvent(event);1523    });15241525    expect(getMeaningfulChildren(document)).toEqual(1526      <html>1527        <head>1528          <link rel="stylesheet" href="initial" data-precedence="one" />1529          <link rel="stylesheet" href="foo" data-precedence="one" />1530          <link rel="stylesheet" href="preset" data-precedence="preset" />1531          <link rel="stylesheet" href="bar" data-precedence="default" />1532        </head>1533        <body>1534          <div>loading foo bar...</div>1535          <div>loading bar baz qux...</div>1536          <div>loading bar baz qux...</div>1537          <link rel="preload" href="bar" as="style" />1538        </body>1539      </html>,1540    );15411542    await act(() => {1543      const link = document.querySelector('link[rel="stylesheet"][href="bar"]');1544      const event = document.createEvent('Events');1545      event.initEvent('load', true, true);1546      link.dispatchEvent(event);1547    });15481549    expect(getMeaningfulChildren(document)).toEqual(1550      <html>1551        <head>1552          <link rel="stylesheet" href="initial" data-precedence="one" />1553          <link rel="stylesheet" href="foo" data-precedence="one" />1554          <link rel="stylesheet" href="preset" data-precedence="preset" />1555          <link rel="stylesheet" href="bar" data-precedence="default" />1556        </head>1557        <body>1558          <div>1559            <div>foo</div>1560            <div>bar</div>1561          </div>1562          <div>loading bar baz qux...</div>1563          <div>loading bar baz qux...</div>1564          <link rel="preload" href="bar" as="style" />1565        </body>1566      </html>,1567    );15681569    await act(() => {1570      resolveText('baz');1571    });15721573    expect(getMeaningfulChildren(document)).toEqual(1574      <html>1575        <head>1576          <link rel="stylesheet" href="initial" data-precedence="one" />1577          <link rel="stylesheet" href="foo" data-precedence="one" />1578          <link rel="stylesheet" href="preset" data-precedence="preset" />1579          <link rel="stylesheet" href="bar" data-precedence="default" />1580        </head>1581        <body>1582          <div>1583            <div>foo</div>1584            <div>bar</div>1585          </div>1586          <div>loading bar baz qux...</div>1587          <div>loading bar baz qux...</div>1588          <link rel="preload" as="style" href="bar" />1589          <link rel="preload" as="style" href="baz" />1590        </body>1591      </html>,1592    );15931594    await act(() => {1595      resolveText('qux');1596    });15971598    expect(getMeaningfulChildren(document)).toEqual(1599      <html>1600        <head>1601          <link rel="stylesheet" href="initial" data-precedence="one" />1602          <link rel="stylesheet" href="foo" data-precedence="one" />1603          <link rel="stylesheet" href="qux" data-precedence="one" />1604          <link rel="stylesheet" href="preset" data-precedence="preset" />1605          <link rel="stylesheet" href="bar" data-precedence="default" />1606          <link rel="stylesheet" href="baz" data-precedence="two" />1607        </head>1608        <body>1609          <div>1610            <div>foo</div>1611            <div>bar</div>1612          </div>1613          <div>loading bar baz qux...</div>1614          <div>loading bar baz qux...</div>1615          <link rel="preload" as="style" href="bar" />1616          <link rel="preload" as="style" href="baz" />1617          <link rel="preload" as="style" href="qux" />1618        </body>1619      </html>,1620    );16211622    await act(() => {1623      const bazlink = document.querySelector(1624        'link[rel="stylesheet"][href="baz"]',1625      );1626      const quxlink = document.querySelector(1627        'link[rel="stylesheet"][href="qux"]',1628      );1629      const presetLink = document.querySelector(1630        'link[rel="stylesheet"][href="preset"]',1631      );1632      const event = document.createEvent('Events');1633      event.initEvent('load', true, true);1634      bazlink.dispatchEvent(event);1635      quxlink.dispatchEvent(event);1636      presetLink.dispatchEvent(event);1637    });16381639    expect(getMeaningfulChildren(document)).toEqual(1640      <html>1641        <head>1642          <link rel="stylesheet" href="initial" data-precedence="one" />1643          <link rel="stylesheet" href="foo" data-precedence="one" />1644          <link rel="stylesheet" href="qux" data-precedence="one" />1645          <link rel="stylesheet" href="preset" data-precedence="preset" />1646          <link rel="stylesheet" href="bar" data-precedence="default" />1647          <link rel="stylesheet" href="baz" data-precedence="two" />1648        </head>1649        <body>1650          <div>1651            <div>foo</div>1652            <div>bar</div>1653          </div>1654          <div>1655            <div>bar</div>1656            <div>baz</div>1657            <div>qux</div>1658          </div>1659          <div>loading bar baz qux...</div>1660          <link rel="preload" as="style" href="bar" />1661          <link rel="preload" as="style" href="baz" />1662          <link rel="preload" as="style" href="qux" />1663        </body>1664      </html>,1665    );16661667    await act(() => {1668      resolveText('unblock');1669    });16701671    expect(getMeaningfulChildren(document)).toEqual(1672      <html>1673        <head>1674          <link rel="stylesheet" href="initial" data-precedence="one" />1675          <link rel="stylesheet" href="foo" data-precedence="one" />1676          <link rel="stylesheet" href="qux" data-precedence="one" />1677          <link rel="stylesheet" href="preset" data-precedence="preset" />1678          <link rel="stylesheet" href="bar" data-precedence="default" />1679          <link rel="stylesheet" href="baz" data-precedence="two" />1680        </head>1681        <body>1682          <div>1683            <div>foo</div>1684            <div>bar</div>1685          </div>1686          <div>1687            <div>bar</div>1688            <div>baz</div>1689            <div>qux</div>1690          </div>1691          <div>1692            <div>bar</div>1693            <div>baz</div>1694            <div>qux</div>1695          </div>1696          <link rel="preload" as="style" href="bar" />1697          <link rel="preload" as="style" href="baz" />1698          <link rel="preload" as="style" href="qux" />1699        </body>1700      </html>,1701    );1702  });17031704  it('normalizes stylesheet resource precedence for all boundaries inlined as part of the shell flush', async () => {1705    await act(() => {1706      const {pipe} = renderToPipeableStream(1707        <html>1708          <head />1709          <body>1710            <div>1711              outer1712              <link rel="stylesheet" href="1one" precedence="one" />1713              <link rel="stylesheet" href="1two" precedence="two" />1714              <link rel="stylesheet" href="1three" precedence="three" />1715              <link rel="stylesheet" href="1four" precedence="four" />1716              <Suspense fallback={null}>1717                <div>1718                  middle1719                  <link rel="stylesheet" href="2one" precedence="one" />1720                  <link rel="stylesheet" href="2two" precedence="two" />1721                  <link rel="stylesheet" href="2three" precedence="three" />1722                  <link rel="stylesheet" href="2four" precedence="four" />1723                  <Suspense fallback={null}>1724                    <div>1725                      inner1726                      <link rel="stylesheet" href="3five" precedence="five" />1727                      <link rel="stylesheet" href="3one" precedence="one" />1728                      <link rel="stylesheet" href="3two" precedence="two" />1729                      <link rel="stylesheet" href="3three" precedence="three" />1730                      <link rel="stylesheet" href="3four" precedence="four" />1731                    </div>1732                  </Suspense>1733                </div>1734              </Suspense>1735              <Suspense fallback={null}>1736                <div>middle</div>1737                <link rel="stylesheet" href="4one" precedence="one" />1738                <link rel="stylesheet" href="4two" precedence="two" />1739                <link rel="stylesheet" href="4three" precedence="three" />1740                <link rel="stylesheet" href="4four" precedence="four" />1741              </Suspense>1742            </div>1743          </body>1744        </html>,1745      );1746      pipe(writable);1747    });17481749    expect(getMeaningfulChildren(document)).toEqual(1750      <html>1751        <head>1752          <link rel="stylesheet" href="1one" data-precedence="one" />1753          <link rel="stylesheet" href="2one" data-precedence="one" />1754          <link rel="stylesheet" href="3one" data-precedence="one" />1755          <link rel="stylesheet" href="4one" data-precedence="one" />17561757          <link rel="stylesheet" href="1two" data-precedence="two" />1758          <link rel="stylesheet" href="2two" data-precedence="two" />1759          <link rel="stylesheet" href="3two" data-precedence="two" />1760          <link rel="stylesheet" href="4two" data-precedence="two" />17611762          <link rel="stylesheet" href="1three" data-precedence="three" />1763          <link rel="stylesheet" href="2three" data-precedence="three" />1764          <link rel="stylesheet" href="3three" data-precedence="three" />1765          <link rel="stylesheet" href="4three" data-precedence="three" />17661767          <link rel="stylesheet" href="1four" data-precedence="four" />1768          <link rel="stylesheet" href="2four" data-precedence="four" />1769          <link rel="stylesheet" href="3four" data-precedence="four" />1770          <link rel="stylesheet" href="4four" data-precedence="four" />17711772          <link rel="stylesheet" href="3five" data-precedence="five" />1773        </head>1774        <body>1775          <div>1776            outer1777            <div>1778              middle<div>inner</div>1779            </div>1780            <div>middle</div>1781          </div>1782        </body>1783      </html>,1784    );1785  });17861787  it('stylesheet resources are inserted according to precedence order on the client', async () => {1788    await act(() => {1789      const {pipe} = renderToPipeableStream(1790        <html>1791          <head />1792          <body>1793            <div>1794              <link rel="stylesheet" href="foo" precedence="one" />1795              <link rel="stylesheet" href="bar" precedence="two" />1796              Hello1797            </div>1798          </body>1799        </html>,1800      );1801      pipe(writable);1802    });18031804    expect(getMeaningfulChildren(document)).toEqual(1805      <html>1806        <head>1807          <link rel="stylesheet" href="foo" data-precedence="one" />1808          <link rel="stylesheet" href="bar" data-precedence="two" />1809        </head>1810        <body>1811          <div>Hello</div>1812        </body>1813      </html>,1814    );18151816    const root = ReactDOMClient.hydrateRoot(1817      document,1818      <html>1819        <head />1820        <body>1821          <div>1822            <link rel="stylesheet" href="foo" precedence="one" />1823            <link rel="stylesheet" href="bar" precedence="two" />1824            Hello1825          </div>1826        </body>1827      </html>,1828    );1829    await waitForAll([]);1830    expect(getMeaningfulChildren(document)).toEqual(1831      <html>1832        <head>1833          <link rel="stylesheet" href="foo" data-precedence="one" />1834          <link rel="stylesheet" href="bar" data-precedence="two" />1835        </head>1836        <body>1837          <div>Hello</div>1838        </body>1839      </html>,1840    );18411842    root.render(1843      <html>1844        <head />1845        <body>1846          <div>Goodbye</div>1847          <link rel="stylesheet" href="baz" precedence="one" />1848        </body>1849      </html>,1850    );1851    await waitForAll([]);1852    await act(() => {1853      loadPreloads();1854      loadStylesheets();1855    });1856    await assertLog([1857      'load preload: baz',1858      'load stylesheet: foo',1859      'load stylesheet: baz',1860      'load stylesheet: bar',1861    ]);1862    expect(getMeaningfulChildren(document)).toEqual(1863      <html>1864        <head>1865          <link rel="stylesheet" href="foo" data-precedence="one" />1866          <link rel="stylesheet" href="baz" data-precedence="one" />1867          <link rel="stylesheet" href="bar" data-precedence="two" />1868          <link rel="preload" as="style" href="baz" />1869        </head>1870        <body>1871          <div>Goodbye</div>1872        </body>1873      </html>,1874    );1875  });18761877  it('inserts preloads in render phase eagerly', async () => {1878    function Throw() {1879      throw new Error('Uh oh!');1880    }1881    class ErrorBoundary extends React.Component {1882      state = {hasError: false, error: null};1883      static getDerivedStateFromError(error) {1884        return {1885          hasError: true,1886          error,1887        };1888      }1889      render() {1890        if (this.state.hasError) {1891          return this.state.error.message;1892        }1893        return this.props.children;1894      }1895    }18961897    const root = ReactDOMClient.createRoot(container);1898    root.render(1899      <ErrorBoundary>1900        <link rel="stylesheet" href="foo" precedence="default" />1901        <div>foo</div>1902        <Throw />1903      </ErrorBoundary>,1904    );1905    await waitForAll([]);1906    expect(getMeaningfulChildren(document)).toEqual(1907      <html>1908        <head>1909          <link rel="preload" href="foo" as="style" />1910        </head>1911        <body>1912          <div id="container">Uh oh!</div>1913        </body>1914      </html>,1915    );1916  });19171918  it('will include child boundary stylesheet resources in the boundary reveal instruction', async () => {1919    await act(() => {1920      const {pipe} = renderToPipeableStream(1921        <html>1922          <head />1923          <body>1924            <div>1925              <Suspense fallback="loading foo...">1926                <BlockedOn value="foo">1927                  <div>foo</div>1928                  <link rel="stylesheet" href="foo" precedence="default" />1929                  <Suspense fallback="loading bar...">1930                    <BlockedOn value="bar">1931                      <div>bar</div>1932                      <link rel="stylesheet" href="bar" precedence="default" />1933                      <Suspense fallback="loading baz...">1934                        <BlockedOn value="baz">1935                          <div>baz</div>1936                          <link1937                            rel="stylesheet"1938                            href="baz"1939                            precedence="default"1940                          />1941                        </BlockedOn>1942                      </Suspense>1943                    </BlockedOn>1944                  </Suspense>1945                </BlockedOn>1946              </Suspense>1947            </div>1948          </body>1949        </html>,1950      );1951      pipe(writable);1952    });19531954    expect(getMeaningfulChildren(document)).toEqual(1955      <html>1956        <head />1957        <body>1958          <div>loading foo...</div>1959        </body>1960      </html>,1961    );19621963    await act(() => {1964      resolveText('bar');1965    });1966    expect(getMeaningfulChildren(document)).toEqual(1967      <html>1968        <head />1969        <body>1970          <div>loading foo...</div>1971        </body>1972      </html>,1973    );19741975    await act(() => {1976      resolveText('baz');1977    });1978    expect(getMeaningfulChildren(document)).toEqual(1979      <html>1980        <head />1981        <body>1982          <div>loading foo...</div>1983        </body>1984      </html>,1985    );19861987    await act(() => {1988      resolveText('foo');1989    });1990    expect(getMeaningfulChildren(document)).toEqual(1991      <html>1992        <head>1993          <link rel="stylesheet" href="foo" data-precedence="default" />1994          <link rel="stylesheet" href="bar" data-precedence="default" />1995          <link rel="stylesheet" href="baz" data-precedence="default" />1996        </head>1997        <body>1998          <div>loading foo...</div>1999          <link rel="preload" href="foo" as="style" />2000          <link rel="preload" href="bar" as="style" />

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.

Generated by searchcode.com · powered by code_get_file + code_get_findings