Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opted for a new test. The store test is already getting crowded and should be split into Activty, Suspense etc versions anyway.

Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* 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
*/
import {getVersionedRenderImplementation} from './utils';

describe('Store React.optimisticKey', () => {
let act;
let actAsync;
let React;
let TestRenderer;
let bridge;
let store;

let BridgeContext;
let StoreContext;
let TreeContext;

let dispatch;
let state;

beforeAll(() => {
// JSDDOM doesn't implement getClientRects so we're just faking one for testing purposes
Element.prototype.getClientRects = function (this: Element) {
const textContent = this.textContent;
return [
new DOMRect(1, 2, textContent.length, textContent.split('\n').length),
];
};
});

beforeEach(() => {
global.IS_REACT_ACT_ENVIRONMENT = true;

store = global.store;
bridge = global.bridge;

React = require('react');

const utils = require('./utils');
act = utils.act;
actAsync = utils.actAsync;
TestRenderer = utils.requireTestRenderer();

BridgeContext =
require('react-devtools-shared/src/devtools/views/context').BridgeContext;
StoreContext =
require('react-devtools-shared/src/devtools/views/context').StoreContext;
TreeContext = require('react-devtools-shared/src/devtools/views/Components/TreeContext');
});

const {render} = getVersionedRenderImplementation();

const Capture = () => {
dispatch = React.useContext(TreeContext.TreeDispatcherContext);
state = React.useContext(TreeContext.TreeStateContext);
return null;
};

const Contexts = () => {
return (
<BridgeContext.Provider value={bridge}>
<StoreContext.Provider value={store}>
<TreeContext.TreeContextController>
<Capture />
</TreeContext.TreeContextController>
</StoreContext.Provider>
</BridgeContext.Provider>
);
};

// @reactVersion >= 19.3
it('is included in the tree', async () => {
if (React.optimisticKey === undefined) {
return;
}

function Component() {
return null;
}

await actAsync(() => {
render(<Component key={React.optimisticKey} />);
});

expect(store).toMatchInlineSnapshot(`
[root]
<Component key="React.optimisticKey">
`);
expect(store.getElementAtIndex(0)).toEqual(
expect.objectContaining({key: 'React.optimisticKey'}),
);
});

// @reactVersion >= 19.3
it('is searchable', async () => {
if (React.optimisticKey === undefined) {
return;
}
await actAsync(() => {
render(<React.Fragment key={React.optimisticKey} />);
});
let renderer;
act(() => (renderer = TestRenderer.create(<Contexts />)));

expect(state).toMatchInlineSnapshot(`
[root]
<Fragment key="React.optimisticKey">
`);

act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'optimistic'}));
act(() => renderer.update(<Contexts />));

expect(state).toMatchInlineSnapshot(`
[root]
<Fragment key="React.optimisticKey">
`);

act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'react'}));
act(() => renderer.update(<Contexts />));

expect(state).toMatchInlineSnapshot(`
[root]
→ <Fragment key="React.optimisticKey">
`);
});
});
21 changes: 17 additions & 4 deletions packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2606,7 +2606,12 @@ export function attach(

// This check is a guard to handle a React element that has been modified
// in such a way as to bypass the default stringification of the "key" property.
const keyString = key === null ? null : String(key);
const keyString =
key === null
? null
: key === REACT_OPTIMISTIC_KEY
? 'React.optimisticKey'
: String(key);
const keyStringID = getStringID(keyString);

const nameProp =
Expand Down Expand Up @@ -6179,7 +6184,10 @@ export function attach(
return {
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
id: instance.id,
key: fiber.key === REACT_OPTIMISTIC_KEY ? null : fiber.key,
key:
fiber.key === REACT_OPTIMISTIC_KEY
? 'React.optimisticKey'
: fiber.key,
env: null,
stack:
fiber._debugOwner == null || fiber._debugStack == null
Expand All @@ -6195,7 +6203,7 @@ export function attach(
key:
componentInfo.key == null ||
componentInfo.key === REACT_OPTIMISTIC_KEY
? null
? 'React.optimisticKey'
: componentInfo.key,
env: componentInfo.env == null ? null : componentInfo.env,
stack:
Expand Down Expand Up @@ -7120,7 +7128,12 @@ export function attach(
// Does the component have legacy context attached to it.
hasLegacyContext,

key: key != null && key !== REACT_OPTIMISTIC_KEY ? key : null,
key:
key != null
? key === REACT_OPTIMISTIC_KEY
? 'React.optimisticKey'
: key
: null,

type: elementType,

Expand Down
Loading