mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Stacked on #30132. This way we can get parent and owner stacks from the error. This forces us to confront multiple errors and whether or not a Flight error that ends up being unobservable needs to really reject the render. This implements stashing of Flight errors with a digest and only errors if they end up erroring the Fizz render too. At this point they'll have parent stacks so we can surface those.
237 lines
6.1 KiB
JavaScript
237 lines
6.1 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
let React;
|
|
let ReactHTML;
|
|
|
|
function normalizeCodeLocInfo(str) {
|
|
return (
|
|
str &&
|
|
String(str).replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
|
|
return '\n in ' + name + ' (at **)';
|
|
})
|
|
);
|
|
}
|
|
|
|
if (!__EXPERIMENTAL__) {
|
|
it('should not be built in stable', () => {
|
|
try {
|
|
require('react-html');
|
|
} catch (x) {
|
|
return;
|
|
}
|
|
throw new Error('Expected react-html not to exist in stable.');
|
|
});
|
|
} else {
|
|
describe('ReactHTML', () => {
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
React = require('react');
|
|
ReactHTML = require('react-html');
|
|
});
|
|
|
|
it('should be able to render a simple component', async () => {
|
|
function Component() {
|
|
return <div>hello world</div>;
|
|
}
|
|
|
|
const html = await ReactHTML.renderToMarkup(<Component />);
|
|
expect(html).toBe('<div>hello world</div>');
|
|
});
|
|
|
|
it('should be able to render a large string', async () => {
|
|
function Component() {
|
|
return <div>{'hello '.repeat(200)}world</div>;
|
|
}
|
|
|
|
const html = await ReactHTML.renderToMarkup(
|
|
React.createElement(Component),
|
|
);
|
|
expect(html).toBe('<div>' + ('hello '.repeat(200) + 'world') + '</div>');
|
|
});
|
|
|
|
it('should prefix html tags with a doctype', async () => {
|
|
const html = await ReactHTML.renderToMarkup(
|
|
<html>
|
|
<body>hello</body>
|
|
</html>,
|
|
);
|
|
expect(html).toBe(
|
|
'<!DOCTYPE html><html><head></head><body>hello</body></html>',
|
|
);
|
|
});
|
|
|
|
it('should error on useState', async () => {
|
|
function Component() {
|
|
const [state] = React.useState('hello');
|
|
return <div>{state}</div>;
|
|
}
|
|
|
|
await expect(async () => {
|
|
await ReactHTML.renderToMarkup(<Component />);
|
|
}).rejects.toThrow();
|
|
});
|
|
|
|
it('should error on refs passed to host components', async () => {
|
|
function Component() {
|
|
const ref = React.createRef();
|
|
return <div ref={ref} />;
|
|
}
|
|
|
|
await expect(async () => {
|
|
await ReactHTML.renderToMarkup(<Component />);
|
|
}).rejects.toThrow();
|
|
});
|
|
|
|
it('should error on callbacks passed to event handlers', async () => {
|
|
function Component() {
|
|
function onClick() {
|
|
// This won't be able to be called.
|
|
}
|
|
return <div onClick={onClick} />;
|
|
}
|
|
|
|
await expect(async () => {
|
|
await ReactHTML.renderToMarkup(<Component />);
|
|
}).rejects.toThrow();
|
|
});
|
|
|
|
it('supports the useId Hook', async () => {
|
|
function Component() {
|
|
const firstNameId = React.useId();
|
|
const lastNameId = React.useId();
|
|
return React.createElement(
|
|
'div',
|
|
null,
|
|
React.createElement(
|
|
'h2',
|
|
{
|
|
id: firstNameId,
|
|
},
|
|
'First',
|
|
),
|
|
React.createElement(
|
|
'p',
|
|
{
|
|
'aria-labelledby': firstNameId,
|
|
},
|
|
'Sebastian',
|
|
),
|
|
React.createElement(
|
|
'h2',
|
|
{
|
|
id: lastNameId,
|
|
},
|
|
'Last',
|
|
),
|
|
React.createElement(
|
|
'p',
|
|
{
|
|
'aria-labelledby': lastNameId,
|
|
},
|
|
'Smith',
|
|
),
|
|
);
|
|
}
|
|
|
|
const html = await ReactHTML.renderToMarkup(<Component />);
|
|
const container = document.createElement('div');
|
|
container.innerHTML = html;
|
|
|
|
expect(container.getElementsByTagName('h2')[0].id).toBe(
|
|
container.getElementsByTagName('p')[0].getAttribute('aria-labelledby'),
|
|
);
|
|
expect(container.getElementsByTagName('h2')[1].id).toBe(
|
|
container.getElementsByTagName('p')[1].getAttribute('aria-labelledby'),
|
|
);
|
|
|
|
// It's not the same id between them.
|
|
expect(container.getElementsByTagName('h2')[0].id).not.toBe(
|
|
container.getElementsByTagName('p')[1].getAttribute('aria-labelledby'),
|
|
);
|
|
});
|
|
|
|
// @gate disableClientCache
|
|
it('does NOT support cache yet because it is a client component', async () => {
|
|
let counter = 0;
|
|
const getCount = React.cache(() => {
|
|
return counter++;
|
|
});
|
|
function Component() {
|
|
const a = getCount();
|
|
const b = getCount();
|
|
return (
|
|
<div>
|
|
{a}
|
|
{b}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const html = await ReactHTML.renderToMarkup(<Component />);
|
|
expect(html).toBe('<div>01</div>');
|
|
});
|
|
|
|
it('can get the component owner stacks for onError in dev', async () => {
|
|
const thrownError = new Error('hi');
|
|
const caughtErrors = [];
|
|
|
|
function Foo() {
|
|
return <Bar />;
|
|
}
|
|
function Bar() {
|
|
return (
|
|
<div>
|
|
<Baz />
|
|
</div>
|
|
);
|
|
}
|
|
function Baz({unused}) {
|
|
throw thrownError;
|
|
}
|
|
|
|
await expect(async () => {
|
|
await ReactHTML.renderToMarkup(
|
|
<div>
|
|
<Foo />
|
|
</div>,
|
|
{
|
|
onError(error, errorInfo) {
|
|
caughtErrors.push({
|
|
error: error,
|
|
parentStack: errorInfo.componentStack,
|
|
ownerStack: React.captureOwnerStack
|
|
? React.captureOwnerStack()
|
|
: null,
|
|
});
|
|
},
|
|
},
|
|
);
|
|
}).rejects.toThrow(thrownError);
|
|
|
|
expect(caughtErrors.length).toBe(1);
|
|
expect(caughtErrors[0].error).toBe(thrownError);
|
|
expect(normalizeCodeLocInfo(caughtErrors[0].parentStack)).toBe(
|
|
'\n in Baz (at **)' +
|
|
'\n in div (at **)' +
|
|
'\n in Bar (at **)' +
|
|
'\n in Foo (at **)' +
|
|
'\n in div (at **)',
|
|
);
|
|
expect(normalizeCodeLocInfo(caughtErrors[0].ownerStack)).toBe(
|
|
__DEV__ && gate(flags => flags.enableOwnerStacks)
|
|
? '\n in Bar (at **)' + '\n in Foo (at **)'
|
|
: null,
|
|
);
|
|
});
|
|
});
|
|
}
|