Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for unstable_Profiler to mount API #2055

Merged
merged 1 commit into from
Apr 5, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ function toTree(vnode) {
case FiberTags.ContextProvider:
case FiberTags.ContextConsumer:
return childrenToTree(node.child);
case FiberTags.Profiler:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was the mistake. I previously included this in the same branch as the context tags, strict mode etc. That branch will not be considered a node. At least this is how I understood it.

There's probably an argument to be made that they should also be considered as valid nodes (since they also appear in reacts component stack and devtools) but that's for another time (personally I'm not interested in those components WRT to find/debug).

Not sure if Profiler should be handled exactly like forwardRef but tests are passing for now so 🤷‍♂️

Copy link
Member

Choose a reason for hiding this comment

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

hmm - if they appear in react devtools, and the stack, then they definitely need to appear in debug.

case FiberTags.ForwardRef: {
return {
nodeType: 'function',
Expand Down
4 changes: 4 additions & 0 deletions packages/enzyme-adapter-react-16/src/detectFiberTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = function detectFiberTags() {
const supportsContext = typeof React.createContext !== 'undefined';
const supportsForwardRef = typeof React.forwardRef !== 'undefined';
const supportsMemo = typeof React.memo !== 'undefined';
const supportsProfiler = typeof React.unstable_Profiler !== 'undefined';

function Fn() {
return null;
Expand Down Expand Up @@ -66,5 +67,8 @@ module.exports = function detectFiberTags() {
ForwardRef: supportsForwardRef
? getFiber(React.createElement(FwdRef)).tag
: -1,
Profiler: supportsProfiler
? getFiber(React.createElement(React.unstable_Profiler, { id: 'mock', onRender: () => {} })).tag
: -1,
};
};
117 changes: 117 additions & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
Fragment,
forwardRef,
memo,
Profiler,
PureComponent,
useEffect,
useState,
Expand Down Expand Up @@ -844,6 +845,122 @@ describeWithDOM('mount', () => {
});
});

describeIf(is('>= 16.4'), 'Profiler', () => {
function SomeComponent() {
return (
<Profiler id="SomeComponent" onRender={() => {}}>
<main>
<div className="child" />
</main>
</Profiler>
);
}

wrap()
.withConsoleThrows()
.it('mounts without complaint', () => {
expect(() => mount(<SomeComponent />)).not.to.throw();
});

it('renders', () => {
const wrapper = mount(<SomeComponent />);
expect(wrapper.debug()).to.equal(`<SomeComponent>
<Profiler id="SomeComponent" onRender={[Function: onRender]}>
<main>
<div className="child" />
</main>
</Profiler>
</SomeComponent>`);
});

it('finds elements through Profiler elements', () => {
const wrapper = mount(<SomeComponent />);

expect(wrapper.find('.child')).to.have.lengthOf(1);
});

it('finds Profiler element', () => {
const Parent = () => <span><SomeComponent foo="hello" /></span>;

const wrapper = mount(<Parent foo="hello" />);
const results = wrapper.find(SomeComponent);

expect(results).to.have.lengthOf(1);
expect(results.type()).to.equal(SomeComponent);
expect(results.props()).to.eql({ foo: 'hello' });
});

it('can find Profiler by id', () => {
const wrapper = mount(<SomeComponent />);
expect(wrapper.find('[id="SomeComponent"]').exists()).to.equal(true);
});

it('can find Profiler by display name', () => {
const wrapper = mount(<SomeComponent />);
const profiler = wrapper.find('Profiler');
expect(profiler).to.have.lengthOf(1);
expect(profiler.type()).to.equal(Profiler);
});

it('recognizes render phases', () => {
const handleRender = sinon.spy();
function AnotherComponent() {
return (
<Profiler id="AnotherComponent" onRender={handleRender}>
<div />
</Profiler>
);
}

const wrapper = mount(<AnotherComponent />);
expect(handleRender).to.have.property('callCount', 1);
expect(handleRender.args[0][1]).to.equal('mount');

wrapper.setProps({ unusedProp: true });
expect(handleRender).to.have.property('callCount', 2);
expect(handleRender.args[1][1]).to.equal('update');
});

it('measures timings', () => {
/**
* test environment has no access to the performance API at which point
* the profiling API has to fallback to Date.now() which isn't precise enough
* which results in 0 duration for these simple examples most of the time.
* With performance API it should test for greaterThan(0) instead of least(0)
*/
const handleRender = sinon.spy();
function AnotherComponent() {
return (
<Profiler id="AnotherComponent" onRender={handleRender}>
<div />
</Profiler>
);
}

const wrapper = mount(<AnotherComponent />);
expect(handleRender).to.have.property('callCount', 1);
const [firstArgs] = handleRender.args;
if (typeof performance === 'undefined') {
expect(firstArgs[2]).to.be.least(0);
expect(firstArgs[3]).to.be.least(0);
} else {
expect(firstArgs[2]).to.be.greaterThan(0);
expect(firstArgs[3]).to.be.greaterThan(0);
}

wrapper.setProps({ unusedProp: true });
expect(handleRender).to.have.property('callCount', 2);
const [, secondArgs] = handleRender.args;
if (typeof performance === 'undefined') {
expect(secondArgs[2]).to.be.least(0);
expect(secondArgs[3]).to.be.least(0);
} else {
expect(secondArgs[2]).to.be.greaterThan(0);
expect(secondArgs[3]).to.be.greaterThan(0);
}
});
});

describeIf(is('>= 16.8'), 'hooks', () => {
it('works with `useEffect`', (done) => {
function ComponentUsingEffectHook() {
Expand Down
117 changes: 117 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
PureComponent,
useEffect,
useState,
Profiler,
} from './_helpers/react-compat';
import {
describeIf,
Expand Down Expand Up @@ -1007,6 +1008,122 @@ describe('shallow', () => {
});
});

describeIf(is('>= 16.4'), 'Profiler', () => {
function SomeComponent() {
return (
<Profiler id="SomeComponent" onRender={() => {}}>
<main>
<div className="child" />
</main>
</Profiler>
);
}

wrap()
.withConsoleThrows()
.it('mounts without complaint', () => {
expect(() => shallow(<SomeComponent />)).not.to.throw();
});

it('renders', () => {
const wrapper = shallow(<SomeComponent />);
expect(wrapper.debug()).to.equal(`<Profiler id="SomeComponent" onRender={[Function: onRender]}>
<main>
<div className="child" />
</main>
</Profiler>`);
});

it('finds elements through Profiler elements', () => {
const wrapper = shallow(<SomeComponent />);

expect(wrapper.find('.child')).to.have.lengthOf(1);
});

it('finds Profiler element', () => {
const Parent = () => <span><SomeComponent foo="hello" /></span>;

const wrapper = shallow(<Parent foo="hello" />);
const results = wrapper.find(SomeComponent);

expect(results).to.have.lengthOf(1);
expect(results.type()).to.equal(SomeComponent);
expect(results.props()).to.eql({ foo: 'hello' });
});

it('can find Profiler by id', () => {
const wrapper = shallow(<SomeComponent />);
expect(wrapper.find('[id="SomeComponent"]').exists()).to.equal(true);
});

it('can find Profiler by display name', () => {
const wrapper = shallow(<SomeComponent />);
const profiler = wrapper.find('Profiler');
expect(profiler).to.have.lengthOf(1);
expect(profiler.type()).to.equal(Profiler);
});

// TODO: enable when Profiler is no longer unstable
it.skip('recognizes render phases', () => {
const handleRender = sinon.spy();
function AnotherComponent() {
return (
<Profiler id="AnotherComponent" onRender={handleRender}>
<div />
</Profiler>
);
}

const wrapper = shallow(<AnotherComponent />);
expect(handleRender).to.have.property('callCount', 1);
expect(handleRender.args[0][1]).to.equal('mount');

wrapper.setProps({ unusedProp: true });
expect(handleRender).to.have.property('callCount', 2);
expect(handleRender.args[1][1]).to.equal('update');
});

// TODO: enable when Profiler is no longer unstable
it.skip('measures timings', () => {
/**
* test environment has no access to the performance API at which point
* the profiling API has to fallback to Date.now() which isn't precise enough
* which results in 0 duration for these simple examples most of the time.
* With performance API it should test for greaterThan(0) instead of least(0)
*/
const handleRender = sinon.spy();
function AnotherComponent() {
return (
<Profiler id="AnotherComponent" onRender={handleRender}>
<div />
</Profiler>
);
}

const wrapper = shallow(<AnotherComponent />);
expect(handleRender).to.have.property('callCount', 1);
const [firstArgs] = handleRender.args;
if (typeof performance === 'undefined') {
expect(firstArgs[2]).to.be.least(0);
expect(firstArgs[3]).to.be.least(0);
} else {
expect(firstArgs[2]).to.be.greaterThan(0);
expect(firstArgs[3]).to.be.greaterThan(0);
}

wrapper.setProps({ unusedProp: true });
expect(handleRender).to.have.property('callCount', 2);
const [, secondArgs] = handleRender.args;
if (typeof performance === 'undefined') {
expect(secondArgs[2]).to.be.least(0);
expect(secondArgs[3]).to.be.least(0);
} else {
expect(secondArgs[2]).to.be.greaterThan(0);
expect(secondArgs[3]).to.be.greaterThan(0);
}
});
});

describeIf(is('>= 16.8.5'), 'hooks', () => {
// TODO: enable when the shallow renderer fixes its bug
it.skip('works with `useEffect`', (done) => {
Expand Down