'use strict';
let React;
let Scheduler;
let ReactNoop;
let act;
let assertLog;
describe('ReactFiberRefs', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
Scheduler = require('scheduler');
ReactNoop = require('react-noop-renderer');
act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;
});
it('ref is attached even if there are no other updates (class)', async () => {
let component;
class Component extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
Scheduler.log('Render');
component = this;
return 'Hi';
}
}
const ref1 = React.createRef();
const ref2 = React.createRef();
const root = ReactNoop.createRoot();
await act(() => root.render(<Component ref={ref1} />));
assertLog(['Render']);
expect(root).toMatchRenderedOutput('Hi');
expect(ref1.current).toBe(component);
expect(ref2.current).toBe(null);
await act(() => root.render(<Component ref={ref2} />));
assertLog([]);
expect(root).toMatchRenderedOutput('Hi');
expect(ref1.current).toBe(null);
expect(ref2.current).toBe(component);
});
it('ref is attached even if there are no other updates (host component)', async () => {
const ref1 = React.createRef();
const ref2 = React.createRef();
const root = ReactNoop.createRoot();
await act(() => root.render(<div ref={ref1}>Hi</div>));
expect(root).toMatchRenderedOutput(<div>Hi</div>);
expect(ref1.current).not.toBe(null);
expect(ref2.current).toBe(null);
await act(() => root.render(<div ref={ref2}>Hi</div>));
expect(root).toMatchRenderedOutput(<div>Hi</div>);
expect(ref1.current).toBe(null);
expect(ref2.current).not.toBe(null);
});
it('throw if a string ref is passed to a ref-receiving component', async () => {
let refProp;
function Child({ref}) {
refProp = ref;
return <div ref={ref} />;
}
class Owner extends React.Component {
render() {
return <Child ref="child" />;
}
}
const root = ReactNoop.createRoot();
await expect(act(() => root.render(<Owner />))).rejects.toThrow(
'Expected ref to be a function',
);
expect(refProp).toBe('child');
});
it('strings refs can be codemodded to callback refs', async () => {
let app;
class App extends React.Component {
render() {
app = this;
return (
<div
prop="Hello!"
ref={el => {
// `refs` used to be a shared frozen object unless/until a string
// ref attached by the reconciler, but it's not anymore so that we
// can codemod string refs to userspace callback refs.
this.refs.div = el;
}}
/>
);
}
}
const root = ReactNoop.createRoot();
await act(() => root.render(<App />));
expect(app.refs.div.prop).toBe('Hello!');
});
it('class refs are initialized to a frozen shared object', async () => {
const refsCollection = new Set();
class Component extends React.Component {
constructor(props) {
super(props);
refsCollection.add(this.refs);
}
render() {
return <div />;
}
}
const root = ReactNoop.createRoot();
await act(() =>
root.render(
<>
<Component />
<Component />
</>,
),
);
expect(refsCollection.size).toBe(1);
const refsInstance = Array.from(refsCollection)[0];
expect(Object.isFrozen(refsInstance)).toBe(__DEV__);
});
});