Skip to content
Closed
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
247 changes: 245 additions & 2 deletions packages/react-dom/src/__tests__/ReactFresh-test.internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('ReactFresh', () => {
let updatedFamilies;
let performHotReload;
let signaturesByType;
let hostNodesForVisualFeedback;

beforeEach(() => {
let scheduleHotUpdate;
Expand All @@ -45,19 +46,20 @@ describe('ReactFresh', () => {
Scheduler = require('scheduler');
act = require('react-dom/test-utils').act;
container = document.createElement('div');
container.className = 'container';
document.body.appendChild(container);

familiesByID = new Map();
familiesByType = new WeakMap();

if (__DEV__) {
performHotReload = function(staleFamilies) {
scheduleHotUpdate({
hostNodesForVisualFeedback = scheduleHotUpdate({
root: lastRoot,
familiesByType,
updatedFamilies,
staleFamilies,
});
}).hostNodesForVisualFeedback;
};
}
});
Expand Down Expand Up @@ -98,6 +100,7 @@ describe('ReactFresh', () => {
newFamilies = new Set();
updatedFamilies = new Set();
signaturesByType = new Map();
hostNodesForVisualFeedback = null;
const Component = version();

// Fill in the signatures.
Expand Down Expand Up @@ -2796,4 +2799,244 @@ describe('ReactFresh', () => {
expect(helloNode.textContent).toBe('Nice.');
}
});

it('provides visual feedback by returning related host nodes', () => {
if (__DEV__) {
let ParentV1;
render(() => {
function Child({children}) {
return <div className="Child">{children}</div>;
}
__register__(Child, 'Child');

function Parent({children}) {
return (
<div className="Parent">
<div>
<Child />
</div>
<div>
<Child />
</div>
</div>
);
}
__register__(Parent, 'Parent');
ParentV1 = Parent;

function App() {
return (
<div className="App">
<Parent />
<Parent />
</div>
);
}
__register__(App, 'App');

return App;
});

// First, edit Child alone.
// This should flash only the four Child nodes.
patch(() => {
function Child({children}) {
return <div className="Child">{children}</div>;
}
__register__(Child, 'Child');
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'Child',
'Child',
'Child',
'Child',
]);

// Edit the App alone.
// This should flash just its root node.
patch(() => {
function App() {
return (
<div className="App">
<ParentV1 />
<ParentV1 />
</div>
);
}
__register__(App, 'App');
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'App',
]);

// Edit *both* Parent and Child.
// Parent contains Child, but we only want to flash Parent nodes.
// Visual feedback includes only the outermost edited components.
patch(() => {
function Child({children}) {
return <div className="Child">{children}</div>;
}
__register__(Child, 'Child');

function Parent({children}) {
return (
<div className="Parent">
<div>
<Child />
</div>
<div>
<Child />
</div>
</div>
);
}
__register__(Parent, 'Parent');
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'Parent',
'Parent',
]);
}
});

it('uses closest parent nodes for visual feedback when there is no children', () => {
if (__DEV__) {
render(() => {
function Child({children}) {
return <div className="Child">{children}</div>;
}
__register__(Child, 'Child');

function Parent({children}) {
return (
<div className="Parent">
<div className="Parent-childWrapper">
<Child />
</div>
<div className="Parent-childWrapper">
<Child />
</div>
</div>
);
}
__register__(Parent, 'Parent');

function App() {
return (
<div className="App">
<Parent />
<Parent />
</div>
);
}
__register__(App, 'App');

return App;
});

// Child doesn't have its own host node anymore,
// so we expect to find closest parent host node instead.
patch(() => {
function Child({children}) {
return null;
}
__register__(Child, 'Child');
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'Parent-childWrapper',
'Parent-childWrapper',
'Parent-childWrapper',
'Parent-childWrapper',
]);

// Now Child will have more than one child node.
// Expect to find them all.
patch(() => {
function Child({children}) {
return (
<React.Fragment>
<p className="Child-p1" />
<p className="Child-p2" />
</React.Fragment>
);
}
__register__(Child, 'Child');
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'Child-p1',
'Child-p2',
'Child-p1',
'Child-p2',
'Child-p1',
'Child-p2',
'Child-p1',
'Child-p2',
]);

// We should find the root host node if there's nothing else above.
patch(() => {
function App() {
return null;
}
__register__(App, 'App');
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'container',
]);
}
});

it('reports visual feedback for parents of deleted nodes', () => {
if (__DEV__) {
render(() => {
function Child({children}) {
return <div className="Child">{children}</div>;
}
__register__(Child, 'Child');
__signature__(Child, '1');

function Parent({children}) {
return (
<div className="Parent">
<div className="Parent-childWrapper">
<Child />
</div>
<div className="Parent-childWrapper">
<Child />
</div>
</div>
);
}
__register__(Parent, 'Parent');

function App() {
return (
<div className="App">
<Parent />
<Parent />
</div>
);
}
__register__(App, 'App');

return App;
});

// This edit will remount the Child component.
// As a result, we should highlight the wrappers instead.
patch(() => {
function Child({children}) {
return <div className="Child">{children}</div>;
}
__register__(Child, 'Child');
__signature__(Child, '2'); // This will force a remount.
});
expect(hostNodesForVisualFeedback.map(node => node.className)).toEqual([
'Parent-childWrapper',
'Parent-childWrapper',
'Parent-childWrapper',
'Parent-childWrapper',
]);
}
});
});
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {onCommitUnmount} from './ReactFiberDevToolsHook';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
import {logCapturedError} from './ReactFiberErrorLogger';
import {markDeletedFiberForHotReloading} from './ReactFiberHotReloading';
import {resolveDefaultProps} from './ReactFiberLazyComponent';
import {getCommitTime} from './ReactProfilerTimer';
import {commitUpdateQueue} from './ReactUpdateQueue';
Expand Down Expand Up @@ -738,6 +739,9 @@ function commitDetachRef(current: Fiber) {
// interrupt deletion, so it's okay
function commitUnmount(current: Fiber): void {
onCommitUnmount(current);
if (__DEV__) {
markDeletedFiberForHotReloading(current);
}

switch (current.tag) {
case FunctionComponent:
Expand Down
Loading