mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Add support for rendering BigInt (#24580)
This commit is contained in:
committed by
GitHub
parent
a27fe22548
commit
ce5eb600d0
+5
-1
@@ -58,5 +58,9 @@ export default function UnserializableProps(): React.Node {
|
||||
}
|
||||
|
||||
function ChildComponent(props: any) {
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<div>{props.bigInt}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+19
-4
@@ -66,6 +66,7 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
|
||||
import sanitizeURL from '../shared/sanitizeURL';
|
||||
|
||||
import {
|
||||
enableBigIntSupport,
|
||||
enableCustomElementPropertySupport,
|
||||
enableClientRenderFallbackOnTextMismatch,
|
||||
enableFormActions,
|
||||
@@ -326,7 +327,7 @@ function normalizeMarkupForTextOrAttribute(markup: mixed): string {
|
||||
|
||||
export function checkForUnmatchedText(
|
||||
serverText: string,
|
||||
clientText: string | number,
|
||||
clientText: string | number | bigint,
|
||||
isConcurrentMode: boolean,
|
||||
shouldWarnDev: boolean,
|
||||
) {
|
||||
@@ -397,12 +398,17 @@ function setProp(
|
||||
if (canSetTextContent) {
|
||||
setTextContent(domElement, value);
|
||||
}
|
||||
} else if (typeof value === 'number') {
|
||||
} else if (
|
||||
typeof value === 'number' ||
|
||||
(enableBigIntSupport && typeof value === 'bigint')
|
||||
) {
|
||||
if (__DEV__) {
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
validateTextNesting('' + value, tag);
|
||||
}
|
||||
const canSetTextContent = tag !== 'body';
|
||||
if (canSetTextContent) {
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
setTextContent(domElement, '' + value);
|
||||
}
|
||||
}
|
||||
@@ -955,7 +961,11 @@ function setPropOnCustomElement(
|
||||
case 'children': {
|
||||
if (typeof value === 'string') {
|
||||
setTextContent(domElement, value);
|
||||
} else if (typeof value === 'number') {
|
||||
} else if (
|
||||
typeof value === 'number' ||
|
||||
(enableBigIntSupport && typeof value === 'bigint')
|
||||
) {
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
setTextContent(domElement, '' + value);
|
||||
}
|
||||
break;
|
||||
@@ -2817,7 +2827,12 @@ export function diffHydratedProperties(
|
||||
// even listeners these nodes might be wired up to.
|
||||
// TODO: Warn if there is more than a single textNode as a child.
|
||||
// TODO: Should we use domElement.firstChild.nodeValue to compare?
|
||||
if (typeof children === 'string' || typeof children === 'number') {
|
||||
if (
|
||||
typeof children === 'string' ||
|
||||
typeof children === 'number' ||
|
||||
(enableBigIntSupport && typeof children === 'bigint')
|
||||
) {
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
if (domElement.textContent !== '' + children) {
|
||||
if (props.suppressHydrationWarning !== true) {
|
||||
checkForUnmatchedText(
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import {Children} from 'react';
|
||||
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
|
||||
|
||||
let didWarnSelectedSetOnOption = false;
|
||||
let didWarnInvalidChild = false;
|
||||
@@ -26,7 +27,11 @@ export function validateOptionProps(element: Element, props: Object) {
|
||||
if (child == null) {
|
||||
return;
|
||||
}
|
||||
if (typeof child === 'string' || typeof child === 'number') {
|
||||
if (
|
||||
typeof child === 'string' ||
|
||||
typeof child === 'number' ||
|
||||
(enableBigIntSupport && typeof child === 'bigint')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!didWarnInvalidChild) {
|
||||
|
||||
@@ -89,6 +89,7 @@ import {
|
||||
import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
|
||||
|
||||
import {
|
||||
enableBigIntSupport,
|
||||
enableCreateEventHandleAPI,
|
||||
enableScopeAPI,
|
||||
enableFloat,
|
||||
@@ -548,6 +549,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
|
||||
type === 'noscript' ||
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number' ||
|
||||
(enableBigIntSupport && typeof props.children === 'bigint') ||
|
||||
(typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||
props.dangerouslySetInnerHTML !== null &&
|
||||
props.dangerouslySetInnerHTML.__html != null)
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
*/
|
||||
|
||||
import {checkFormFieldValueStringCoercion} from 'shared/CheckStringCoercion';
|
||||
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
|
||||
|
||||
export opaque type ToStringValue =
|
||||
| boolean
|
||||
| number
|
||||
| bigint
|
||||
| Object
|
||||
| string
|
||||
| null
|
||||
@@ -28,6 +30,12 @@ export function toString(value: ToStringValue): string {
|
||||
|
||||
export function getToStringValue(value: mixed): ToStringValue {
|
||||
switch (typeof value) {
|
||||
case 'bigint':
|
||||
if (!enableBigIntSupport) {
|
||||
// bigint is assigned as empty string
|
||||
return '';
|
||||
}
|
||||
// fallthrough for BigInt support
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import {Children} from 'react';
|
||||
|
||||
import {
|
||||
enableBigIntSupport,
|
||||
enableFilterEmptyStringAttributesDOM,
|
||||
enableCustomElementPropertySupport,
|
||||
enableFloat,
|
||||
@@ -1626,7 +1627,9 @@ function flattenOptionChildren(children: mixed): string {
|
||||
if (
|
||||
!didWarnInvalidOptionChildren &&
|
||||
typeof child !== 'string' &&
|
||||
typeof child !== 'number'
|
||||
typeof child !== 'number' &&
|
||||
((enableBigIntSupport && typeof child !== 'bigint') ||
|
||||
!enableBigIntSupport)
|
||||
) {
|
||||
didWarnInvalidOptionChildren = true;
|
||||
console.error(
|
||||
@@ -2960,36 +2963,40 @@ function pushTitle(
|
||||
|
||||
if (Array.isArray(children) && children.length > 1) {
|
||||
console.error(
|
||||
'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an Array with length %s instead.' +
|
||||
'React expects the `children` prop of <title> tags to be a string, number%s, or object with a novel `toString` method but found an Array with length %s instead.' +
|
||||
' Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value' +
|
||||
' which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes.' +
|
||||
' For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop' +
|
||||
' is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
|
||||
enableBigIntSupport ? ', bigint' : '',
|
||||
children.length,
|
||||
);
|
||||
} else if (typeof child === 'function' || typeof child === 'symbol') {
|
||||
const childType =
|
||||
typeof child === 'function' ? 'a Function' : 'a Sybmol';
|
||||
console.error(
|
||||
'React expect children of <title> tags to be a string, number, or object with a novel `toString` method but found %s instead.' +
|
||||
'React expect children of <title> tags to be a string, number%s, or object with a novel `toString` method but found %s instead.' +
|
||||
' Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title>' +
|
||||
' tags to a single string value.',
|
||||
enableBigIntSupport ? ', bigint' : '',
|
||||
childType,
|
||||
);
|
||||
} else if (child && child.toString === {}.toString) {
|
||||
if (child.$$typeof != null) {
|
||||
console.error(
|
||||
'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that appears to be' +
|
||||
'React expects the `children` prop of <title> tags to be a string, number%s, or object with a novel `toString` method but found an object that appears to be' +
|
||||
' a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to' +
|
||||
' be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is' +
|
||||
' a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
|
||||
enableBigIntSupport ? ', bigint' : '',
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that does not implement' +
|
||||
'React expects the `children` prop of <title> tags to be a string, number%s, or object with a novel `toString` method but found an object that does not implement' +
|
||||
' a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags' +
|
||||
' to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this <title>' +
|
||||
' is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid <title>.',
|
||||
enableBigIntSupport ? ', bigint' : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3123,14 +3130,17 @@ function pushStartTitle(
|
||||
} else if (
|
||||
childForValidation != null &&
|
||||
typeof childForValidation !== 'string' &&
|
||||
typeof childForValidation !== 'number'
|
||||
typeof childForValidation !== 'number' &&
|
||||
((enableBigIntSupport && typeof childForValidation !== 'bigint') ||
|
||||
!enableBigIntSupport)
|
||||
) {
|
||||
console.error(
|
||||
'A title element received a value that was not a string or number for children. ' +
|
||||
'A title element received a value that was not a string or number%s for children. ' +
|
||||
'In the browser title Elements can only have Text Nodes as children. If ' +
|
||||
'the children being rendered output more than a single text node in aggregate the browser ' +
|
||||
'will display markup and comments as text in the title and hydration will likely fail and ' +
|
||||
'fall back to client rendering',
|
||||
enableBigIntSupport ? ' or bigint' : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
*/
|
||||
|
||||
import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion';
|
||||
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const matchHtmlRegExp = /["'&<>]/;
|
||||
|
||||
@@ -106,7 +107,11 @@ function escapeHtml(string: string) {
|
||||
* @return {string} An escaped string.
|
||||
*/
|
||||
function escapeTextForBrowser(text: string | number | boolean): string {
|
||||
if (typeof text === 'boolean' || typeof text === 'number') {
|
||||
if (
|
||||
typeof text === 'boolean' ||
|
||||
typeof text === 'number' ||
|
||||
(enableBigIntSupport && typeof text === 'bigint')
|
||||
) {
|
||||
// this shortcircuit helps perf for types that we know will never have
|
||||
// special characters, especially given that this function is used often
|
||||
// for numeric dom ids.
|
||||
|
||||
@@ -60,6 +60,17 @@ describe('ReactDOMFiber', () => {
|
||||
expect(container.textContent).toEqual('10');
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('should render bigints as children', async () => {
|
||||
const Box = ({value}) => <div>{value}</div>;
|
||||
|
||||
await act(async () => {
|
||||
root.render(<Box value={10n} />);
|
||||
});
|
||||
|
||||
expect(container.textContent).toEqual('10');
|
||||
});
|
||||
|
||||
it('should call an effect after mount/update (replacing render callback pattern)', async () => {
|
||||
function Component() {
|
||||
React.useEffect(() => {
|
||||
|
||||
+129
-2
@@ -3373,6 +3373,17 @@ describe('ReactDOMFizzServer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('Supports bigint', async () => {
|
||||
await act(async () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
|
||||
<div>{10n}</div>,
|
||||
);
|
||||
pipe(writable);
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(<div>10</div>);
|
||||
});
|
||||
|
||||
it('Supports custom abort reasons with a string', async () => {
|
||||
function App() {
|
||||
return (
|
||||
@@ -5642,6 +5653,60 @@ describe('ReactDOMFizzServer', () => {
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
|
||||
});
|
||||
|
||||
it('should accept a single number child', async () => {
|
||||
// a Single number child
|
||||
function App() {
|
||||
return (
|
||||
<head>
|
||||
<title>4</title>
|
||||
</head>
|
||||
);
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
const {pipe} = renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>4</title>);
|
||||
|
||||
const errors = [];
|
||||
ReactDOMClient.hydrateRoot(container, <App />, {
|
||||
onRecoverableError(error) {
|
||||
errors.push(error.message);
|
||||
},
|
||||
});
|
||||
await waitForAll([]);
|
||||
expect(errors).toEqual([]);
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>4</title>);
|
||||
});
|
||||
|
||||
it('should accept a single bigint child', async () => {
|
||||
// a Single number child
|
||||
function App() {
|
||||
return (
|
||||
<head>
|
||||
<title>5n</title>
|
||||
</head>
|
||||
);
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
const {pipe} = renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>5n</title>);
|
||||
|
||||
const errors = [];
|
||||
ReactDOMClient.hydrateRoot(container, <App />, {
|
||||
onRecoverableError(error) {
|
||||
errors.push(error.message);
|
||||
},
|
||||
});
|
||||
await waitForAll([]);
|
||||
expect(errors).toEqual([]);
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>5n</title>);
|
||||
});
|
||||
|
||||
it('should accept children array of length 1 containing a string', async () => {
|
||||
// a Single string child
|
||||
function App() {
|
||||
@@ -5684,7 +5749,9 @@ describe('ReactDOMFizzServer', () => {
|
||||
pipe(writable);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an Array with length 2 instead. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes. For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
|
||||
'React expects the `children` prop of <title> tags to be a string, number' +
|
||||
gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) +
|
||||
', or object with a novel `toString` method but found an Array with length 2 instead. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert `children` of <title> tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be commong to combine text nodes and value nodes. For example: <title>hello {nameOfUser}</title>. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: <title>{`hello ${nameOfUser}`}</title>.',
|
||||
]);
|
||||
|
||||
if (gate(flags => flags.enableFloat)) {
|
||||
@@ -5744,7 +5811,67 @@ describe('ReactDOMFizzServer', () => {
|
||||
pipe(writable);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'React expects the `children` prop of <title> tags to be a string, number, or object with a novel `toString` method but found an object that appears to be a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
|
||||
'React expects the `children` prop of <title> tags to be a string, number' +
|
||||
gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) +
|
||||
', or object with a novel `toString` method but found an object that appears to be a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value which is why rendering React elements is not supported. If the `children` of <title> is a React Component try moving the <title> tag into that component. If the `children` of <title> is some HTML markup change it to be Text only to be valid HTML.',
|
||||
]);
|
||||
} else {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
const {pipe} = renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'A title element received a React element for children. In the browser title Elements can only have Text Nodes as children. If the children being rendered output more than a single text node in aggregate the browser will display markup and comments as text in the title and hydration will likely fail and fall back to client rendering',
|
||||
]);
|
||||
}
|
||||
|
||||
if (gate(flags => flags.enableFloat)) {
|
||||
// object titles are toStringed when float is on
|
||||
expect(getVisibleChildren(document.head)).toEqual(
|
||||
<title>{'[object Object]'}</title>,
|
||||
);
|
||||
} else {
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
ReactDOMClient.hydrateRoot(document.head, <App />, {
|
||||
onRecoverableError(error) {
|
||||
errors.push(error.message);
|
||||
},
|
||||
});
|
||||
await waitForAll([]);
|
||||
expect(errors).toEqual([]);
|
||||
if (gate(flags => flags.enableFloat)) {
|
||||
// object titles are toStringed when float is on
|
||||
expect(getVisibleChildren(document.head)).toEqual(
|
||||
<title>{'[object Object]'}</title>,
|
||||
);
|
||||
} else {
|
||||
expect(getVisibleChildren(document.head)).toEqual(<title>hello</title>);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn in dev if you pass an object that does not implement toString as a child to <title>', async () => {
|
||||
function App() {
|
||||
return (
|
||||
<head>
|
||||
<title>{{}}</title>
|
||||
</head>
|
||||
);
|
||||
}
|
||||
|
||||
if (gate(flags => flags.enableFloat)) {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
const {pipe} = renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'React expects the `children` prop of <title> tags to be a string, number' +
|
||||
gate(flags => (flags.enableBigIntSupport ? ', bigint' : '')) +
|
||||
', or object with a novel `toString` method but found an object that does not implement a suitable `toString` method. Browsers treat all child Nodes of <title> tags as Text content and React expects to be able to convert children of <title> tags to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this <title> is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid <title>.',
|
||||
]);
|
||||
} else {
|
||||
await expect(async () => {
|
||||
|
||||
@@ -657,6 +657,16 @@ describe('ReactDOMInput', () => {
|
||||
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
|
||||
});
|
||||
|
||||
it('should render bigint defaultValue for SSR', () => {
|
||||
const markup = ReactDOMServer.renderToString(
|
||||
<input type="text" defaultValue={5n} />,
|
||||
);
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = markup;
|
||||
expect(div.firstChild.getAttribute('value')).toBe('5');
|
||||
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
|
||||
});
|
||||
|
||||
it('should render value for SSR', () => {
|
||||
const element = <input type="text" value="1" onChange={() => {}} />;
|
||||
const markup = ReactDOMServer.renderToString(element);
|
||||
@@ -666,6 +676,15 @@ describe('ReactDOMInput', () => {
|
||||
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
|
||||
});
|
||||
|
||||
it('should render bigint value for SSR', () => {
|
||||
const element = <input type="text" value={5n} onChange={() => {}} />;
|
||||
const markup = ReactDOMServer.renderToString(element);
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = markup;
|
||||
expect(div.firstChild.getAttribute('value')).toBe('5');
|
||||
expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
|
||||
});
|
||||
|
||||
it('should render name attribute if it is supplied', async () => {
|
||||
await act(() => {
|
||||
root.render(<input type="text" name="name" />);
|
||||
@@ -830,6 +849,16 @@ describe('ReactDOMInput', () => {
|
||||
expect(node.value).toBe('0');
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('should display `value` of bigint 5', async () => {
|
||||
await act(() => {
|
||||
root.render(<input type="text" value={5n} onChange={emptyFunction} />);
|
||||
});
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('5');
|
||||
});
|
||||
|
||||
it('should allow setting `value` to `true`', async () => {
|
||||
await act(() => {
|
||||
root.render(<input type="text" value="yolo" onChange={emptyFunction} />);
|
||||
|
||||
@@ -172,6 +172,13 @@ describe('ReactDOMOption', () => {
|
||||
expect(node.value).toBe('hello');
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('should support bigint values', () => {
|
||||
const node = ReactTestUtils.renderIntoDocument(<option>{5n}</option>);
|
||||
expect(node.innerHTML).toBe('5');
|
||||
expect(node.value).toBe('5');
|
||||
});
|
||||
|
||||
it('should be able to use dangerouslySetInnerHTML on option', () => {
|
||||
const stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
|
||||
let node;
|
||||
|
||||
@@ -72,6 +72,16 @@ describe('ReactDOMServerIntegration', () => {
|
||||
expect(e.nodeValue).toMatch('42');
|
||||
});
|
||||
|
||||
itRenders('a bigint', async render => {
|
||||
const e = await render(42n);
|
||||
if (gate(flags => flags.enableBigIntSupport)) {
|
||||
expect(e.nodeType).toBe(3);
|
||||
expect(e.nodeValue).toMatch('42');
|
||||
} else {
|
||||
expect(e).toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
itRenders('an array with one child', async render => {
|
||||
const e = await render([<div key={1}>text1</div>]);
|
||||
const parent = e.parentNode;
|
||||
|
||||
@@ -32,7 +32,8 @@ function initModules() {
|
||||
};
|
||||
}
|
||||
|
||||
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
|
||||
const {resetModules, itRenders, serverRender, streamRender} =
|
||||
ReactDOMServerIntegrationUtils(initModules);
|
||||
|
||||
// TODO: Run this in React Fire mode after we figure out the SSR behavior.
|
||||
const desc = disableInputAttributeSyncing ? xdescribe : describe;
|
||||
@@ -46,6 +47,18 @@ desc('ReactDOMServerIntegrationInput', () => {
|
||||
expect(e.value).toBe('foo');
|
||||
});
|
||||
|
||||
itRenders('an input with a bigint value and an onChange', async render => {
|
||||
console.log(gate(flags => flags.enableBigIntSupport));
|
||||
const e = await render(<input value={5n} onChange={() => {}} />);
|
||||
expect(e.value).toBe(
|
||||
gate(flags => flags.enableBigIntSupport) ||
|
||||
render === serverRender ||
|
||||
render === streamRender
|
||||
? '5'
|
||||
: '',
|
||||
);
|
||||
});
|
||||
|
||||
itRenders('an input with a value and readOnly', async render => {
|
||||
const e = await render(<input value="foo" readOnly={true} />);
|
||||
expect(e.value).toBe('foo');
|
||||
|
||||
@@ -218,11 +218,15 @@ describe('ReactDOMServerIntegrationSelect', () => {
|
||||
itRenders('a select option with flattened children', async render => {
|
||||
const e = await render(
|
||||
<select value="bar" readOnly={true}>
|
||||
<option value="bar">A {'B'}</option>
|
||||
<option value="bar">
|
||||
A {'B'} {5n}
|
||||
</option>
|
||||
</select>,
|
||||
);
|
||||
const option = e.options[0];
|
||||
expect(option.textContent).toBe('A B');
|
||||
expect(option.textContent).toBe(
|
||||
gate(flags => flags.enableBigIntSupport) ? 'A B 5' : 'A B ',
|
||||
);
|
||||
expect(option.value).toBe('bar');
|
||||
expect(option.selected).toBe(true);
|
||||
});
|
||||
|
||||
+14
-2
@@ -30,7 +30,8 @@ function initModules() {
|
||||
};
|
||||
}
|
||||
|
||||
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
|
||||
const {resetModules, itRenders, serverRender, streamRender} =
|
||||
ReactDOMServerIntegrationUtils(initModules);
|
||||
|
||||
describe('ReactDOMServerIntegrationTextarea', () => {
|
||||
beforeEach(() => {
|
||||
@@ -47,12 +48,23 @@ describe('ReactDOMServerIntegrationTextarea', () => {
|
||||
expect(e.value).toBe('foo');
|
||||
});
|
||||
|
||||
itRenders('a textarea with a bigint value and an onChange', async render => {
|
||||
const e = await render(<textarea value={5n} onChange={() => {}} />);
|
||||
expect(e.getAttribute('value')).toBe(null);
|
||||
expect(e.value).toBe(
|
||||
gate(flags => flags.enableBigIntSupport) ||
|
||||
render === serverRender ||
|
||||
render === streamRender
|
||||
? '5'
|
||||
: '',
|
||||
);
|
||||
});
|
||||
|
||||
itRenders('a textarea with a value of undefined', async render => {
|
||||
const e = await render(<textarea value={undefined} />);
|
||||
expect(e.getAttribute('value')).toBe(null);
|
||||
expect(e.value).toBe('');
|
||||
});
|
||||
|
||||
itRenders('a textarea with a value and readOnly', async render => {
|
||||
const e = await render(<textarea value="foo" readOnly={true} />);
|
||||
// textarea DOM elements don't have a value **attribute**, the text is
|
||||
|
||||
@@ -80,6 +80,19 @@ describe('ReactDOMTextarea', () => {
|
||||
expect(node.value).toBe('0');
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('should display `defaultValue` of bigint 0', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
const node = await renderTextarea(
|
||||
<textarea defaultValue={0n} />,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
|
||||
expect(node.value).toBe('0');
|
||||
});
|
||||
|
||||
it('should display "false" for `defaultValue` of `false`', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
@@ -58,6 +58,7 @@ const expectChildren = function (container, children) {
|
||||
continue;
|
||||
}
|
||||
textNode = outerNode.childNodes[mountIndex];
|
||||
expect(textNode != null).toBe(true);
|
||||
expect(textNode.nodeType).toBe(3);
|
||||
expect(textNode.data).toBe(child);
|
||||
mountIndex++;
|
||||
@@ -173,6 +174,15 @@ describe('ReactMultiChildText', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('should correctly handle bigint children for render and update', async () => {
|
||||
// prettier-ignore
|
||||
await testAllPermutations([
|
||||
10n, '10',
|
||||
[10n], ['10']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw if rendering both HTML and children', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
+11
-2
@@ -272,7 +272,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
throw new Error('Error in host config.');
|
||||
}
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number' ||
|
||||
typeof props.children === 'bigint'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -828,7 +830,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
return childToJSX(child[0], null);
|
||||
}
|
||||
const children = child.map(c => childToJSX(c, null));
|
||||
if (children.every(c => typeof c === 'string' || typeof c === 'number')) {
|
||||
if (
|
||||
children.every(
|
||||
c =>
|
||||
typeof c === 'string' ||
|
||||
typeof c === 'number' ||
|
||||
typeof c === 'bigint',
|
||||
)
|
||||
) {
|
||||
return children.join('');
|
||||
}
|
||||
return children;
|
||||
|
||||
+13
-4
@@ -25,6 +25,7 @@ import {
|
||||
Forked,
|
||||
PlacementDEV,
|
||||
} from './ReactFiberFlags';
|
||||
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
getIteratorFn,
|
||||
REACT_ELEMENT_TYPE,
|
||||
@@ -665,12 +666,14 @@ function createChildReconciler(
|
||||
): Fiber | null {
|
||||
if (
|
||||
(typeof newChild === 'string' && newChild !== '') ||
|
||||
typeof newChild === 'number'
|
||||
typeof newChild === 'number' ||
|
||||
(enableBigIntSupport && typeof newChild === 'bigint')
|
||||
) {
|
||||
// Text nodes don't have keys. If the previous node is implicitly keyed
|
||||
// we can continue to replace it without aborting even if it is not a text
|
||||
// node.
|
||||
const created = createFiberFromText(
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
'' + newChild,
|
||||
returnFiber.mode,
|
||||
lanes,
|
||||
@@ -785,7 +788,8 @@ function createChildReconciler(
|
||||
|
||||
if (
|
||||
(typeof newChild === 'string' && newChild !== '') ||
|
||||
typeof newChild === 'number'
|
||||
typeof newChild === 'number' ||
|
||||
(enableBigIntSupport && typeof newChild === 'bigint')
|
||||
) {
|
||||
// Text nodes don't have keys. If the previous node is implicitly keyed
|
||||
// we can continue to replace it without aborting even if it is not a text
|
||||
@@ -796,6 +800,7 @@ function createChildReconciler(
|
||||
return updateTextNode(
|
||||
returnFiber,
|
||||
oldFiber,
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
'' + newChild,
|
||||
lanes,
|
||||
debugInfo,
|
||||
@@ -908,7 +913,8 @@ function createChildReconciler(
|
||||
): Fiber | null {
|
||||
if (
|
||||
(typeof newChild === 'string' && newChild !== '') ||
|
||||
typeof newChild === 'number'
|
||||
typeof newChild === 'number' ||
|
||||
(enableBigIntSupport && typeof newChild === 'bigint')
|
||||
) {
|
||||
// Text nodes don't have keys, so we neither have to check the old nor
|
||||
// new node for the key. If both are text nodes, they match.
|
||||
@@ -916,6 +922,7 @@ function createChildReconciler(
|
||||
return updateTextNode(
|
||||
returnFiber,
|
||||
matchedFiber,
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
'' + newChild,
|
||||
lanes,
|
||||
debugInfo,
|
||||
@@ -1723,12 +1730,14 @@ function createChildReconciler(
|
||||
|
||||
if (
|
||||
(typeof newChild === 'string' && newChild !== '') ||
|
||||
typeof newChild === 'number'
|
||||
typeof newChild === 'number' ||
|
||||
(enableBigIntSupport && typeof newChild === 'bigint')
|
||||
) {
|
||||
return placeSingleChild(
|
||||
reconcileSingleTextNode(
|
||||
returnFiber,
|
||||
currentFirstChild,
|
||||
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
|
||||
'' + newChild,
|
||||
lanes,
|
||||
),
|
||||
|
||||
@@ -39,4 +39,12 @@ describe('ReactTopLevelText', () => {
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('10');
|
||||
});
|
||||
|
||||
// @gate enableBigIntSupport
|
||||
it('should render a component returning bigints directly from render', async () => {
|
||||
const Text = ({value}) => value;
|
||||
ReactNoop.render(<Text value={10n} />);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('10');
|
||||
});
|
||||
});
|
||||
|
||||
+5
-1
@@ -139,6 +139,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
disableLegacyContext,
|
||||
disableModulePatternComponents,
|
||||
enableBigIntSupport,
|
||||
enableScopeAPI,
|
||||
enableSuspenseAvoidThisFallbackFizz,
|
||||
enableFloat,
|
||||
@@ -2364,7 +2365,10 @@ function renderNodeDestructive(
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof node === 'number') {
|
||||
if (
|
||||
typeof node === 'number' ||
|
||||
(enableBigIntSupport && typeof node === 'bigint')
|
||||
) {
|
||||
const segment = task.blockedSegment;
|
||||
if (segment === null) {
|
||||
// We assume a text node doesn't have a representation in the replay set,
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import isArray from 'shared/isArray';
|
||||
import {enableBigIntSupport} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
getIteratorFn,
|
||||
REACT_ELEMENT_TYPE,
|
||||
@@ -163,6 +164,11 @@ function mapIntoArray(
|
||||
invokeCallback = true;
|
||||
} else {
|
||||
switch (type) {
|
||||
case 'bigint':
|
||||
if (!enableBigIntSupport) {
|
||||
break;
|
||||
}
|
||||
// fallthrough for enabled BigInt support
|
||||
case 'string':
|
||||
case 'number':
|
||||
invokeCallback = true;
|
||||
|
||||
@@ -183,11 +183,14 @@ describe('ReactChildren', () => {
|
||||
{false}
|
||||
{null}
|
||||
{undefined}
|
||||
{9n}
|
||||
</div>
|
||||
);
|
||||
|
||||
function assertCalls() {
|
||||
expect(callback).toHaveBeenCalledTimes(9);
|
||||
expect(callback).toHaveBeenCalledTimes(
|
||||
gate(flags => flags.enableBigIntSupport) ? 10 : 9,
|
||||
);
|
||||
expect(callback).toHaveBeenCalledWith(div, 0);
|
||||
expect(callback).toHaveBeenCalledWith(span, 1);
|
||||
expect(callback).toHaveBeenCalledWith(a, 2);
|
||||
@@ -197,6 +200,11 @@ describe('ReactChildren', () => {
|
||||
expect(callback).toHaveBeenCalledWith(null, 6);
|
||||
expect(callback).toHaveBeenCalledWith(null, 7);
|
||||
expect(callback).toHaveBeenCalledWith(null, 8);
|
||||
if (gate(flags => flags.enableBigIntSupport)) {
|
||||
expect(callback).toHaveBeenCalledWith(9n, 9);
|
||||
} else {
|
||||
expect(callback).not.toHaveBeenCalledWith(9n, 9);
|
||||
}
|
||||
callback.mockClear();
|
||||
}
|
||||
|
||||
@@ -209,13 +217,24 @@ describe('ReactChildren', () => {
|
||||
context,
|
||||
);
|
||||
assertCalls();
|
||||
expect(mappedChildren).toEqual([
|
||||
<div key=".$divNode" />,
|
||||
<span key=".1:0:$spanNode" />,
|
||||
<a key=".2:$aNode" />,
|
||||
'string',
|
||||
1234,
|
||||
]);
|
||||
expect(mappedChildren).toEqual(
|
||||
gate(flags => flags.enableBigIntSupport)
|
||||
? [
|
||||
<div key=".$divNode" />,
|
||||
<span key=".1:0:$spanNode" />,
|
||||
<a key=".2:$aNode" />,
|
||||
'string',
|
||||
1234,
|
||||
9n,
|
||||
]
|
||||
: [
|
||||
<div key=".$divNode" />,
|
||||
<span key=".1:0:$spanNode" />,
|
||||
<a key=".2:$aNode" />,
|
||||
'string',
|
||||
1234,
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should be called for each child in nested structure', () => {
|
||||
|
||||
@@ -174,6 +174,8 @@ export const disableClientCache = false;
|
||||
// Changes Server Components Reconciliation when they have keys
|
||||
export const enableServerComponentKeys = __NEXT_MAJOR__;
|
||||
|
||||
export const enableBigIntSupport = __NEXT_MAJOR__;
|
||||
|
||||
/**
|
||||
* Enables a new error detection for infinite render loops from updates caused
|
||||
* by setState or similar outside of the component owning the state.
|
||||
|
||||
@@ -104,5 +104,7 @@ export const enableRefAsProp = false;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
|
||||
export const enableBigIntSupport = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -95,5 +95,7 @@ export const enableRefAsProp = false;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
|
||||
export const enableBigIntSupport = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -99,6 +99,7 @@ export const enableInfiniteRenderLoopDetection = false;
|
||||
const __NEXT_MAJOR__ = __EXPERIMENTAL__;
|
||||
export const enableRefAsProp = __NEXT_MAJOR__;
|
||||
export const enableReactTestRendererWarning = false;
|
||||
export const enableBigIntSupport = __NEXT_MAJOR__;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -91,5 +91,7 @@ export const enableRefAsProp = false;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
|
||||
export const enableBigIntSupport = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -94,5 +94,7 @@ export const enableRefAsProp = false;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
|
||||
export const enableBigIntSupport = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -119,5 +119,7 @@ export const enableServerComponentLogs = true;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
|
||||
export const enableBigIntSupport = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
Reference in New Issue
Block a user