diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index a14b5b376ce5e..d8657c3e98a58 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -25,6 +25,8 @@ jobs: matrix: ${{ steps.set-matrix.outputs.result }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/github-script@v7 id: set-matrix with: @@ -42,6 +44,8 @@ jobs: flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -64,6 +68,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -88,6 +94,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -139,6 +147,8 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -166,6 +176,8 @@ jobs: release_channel: [stable, experimental] steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -242,6 +254,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -272,6 +286,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -317,6 +333,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -350,6 +368,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -380,6 +400,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -419,6 +441,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -480,6 +504,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -528,6 +554,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -562,6 +590,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -603,6 +633,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' diff --git a/packages/internal-test-utils/consoleMock.js b/packages/internal-test-utils/consoleMock.js index 6d03c74c1dfee..561004a4dea76 100644 --- a/packages/internal-test-utils/consoleMock.js +++ b/packages/internal-test-utils/consoleMock.js @@ -273,6 +273,7 @@ function normalizeCodeLocInfo(str) { // We strip that out in our normalization to make it look more like component stacks. name = name.slice(0, name.length - 7); } + name = name.replace(/.*\/([^\/]+):\d+:\d+/, '**/$1:**:**'); return '\n in ' + name + ' (at **)'; }); } diff --git a/packages/react-server-dom-parcel/npm/server.edge.js b/packages/react-server-dom-parcel/npm/server.edge.js index 5f13279f75e2b..356cce93a70bc 100644 --- a/packages/react-server-dom-parcel/npm/server.edge.js +++ b/packages/react-server-dom-parcel/npm/server.edge.js @@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') { exports.renderToReadableStream = s.renderToReadableStream; exports.decodeReply = s.decodeReply; +exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable; exports.decodeAction = s.decodeAction; exports.decodeFormState = s.decodeFormState; exports.createClientReference = s.createClientReference; diff --git a/packages/react-server-dom-parcel/server.edge.js b/packages/react-server-dom-parcel/server.edge.js index 0974db3448fb7..42f5c3d65399c 100644 --- a/packages/react-server-dom-parcel/server.edge.js +++ b/packages/react-server-dom-parcel/server.edge.js @@ -10,6 +10,7 @@ export { renderToReadableStream, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, createClientReference, diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js index 73a8741618213..2a365993a7cfb 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js @@ -17,6 +17,8 @@ import { type ServerReferenceId, } from '../client/ReactFlightClientConfigBundlerParcel'; +import {ASYNC_ITERATOR} from 'shared/ReactSymbols'; + import { createRequest, createPrerenderRequest, @@ -30,6 +32,9 @@ import { createResponse, close, getRoot, + reportGlobalError, + resolveField, + resolveFile, } from 'react-server/src/ReactFlightReplyServer'; import { @@ -189,6 +194,50 @@ export function decodeReply( return root; } +export function decodeReplyFromAsyncIterable( + iterable: AsyncIterable<[string, string | File]>, + options?: {temporaryReferences?: TemporaryReferenceSet}, +): Thenable { + const iterator: AsyncIterator<[string, string | File]> = + iterable[ASYNC_ITERATOR](); + + const response = createResponse( + serverManifest, + '', + options ? options.temporaryReferences : undefined, + ); + + function progress( + entry: + | {done: false, +value: [string, string | File], ...} + | {done: true, +value: void, ...}, + ) { + if (entry.done) { + close(response); + } else { + const [name, value] = entry.value; + if (typeof value === 'string') { + resolveField(response, name, value); + } else { + resolveFile(response, name, value); + } + iterator.next().then(progress, error); + } + } + function error(reason: Error) { + reportGlobalError(response, reason); + if (typeof (iterator: any).throw === 'function') { + // The iterator protocol doesn't necessarily include this but a generator do. + // $FlowFixMe should be able to pass mixed + iterator.throw(reason).then(error, error); + } + } + + iterator.next().then(progress, error); + + return getRoot(response); +} + export function decodeAction(body: FormData): Promise<() => T> | null { return decodeActionImpl(body, serverManifest); } diff --git a/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js index c6b3067fbcfdb..54f3dbb2ec346 100644 --- a/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js +++ b/packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js @@ -11,6 +11,7 @@ export { renderToReadableStream, prerender as unstable_prerender, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, createClientReference, diff --git a/packages/react-server-dom-turbopack/npm/server.edge.js b/packages/react-server-dom-turbopack/npm/server.edge.js index e34b18fa0156a..c832080079dd5 100644 --- a/packages/react-server-dom-turbopack/npm/server.edge.js +++ b/packages/react-server-dom-turbopack/npm/server.edge.js @@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') { exports.renderToReadableStream = s.renderToReadableStream; exports.decodeReply = s.decodeReply; +exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable; exports.decodeAction = s.decodeAction; exports.decodeFormState = s.decodeFormState; exports.registerServerReference = s.registerServerReference; diff --git a/packages/react-server-dom-turbopack/server.edge.js b/packages/react-server-dom-turbopack/server.edge.js index c527c7f76a74f..8f0347cd7b5a7 100644 --- a/packages/react-server-dom-turbopack/server.edge.js +++ b/packages/react-server-dom-turbopack/server.edge.js @@ -10,6 +10,7 @@ export { renderToReadableStream, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, registerServerReference, diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js index 11dbe1a7c1358..e8256767fa5b1 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js @@ -12,6 +12,8 @@ import type {Thenable} from 'shared/ReactTypes'; import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler'; import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig'; +import {ASYNC_ITERATOR} from 'shared/ReactSymbols'; + import { createRequest, createPrerenderRequest, @@ -25,6 +27,9 @@ import { createResponse, close, getRoot, + reportGlobalError, + resolveField, + resolveFile, } from 'react-server/src/ReactFlightReplyServer'; import { @@ -183,10 +188,56 @@ function decodeReply( return root; } +function decodeReplyFromAsyncIterable( + iterable: AsyncIterable<[string, string | File]>, + turbopackMap: ServerManifest, + options?: {temporaryReferences?: TemporaryReferenceSet}, +): Thenable { + const iterator: AsyncIterator<[string, string | File]> = + iterable[ASYNC_ITERATOR](); + + const response = createResponse( + turbopackMap, + '', + options ? options.temporaryReferences : undefined, + ); + + function progress( + entry: + | {done: false, +value: [string, string | File], ...} + | {done: true, +value: void, ...}, + ) { + if (entry.done) { + close(response); + } else { + const [name, value] = entry.value; + if (typeof value === 'string') { + resolveField(response, name, value); + } else { + resolveFile(response, name, value); + } + iterator.next().then(progress, error); + } + } + function error(reason: Error) { + reportGlobalError(response, reason); + if (typeof (iterator: any).throw === 'function') { + // The iterator protocol doesn't necessarily include this but a generator do. + // $FlowFixMe should be able to pass mixed + iterator.throw(reason).then(error, error); + } + } + + iterator.next().then(progress, error); + + return getRoot(response); +} + export { renderToReadableStream, prerender, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, }; diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js index 48c4fc4553e6b..9198f9913ed37 100644 --- a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js @@ -11,6 +11,7 @@ export { renderToReadableStream, prerender as unstable_prerender, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, registerServerReference, diff --git a/packages/react-server-dom-webpack/npm/server.edge.js b/packages/react-server-dom-webpack/npm/server.edge.js index 591b84476884d..51a58ea7a9e30 100644 --- a/packages/react-server-dom-webpack/npm/server.edge.js +++ b/packages/react-server-dom-webpack/npm/server.edge.js @@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') { exports.renderToReadableStream = s.renderToReadableStream; exports.decodeReply = s.decodeReply; +exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable; exports.decodeAction = s.decodeAction; exports.decodeFormState = s.decodeFormState; exports.registerServerReference = s.registerServerReference; diff --git a/packages/react-server-dom-webpack/server.edge.js b/packages/react-server-dom-webpack/server.edge.js index c527c7f76a74f..8f0347cd7b5a7 100644 --- a/packages/react-server-dom-webpack/server.edge.js +++ b/packages/react-server-dom-webpack/server.edge.js @@ -10,6 +10,7 @@ export { renderToReadableStream, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, registerServerReference, diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js index f6157dff171d7..2effa9868e99b 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js @@ -272,4 +272,40 @@ describe('ReactFlightDOMReplyEdge', () => { expect(error).not.toBe(null); expect(error.message).toBe('Connection closed.'); }); + + it('can stream the decoding using an async iterable', async () => { + let resolve; + const promise = new Promise(r => (resolve = r)); + + const buffer = new Uint8Array([ + 123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20, + ]); + + const formData = await ReactServerDOMClient.encodeReply({ + a: Promise.resolve('hello'), + b: Promise.resolve(buffer), + }); + + const iterable = { + async *[Symbol.asyncIterator]() { + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (const entry of formData) { + yield entry; + await promise; + } + }, + }; + + const decoded = await ReactServerDOMServer.decodeReplyFromAsyncIterable( + iterable, + webpackServerMap, + ); + + expect(Object.keys(decoded)).toEqual(['a', 'b']); + + await resolve(); + + expect(await decoded.a).toBe('hello'); + expect(Array.from(await decoded.b)).toEqual(Array.from(buffer)); + }); }); diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js index 7954417b95a25..e5b834be0543f 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js @@ -12,6 +12,8 @@ import type {Thenable} from 'shared/ReactTypes'; import type {ClientManifest} from './ReactFlightServerConfigWebpackBundler'; import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig'; +import {ASYNC_ITERATOR} from 'shared/ReactSymbols'; + import { createRequest, createPrerenderRequest, @@ -25,6 +27,9 @@ import { createResponse, close, getRoot, + reportGlobalError, + resolveField, + resolveFile, } from 'react-server/src/ReactFlightReplyServer'; import { @@ -183,10 +188,56 @@ function decodeReply( return root; } +function decodeReplyFromAsyncIterable( + iterable: AsyncIterable<[string, string | File]>, + webpackMap: ServerManifest, + options?: {temporaryReferences?: TemporaryReferenceSet}, +): Thenable { + const iterator: AsyncIterator<[string, string | File]> = + iterable[ASYNC_ITERATOR](); + + const response = createResponse( + webpackMap, + '', + options ? options.temporaryReferences : undefined, + ); + + function progress( + entry: + | {done: false, +value: [string, string | File], ...} + | {done: true, +value: void, ...}, + ) { + if (entry.done) { + close(response); + } else { + const [name, value] = entry.value; + if (typeof value === 'string') { + resolveField(response, name, value); + } else { + resolveFile(response, name, value); + } + iterator.next().then(progress, error); + } + } + function error(reason: Error) { + reportGlobalError(response, reason); + if (typeof (iterator: any).throw === 'function') { + // The iterator protocol doesn't necessarily include this but a generator do. + // $FlowFixMe should be able to pass mixed + iterator.throw(reason).then(error, error); + } + } + + iterator.next().then(progress, error); + + return getRoot(response); +} + export { renderToReadableStream, prerender, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, }; diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js index 48c4fc4553e6b..9198f9913ed37 100644 --- a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js @@ -11,6 +11,7 @@ export { renderToReadableStream, prerender as unstable_prerender, decodeReply, + decodeReplyFromAsyncIterable, decodeAction, decodeFormState, registerServerReference, diff --git a/packages/react/src/__tests__/ReactChildren-test.js b/packages/react/src/__tests__/ReactChildren-test.js index 2723755abfe4a..5cce6b24873f0 100644 --- a/packages/react/src/__tests__/ReactChildren-test.js +++ b/packages/react/src/__tests__/ReactChildren-test.js @@ -13,12 +13,13 @@ describe('ReactChildren', () => { let React; let ReactDOMClient; let act; + let assertConsoleErrorDev; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); it('should support identity for simple', () => { @@ -331,14 +332,16 @@ describe('ReactChildren', () => { callback.mockClear(); } - let instance; - expect(() => { - instance =
{threeDivIterable}
; - }).toErrorDev( + const instance =
{threeDivIterable}
; + assertConsoleErrorDev( // With the flag on this doesn't warn eagerly but only when rendered gate(flag => flag.enableOwnerStacks) ? [] - : ['Each child in a list should have a unique "key" prop.'], + : [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using
. See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ], ); React.Children.forEach(instance.props.children, callback, context); @@ -359,11 +362,16 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(instance); - }); - }).toErrorDev('Each child in a list should have a unique "key" prop.'); + await act(() => { + root.render(instance); + }); + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using
. It was passed a child from div.' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)' + + (gate(flag => flag.enableOwnerStacks) ? '' : '\n in div (at **)'), + ]); }); it('should be called for each child in an iterable with keys', () => { @@ -879,15 +887,29 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( - - {[
]} - , - ); - }); - }).toErrorDev(['Each child in a list should have a unique "key" prop.']); + await act(() => { + root.render( + + {[
]} + , + ); + }); + assertConsoleErrorDev( + gate(flags => flags.enableOwnerStacks) + ? [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the render method of `ComponentRenderingMappedChildren`.' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)\n' + + ' in **/ReactChildren-test.js:**:** (at **)', + ] + : [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using .' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ], + ); }); it('does not warn for mapped static children without keys', async () => { @@ -903,16 +925,14 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( - -
-
- , - ); - }); - }).toErrorDev([]); + await act(() => { + root.render( + +
+
+ , + ); + }); }); it('warns for cloned list children without keys', async () => { @@ -926,15 +946,28 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( - - {[
]} - , - ); - }); - }).toErrorDev(['Each child in a list should have a unique "key" prop.']); + await act(() => { + root.render( + + {[
]} + , + ); + }); + assertConsoleErrorDev( + gate(flags => flags.enableOwnerStacks) + ? [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the render method of `ComponentRenderingClonedChildren`.' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ] + : [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using .' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ], + ); }); it('does not warn for cloned static children without keys', async () => { @@ -948,16 +981,14 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( - -
-
- , - ); - }); - }).toErrorDev([]); + await act(() => { + root.render( + +
+
+ , + ); + }); }); it('warns for flattened list children without keys', async () => { @@ -967,15 +998,28 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( - - {[
]} - , - ); - }); - }).toErrorDev(['Each child in a list should have a unique "key" prop.']); + await act(() => { + root.render( + + {[
]} + , + ); + }); + assertConsoleErrorDev( + gate(flags => flags.enableOwnerStacks) + ? [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the render method of `ComponentRenderingFlattenedChildren`.' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ] + : [ + 'Each child in a list should have a unique "key" prop.\n\n' + + 'Check the top-level render call using .' + + ' See https://react.dev/link/warning-keys for more information.\n' + + ' in div (at **)', + ], + ); }); it('does not warn for flattened static children without keys', async () => { @@ -985,16 +1029,14 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render( - -
-
- , - ); - }); - }).toErrorDev([]); + await act(() => { + root.render( + +
+
+ , + ); + }); }); it('should escape keys', () => { @@ -1153,18 +1195,16 @@ describe('ReactChildren', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( - '' + - 'Each child in a list should have a unique "key" prop.' + + await act(() => { + root.render(); + }); + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . It was passed a child from ComponentReturningArray. ' + 'See https://react.dev/link/warning-keys for more information.' + '\n in div (at **)' + '\n in ComponentReturningArray (at **)', - ); + ]); }); it('does not warn when there are keys on elements in a fragment', async () => { @@ -1184,17 +1224,15 @@ describe('ReactChildren', () => { it('warns for keys for arrays at the top level', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render([
,
]); - }); - }).toErrorDev( - '' + - 'Each child in a list should have a unique "key" prop.' + + await act(() => { + root.render([
,
]); + }); + assertConsoleErrorDev([ + 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . ' + 'See https://react.dev/link/warning-keys for more information.' + '\n in div (at **)', - ); + ]); }); }); }); diff --git a/packages/react/src/__tests__/forwardRef-test.js b/packages/react/src/__tests__/forwardRef-test.js index f0f3bb4d5023d..e08990c806355 100644 --- a/packages/react/src/__tests__/forwardRef-test.js +++ b/packages/react/src/__tests__/forwardRef-test.js @@ -13,6 +13,7 @@ describe('forwardRef', () => { let React; let ReactNoop; let waitForAll; + let assertConsoleErrorDev; beforeEach(() => { jest.resetModules(); @@ -21,6 +22,7 @@ describe('forwardRef', () => { const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; + assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev; }); it('should update refs when switching between children', async () => { @@ -114,25 +116,31 @@ describe('forwardRef', () => { }); it('should warn if not provided a callback during creation', () => { - expect(() => React.forwardRef(undefined)).toErrorDev( - 'forwardRef requires a render function but was given undefined.', + React.forwardRef(undefined); + assertConsoleErrorDev( + ['forwardRef requires a render function but was given undefined.'], {withoutStack: true}, ); - expect(() => React.forwardRef(null)).toErrorDev( - 'forwardRef requires a render function but was given null.', + + React.forwardRef(null); + assertConsoleErrorDev( + ['forwardRef requires a render function but was given null.'], { withoutStack: true, }, ); - expect(() => React.forwardRef('foo')).toErrorDev( - 'forwardRef requires a render function but was given string.', + + React.forwardRef('foo'); + assertConsoleErrorDev( + ['forwardRef requires a render function but was given string.'], {withoutStack: true}, ); }); it('should warn if no render function is provided', () => { - expect(React.forwardRef).toErrorDev( - 'forwardRef requires a render function but was given undefined.', + React.forwardRef(); + assertConsoleErrorDev( + ['forwardRef requires a render function but was given undefined.'], {withoutStack: true}, ); }); @@ -143,9 +151,12 @@ describe('forwardRef', () => { } renderWithDefaultProps.defaultProps = {}; - expect(() => React.forwardRef(renderWithDefaultProps)).toErrorDev( - 'forwardRef render functions do not support defaultProps. ' + - 'Did you accidentally pass a React component?', + React.forwardRef(renderWithDefaultProps); + assertConsoleErrorDev( + [ + 'forwardRef render functions do not support defaultProps. ' + + 'Did you accidentally pass a React component?', + ], {withoutStack: true}, ); }); @@ -159,9 +170,12 @@ describe('forwardRef', () => { it('should warn if the render function provided does not use the forwarded ref parameter', () => { const arityOfOne = props =>
; - expect(() => React.forwardRef(arityOfOne)).toErrorDev( - 'forwardRef render functions accept exactly two parameters: props and ref. ' + - 'Did you forget to use the ref parameter?', + React.forwardRef(arityOfOne); + assertConsoleErrorDev( + [ + 'forwardRef render functions accept exactly two parameters: props and ref. ' + + 'Did you forget to use the ref parameter?', + ], {withoutStack: true}, ); }); @@ -174,9 +188,12 @@ describe('forwardRef', () => { it('should warn if the render function provided expects to use more than two parameters', () => { const arityOfThree = (props, ref, x) =>
; - expect(() => React.forwardRef(arityOfThree)).toErrorDev( - 'forwardRef render functions accept exactly two parameters: props and ref. ' + - 'Any additional parameter will be undefined.', + React.forwardRef(arityOfThree); + assertConsoleErrorDev( + [ + 'forwardRef render functions accept exactly two parameters: props and ref. ' + + 'Any additional parameter will be undefined.', + ], {withoutStack: true}, ); }); @@ -190,15 +207,16 @@ describe('forwardRef', () => {

, ); - await expect(async () => { - await waitForAll([]); - }).toErrorDev( + await waitForAll([]); + assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . It was passed a child from ForwardRef. ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in span (at **)\n' + - ' in ', - ); + (gate(flags => flags.enableOwnerStacks) + ? ' in **/forwardRef-test.js:**:** (at **)' + : ' in p (at **)'), + ]); }); it('should use the inner function name for the stack', async () => { @@ -210,16 +228,16 @@ describe('forwardRef', () => {

, ); - await expect(async () => { - await waitForAll([]); - }).toErrorDev( + + await waitForAll([]); + assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . It was passed a child from ForwardRef(Inner). ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in span (at **)\n' + ' in Inner (at **)' + (gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'), - ); + ]); }); it('should use the inner name in the stack', async () => { @@ -233,16 +251,15 @@ describe('forwardRef', () => {

, ); - await expect(async () => { - await waitForAll([]); - }).toErrorDev( + await waitForAll([]); + assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . It was passed a child from ForwardRef(Inner). ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in span (at **)\n' + ' in Inner (at **)' + (gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'), - ); + ]); }); it('can use the outer displayName in the stack', async () => { @@ -255,16 +272,15 @@ describe('forwardRef', () => {

, ); - await expect(async () => { - await waitForAll([]); - }).toErrorDev( + await waitForAll([]); + assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . It was passed a child from Outer. ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in span (at **)\n' + ' in Outer (at **)' + (gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'), - ); + ]); }); it('should prefer the inner name to the outer displayName in the stack', async () => { @@ -279,16 +295,15 @@ describe('forwardRef', () => {

, ); - await expect(async () => { - await waitForAll([]); - }).toErrorDev( + await waitForAll([]); + assertConsoleErrorDev([ 'Each child in a list should have a unique "key" prop.' + '\n\nCheck the top-level render call using . It was passed a child from Outer. ' + 'See https://react.dev/link/warning-keys for more information.\n' + ' in span (at **)\n' + ' in Inner (at **)' + (gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'), - ); + ]); }); it('should not bailout if forwardRef is not wrapped in memo', async () => { @@ -419,13 +434,12 @@ describe('forwardRef', () => { }); it('warns on forwardRef(memo(...))', () => { - expect(() => { - React.forwardRef( - React.memo((props, ref) => { - return null; - }), - ); - }).toErrorDev( + React.forwardRef( + React.memo((props, ref) => { + return null; + }), + ); + assertConsoleErrorDev( [ 'forwardRef requires a render function but received a `memo` ' + 'component. Instead of forwardRef(memo(...)), use ' + diff --git a/packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js b/packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js new file mode 100644 index 0000000000000..d9d3dc733860f --- /dev/null +++ b/packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js @@ -0,0 +1,18 @@ +/** + * 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. + * + * @flow + */ + +export const enableProfiling = __DEV__; +export const frameYieldMs = 5; + +export const userBlockingPriorityTimeout = 250; +export const normalPriorityTimeout = 5000; +export const lowPriorityTimeout = 10000; +export const enableRequestPaint = true; + +export const enableAlwaysYieldScheduler = false; diff --git a/scripts/release/shared-commands/download-build-artifacts.js b/scripts/release/shared-commands/download-build-artifacts.js index 85cbb8b9fa176..cf7301688027a 100644 --- a/scripts/release/shared-commands/download-build-artifacts.js +++ b/scripts/release/shared-commands/download-build-artifacts.js @@ -43,12 +43,7 @@ async function getWorkflowRun(commit) { ); const json = JSON.parse(res.stdout); - const workflowRun = json.workflow_runs.find( - run => - run.head_sha === commit && - run.status === 'completed' && - run.conclusion === 'success' - ); + const workflowRun = json.workflow_runs.find(run => run.head_sha === commit); if (workflowRun == null || workflowRun.id == null) { console.log( diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index 2d6f718c76360..5460304366253 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -195,14 +195,18 @@ const forks = Object.freeze({ entry, dependencies ) => { - if ( - bundleType === FB_WWW_DEV || - bundleType === FB_WWW_PROD || - bundleType === FB_WWW_PROFILING - ) { - return './packages/scheduler/src/forks/SchedulerFeatureFlags.www.js'; + switch (bundleType) { + case FB_WWW_DEV: + case FB_WWW_PROD: + case FB_WWW_PROFILING: + return './packages/scheduler/src/forks/SchedulerFeatureFlags.www.js'; + case RN_FB_DEV: + case RN_FB_PROD: + case RN_FB_PROFILING: + return './packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js'; + default: + return './packages/scheduler/src/SchedulerFeatureFlags.js'; } - return './packages/scheduler/src/SchedulerFeatureFlags.js'; }, './packages/shared/consoleWithStackDev.js': (bundleType, entry) => {