├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── dependabot-dedupe.yml
│ └── ci.yml
├── .eslintignore
├── test
├── .eslintignore
├── test
│ ├── _helpers
│ │ ├── adapter.js
│ │ ├── realArrowFunction.js
│ │ ├── selectors.js
│ │ ├── untranspiledSloppyReturnThis.js
│ │ ├── untranspiledArrowFunction.js
│ │ ├── version.js
│ │ ├── getLoadedLazyComponent.js
│ │ ├── withDom.js
│ │ ├── setupAdapters.js
│ │ ├── describeHooks.js
│ │ ├── describeMethods.js
│ │ ├── describeLifecycles.js
│ │ ├── react-compat.js
│ │ └── index.jsx
│ ├── shared
│ │ ├── methods
│ │ │ ├── getNodes.jsx
│ │ │ ├── getNode.jsx
│ │ │ ├── childAt.jsx
│ │ │ ├── last.jsx
│ │ │ ├── first.jsx
│ │ │ ├── tap.jsx
│ │ │ ├── every.jsx
│ │ │ ├── getElements.jsx
│ │ │ ├── someWhere.jsx
│ │ │ ├── everyWhere.jsx
│ │ │ ├── not.jsx
│ │ │ ├── key.jsx
│ │ │ ├── _method.template
│ │ │ ├── some.jsx
│ │ │ ├── forEach.jsx
│ │ │ ├── isEmpty.jsx
│ │ │ ├── flatMap.jsx
│ │ │ ├── unmount.jsx
│ │ │ ├── at.jsx
│ │ │ ├── deprecatedInstanceProperties.jsx
│ │ │ ├── filter.jsx
│ │ │ ├── root.jsx
│ │ │ ├── get.jsx
│ │ │ ├── map.jsx
│ │ │ ├── filterWhere.jsx
│ │ │ ├── single.jsx
│ │ │ ├── wrap.jsx
│ │ │ ├── @@iterator.jsx
│ │ │ ├── exists.jsx
│ │ │ ├── render.jsx
│ │ │ ├── closest.jsx
│ │ │ ├── slice.jsx
│ │ │ ├── instance.jsx
│ │ │ ├── reduce.jsx
│ │ │ ├── reduceRight.jsx
│ │ │ ├── parent.jsx
│ │ │ ├── getElement.jsx
│ │ │ ├── prop.jsx
│ │ │ ├── is.jsx
│ │ │ ├── matchesElement.jsx
│ │ │ ├── equals.jsx
│ │ │ ├── invoke.jsx
│ │ │ ├── html.jsx
│ │ │ ├── hostNodes.jsx
│ │ │ ├── context.jsx
│ │ │ ├── containsAnyMatchingElements.jsx
│ │ │ ├── contains.jsx
│ │ │ ├── name.jsx
│ │ │ ├── simulateError.jsx
│ │ │ ├── containsAllMatchingElements.jsx
│ │ │ ├── parents.jsx
│ │ │ ├── setContext.jsx
│ │ │ ├── state.jsx
│ │ │ ├── props.jsx
│ │ │ ├── isEmptyRender.jsx
│ │ │ └── renderProp.jsx
│ │ ├── lifecycles
│ │ │ ├── componentWillUnmount.jsx
│ │ │ ├── componentDidMount.jsx
│ │ │ └── getSnapshotBeforeUpdate.jsx
│ │ └── hooks
│ │ │ ├── _hook.template
│ │ │ ├── useRef.jsx
│ │ │ ├── useImperativeHandle.jsx
│ │ │ ├── useDebugValue.jsx
│ │ │ ├── useLayoutEffect.jsx
│ │ │ ├── useMemo.jsx
│ │ │ ├── useCallback.jsx
│ │ │ ├── useReducer.jsx
│ │ │ ├── useContext.jsx
│ │ │ ├── useState.jsx
│ │ │ └── custom.jsx
│ ├── enzyme-shallow-equal.spec.js
│ └── staticRender.spec.jsx
├── vitest.config.ts
├── vitest.setup.ts
├── .eslintrc.json
└── package.json
├── .prettierignore
├── src
├── index.js
├── detectFiberTags.js
└── findCurrentFiberUsingSlowPath.js
├── .babelrc
├── .husky
└── pre-commit
├── .prettierrc.json
├── .eslintrc.json
├── .vscode
├── extensions.json
└── settings.json
├── index.d.ts
├── .yarn
└── plugins
│ ├── plugin-remove-postinstall.cjs
│ └── plugin-remove-workspaces-field.cjs
├── .gitignore
├── .yarnrc.yml
├── README.md
├── LICENSE
└── package.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: wojtekmaj
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | **/node_modules/
4 |
--------------------------------------------------------------------------------
/test/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | **/node_modules/
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .yarn
3 | build
4 | coverage
5 | *.yml
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./ReactSeventeenAdapter');
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/react"],
3 | "sourceMaps": "both"
4 | }
5 |
--------------------------------------------------------------------------------
/test/test/_helpers/adapter.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@wojtekmaj/enzyme-adapter-react-17');
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn pretty-quick --staged
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "all"
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "wojtekmaj/node",
3 | "rules": {
4 | "import/default": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/test/_helpers/realArrowFunction.js:
--------------------------------------------------------------------------------
1 | try {
2 | module.exports = require('./untranspiledArrowFunction');
3 | } catch (e) {
4 | module.exports = (x) => () => x;
5 | }
6 |
--------------------------------------------------------------------------------
/test/test/_helpers/selectors.js:
--------------------------------------------------------------------------------
1 | export const getElementPropSelector = (prop) => (x) => x.props[prop];
2 |
3 | export const getWrapperPropSelector = (prop) => (x) => x.prop(prop);
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint", "eamodio.gitlens", "esbenp.prettier-vscode"],
3 | "unwantedRecommendations": ["dbaeumer.jshint"]
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "esbenp.prettier-vscode",
3 | "editor.formatOnSave": true,
4 | "search.exclude": {
5 | "**/.yarn": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/test/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: 'jsdom',
6 | setupFiles: 'vitest.setup.ts',
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import { EnzymeAdapter } from 'enzyme';
2 |
3 | declare class ReactSeventeenAdapter extends EnzymeAdapter {}
4 |
5 | declare namespace ReactSeventeenAdapter {}
6 |
7 | export = ReactSeventeenAdapter;
8 |
--------------------------------------------------------------------------------
/test/test/_helpers/untranspiledSloppyReturnThis.js:
--------------------------------------------------------------------------------
1 | module.exports = function (fn) {
2 | return function () {
3 | return fn.apply(null, [this].concat(Array.prototype.slice.call(arguments)));
4 | };
5 | };
6 |
--------------------------------------------------------------------------------
/test/test/_helpers/untranspiledArrowFunction.js:
--------------------------------------------------------------------------------
1 | // this file is ignored in babelrc, specifically so that tests will be able to run
2 | // on a real arrow function that lacks a .prototype
3 | module.exports = (x) => () => x;
4 |
--------------------------------------------------------------------------------
/test/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import { afterEach, beforeEach, describe, it } from 'vitest';
2 |
3 | // Temporary workaround to make mocha-wrap work
4 | global.afterEach = afterEach;
5 | global.beforeEach = beforeEach;
6 | global.describe = describe;
7 | global.it = it;
8 |
--------------------------------------------------------------------------------
/.yarn/plugins/plugin-remove-postinstall.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'plugin-remove-postinstall',
3 | factory: () => ({
4 | hooks: {
5 | beforeWorkspacePacking(workspace, rawManifest) {
6 | delete rawManifest.scripts.postinstall;
7 | },
8 | },
9 | }),
10 | };
11 |
--------------------------------------------------------------------------------
/.yarn/plugins/plugin-remove-workspaces-field.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'plugin-remove-workspaces-field',
3 | factory: () => ({
4 | hooks: {
5 | beforeWorkspacePacking(workspace, rawManifest) {
6 | delete rawManifest.workspaces;
7 | },
8 | },
9 | }),
10 | };
11 |
--------------------------------------------------------------------------------
/test/test/_helpers/version.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const VERSION = React.version;
4 |
5 | // The shallow renderer in React 17 does not yet support batched updates. When it does,
6 | // we should be able to go un-skip all of the tests that are skipped with this flag.
7 | export const BATCHING = false;
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Cache and IDE-related files
2 | .cache
3 | .idea
4 |
5 | # Yarn-related files
6 | **/.yarn/*
7 | !**/.yarn/releases
8 | !**/.yarn/plugins
9 | !**/.yarn/sdks
10 | yarn-error.log
11 |
12 | # Project-generated directories
13 | build
14 | coverage
15 | node_modules
16 |
17 | # Other
18 | **/.DS_Store
19 | **/.env
20 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | logFilters:
2 | - code: YN0076
3 | level: discard
4 |
5 | nodeLinker: node-modules
6 |
7 | plugins:
8 | - path: .yarn/plugins/plugin-remove-postinstall.cjs
9 | - path: .yarn/plugins/plugin-remove-workspaces-field.cjs
10 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
11 | spec: "@yarnpkg/plugin-interactive-tools"
12 |
--------------------------------------------------------------------------------
/test/test/_helpers/getLoadedLazyComponent.js:
--------------------------------------------------------------------------------
1 | import { lazy } from './react-compat';
2 |
3 | function fakeSyncThenable(result) {
4 | return {
5 | then(resolve) {
6 | return resolve({ default: result });
7 | },
8 | };
9 | }
10 |
11 | export default function getLoadedLazyComponent(wrappedComponent) {
12 | return lazy(() => fakeSyncThenable(wrappedComponent));
13 | }
14 |
--------------------------------------------------------------------------------
/test/test/shared/methods/getNodes.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeGetNodes({ Wrap, WrapperName }) {
5 | describe('.getNodes()', () => {
6 | it('throws', () => {
7 | const wrapper = Wrap(
);
8 | expect(() => wrapper.getNodes()).to.throw(
9 | `${WrapperName}::getNodes() is no longer supported.`,
10 | );
11 | });
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/test/test/shared/methods/getNode.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeGetNode({ Wrap, WrapperName, isShallow }) {
5 | describe('.getNode()', () => {
6 | it('throws', () => {
7 | const wrapper = Wrap(
);
8 | expect(() => wrapper.getNode()).to.throw(
9 | `${WrapperName}::getNode() is no longer supported. Use ${WrapperName}::${
10 | isShallow ? 'getElement' : 'instance'
11 | }() instead`,
12 | );
13 | });
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/test/test/shared/methods/childAt.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeChildAt({ Wrap }) {
5 | describe('.childAt(index)', () => {
6 | it('gets a wrapped node at the specified index', () => {
7 | const wrapper = Wrap(
8 | ,
12 | );
13 |
14 | expect(wrapper.childAt(0).hasClass('bar')).to.equal(true);
15 | expect(wrapper.childAt(1).hasClass('baz')).to.equal(true);
16 | });
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/test/test/shared/methods/last.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeLast({ Wrap }) {
5 | describe('.last()', () => {
6 | it('returns the last node in the current set', () => {
7 | const wrapper = Wrap(
8 | ,
14 | );
15 | expect(wrapper.find('.bar').last().hasClass('baz')).to.equal(true);
16 | });
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/test/test/shared/methods/first.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeFirst({ Wrap }) {
5 | describe('.first()', () => {
6 | it('returns the first node in the current set', () => {
7 | const wrapper = Wrap(
8 | ,
14 | );
15 | expect(wrapper.find('.bar').first().hasClass('baz')).to.equal(true);
16 | });
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/test/test/_helpers/withDom.js:
--------------------------------------------------------------------------------
1 | require('raf/polyfill');
2 |
3 | const { JSDOM } = require('jsdom');
4 |
5 | const jsdom = new JSDOM('', { url: 'http://localhost' });
6 |
7 | global.window = jsdom.window;
8 | global.document = jsdom.window.document;
9 | global.HTMLElement = window.HTMLElement;
10 | global.HTMLButtonElement = window.HTMLButtonElement;
11 | Object.keys(global.document.defaultView).forEach((property) => {
12 | if (typeof global[property] === 'undefined') {
13 | global[property] = global.document.defaultView[property];
14 | }
15 | });
16 |
17 | global.navigator = {
18 | userAgent: 'node.js',
19 | };
20 |
--------------------------------------------------------------------------------
/test/test/shared/methods/tap.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeTap({ Wrap }) {
5 | describe('.tap()', () => {
6 | it('calls the passed function with current Wrapper and returns itself', () => {
7 | const spy = vi.fn();
8 | const wrapper = Wrap(
9 |
10 | xxx
11 | yyy
12 | zzz
13 | ,
14 | ).find('li');
15 | const result = wrapper.tap(spy);
16 | expect(spy).toHaveBeenCalledWith(wrapper);
17 | expect(result).to.equal(wrapper);
18 | });
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "wojtekmaj/react-no-automatic-runtime",
3 | "rules": {
4 | "jsx-a11y/anchor-has-content": "off",
5 | "jsx-a11y/anchor-is-valid": "off",
6 | "jsx-a11y/click-events-have-key-events": "off",
7 | "jsx-a11y/label-has-associated-control": "off",
8 | "jsx-a11y/no-static-element-interactions": "off",
9 | "react/display-name": "off",
10 | "react/jsx-key": "off",
11 | "react/no-direct-mutation-state": "off",
12 | "react/no-danger": "off",
13 | "react/no-deprecated": "off",
14 | "react/no-string-refs": "off",
15 | "react/prop-types": "off",
16 | "react-hooks/exhaustive-deps": "off",
17 | "react-hooks/rules-of-hooks": "off"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/test/shared/methods/every.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeEvery({ Wrap }) {
5 | describe('.every(selector)', () => {
6 | it('returns if every node matches a selector', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | expect(wrapper.find('.foo').every('.foo')).to.equal(true);
15 | expect(wrapper.find('.foo').every('.qoo')).to.equal(false);
16 | expect(wrapper.find('.foo').every('.bar')).to.equal(false);
17 | });
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/test/test/shared/methods/getElements.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeGetElements({ Wrap }) {
5 | describe('.getElements()', () => {
6 | it('returns the wrapped elements', () => {
7 | const one = ;
8 | const two = ;
9 |
10 | class Test extends React.Component {
11 | render() {
12 | return (
13 |
14 | {one}
15 | {two}
16 |
17 | );
18 | }
19 | }
20 |
21 | const wrapper = Wrap( );
22 | expect(wrapper.find('span').getElements()).to.deep.equal([one, two]);
23 | });
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/test/test/shared/lifecycles/componentWillUnmount.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeCWU({ Wrap }) {
5 | describe('componentWillUnmount', () => {
6 | it('calls componentWillUnmount', () => {
7 | const spy = vi.fn();
8 | class Foo extends React.Component {
9 | componentWillUnmount() {
10 | spy('componentWillUnmount');
11 | }
12 |
13 | render() {
14 | spy('render');
15 | return foo
;
16 | }
17 | }
18 | const wrapper = Wrap( );
19 | wrapper.unmount();
20 | expect(spy.mock.calls).to.deep.equal([['render'], ['componentWillUnmount']]);
21 | });
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/test/test/shared/methods/someWhere.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeSomeWhere({ Wrap }) {
5 | describe('.someWhere(predicate)', () => {
6 | it('returns if a node matches a predicate', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | const foo = wrapper.find('.foo');
15 | expect(foo.someWhere((n) => n.hasClass('qoo'))).to.equal(true);
16 | expect(foo.someWhere((n) => n.hasClass('foo'))).to.equal(true);
17 | expect(foo.someWhere((n) => n.hasClass('bar'))).to.equal(false);
18 | });
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/_hook.template:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import wrap from 'mocha-wrap';
4 | import { Portal } from 'react-is';
5 |
6 | import { render } from 'enzyme';
7 | import getAdapter from 'enzyme/build/getAdapter';
8 |
9 | import {
10 | describeIf,
11 | itIf,
12 | } from '../../_helpers';
13 |
14 | import {
15 | useCallback,
16 | useContext,
17 | useEffect,
18 | useLayoutEffect,
19 | useMemo,
20 | useReducer,
21 | useState,
22 | act,
23 | } from '../../_helpers/react-compat';
24 |
25 | export default function describe$Hook({
26 | Wrap,
27 | WrapRendered,
28 | Wrapper,
29 | WrapperName,
30 | isShallow,
31 | isMount,
32 | makeDOMElement,
33 | }) {
34 | describe('hooks: $Hook', () => {
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/test/test/shared/methods/everyWhere.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeEveryWhere({ Wrap }) {
5 | describe('.everyWhere(predicate)', () => {
6 | it('returns if every node matches a predicate', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | const foo = wrapper.find('.foo');
15 | expect(foo.everyWhere((n) => n.hasClass('foo'))).to.equal(true);
16 | expect(foo.everyWhere((n) => n.hasClass('qoo'))).to.equal(false);
17 | expect(foo.everyWhere((n) => n.hasClass('bar'))).to.equal(false);
18 | });
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/test/test/shared/methods/not.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeNot({ Wrap }) {
5 | describe('.not(selector)', () => {
6 | it('filters to things not matching a selector', () => {
7 | const wrapper = Wrap(
8 |
9 |
10 |
11 |
12 |
13 |
14 |
,
15 | );
16 |
17 | expect(wrapper.find('.foo').not('.bar')).to.have.lengthOf(1);
18 | expect(wrapper.find('.baz').not('.foo')).to.have.lengthOf(2);
19 | expect(wrapper.find('.foo').not('div')).to.have.lengthOf(0);
20 | });
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useRef.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { useRef } from '../../_helpers/react-compat';
5 |
6 | export default function describeUseRef({ Wrap }) {
7 | describe('hooks: useRef', () => {
8 | function ComponentUsingRef() {
9 | const id = useRef(Math.floor(100 * Math.random()));
10 | return {id.current}
;
11 | }
12 |
13 | it('`current` should be the same between two renders', () => {
14 | const wrapper = Wrap( );
15 |
16 | const childBefore = wrapper.find('div').prop('children');
17 | wrapper.setProps({ foo: 'bar' });
18 | const childAfter = wrapper.find('div').prop('children');
19 |
20 | expect(childBefore).to.equal(childAfter);
21 | });
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/test/test/shared/methods/key.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeKey({ Wrap }) {
5 | describe('.key()', () => {
6 | it('returns the key of the node', () => {
7 | const wrapper = Wrap(
8 |
9 | {['foo', 'bar', ''].map((s) => (
10 | {s}
11 | ))}
12 | ,
13 | ).find('li');
14 | expect(wrapper.at(0).key()).to.equal('foo');
15 | expect(wrapper.at(1).key()).to.equal('bar');
16 | expect(wrapper.at(2).key()).to.equal('');
17 | });
18 |
19 | it('returns null when no key is specified', () => {
20 | const wrapper = Wrap(
21 | ,
24 | ).find('li');
25 | expect(wrapper.key()).to.equal(null);
26 | });
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/test/test/shared/methods/_method.template:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import wrap from 'mocha-wrap';
4 | import { Portal } from 'react-is';
5 |
6 | import { render } from 'enzyme';
7 | import getAdapter from 'enzyme/build/getAdapter';
8 | import {
9 | ITERATOR_SYMBOL,
10 | sym,
11 | } from 'enzyme/build/Utils';
12 |
13 | import {
14 | describeIf,
15 | itIf,
16 | } from '../../_helpers';
17 | import realArrowFunction from '../../_helpers/realArrowFunction';
18 | import { getElementPropSelector, getWrapperPropSelector } from '../../_helpers/selectors';
19 |
20 | import {
21 | createClass,
22 | createPortal,
23 | createRef,
24 | Fragment,
25 | } from '../../_helpers/react-compat';
26 |
27 | export default function describe$Method({
28 | Wrap,
29 | WrapRendered,
30 | Wrapper,
31 | WrapperName,
32 | isShallow,
33 | isMount,
34 | makeDOMElement,
35 | }) {
36 | }
37 |
--------------------------------------------------------------------------------
/test/test/shared/methods/some.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeSome({ Wrap, WrapperName }) {
5 | describe('.some(selector)', () => {
6 | it('returns if a node matches a selector', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | const foo = wrapper.find('.foo');
15 | expect(foo.some('.qoo')).to.equal(true);
16 | expect(foo.some('.foo')).to.equal(true);
17 | expect(foo.some('.bar')).to.equal(false);
18 | });
19 |
20 | it('throws if called on root', () => {
21 | const wrapper = Wrap(
22 | ,
25 | );
26 | expect(() => wrapper.some('.foo')).to.throw(
27 | Error,
28 | `${WrapperName}::some() can not be called on the root`,
29 | );
30 | });
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@wojtekmaj/enzyme-adapter-react-17)  [](https://github.com/wojtekmaj/enzyme-adapter-react-17/actions)
2 |
3 | # @wojtekmaj/enzyme-adapter-react-17
4 |
5 | Unofficial adapter for React 17 for [Enzyme](https://enzymejs.github.io/enzyme/).
6 |
7 | ## Installation
8 |
9 | ```
10 | npm install --save-dev @wojtekmaj/enzyme-adapter-react-17
11 | ```
12 |
13 | or, if you're using Yarn:
14 |
15 | ```
16 | yarn add --dev @wojtekmaj/enzyme-adapter-react-17
17 | ```
18 |
19 | ## Configuration
20 |
21 | Finally, you need to configure enzyme to use the adapter you want it to use. To do this, you can use the top level `configure(...)` API.
22 |
23 | ```js
24 | import Enzyme from 'enzyme';
25 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
26 |
27 | Enzyme.configure({ adapter: new Adapter() });
28 | ```
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2015-2023 Airbnb, Inc. and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/test/_helpers/setupAdapters.js:
--------------------------------------------------------------------------------
1 | const util = require('util');
2 | const Enzyme = require('enzyme');
3 | const wrap = require('mocha-wrap');
4 | const resetWarningCache = require('prop-types').checkPropTypes.resetWarningCache;
5 |
6 | const Adapter = require('./adapter');
7 |
8 | Enzyme.configure({ adapter: new Adapter() });
9 |
10 | // eslint-disable-next-line no-console
11 | const origWarn = console.warn;
12 | // eslint-disable-next-line no-console
13 | const origError = console.error;
14 | wrap.register(function withConsoleThrows() {
15 | return this.withOverrides(
16 | () => console,
17 | () => ({
18 | error(msg) {
19 | origError.apply(console, arguments);
20 | throw new EvalError(arguments.length > 1 ? util.format.apply(util, arguments) : msg);
21 | },
22 | warn(msg) {
23 | origWarn.apply(console, arguments);
24 | throw new EvalError(arguments.length > 1 ? util.format.apply(util, arguments) : msg);
25 | },
26 | }),
27 | ).extend('with console throws', {
28 | beforeEach() {
29 | resetWarningCache();
30 | },
31 | afterEach() {
32 | resetWarningCache();
33 | },
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/test/shared/methods/forEach.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeForEach({ Wrap, Wrapper }) {
5 | describe('.forEach(fn)', () => {
6 | it('calls a function for each node in the wrapper', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | const spy = vi.fn();
15 |
16 | wrapper.find('.foo').forEach(spy);
17 |
18 | expect(spy).to.have.property('callCount', 3);
19 | expect(spy.mock.calls[0][0]).to.be.instanceOf(Wrapper);
20 | expect(spy.mock.calls[0][0].hasClass('bax')).to.equal(true);
21 | expect(spy.mock.calls[0][1]).to.equal(0);
22 | expect(spy.mock.calls[1][0]).to.be.instanceOf(Wrapper);
23 | expect(spy.mock.calls[1][0].hasClass('bar')).to.equal(true);
24 | expect(spy.mock.calls[1][1]).to.equal(1);
25 | expect(spy.mock.calls[2][0]).to.be.instanceOf(Wrapper);
26 | expect(spy.mock.calls[2][0].hasClass('baz')).to.equal(true);
27 | expect(spy.mock.calls[2][1]).to.equal(2);
28 | });
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/test/test/shared/methods/isEmpty.jsx:
--------------------------------------------------------------------------------
1 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeLast({ Wrap }) {
5 | describe('.isEmpty()', () => {
6 | let warningStub;
7 | let fooNode;
8 | let missingNode;
9 |
10 | beforeEach(() => {
11 | warningStub = vi.spyOn(console, 'warn');
12 | const wrapper = Wrap(
);
13 | fooNode = wrapper.find('.foo');
14 | missingNode = wrapper.find('.missing');
15 | });
16 | afterEach(() => {
17 | warningStub.restore();
18 | });
19 |
20 | it('displays a deprecation warning', () => {
21 | fooNode.isEmpty();
22 | expect(warningStub).toHaveBeenCalledWith(
23 | 'Enzyme::Deprecated method isEmpty() called, use exists() instead.',
24 | );
25 | });
26 |
27 | it('calls exists() instead', () => {
28 | const existsSpy = vi.fn();
29 | fooNode.exists = existsSpy;
30 | expect(fooNode.isEmpty()).to.equal(true);
31 | expect(existsSpy).to.have.property('called', true);
32 | });
33 |
34 | it('returns true if wrapper is empty', () => {
35 | expect(fooNode.isEmpty()).to.equal(false);
36 | expect(missingNode.isEmpty()).to.equal(true);
37 | });
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/test/test/shared/methods/flatMap.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeFlatMap({ Wrap }) {
5 | describe('.flatMap(fn)', () => {
6 | it('returns a wrapper with the mapped and flattened nodes', () => {
7 | const wrapper = Wrap(
8 | ,
22 | );
23 |
24 | const nodes = wrapper.find('.foo').flatMap((w) => w.children().getElements());
25 |
26 | expect(nodes).to.have.lengthOf(6);
27 | expect(nodes.at(0).hasClass('bar')).to.equal(true);
28 | expect(nodes.at(1).hasClass('bar')).to.equal(true);
29 | expect(nodes.at(2).hasClass('baz')).to.equal(true);
30 | expect(nodes.at(3).hasClass('baz')).to.equal(true);
31 | expect(nodes.at(4).hasClass('bax')).to.equal(true);
32 | expect(nodes.at(5).hasClass('bax')).to.equal(true);
33 | });
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/test/test/shared/methods/unmount.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | export default function describeUnmount({ isShallow, Wrap, WrapRendered }) {
7 | describe('.unmount()', () => {
8 | class WillUnmount extends React.Component {
9 | componentWillUnmount() {}
10 |
11 | render() {
12 | const { id } = this.props;
13 | return (
14 |
15 | {id}
16 |
17 | );
18 | }
19 | }
20 |
21 | it('calls componentWillUnmount()', () => {
22 | const spy = vi.spyOn(WillUnmount.prototype, 'componentWillUnmount');
23 | const wrapper = Wrap( );
24 | expect(spy).to.have.property('callCount', 0);
25 |
26 | wrapper.unmount();
27 |
28 | expect(spy).to.have.property('callCount', 1);
29 | const [args] = spy.mock.calls;
30 | expect(args).to.eql([]);
31 | });
32 |
33 | itIf(!isShallow, 'throws on non-root', () => {
34 | const wrapper = WrapRendered( );
35 | const span = wrapper.find('span');
36 | expect(span).to.have.lengthOf(1);
37 | expect(() => span.unmount()).to.throw(Error);
38 | });
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/test/test/shared/lifecycles/componentDidMount.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeCDM({ Wrap }) {
5 | describe('componentDidUpdate()', () => {
6 | it('does not call `componentDidMount` twice when a child component is created', () => {
7 | const spy = vi.fn();
8 |
9 | class Foo extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | foo: 'init',
14 | };
15 | }
16 |
17 | componentDidMount() {
18 | spy('componentDidMount');
19 | }
20 |
21 | render() {
22 | spy('render');
23 |
24 | const { foo } = this.state;
25 | return (
26 |
27 | this.setState({ foo: 'update2' })}>
28 | click
29 |
30 | {foo}
31 |
32 | );
33 | }
34 | }
35 |
36 | const wrapper = Wrap( );
37 | expect(spy.mock.calls).to.eql([['render'], ['componentDidMount']]);
38 | spy.mockReset();
39 |
40 | wrapper.find('button').prop('onClick')();
41 | expect(spy).to.have.property('callCount', 1);
42 | });
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/test/test/shared/methods/at.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | import React from 'react';
4 |
5 | export default function describeAt({ Wrap }) {
6 | describe('.at(index)', () => {
7 | it('gets a wrapper of the node at the specified index', () => {
8 | const wrapper = Wrap(
9 | ,
15 | );
16 | const bar = wrapper.find('.bar');
17 | expect(bar.at(0).hasClass('foo')).to.equal(true);
18 | expect(bar.at(1).hasClass('bax')).to.equal(true);
19 | expect(bar.at(2).hasClass('bux')).to.equal(true);
20 | expect(bar.at(3).hasClass('baz')).to.equal(true);
21 | });
22 |
23 | it('`.at()` does not affect the results of `.exists()`', () => {
24 | const wrapper = Wrap(
25 | ,
28 | );
29 | const bar = wrapper.find('.bar');
30 | expect(bar.exists()).to.equal(false);
31 | expect(bar.at(0).exists()).to.equal(false);
32 |
33 | const foo = wrapper.find('.foo');
34 | expect(foo.exists()).to.equal(true);
35 | expect(foo.at(0).exists()).to.equal(true);
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/test/test/shared/methods/deprecatedInstanceProperties.jsx:
--------------------------------------------------------------------------------
1 | import { beforeEach, describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeDeprecatedInstanceProperties({ Wrap, isShallow }) {
5 | class Foo extends React.Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 | }
16 |
17 | describe('deprecated instance properties', () => {
18 | let wrapper;
19 | beforeEach(() => {
20 | wrapper = Wrap( );
21 | });
22 |
23 | const tuples = [
24 | ['node', 'Consider using the getElement() method instead.'],
25 | ['nodes', 'Consider using the getElements() method instead.'],
26 | ['renderer', ''],
27 | ['options', ''],
28 | ['complexSelector', ''],
29 | ];
30 |
31 | tuples.forEach(([prop, extra]) => {
32 | it(`warns on \`${prop}\``, () => {
33 | expect(() => wrapper[prop]).to.throw(
34 | Error,
35 | new RegExp(`Attempted to access ${isShallow ? 'Shallow' : 'React'}Wrapper::${prop}`),
36 | );
37 | expect(() => wrapper[prop]).to.throw(
38 | Error,
39 | new RegExp(`${extra.replace(/([(){}.\\])/g, '\\$1')}`),
40 | );
41 | });
42 | });
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useImperativeHandle.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { useImperativeHandle, useRef, forwardRef } from '../../_helpers/react-compat';
5 |
6 | export default function describeUseImperativeHandle({ Wrap, isShallow }) {
7 | describe('hooks: useImperativeHandle', () => {
8 | function Computer({ compute }, ref) {
9 | const computerRef = useRef({ compute });
10 | useImperativeHandle(ref, () => ({
11 | compute: () => {
12 | computerRef.current.compute();
13 | },
14 | }));
15 | return
;
16 | }
17 |
18 | const FancyComputer = forwardRef(Computer);
19 |
20 | class ParentComputer extends React.Component {
21 | componentDidMount() {
22 | if (this.ref) {
23 | this.ref.compute();
24 | }
25 | }
26 |
27 | render() {
28 | return (
29 | {
31 | this.ref = ref;
32 | }}
33 | {...this.props}
34 | />
35 | );
36 | }
37 | }
38 |
39 | it('able to call method with imperative handle', () => {
40 | const compute = vi.fn();
41 | Wrap( );
42 |
43 | expect(compute).to.have.property('callCount', isShallow ? 0 : 1);
44 | });
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useDebugValue.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { useDebugValue } from '../../_helpers/react-compat';
5 |
6 | export default function describeUseDebugValue({ Wrap }) {
7 | // TODO: `useDebugValue`: test using react debug tools, verify it actually works, and try to add it to `.debug`
8 | describe('hooks: useDebugValue', () => {
9 | function ComponentUsingDebugValue({ value }) {
10 | useDebugValue(`debug value: ${value}`);
11 |
12 | return {value}
;
13 | }
14 |
15 | function ComponentUsingDebugValueAndCallback({ value, fn = (x) => `debug value: ${x}` }) {
16 | useDebugValue(value, fn);
17 |
18 | return {value}
;
19 | }
20 |
21 | it('can render component using useDebugValue', () => {
22 | const value = 'foo';
23 | const wrapper = Wrap( );
24 | expect(wrapper.find('div').prop('children')).to.equal(value);
25 | });
26 |
27 | it('can render component using useDebugValue and callback', () => {
28 | const value = 'foo';
29 | const spy = vi.fn();
30 | const wrapper = Wrap( );
31 | expect(wrapper.find('div').prop('children')).to.equal(value);
32 | expect(spy).to.have.property('callCount', 0);
33 | });
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/test/test/shared/methods/filter.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeFilter({ Wrap }) {
5 | describe('.filter(selector)', () => {
6 | it('returns a new wrapper of just the nodes that matched the selector', () => {
7 | const wrapper = Wrap(
8 |
9 |
10 |
11 |
15 |
16 |
17 |
,
18 | );
19 |
20 | expect(wrapper.find('.foo').filter('.bar')).to.have.lengthOf(3);
21 | expect(wrapper.find('.bar').filter('.foo')).to.have.lengthOf(3);
22 | expect(wrapper.find('.bar').filter('.bax')).to.have.lengthOf(0);
23 | expect(wrapper.find('.foo').filter('.baz.bar')).to.have.lengthOf(2);
24 | });
25 |
26 | it('only looks in the current wrappers nodes, not their children', () => {
27 | const wrapper = Wrap(
28 | ,
34 | );
35 |
36 | expect(wrapper.find('.foo').filter('.bar')).to.have.lengthOf(1);
37 | });
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/test/test/shared/methods/root.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | export default function describeRoot({ Wrap, isMount }) {
7 | describe('.root()', () => {
8 | class Fixture extends React.Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 | );
16 | }
17 | }
18 |
19 | itIf(isMount, 'returns the root component instance', () => {
20 | const wrapper = Wrap( );
21 | const root = wrapper.root();
22 | expect(root.is(Fixture)).to.equal(true);
23 | expect(root.childAt(0).children().debug()).to.equal(' \n\n\n ');
24 |
25 | expect(wrapper.find('span').root()).to.equal(root);
26 | });
27 |
28 | itIf(!isMount, 'returns the root rendered node', () => {
29 | const wrapper = Wrap( );
30 | const root = wrapper.root();
31 | expect(root.is('div')).to.equal(true);
32 | expect(root.children().debug()).to.equal(' \n\n\n ');
33 |
34 | expect(wrapper.find('span').root()).to.equal(root);
35 | });
36 |
37 | it('returns the root wrapper from a child', () => {
38 | const wrapper = Wrap( );
39 | const root = wrapper.root();
40 | expect(wrapper.find('span').root()).to.equal(root);
41 | });
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/test/test/shared/methods/get.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeGet({ Wrap }) {
5 | describe('.get(index)', () => {
6 | it('gets the node at the specified index', () => {
7 | const wrapper = Wrap(
8 | ,
14 | );
15 | const bar = wrapper.find('.bar');
16 | expect(bar.get(0)).to.deep.equal(wrapper.find('.foo').getElement());
17 | expect(bar.get(1)).to.deep.equal(wrapper.find('.bax').getElement());
18 | expect(bar.get(2)).to.deep.equal(wrapper.find('.bux').getElement());
19 | expect(bar.get(3)).to.deep.equal(wrapper.find('.baz').getElement());
20 | });
21 |
22 | it('does not add a "null" key to elements with a ref and no key', () => {
23 | class Foo extends React.Component {
24 | constructor(props) {
25 | super(props);
26 | this.setRef = this.setRef.bind(this);
27 | }
28 |
29 | setRef(node) {
30 | this.node = node;
31 | }
32 |
33 | render() {
34 | return
;
35 | }
36 | }
37 | const wrapper = Wrap( );
38 | expect(wrapper.get(0)).to.have.property('key', null);
39 | });
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/test/test/_helpers/describeHooks.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import { fileURLToPath } from 'node:url';
4 | import { asyncForEach } from '@wojtekmaj/async-array-utils';
5 |
6 | const dirname = path.dirname(fileURLToPath(import.meta.url));
7 | const workingDirectory = path.join(dirname, '..', 'shared', 'hooks');
8 | const filenames = await fs.readdir(workingDirectory);
9 | const map = new Map();
10 | await asyncForEach(filenames, async (methodFilename) => {
11 | const methodName = path.basename(methodFilename, '.jsx');
12 | map.set(methodName, (await import(path.join(workingDirectory, methodFilename))).default);
13 | });
14 |
15 | export default function describeHooks({ Wrap, Wrapper }, ...hooks) {
16 | const WrapperName = Wrapper.name;
17 | const isShallow = WrapperName === 'ShallowWrapper';
18 | const isMount = WrapperName === 'ReactWrapper';
19 | const hasDOM = isMount;
20 | const makeDOMElement = () => (hasDOM ? global.document.createElement('div') : { nodeType: 1 });
21 |
22 | hooks.forEach((hook) => {
23 | if (!map.has(hook)) {
24 | throw new Error(`Hook ${hook} does not exist`);
25 | }
26 |
27 | const hookModule = map.get(hook);
28 |
29 | hookModule({
30 | Wrap,
31 | WrapRendered: isShallow ? Wrap : (...args) => Wrap(...args).children(),
32 | Wrapper,
33 | WrapperName,
34 | isShallow,
35 | isMount,
36 | hasDOM,
37 | makeDOMElement,
38 | });
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/test/test/_helpers/describeMethods.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import { fileURLToPath } from 'node:url';
4 | import { asyncForEach } from '@wojtekmaj/async-array-utils';
5 |
6 | const dirname = path.dirname(fileURLToPath(import.meta.url));
7 | const workingDirectory = path.join(dirname, '..', 'shared', 'methods');
8 | const filenames = await fs.readdir(workingDirectory);
9 | const map = new Map();
10 | await asyncForEach(filenames, async (methodFilename) => {
11 | const methodName = path.basename(methodFilename, '.jsx');
12 | map.set(methodName, (await import(path.join(workingDirectory, methodFilename))).default);
13 | });
14 |
15 | export default function describeMethods({ Wrap, Wrapper }, ...methodNames) {
16 | const WrapperName = Wrapper.name;
17 | const isShallow = WrapperName === 'ShallowWrapper';
18 | const isMount = WrapperName === 'ReactWrapper';
19 | const hasDOM = isMount;
20 | const makeDOMElement = () => (hasDOM ? global.document.createElement('div') : { nodeType: 1 });
21 |
22 | methodNames.forEach((methodName) => {
23 | if (!map.has(methodName)) {
24 | throw new Error(`Method ${methodName} does not exist`);
25 | }
26 |
27 | const methodModule = map.get(methodName);
28 |
29 | methodModule({
30 | Wrap,
31 | WrapRendered: isShallow ? Wrap : (...args) => Wrap(...args).children(),
32 | Wrapper,
33 | WrapperName,
34 | isShallow,
35 | isMount,
36 | hasDOM,
37 | makeDOMElement,
38 | });
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/test/test/_helpers/describeLifecycles.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises';
2 | import path from 'node:path';
3 | import { fileURLToPath } from 'node:url';
4 | import { asyncForEach } from '@wojtekmaj/async-array-utils';
5 |
6 | const dirname = path.dirname(fileURLToPath(import.meta.url));
7 | const workingDirectory = path.join(dirname, '..', 'shared', 'lifecycles');
8 | const filenames = await fs.readdir(workingDirectory);
9 | const map = new Map();
10 | await asyncForEach(filenames, async (methodFilename) => {
11 | const methodName = path.basename(methodFilename, '.jsx');
12 | map.set(methodName, (await import(path.join(workingDirectory, methodFilename))).default);
13 | });
14 |
15 | export default function describeLifecycles({ Wrap, Wrapper }, ...lifecycles) {
16 | const WrapperName = Wrapper.name;
17 | const isShallow = WrapperName === 'ShallowWrapper';
18 | const isMount = WrapperName === 'ReactWrapper';
19 | const hasDOM = isMount;
20 | const makeDOMElement = () => (hasDOM ? global.document.createElement('div') : { nodeType: 1 });
21 |
22 | lifecycles.forEach((lifecycle) => {
23 | if (!map.has(lifecycle)) {
24 | throw new Error(`Lifecycle ${lifecycle} does not exist`);
25 | }
26 |
27 | const lifecycleModule = map.get(lifecycle);
28 |
29 | lifecycleModule({
30 | Wrap,
31 | WrapRendered: isShallow ? Wrap : (...args) => Wrap(...args).children(),
32 | Wrapper,
33 | WrapperName,
34 | isShallow,
35 | isMount,
36 | hasDOM,
37 | makeDOMElement,
38 | });
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useLayoutEffect.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { useLayoutEffect, useState } from '../../_helpers/react-compat';
7 |
8 | export default function describeUseLayoutEffect({ Wrap, isShallow }) {
9 | describe('hooks: useLayoutEffect', () => {
10 | function ComponentUsingLayoutEffectHook() {
11 | const [ctr, setCtr] = useState(0);
12 | useLayoutEffect(() => {
13 | setCtr(1);
14 | setTimeout(() => {
15 | setCtr(2);
16 | }, 100);
17 | }, []);
18 | return {ctr}
;
19 | }
20 |
21 | // TODO: enable when https://github.com/facebook/react/issues/15275 is fixed
22 | itIf(!isShallow, 'works with `useLayoutEffect`', () => {
23 | return new Promise((resolve) => {
24 | const wrapper = Wrap( );
25 |
26 | expect(wrapper.debug()).to.equal(
27 | isShallow
28 | ? `
29 | 1
30 |
`
31 | : `
32 |
33 | 1
34 |
35 | `,
36 | );
37 |
38 | setTimeout(() => {
39 | wrapper.update();
40 | expect(wrapper.debug()).to.equal(
41 | isShallow
42 | ? `
43 | 2
44 |
`
45 | : `
46 |
47 | 2
48 |
49 | `,
50 | );
51 | resolve();
52 | }, 100);
53 | });
54 | });
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/test/test/shared/methods/map.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeMap({ Wrap, Wrapper }) {
5 | describe('.map(fn)', () => {
6 | it('calls a function with a wrapper for each node in the wrapper', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | const spy = vi.fn();
15 |
16 | wrapper.find('.foo').map(spy);
17 |
18 | expect(spy).to.have.property('callCount', 3);
19 | expect(spy.mock.calls[0][0]).to.be.instanceOf(Wrapper);
20 | expect(spy.mock.calls[0][0].hasClass('bax')).to.equal(true);
21 | expect(spy.mock.calls[0][1]).to.equal(0);
22 | expect(spy.mock.calls[1][0]).to.be.instanceOf(Wrapper);
23 | expect(spy.mock.calls[1][0].hasClass('bar')).to.equal(true);
24 | expect(spy.mock.calls[1][1]).to.equal(1);
25 | expect(spy.mock.calls[2][0]).to.be.instanceOf(Wrapper);
26 | expect(spy.mock.calls[2][0].hasClass('baz')).to.equal(true);
27 | expect(spy.mock.calls[2][1]).to.equal(2);
28 | });
29 |
30 | it('returns an array with the mapped values', () => {
31 | const wrapper = Wrap(
32 | ,
37 | );
38 | const result = wrapper.find('.foo').map((w) => w.props().className);
39 |
40 | expect(result).to.eql(['foo bax', 'foo bar', 'foo baz']);
41 | });
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/test/test/shared/methods/filterWhere.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeFilterWhere({ Wrap, Wrapper }) {
5 | describe('.filterWhere(predicate)', () => {
6 | it('filters only the nodes of the wrapper', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 |
15 | const stub = vi.fn();
16 | stub.mockReturnValueOnce(false);
17 | stub.mockReturnValueOnce(true);
18 | stub.mockReturnValueOnce(false);
19 |
20 | const baz = wrapper.find('.foo').filterWhere(stub);
21 | expect(baz).to.have.lengthOf(1);
22 | expect(baz.hasClass('baz')).to.equal(true);
23 | });
24 |
25 | it('calls the predicate with the wrapped node as the first argument', () => {
26 | const wrapper = Wrap(
27 | ,
32 | );
33 |
34 | const spy = vi.fn();
35 | spy.mockReturnValue(true);
36 | wrapper.find('.foo').filterWhere(spy);
37 | expect(spy).to.have.property('callCount', 3);
38 | expect(spy.mock.calls[0][0]).to.be.instanceOf(Wrapper);
39 | expect(spy.mock.calls[1][0]).to.be.instanceOf(Wrapper);
40 | expect(spy.mock.calls[2][0]).to.be.instanceOf(Wrapper);
41 | expect(spy.mock.calls[0][0].hasClass('bar')).to.equal(true);
42 | expect(spy.mock.calls[1][0].hasClass('baz')).to.equal(true);
43 | expect(spy.mock.calls[2][0].hasClass('bux')).to.equal(true);
44 | });
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useMemo.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { useMemo } from '../../_helpers/react-compat';
5 |
6 | export default function describeUseMemo({ Wrap }) {
7 | describe('hooks: useMemo', () => {
8 | function RendersNull() {
9 | return null;
10 | }
11 |
12 | it('get same value when using `useMemo` and rerender with same prop in dependencies', () => {
13 | function ComponentUsingMemoHook(props) {
14 | const { count } = props;
15 | const memoized = useMemo(() => ({ result: count * 2 }), [count]);
16 | return ;
17 | }
18 | const wrapper = Wrap( );
19 | const initialValue = wrapper.find(RendersNull).prop('memoized');
20 | wrapper.setProps({ unRelatedProp: '123' });
21 | const nextValue = wrapper.find(RendersNull).prop('memoized');
22 | expect(initialValue).to.equal(nextValue);
23 | });
24 |
25 | it('get different value when using `useMemo` and rerender with different prop in dependencies', () => {
26 | function ComponentUsingMemoHook(props) {
27 | const { count, relatedProp } = props;
28 | const memoized = useMemo(() => ({ result: count * 2 }), [count, relatedProp]);
29 | return ;
30 | }
31 | const wrapper = Wrap( );
32 | const initialValue = wrapper.find(RendersNull).prop('memoized');
33 | wrapper.setProps({ relatedProp: '123' });
34 | const nextValue = wrapper.find(RendersNull).prop('memoized');
35 | expect(initialValue).not.to.equal(nextValue);
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/test/test/shared/methods/single.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { sym } from 'enzyme/build/Utils';
5 |
6 | export default function describeSingle({ Wrap }) {
7 | describe('#single()', () => {
8 | it('throws if run on multiple nodes', () => {
9 | const wrapper = Wrap(
10 |
11 |
12 |
13 |
,
14 | ).children();
15 | expect(wrapper).to.have.lengthOf(2);
16 | expect(() => wrapper.single('name!')).to.throw(
17 | Error,
18 | 'Method “name!” is meant to be run on 1 node. 2 found instead.',
19 | );
20 | });
21 |
22 | it('throws if run on zero nodes', () => {
23 | const wrapper = Wrap(
).children();
24 | expect(wrapper).to.have.lengthOf(0);
25 | expect(() => wrapper.single('name!')).to.throw(
26 | Error,
27 | 'Method “name!” is meant to be run on 1 node. 0 found instead.',
28 | );
29 | });
30 |
31 | it('throws if run on zero nodes', () => {
32 | const wrapper = Wrap(
).children();
33 | expect(wrapper).to.have.lengthOf(0);
34 | expect(() => wrapper.single('name!')).to.throw(
35 | Error,
36 | 'Method “name!” is meant to be run on 1 node. 0 found instead.',
37 | );
38 | });
39 |
40 | it('works with a name', () => {
41 | const wrapper = Wrap(
);
42 | wrapper.single('foo', (node) => {
43 | expect(node).to.equal(wrapper[sym('__node__')]);
44 | });
45 | });
46 |
47 | it('works without a name', () => {
48 | const wrapper = Wrap(
);
49 | wrapper.single((node) => {
50 | expect(node).to.equal(wrapper[sym('__node__')]);
51 | });
52 | });
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/test/test/shared/methods/wrap.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | export default function describeWrap({ Wrap, Wrapper, isShallow, isMount }) {
7 | describe('.wrap()', () => {
8 | class Foo extends React.Component {
9 | render() {
10 | return (
11 |
15 | );
16 | }
17 | }
18 |
19 | it('returns itself when it is already a Wrapper', () => {
20 | const wrapperDiv = Wrap(
);
21 | const wrapperFoo = Wrap( );
22 |
23 | expect(wrapperDiv.wrap(wrapperFoo)).to.equal(wrapperFoo);
24 | expect(wrapperFoo.wrap(wrapperDiv)).to.equal(wrapperDiv);
25 | });
26 |
27 | itIf(isShallow, 'wraps when it is not already a Wrapper', () => {
28 | const wrapper = Wrap( );
29 | const el = wrapper.find('a').at(1);
30 | const wrappedEl = wrapper.wrap(el.getElement());
31 | expect(wrappedEl).to.be.instanceOf(Wrapper);
32 | expect(wrappedEl.props()).to.eql(el.props());
33 |
34 | expect(wrappedEl.shallow().debug()).to.equal(el.debug());
35 | });
36 |
37 | itIf(isMount, 'wraps when it is not already a Wrapper', () => {
38 | const wrapper = Wrap( );
39 | const el = wrapper.find('a').at(1);
40 | const wrappedEl = wrapper.wrap(el.getElement());
41 | expect(wrappedEl).to.be.instanceOf(Wrapper);
42 | expect(wrappedEl.props()).to.eql(el.props());
43 |
44 | // FIXME: enable this instead of that:
45 | // expect(wrappedEl.mount().debug()).to.equal(el.debug());
46 | expect(wrappedEl.debug()).to.equal(' ');
47 | });
48 | });
49 | }
50 |
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "enzyme-test-suite",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "JavaScript Testing utilities for React",
6 | "homepage": "https://enzymejs.github.io/enzyme/",
7 | "scripts": {
8 | "lint": "eslint . --ext .js,.jsx",
9 | "prettier": "prettier --check . --cache",
10 | "run": "vitest run test",
11 | "test": "yarn lint && yarn prettier"
12 | },
13 | "keywords": [
14 | "javascript",
15 | "shallow rendering",
16 | "shallowRender",
17 | "test",
18 | "reactjs",
19 | "react",
20 | "flux",
21 | "testing",
22 | "test utils",
23 | "assertion helpers",
24 | "tdd",
25 | "mocha"
26 | ],
27 | "author": "Jordan Harband ",
28 | "contributors": [
29 | "Wojciech Maj "
30 | ],
31 | "license": "MIT",
32 | "dependencies": {
33 | "@wojtekmaj/async-array-utils": "^1.6.1",
34 | "@wojtekmaj/enzyme-adapter-react-17": "portal:../",
35 | "@wojtekmaj/enzyme-adapter-utils": "^0.2.0",
36 | "create-react-class": "^15.7.0",
37 | "enzyme": "^3.11.0",
38 | "enzyme-shallow-equal": "^1.0.0",
39 | "html-element-map": "^1.3.0",
40 | "jsdom": "~15.1.0",
41 | "lodash.isequal": "^4.5.0",
42 | "mocha-wrap": "^2.1.0",
43 | "object-inspect": "^1.11.0",
44 | "prop-types": "^15.7.0",
45 | "raf": "^3.4.1",
46 | "react": "^17.0.0",
47 | "react-dom": "^17.0.0",
48 | "react-is": "^17.0.0",
49 | "vitest": "^0.30.1"
50 | },
51 | "devDependencies": {
52 | "eslint": "^8.26.0",
53 | "eslint-config-wojtekmaj": "^0.8.3",
54 | "prettier": "^2.7.0"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "https://github.com/wojtekmaj/enzyme-adapter-react-17.git",
59 | "directory": "test"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot-dedupe.yml:
--------------------------------------------------------------------------------
1 | name: Dedupe Dependabot PRs
2 |
3 | on:
4 | push:
5 | branches: ['dependabot/**']
6 |
7 | permissions:
8 | contents: write
9 | pull-requests: write
10 | repository-projects: write
11 |
12 | jobs:
13 | dedupe:
14 | name: Dedupe Dependabot PRs
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v3
20 |
21 | - name: Cache .yarn/cache
22 | uses: actions/cache@v3
23 | env:
24 | cache-name: yarn-cache
25 | with:
26 | path: .yarn/cache
27 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
28 | restore-keys: |
29 | ${{ runner.os }}-${{ env.cache-name }}
30 |
31 | - name: Use Node.js
32 | uses: actions/setup-node@v3
33 | with:
34 | node-version: '18'
35 |
36 | - name: Enable Corepack
37 | run: corepack enable
38 |
39 | - name: Configure Git
40 | run: |
41 | git config user.name 'github-actions[bot]'
42 | git config user.email 'github-actions[bot]@users.noreply.github.com'
43 |
44 | - name: Detect working directory
45 | run: |
46 | echo "WORKING_DIRECTORY=$(git log -1 --pretty=%B | sed -n 's/.* in \/\(.*\)$/\1/p')" >> $GITHUB_ENV
47 |
48 | - name: Dedupe dependencies
49 | run: yarn dedupe
50 | working-directory: ${{ env.WORKING_DIRECTORY }}
51 | env:
52 | HUSKY: 0
53 |
54 | - name: Commit changes
55 | run: |
56 | git add .
57 | git commit -m '[dependabot skip] Dedupe dependencies' || true
58 | working-directory: ${{ env.WORKING_DIRECTORY }}
59 |
60 | - name: Push changes
61 | run: git push
62 | working-directory: ${{ env.WORKING_DIRECTORY }}
63 |
--------------------------------------------------------------------------------
/test/test/shared/methods/@@iterator.jsx:
--------------------------------------------------------------------------------
1 | import { expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { ITERATOR_SYMBOL } from 'enzyme/build/Utils';
5 |
6 | import { describeIf } from '../../_helpers';
7 |
8 | export default function describeIterator({ Wrap }) {
9 | describeIf(!!ITERATOR_SYMBOL, '@@iterator', () => {
10 | it('is iterable', () => {
11 | class Foo extends React.Component {
12 | render() {
13 | return (
14 |
20 | );
21 | }
22 | }
23 | const wrapper = Wrap( );
24 | const [a, b, c, d, ...e] = wrapper.find('a');
25 | const a1 = wrapper.find('a').get(0);
26 | const b1 = wrapper.find('a').get(1);
27 | const c1 = wrapper.find('a').get(2);
28 | const d1 = wrapper.find('a').get(3);
29 | expect(a1).to.deep.equal(a);
30 | expect(b1).to.deep.equal(b);
31 | expect(c1).to.deep.equal(c);
32 | expect(d1).to.deep.equal(d);
33 | expect(e).to.eql([]);
34 | });
35 |
36 | it('returns an iterable iterator', () => {
37 | class Foo extends React.Component {
38 | render() {
39 | return (
40 |
46 | );
47 | }
48 | }
49 | const wrapper = Wrap( );
50 |
51 | const iter = wrapper[ITERATOR_SYMBOL]();
52 | expect(iter).to.have.property(ITERATOR_SYMBOL).and.be.a('function');
53 | expect(iter[ITERATOR_SYMBOL]()).to.equal(iter);
54 | });
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/test/test/shared/methods/exists.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeExists({ Wrap, Wrapper }) {
5 | describe('.exists()', () => {
6 | it('has no required arguments', () => {
7 | expect(Wrapper.prototype.exists).to.have.lengthOf(0);
8 | });
9 |
10 | describe('without argument', () => {
11 | it('returns true if node exists in wrapper', () => {
12 | const wrapper = Wrap(
);
13 | expect(wrapper.find('.bar').exists()).to.equal(false);
14 | expect(wrapper.find('.foo').exists()).to.equal(true);
15 | });
16 | });
17 | describe('with argument', () => {
18 | it('throws on invalid EnzymeSelector', () => {
19 | const wrapper = Wrap(
);
20 |
21 | expect(() => wrapper.exists(null)).to.throw(TypeError);
22 | expect(() => wrapper.exists(undefined)).to.throw(TypeError);
23 | expect(() => wrapper.exists(45)).to.throw(TypeError);
24 | expect(() => wrapper.exists({})).to.throw(TypeError);
25 | });
26 |
27 | it('returns .find(arg).exists() instead', () => {
28 | const wrapper = Wrap(
);
29 | const fakeFindExistsReturnVal = {
30 | toString() {
31 | return 'fake .find(arg).exists() return value';
32 | },
33 | };
34 | const fakeSelector = '.someClass';
35 | wrapper.find = vi.fn().mockReturnValue({
36 | exists() {
37 | return fakeFindExistsReturnVal;
38 | },
39 | });
40 | const existsResult = wrapper.exists(fakeSelector);
41 | expect(wrapper.find).to.have.property('callCount', 1);
42 | expect(wrapper.find.mock.calls[0][0]).to.equal(fakeSelector);
43 | expect(existsResult).to.equal(fakeFindExistsReturnVal);
44 | });
45 | });
46 | });
47 | }
48 |
--------------------------------------------------------------------------------
/test/test/shared/methods/render.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeRender({ Wrap }) {
5 | describe('.render()', () => {
6 | it('returns a cheerio wrapper around the current node', () => {
7 | class Foo extends React.Component {
8 | render() {
9 | return
;
10 | }
11 | }
12 |
13 | class Bar extends React.Component {
14 | render() {
15 | return (
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
23 | const wrapper = Wrap( );
24 |
25 | expect(wrapper.render().find('.in-foo')).to.have.lengthOf(1);
26 |
27 | const rendered = wrapper.render();
28 | expect(rendered.is('.in-bar')).to.equal(true);
29 | expect(rendered).to.have.lengthOf(1);
30 |
31 | const renderedFoo = wrapper.find(Foo).render();
32 | expect(renderedFoo.is('.in-foo')).to.equal(true);
33 | expect(renderedFoo.is('.in-bar')).to.equal(false);
34 | expect(renderedFoo.find('.in-bar')).to.have.lengthOf(0);
35 | });
36 |
37 | describe('stateless function components (SFCs)', () => {
38 | it('returns a cheerio wrapper around the current node', () => {
39 | const Foo = () =>
;
40 |
41 | const Bar = () => (
42 |
43 |
44 |
45 | );
46 |
47 | const wrapper = Wrap( );
48 |
49 | expect(wrapper.render().find('.in-foo')).to.have.lengthOf(1);
50 | expect(wrapper.render().is('.in-bar')).to.equal(true);
51 |
52 | const renderedFoo = wrapper.find(Foo).render();
53 | expect(renderedFoo.is('.in-foo')).to.equal(true);
54 | expect(renderedFoo.is('.in-bar')).to.equal(false);
55 | expect(renderedFoo.find('.in-bar')).to.have.lengthOf(0);
56 | });
57 | });
58 | });
59 | }
60 |
--------------------------------------------------------------------------------
/test/test/shared/methods/closest.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeClosest({ Wrap }) {
5 | describe('.closest(selector)', () => {
6 | it('returns the closest ancestor for a given selector', () => {
7 | const wrapper = Wrap(
8 | ,
15 | );
16 |
17 | const closestFoo = wrapper.find('.bar').closest('.foo');
18 | expect(closestFoo).to.have.lengthOf(1);
19 | expect(closestFoo.hasClass('baz')).to.equal(true);
20 | });
21 |
22 | it('only ever returns a wrapper of a single node', () => {
23 | const wrapper = Wrap(
24 | ,
31 | );
32 |
33 | expect(wrapper.find('.baz').parent().hasClass('bar')).to.equal(true);
34 | });
35 |
36 | it('returns itself if matching', () => {
37 | const wrapper = Wrap(
38 | ,
45 | );
46 |
47 | expect(wrapper.find('.bux').closest('.baz').hasClass('bux')).to.equal(true);
48 | });
49 |
50 | it('does not find a nonexistent match', () => {
51 | const wrapper = Wrap(
52 | ,
55 | );
56 |
57 | expect(wrapper.find('.fooooo')).to.have.lengthOf(0);
58 |
59 | const bar = wrapper.find('.bar');
60 | expect(bar).to.have.lengthOf(1);
61 |
62 | expect(bar.closest('.fooooo')).to.have.lengthOf(0);
63 | });
64 | });
65 | }
66 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useCallback.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { useCallback } from '../../_helpers/react-compat';
7 |
8 | export default function describeUseCallback({ Wrap, isShallow }) {
9 | describe('hooks: useCallback', () => {
10 | function RendersNull() {
11 | return null;
12 | }
13 |
14 | function ComponentUsingCallbackHook({ onChange }) {
15 | const callback = useCallback((value) => onChange(value), [onChange]);
16 | return ;
17 | }
18 |
19 | function ComponentUsingCallbackHookWithRelatedProp({ onChange, relatedProp }) {
20 | const callback = useCallback((value) => onChange(value), [onChange, relatedProp]);
21 | return ;
22 | }
23 |
24 | // TODO: fix pending https://github.com/facebook/react/issues/15774
25 | itIf(
26 | !isShallow,
27 | 'get same callback when using `useCallback` and rerender with same prop in dependencies',
28 | () => {
29 | const onChange = () => {};
30 | const wrapper = Wrap( );
31 | const initialCallback = wrapper.find(RendersNull).prop('callback');
32 |
33 | wrapper.setProps({ unRelatedProp: '123' });
34 |
35 | const nextCallback = wrapper.find(RendersNull).prop('callback');
36 | expect(initialCallback).to.equal(nextCallback);
37 | },
38 | );
39 |
40 | it('get different callback when using `useCallback` and rerender with different prop in dependencies', () => {
41 | const onChange = () => {};
42 | const wrapper = Wrap(
43 | ,
44 | );
45 | const initialCallback = wrapper.find(RendersNull).prop('callback');
46 |
47 | wrapper.setProps({ relatedProp: '123' });
48 |
49 | const nextCallback = wrapper.find(RendersNull).prop('callback');
50 | expect(initialCallback).not.to.equal(nextCallback);
51 | });
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/test/test/_helpers/react-compat.js:
--------------------------------------------------------------------------------
1 | const { createFactory } = require('react');
2 |
3 | let createClass;
4 | let renderToString;
5 | let createPortal;
6 | let createContext;
7 | let createRef;
8 | let forwardRef;
9 | let Fragment;
10 | let StrictMode;
11 | let AsyncMode;
12 | let ConcurrentMode;
13 | let createRoot;
14 | let Profiler;
15 | let PureComponent;
16 | let Suspense;
17 | let lazy;
18 | let memo;
19 | let useCallback;
20 | let useContext;
21 | let useDebugValue;
22 | let useEffect;
23 | let useImperativeHandle;
24 | let useLayoutEffect;
25 | let useMemo;
26 | let useReducer;
27 | let useRef;
28 | let useState;
29 | let act;
30 |
31 | createClass = require('create-react-class');
32 |
33 | ({ renderToString } = require('react-dom/server'));
34 |
35 | ({ createPortal } = require('react-dom'));
36 |
37 | ({ PureComponent } = require('react'));
38 |
39 | ({ Fragment } = require('react'));
40 |
41 | ({
42 | createContext,
43 | createRef,
44 | forwardRef,
45 | StrictMode,
46 | unstable_AsyncMode: AsyncMode,
47 | } = require('react'));
48 |
49 | ({ Profiler } = require('react'));
50 |
51 | ({ Suspense, lazy, memo } = require('react'));
52 |
53 | ({ unstable_ConcurrentMode: ConcurrentMode } = require('react'));
54 |
55 | ({ unstable_createRoot: createRoot } = require('react'));
56 |
57 | ({
58 | useCallback,
59 | useContext,
60 | useDebugValue,
61 | useEffect,
62 | useImperativeHandle,
63 | useLayoutEffect,
64 | useMemo,
65 | useReducer,
66 | useRef,
67 | useState,
68 | } = require('react'));
69 |
70 | ({ act } = require('react-dom/test-utils'));
71 |
72 | export {
73 | createClass,
74 | createFactory,
75 | renderToString,
76 | createPortal,
77 | createContext,
78 | createRef,
79 | createRoot,
80 | forwardRef,
81 | Fragment,
82 | StrictMode,
83 | AsyncMode,
84 | ConcurrentMode,
85 | Profiler,
86 | PureComponent,
87 | Suspense,
88 | lazy,
89 | memo,
90 | useCallback,
91 | useContext,
92 | useDebugValue,
93 | useEffect,
94 | useImperativeHandle,
95 | useLayoutEffect,
96 | useMemo,
97 | useReducer,
98 | useRef,
99 | useState,
100 | act,
101 | };
102 |
--------------------------------------------------------------------------------
/test/test/enzyme-shallow-equal.spec.js:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | import shallowEqual from 'enzyme-shallow-equal';
4 |
5 | describe('shallowEqual', () => {
6 | it('returns true for things that are SameValue', () => {
7 | [{}, [], NaN, 42, 'foo', '', 0, Infinity, () => {}, /a/g, true, false, null, undefined].forEach(
8 | (x) => {
9 | expect(shallowEqual(x, x)).to.equal(true);
10 | },
11 | );
12 | });
13 |
14 | it('returns false if one is falsy and one is truthy', () => {
15 | [null, undefined, false, 0, NaN].forEach((x) => {
16 | expect(shallowEqual(true, x)).to.equal(false);
17 | });
18 | });
19 |
20 | it('returns true if both have zero keys', () => {
21 | expect(shallowEqual({}, {})).to.equal(true);
22 | });
23 |
24 | it('returns false if they have different numbers of keys', () => {
25 | expect(shallowEqual({ a: 1 }, {})).to.equal(false);
26 | expect(shallowEqual({}, { a: 1 })).to.equal(false);
27 | });
28 |
29 | it('returns false if they have the same number, but differently named, keys', () => {
30 | expect(shallowEqual({ a: 1 }, { b: 1 })).to.equal(false);
31 | });
32 |
33 | it('returns false if they have the same keys, with different values', () => {
34 | [{}, [], NaN, 42, 'foo', '', 0, Infinity, () => {}, /a/g, true, false, null, undefined].forEach(
35 | (x) => {
36 | expect(shallowEqual({ a: x }, { a: {} })).to.equal(false);
37 | expect(shallowEqual({ a: {} }, { a: x })).to.equal(false);
38 | },
39 | );
40 | });
41 |
42 | it('returns false if an undefined key in one is absent in the other', () => {
43 | expect(shallowEqual({ a: undefined, b: true }, { b: true, c: undefined })).to.equal(false);
44 | expect(shallowEqual({ c: undefined, b: true }, { b: true, a: undefined })).to.equal(false);
45 | });
46 |
47 | it('returns true if they have the same keys, with the same values', () => {
48 | [{}, [], NaN, 42, 'foo', '', 0, Infinity, () => {}, /a/g, true, false, null, undefined].forEach(
49 | (x) => {
50 | expect(shallowEqual({ a: x }, { a: x })).to.equal(true);
51 | expect(shallowEqual({ a: x }, { a: x })).to.equal(true);
52 | },
53 | );
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/test/test/shared/methods/slice.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeSlice({ Wrap }) {
5 | describe('.slice([begin[, end]])', () => {
6 | it('returns an identical wrapper if no params are set', () => {
7 | const wrapper = Wrap(
8 | ,
13 | );
14 | const slice = wrapper.find('.foo').slice();
15 | expect(slice).to.have.lengthOf(3);
16 | expect(slice.at(0).hasClass('bax')).to.equal(true);
17 | expect(slice.at(1).hasClass('bar')).to.equal(true);
18 | expect(slice.at(2).hasClass('baz')).to.equal(true);
19 | });
20 |
21 | it('returns a new wrapper if begin is set', () => {
22 | const wrapper = Wrap(
23 | ,
28 | );
29 | const slice = wrapper.find('.foo').slice(1);
30 | expect(slice).to.have.lengthOf(2);
31 | expect(slice.at(0).hasClass('bar')).to.equal(true);
32 | expect(slice.at(1).hasClass('baz')).to.equal(true);
33 | });
34 |
35 | it('returns a new wrapper if begin and end are set', () => {
36 | const wrapper = Wrap(
37 | ,
42 | );
43 | const slice = wrapper.find('.foo').slice(1, 2);
44 | expect(slice).to.have.lengthOf(1);
45 | expect(slice.at(0).hasClass('bar')).to.equal(true);
46 | });
47 |
48 | it('returns a new wrapper if begin and end are set (negative)', () => {
49 | const wrapper = Wrap(
50 | ,
55 | );
56 | const slice = wrapper.find('.foo').slice(-2, -1);
57 | expect(slice).to.have.lengthOf(1);
58 | expect(slice.at(0).hasClass('bar')).to.equal(true);
59 | });
60 | });
61 | }
62 |
--------------------------------------------------------------------------------
/test/test/shared/methods/instance.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | export default function describeInstance({ Wrap, WrapperName, isShallow }) {
7 | describe('.instance()', () => {
8 | it('returns the component instance', () => {
9 | class Foo extends React.Component {
10 | render() {
11 | return
;
12 | }
13 | }
14 |
15 | const wrapper = Wrap( );
16 | expect(wrapper.instance()).to.be.instanceof(Foo);
17 | expect(wrapper.instance().render).to.equal(Foo.prototype.render);
18 | });
19 |
20 | describe('stateless function components (SFCs)', () => {
21 | function SFC() {
22 | return
;
23 | }
24 |
25 | it('has no instance', () => {
26 | const wrapper = Wrap( );
27 | expect(wrapper.instance()).to.equal(null);
28 | });
29 |
30 | itIf(false, 'has an instance', () => {
31 | const wrapper = Wrap( );
32 | expect(wrapper.instance()).not.to.equal(null);
33 | });
34 | });
35 |
36 | itIf(!isShallow, 'throws when wrapping multiple elements', () => {
37 | class Test extends React.Component {
38 | render() {
39 | return (
40 |
41 |
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | const wrapper = Wrap( ).find('span');
49 | expect(() => wrapper.instance()).to.throw(
50 | Error,
51 | 'Method “instance” is meant to be run on 1 node. 2 found instead.',
52 | );
53 | });
54 |
55 | itIf(isShallow, 'throws if called on something other than the root node', () => {
56 | class Foo extends React.Component {
57 | render() {
58 | return (
59 |
62 | );
63 | }
64 | }
65 |
66 | const wrapper = Wrap( );
67 | const div = wrapper.find('div');
68 |
69 | expect(() => div.instance()).to.throw(
70 | Error,
71 | `${WrapperName}::instance() can only be called on the root`,
72 | );
73 | });
74 | });
75 | }
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@wojtekmaj/enzyme-adapter-react-17",
3 | "version": "0.8.0",
4 | "description": "JavaScript Testing utilities for React",
5 | "homepage": "https://enzymejs.github.io/enzyme/",
6 | "main": "build",
7 | "workspaces": [
8 | "test"
9 | ],
10 | "scripts": {
11 | "build": "babel --source-maps=both src -d build",
12 | "clean": "rimraf build",
13 | "lint": "eslint src test --ext .js,.jsx",
14 | "postinstall": "husky install",
15 | "prepack": "yarn clean && yarn build",
16 | "prettier": "prettier --check . --cache",
17 | "test": "yarn lint && yarn prettier"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "shallow rendering",
22 | "shallowRender",
23 | "test",
24 | "reactjs",
25 | "react",
26 | "flux",
27 | "testing",
28 | "test utils",
29 | "assertion helpers",
30 | "tdd",
31 | "mocha"
32 | ],
33 | "author": "Jordan Harband ",
34 | "contributors": [
35 | "Wojciech Maj "
36 | ],
37 | "license": "MIT",
38 | "dependencies": {
39 | "@wojtekmaj/enzyme-adapter-utils": "^0.2.0",
40 | "enzyme-shallow-equal": "^1.0.0",
41 | "has": "^1.0.0",
42 | "prop-types": "^15.7.0",
43 | "react-is": "^17.0.0",
44 | "react-test-renderer": "^17.0.0"
45 | },
46 | "devDependencies": {
47 | "@babel/cli": "^7.15.0",
48 | "@babel/core": "^7.15.0",
49 | "@babel/preset-env": "^7.15.0",
50 | "@babel/preset-react": "^7.15.0",
51 | "enzyme": "^3.0.0",
52 | "eslint": "^8.26.0",
53 | "eslint-config-wojtekmaj": "^0.8.3",
54 | "husky": "^8.0.0",
55 | "prettier": "^2.7.0",
56 | "pretty-quick": "^3.1.0",
57 | "react": "^17.0.0",
58 | "react-dom": "^17.0.0",
59 | "rimraf": "^3.0.0"
60 | },
61 | "peerDependencies": {
62 | "enzyme": "^3.0.0",
63 | "react": "^17.0.0-0",
64 | "react-dom": "^17.0.0-0"
65 | },
66 | "publishConfig": {
67 | "access": "public"
68 | },
69 | "files": [
70 | "build",
71 | "src",
72 | "index.d.ts"
73 | ],
74 | "repository": {
75 | "type": "git",
76 | "url": "https://github.com/wojtekmaj/enzyme-adapter-react-17.git"
77 | },
78 | "funding": "https://github.com/wojtekmaj/enzyme-adapter-react-17?sponsor=1",
79 | "packageManager": "yarn@3.1.0"
80 | }
81 |
--------------------------------------------------------------------------------
/test/test/shared/lifecycles/getSnapshotBeforeUpdate.jsx:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { argSpy, expectArgs } from '../../_helpers';
5 |
6 | export default function describeGSBU({ Wrap }) {
7 | describe('getSnapshotBeforeUpdate()', () => {
8 | it('calls getSnapshotBeforeUpdate and pass snapshot to componentDidUpdate', () => {
9 | const spy = argSpy();
10 |
11 | class Foo extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | foo: 'bar',
16 | };
17 | }
18 |
19 | componentDidUpdate(prevProps, prevState, snapshot) {
20 | spy('componentDidUpdate', prevProps, this.props, prevState, this.state, snapshot);
21 | }
22 |
23 | getSnapshotBeforeUpdate(prevProps, prevState) {
24 | spy('getSnapshotBeforeUpdate', prevProps, this.props, prevState, this.state);
25 | return { snapshot: 'ok' };
26 | }
27 |
28 | render() {
29 | spy('render');
30 | return foo
;
31 | }
32 | }
33 | const wrapper = Wrap( );
34 | expectArgs(spy, 1, [['render']]);
35 |
36 | wrapper.setProps({ name: 'bar' });
37 | expectArgs(spy, 2, [
38 | ['render'],
39 | [
40 | 'getSnapshotBeforeUpdate',
41 | { name: 'foo' },
42 | { name: 'bar' },
43 | { foo: 'bar' },
44 | { foo: 'bar' },
45 | ],
46 | [
47 | 'componentDidUpdate',
48 | { name: 'foo' },
49 | { name: 'bar' },
50 | { foo: 'bar' },
51 | { foo: 'bar' },
52 | { snapshot: 'ok' },
53 | ],
54 | ]);
55 |
56 | wrapper.setState({ foo: 'baz' });
57 | expectArgs(spy, 3, [
58 | ['render'],
59 | [
60 | 'getSnapshotBeforeUpdate',
61 | { name: 'bar' },
62 | { name: 'bar' },
63 | { foo: 'bar' },
64 | { foo: 'baz' },
65 | ],
66 | [
67 | 'componentDidUpdate',
68 | { name: 'bar' },
69 | { name: 'bar' },
70 | { foo: 'bar' },
71 | { foo: 'baz' },
72 | { snapshot: 'ok' },
73 | ],
74 | ]);
75 | });
76 | });
77 | }
78 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useReducer.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { useReducer } from '../../_helpers/react-compat';
5 |
6 | export default function describeUseReducer({ Wrap }) {
7 | describe('hooks: useReducer', () => {
8 | describe('with custom dispatch', () => {
9 | const initialState = [];
10 |
11 | function Child({ dispatch, text }) {
12 | function fire() {
13 | dispatch({
14 | type: 'ADD_TEXT',
15 | payload: text,
16 | });
17 | }
18 |
19 | return (
20 |
21 | Add
22 | {text}
23 |
24 | );
25 | }
26 |
27 | function reducer(state, action) {
28 | switch (action.type) {
29 | case 'ADD_TEXT':
30 | return [...state, action.payload];
31 | default:
32 | throw new Error();
33 | }
34 | }
35 |
36 | function FooBarTextList() {
37 | const [state, dispatch] = useReducer(reducer, initialState);
38 |
39 | return (
40 |
41 |
42 |
43 | {state.map((text) => (
44 |
{text}
45 | ))}
46 |
47 | );
48 | }
49 |
50 | it('render with initial state from useReducer', () => {
51 | const wrapper = Wrap( );
52 | expect(wrapper.find('p')).to.have.lengthOf(0);
53 | });
54 |
55 | it('Test with Add Foo & Bar tex', () => {
56 | const wrapper = Wrap( );
57 | expect(wrapper.find('p')).to.have.lengthOf(0);
58 | wrapper.find('Child').at(0).props().dispatch({
59 | type: 'ADD_TEXT',
60 | payload: 'foo',
61 | });
62 | wrapper.update();
63 |
64 | expect(wrapper.find('p')).to.have.lengthOf(1);
65 | expect(wrapper.find('p').at(0).text()).to.equal('foo');
66 |
67 | wrapper.find('Child').at(1).props().dispatch({
68 | type: 'ADD_TEXT',
69 | payload: 'bar',
70 | });
71 | wrapper.update();
72 | expect(wrapper.find('p')).to.have.length(2);
73 | expect(wrapper.find('p').at(0).text()).to.equal('foo');
74 | expect(wrapper.find('p').at(1).text()).to.equal('bar');
75 | });
76 | });
77 | });
78 | }
79 |
--------------------------------------------------------------------------------
/test/test/shared/methods/reduce.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { getElementPropSelector, getWrapperPropSelector } from '../../_helpers/selectors';
5 |
6 | export default function describeReduce({ Wrap, Wrapper }) {
7 | describe('.reduce(fn[, initialValue])', () => {
8 | it('has the right length', () => {
9 | expect(Wrapper.prototype.reduce).to.have.lengthOf(1);
10 | });
11 |
12 | it('calls a function with a wrapper for each node in the wrapper', () => {
13 | const wrapper = Wrap(
14 | ,
19 | );
20 | const spy = vi.fn((n) => n + 1);
21 |
22 | wrapper.find('.foo').reduce(spy, 0);
23 |
24 | expect(spy).to.have.property('callCount', 3);
25 | expect(spy.mock.calls[0][1]).to.be.instanceOf(Wrapper);
26 | expect(spy.mock.calls[0][1].hasClass('bax')).to.equal(true);
27 | expect(spy.mock.calls[1][1]).to.be.instanceOf(Wrapper);
28 | expect(spy.mock.calls[1][1].hasClass('bar')).to.equal(true);
29 | expect(spy.mock.calls[2][1]).to.be.instanceOf(Wrapper);
30 | expect(spy.mock.calls[2][1].hasClass('baz')).to.equal(true);
31 | });
32 |
33 | it('accumulates a value', () => {
34 | const wrapper = Wrap(
35 | ,
40 | );
41 | const result = wrapper.find('.foo').reduce((obj, n) => {
42 | obj[n.prop('id')] = n.prop('className');
43 | return obj;
44 | }, {});
45 |
46 | expect(result).to.eql({
47 | bax: 'foo qoo',
48 | bar: 'foo boo',
49 | baz: 'foo hoo',
50 | });
51 | });
52 |
53 | it('allows the initialValue to be omitted', () => {
54 | const one =
;
55 | const two =
;
56 | const three =
;
57 | const wrapper = Wrap(
58 |
59 | {one}
60 | {two}
61 | {three}
62 |
,
63 | );
64 | const counter = ;
65 | const result = wrapper
66 | .find('.foo')
67 | .reduce((acc, n) => [].concat(acc, n, new Wrapper(counter)))
68 | .map(getWrapperPropSelector('id'));
69 |
70 | expect(result).to.eql([one, two, counter, three, counter].map(getElementPropSelector('id')));
71 | });
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/test/test/shared/methods/reduceRight.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { getElementPropSelector, getWrapperPropSelector } from '../../_helpers/selectors';
5 |
6 | export default function describeReduceRight({ Wrap, Wrapper }) {
7 | describe('.reduceRight(fn[, initialValue])', () => {
8 | it('has the right length', () => {
9 | expect(Wrapper.prototype.reduceRight).to.have.lengthOf(1);
10 | });
11 |
12 | it('calls a function with a wrapper for each node in the wrapper in reverse', () => {
13 | const wrapper = Wrap(
14 | ,
19 | );
20 | const spy = vi.fn((n) => n + 1);
21 |
22 | wrapper.find('.foo').reduceRight(spy, 0);
23 |
24 | expect(spy).to.have.property('callCount', 3);
25 | expect(spy.mock.calls[0][1]).to.be.instanceOf(Wrapper);
26 | expect(spy.mock.calls[0][1].hasClass('baz')).to.equal(true);
27 | expect(spy.mock.calls[1][1]).to.be.instanceOf(Wrapper);
28 | expect(spy.mock.calls[1][1].hasClass('bar')).to.equal(true);
29 | expect(spy.mock.calls[2][1]).to.be.instanceOf(Wrapper);
30 | expect(spy.mock.calls[2][1].hasClass('bax')).to.equal(true);
31 | });
32 |
33 | it('accumulates a value', () => {
34 | const wrapper = Wrap(
35 | ,
40 | );
41 | const result = wrapper.find('.foo').reduceRight((obj, n) => {
42 | obj[n.prop('id')] = n.prop('className');
43 | return obj;
44 | }, {});
45 |
46 | expect(result).to.eql({
47 | bax: 'foo qoo',
48 | bar: 'foo boo',
49 | baz: 'foo hoo',
50 | });
51 | });
52 |
53 | it('allows the initialValue to be omitted', () => {
54 | const one =
;
55 | const two =
;
56 | const three =
;
57 | const wrapper = Wrap(
58 |
59 | {one}
60 | {two}
61 | {three}
62 |
,
63 | );
64 | const counter = ;
65 | const result = wrapper
66 | .find('.foo')
67 | .reduceRight((acc, n) => [].concat(acc, n, new Wrapper(counter)))
68 | .map(getWrapperPropSelector('id'));
69 |
70 | expect(result).to.eql([three, two, counter, one, counter].map(getElementPropSelector('id')));
71 | });
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/test/test/shared/methods/parent.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { createClass } from '../../_helpers/react-compat';
7 |
8 | export default function describeParent({ Wrap, isShallow }) {
9 | describe('.parent()', () => {
10 | it('returns only the immediate parent of the node', () => {
11 | const wrapper = Wrap(
12 | ,
19 | );
20 | expect(wrapper.find('.baz').parent().hasClass('bar')).to.equal(true);
21 | });
22 |
23 | it('works when the sibling node has children', () => {
24 | const wrapper = Wrap(
25 | ,
35 | );
36 |
37 | expect(wrapper.find('.baz').parent().hasClass('bar')).to.equal(true);
38 | });
39 |
40 | it('works for multiple nodes', () => {
41 | const wrapper = Wrap(
42 | ,
53 | );
54 |
55 | const parents = wrapper.find('.baz').parent();
56 | expect(parents).to.have.lengthOf(3);
57 | expect(parents.at(0).hasClass('foo')).to.equal(true);
58 | expect(parents.at(1).hasClass('bar')).to.equal(true);
59 | expect(parents.at(2).hasClass('bax')).to.equal(true);
60 | });
61 |
62 | itIf(isShallow, 'works with component', () => {
63 | const Foo = createClass({
64 | render() {
65 | return
;
66 | },
67 | });
68 | const wrapper = Wrap( );
69 | expect(wrapper.find('.bar')).to.have.lengthOf(1);
70 | expect(wrapper.find('.bar').parent()).to.have.lengthOf(0);
71 | expect(wrapper.parent()).to.have.lengthOf(0);
72 | });
73 |
74 | itIf(!isShallow, 'works with component', () => {
75 | const Foo = createClass({
76 | render() {
77 | return
;
78 | },
79 | });
80 | const wrapper = Wrap( );
81 | expect(wrapper.find('.bar')).to.have.lengthOf(1);
82 | expect(wrapper.find('.bar').parent()).to.have.lengthOf(1);
83 | expect(wrapper.parent()).to.have.lengthOf(0);
84 | });
85 | });
86 | }
87 |
--------------------------------------------------------------------------------
/test/test/shared/methods/getElement.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { createRef } from '../../_helpers/react-compat';
5 |
6 | export default function describeGetElement({ Wrap, isShallow }) {
7 | describe('.getElement()', () => {
8 | it('returns nodes with refs as well', () => {
9 | class Foo extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.setRef = this.setRef.bind(this);
13 | this.node = null;
14 | }
15 |
16 | setRef(node) {
17 | this.node = node;
18 | }
19 |
20 | render() {
21 | return (
22 |
25 | );
26 | }
27 | }
28 | const wrapper = Wrap( );
29 | const mockNode = { mock: true };
30 | wrapper.find('.foo').getElement().ref(mockNode);
31 | expect(wrapper.instance().node).to.equal(mockNode);
32 | });
33 |
34 | it('returns nodes with createRefs as well', () => {
35 | class Foo extends React.Component {
36 | constructor(props) {
37 | super(props);
38 | this.setRef = createRef();
39 | }
40 |
41 | render() {
42 | return (
43 |
46 | );
47 | }
48 | }
49 | const wrapper = Wrap( );
50 | // shallow rendering does not invoke refs
51 | if (isShallow) {
52 | expect(wrapper.instance().setRef).to.have.property('current', null);
53 | } else {
54 | const element = wrapper.find('.foo').instance();
55 | expect(wrapper.instance().setRef).to.have.property('current', element);
56 | }
57 | });
58 |
59 | it('does not add a "null" key to elements with a ref and no key', () => {
60 | class Foo extends React.Component {
61 | constructor(props) {
62 | super(props);
63 | this.setRef = this.setRef.bind(this);
64 | }
65 |
66 | setRef(node) {
67 | this.node = node;
68 | }
69 |
70 | render() {
71 | return
;
72 | }
73 | }
74 | const wrapper = Wrap( );
75 | expect(wrapper.getElement()).to.have.property('key', null);
76 | });
77 |
78 | it('does not add a "null" key to elements with a createRef and no key', () => {
79 | class Foo extends React.Component {
80 | constructor(props) {
81 | super(props);
82 | this.setRef = createRef();
83 | }
84 |
85 | render() {
86 | return
;
87 | }
88 | }
89 | const wrapper = Wrap( );
90 | expect(wrapper.getElement()).to.have.property('key', null);
91 | });
92 | });
93 | }
94 |
--------------------------------------------------------------------------------
/src/detectFiberTags.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { fakeDynamicImport } from '@wojtekmaj/enzyme-adapter-utils';
4 |
5 | function getFiber(element) {
6 | const container = global.document.createElement('div');
7 | let inst = null;
8 | class Tester extends React.Component {
9 | render() {
10 | inst = this;
11 | return element;
12 | }
13 | }
14 | ReactDOM.render(React.createElement(Tester), container);
15 | return inst._reactInternals.child;
16 | }
17 |
18 | function getLazyFiber(LazyComponent) {
19 | const container = global.document.createElement('div');
20 | let inst = null;
21 |
22 | class Tester extends React.Component {
23 | render() {
24 | inst = this;
25 | return React.createElement(LazyComponent);
26 | }
27 | }
28 |
29 | class SuspenseWrapper extends React.Component {
30 | render() {
31 | return React.createElement(React.Suspense, { fallback: false }, React.createElement(Tester));
32 | }
33 | }
34 | ReactDOM.render(React.createElement(SuspenseWrapper), container);
35 | return inst._reactInternals.child;
36 | }
37 |
38 | module.exports = function detectFiberTags() {
39 | function Fn() {
40 | return null;
41 | }
42 | class Cls extends React.Component {
43 | render() {
44 | return null;
45 | }
46 | }
47 | let Ctx = React.createContext();
48 | // React will warn if we don't have both arguments.
49 | // eslint-disable-next-line no-unused-vars
50 | let FwdRef = React.forwardRef((props, ref) => null);
51 | let LazyComponent = React.lazy(() => fakeDynamicImport(() => null));
52 |
53 | return {
54 | HostRoot: getFiber('test').return.return.tag, // Go two levels above to find the root
55 | ClassComponent: getFiber(React.createElement(Cls)).tag,
56 | Fragment: getFiber([['nested']]).tag,
57 | FunctionalComponent: getFiber(React.createElement(Fn)).tag,
58 | MemoSFC: getFiber(React.createElement(React.memo(Fn))).tag,
59 | MemoClass: getFiber(React.createElement(React.memo(Cls))).tag,
60 | HostPortal: getFiber(ReactDOM.createPortal(null, global.document.createElement('div'))).tag,
61 | HostComponent: getFiber(React.createElement('span')).tag,
62 | HostText: getFiber('text').tag,
63 | Mode: getFiber(React.createElement(React.StrictMode)).tag,
64 | ContextConsumer: getFiber(React.createElement(Ctx.Consumer, null, () => null)).tag,
65 | ContextProvider: getFiber(React.createElement(Ctx.Provider, { value: null }, null)).tag,
66 | ForwardRef: getFiber(React.createElement(FwdRef)).tag,
67 | Profiler: getFiber(
68 | React.createElement(React.Profiler, {
69 | id: 'mock',
70 | onRender() {},
71 | }),
72 | ).tag,
73 | Suspense: getFiber(React.createElement(React.Suspense, { fallback: false })).tag,
74 | Lazy: getLazyFiber(LazyComponent).tag,
75 | OffscreenComponent: getLazyFiber('div').return.return.tag, // Go two levels above to find the root
76 | };
77 | };
78 |
--------------------------------------------------------------------------------
/test/test/shared/methods/prop.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | export default function describeProp({ Wrap, WrapRendered, isMount }) {
7 | describe('.prop(name)', () => {
8 | it('returns the prop value for [name]', () => {
9 | const fn = () => {};
10 | const wrapper = Wrap(
11 | ,
15 | );
16 |
17 | expect(wrapper.prop('className')).to.equal('bax');
18 | expect(wrapper.prop('onClick')).to.equal(fn);
19 | expect(wrapper.prop('id')).to.equal('fooId');
20 | });
21 |
22 | it('is allowed to be used on an inner node', () => {
23 | const fn = () => {};
24 | const wrapper = Wrap(
25 | ,
29 | );
30 |
31 | expect(wrapper.find('.baz').prop('onClick')).to.equal(fn);
32 | expect(wrapper.find('.foo').prop('id')).to.equal('fooId');
33 | });
34 |
35 | class Foo extends React.Component {
36 | render() {
37 | const { bar, foo } = this.props;
38 | return
;
39 | }
40 | }
41 |
42 | itIf(isMount, 'called on root should return props of root node', () => {
43 | const wrapper = Wrap( );
44 |
45 | expect(wrapper.prop('foo')).to.equal('hi');
46 | expect(wrapper.prop('bar')).to.equal('bye');
47 | expect(wrapper.prop('className')).to.equal(undefined);
48 | expect(wrapper.prop('id')).to.equal(undefined);
49 | });
50 |
51 | it('returns prop value of root rendered node', () => {
52 | const wrapper = WrapRendered( );
53 |
54 | expect(wrapper.prop('className')).to.equal('bye');
55 | expect(wrapper.prop('id')).to.equal('hi');
56 | expect(wrapper.prop('foo')).to.equal(undefined);
57 | expect(wrapper.prop('bar')).to.equal(undefined);
58 | });
59 |
60 | describe('stateless function components (SFCs)', () => {
61 | const FooSFC = ({ bar, foo }) =>
;
62 |
63 | itIf(isMount, 'called on root should return props of root node', () => {
64 | const wrapper = Wrap( );
65 |
66 | expect(wrapper.prop('foo')).to.equal('hi');
67 | expect(wrapper.prop('bar')).to.equal('bye');
68 | expect(wrapper.prop('className')).to.equal(undefined);
69 | expect(wrapper.prop('id')).to.equal(undefined);
70 | });
71 |
72 | it('returns props of root rendered node', () => {
73 | const wrapper = WrapRendered( );
74 |
75 | expect(wrapper.prop('className')).to.equal('bye');
76 | expect(wrapper.prop('id')).to.equal('hi');
77 | expect(wrapper.prop('foo')).to.equal(undefined);
78 | expect(wrapper.prop('bar')).to.equal(undefined);
79 | });
80 | });
81 | });
82 | }
83 |
--------------------------------------------------------------------------------
/test/test/shared/methods/is.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { memo, forwardRef } from '../../_helpers/react-compat';
5 |
6 | export default function describeIs({ Wrap, WrapRendered }) {
7 | describe('.is(selector)', () => {
8 | it('returns true when selector matches current element', () => {
9 | const wrapper = Wrap(
);
10 | expect(wrapper.is('.foo')).to.equal(true);
11 | });
12 |
13 | it('allows for compound selectors', () => {
14 | const wrapper = Wrap(
);
15 | expect(wrapper.is('.foo.bar')).to.equal(true);
16 | });
17 |
18 | it('ignores insignificant whitespace', () => {
19 | const className = `
20 | foo
21 | `;
22 | const wrapper = Wrap(
);
23 | expect(wrapper.is('.foo')).to.equal(true);
24 | });
25 |
26 | it('handles all significant whitespace', () => {
27 | const className = `foo
28 |
29 | bar
30 | baz`;
31 | const wrapper = Wrap(
);
32 | expect(wrapper.is('.foo.bar.baz')).to.equal(true);
33 | });
34 |
35 | it('returns false when selector does not match', () => {
36 | const wrapper = Wrap(
);
37 | expect(wrapper.is('.foo')).to.equal(false);
38 | });
39 |
40 | class RendersDiv extends React.Component {
41 | render() {
42 | return
;
43 | }
44 | }
45 | const Memoized = memo(RendersDiv);
46 | const ForwardRef = forwardRef(() => );
47 | const MemoForwardRef = memo(() => );
48 |
49 | class RendersChildren extends React.Component {
50 | render() {
51 | const { children } = this.props;
52 | return children;
53 | }
54 | }
55 |
56 | it('recognizes nonmemoized', () => {
57 | const wrapper = WrapRendered(
58 |
59 |
60 | ,
61 | );
62 | expect(wrapper.is(RendersDiv)).to.equal(true);
63 | });
64 |
65 | describe('forwardRef', () => {
66 | it('recognizes forwardRef', () => {
67 | const wrapper = WrapRendered(
68 |
69 |
70 | ,
71 | );
72 | expect(wrapper.is(ForwardRef)).to.equal(true);
73 | });
74 | });
75 |
76 | describe('React.memo', () => {
77 | it('recognizes memoized and inner', () => {
78 | const wrapper = WrapRendered(
79 |
80 |
81 | ,
82 | );
83 | expect(wrapper.is(Memoized)).to.equal(true);
84 | // expect(wrapper.is(RendersDiv)).to.equal(true);
85 | });
86 |
87 | it('recognizes memoized forwardRef and inner', () => {
88 | const wrapper = WrapRendered(
89 |
90 |
91 | ,
92 | );
93 | expect(wrapper.is(MemoForwardRef)).to.equal(true);
94 | });
95 | });
96 | });
97 | }
98 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies and run tests
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: CI
5 |
6 | on:
7 | push:
8 | branches: ['*']
9 | pull_request:
10 | branches: [main]
11 |
12 | jobs:
13 | lint:
14 | name: Static code analysis
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v3
20 |
21 | - name: Cache .yarn/cache
22 | uses: actions/cache@v3
23 | env:
24 | cache-name: yarn-cache
25 | with:
26 | path: .yarn/cache
27 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
28 | restore-keys: |
29 | ${{ runner.os }}-${{ env.cache-name }}
30 |
31 | - name: Use Node.js
32 | uses: actions/setup-node@v3
33 | with:
34 | node-version: '18'
35 |
36 | - name: Enable Corepack
37 | run: corepack enable
38 |
39 | - name: Install dependencies
40 | run: yarn --immutable
41 | env:
42 | HUSKY: 0
43 |
44 | - name: Run tests
45 | run: yarn lint
46 |
47 | prettier:
48 | name: Prettier
49 | runs-on: ubuntu-latest
50 |
51 | steps:
52 | - name: Checkout
53 | uses: actions/checkout@v3
54 |
55 | - name: Cache .yarn/cache
56 | uses: actions/cache@v3
57 | env:
58 | cache-name: yarn-cache
59 | with:
60 | path: .yarn/cache
61 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
62 | restore-keys: |
63 | ${{ runner.os }}-${{ env.cache-name }}
64 |
65 | - name: Use Node.js
66 | uses: actions/setup-node@v3
67 | with:
68 | node-version: '18'
69 |
70 | - name: Enable Corepack
71 | run: corepack enable
72 |
73 | - name: Install dependencies
74 | run: yarn --immutable
75 | env:
76 | HUSKY: 0
77 |
78 | - name: Prettier
79 | run: yarn prettier
80 |
81 | test:
82 | name: Tests
83 | runs-on: ubuntu-latest
84 |
85 | steps:
86 | - name: Checkout
87 | uses: actions/checkout@v3
88 |
89 | - name: Cache .yarn/cache
90 | uses: actions/cache@v3
91 | env:
92 | cache-name: yarn-cache
93 | with:
94 | path: .yarn/cache
95 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
96 | restore-keys: |
97 | ${{ runner.os }}-${{ env.cache-name }}
98 |
99 | - name: Use Node.js
100 | uses: actions/setup-node@v3
101 | with:
102 | node-version: '18'
103 |
104 | - name: Enable Corepack
105 | run: corepack enable
106 |
107 | - name: Install dependencies
108 | run: yarn --immutable
109 | env:
110 | HUSKY: 0
111 |
112 | - name: Build
113 | run: yarn build
114 |
115 | - name: Run tests
116 | run: yarn run
117 | working-directory: test
118 |
--------------------------------------------------------------------------------
/test/test/shared/methods/matchesElement.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeMatchesElement({ Wrap, WrapRendered }) {
5 | describe('.matchesElement(node)', () => {
6 | it('matches on a root node that looks like the rendered one', () => {
7 | const spy = vi.fn();
8 | const wrapper = Wrap(
9 |
10 |
11 | Hello World
12 |
13 |
,
14 | ).first();
15 | expect(
16 | wrapper.matchesElement(
17 | ,
20 | ),
21 | ).to.equal(true);
22 | expect(
23 | wrapper.matchesElement(
24 |
25 |
26 | Hello World
27 |
28 |
,
29 | ),
30 | ).to.equal(true);
31 | expect(
32 | wrapper.matchesElement(
33 | ,
36 | ),
37 | ).to.equal(true);
38 | expect(
39 | wrapper.matchesElement(
40 | ,
43 | ),
44 | ).to.equal(true);
45 | expect(spy).to.have.property('callCount', 0);
46 | });
47 |
48 | it('does not match on a root node that doesn’t looks like the rendered one', () => {
49 | const spy = vi.fn();
50 | const spy2 = vi.fn();
51 | const wrapper = Wrap(
52 |
53 |
54 | Hello World
55 |
56 |
,
57 | ).first();
58 | expect(
59 | wrapper.matchesElement(
60 |
61 |
Bonjour le monde
62 |
,
63 | ),
64 | ).to.equal(false);
65 | expect(
66 | wrapper.matchesElement(
67 |
68 |
69 | Hello World
70 |
71 |
,
72 | ),
73 | ).to.equal(false);
74 | expect(
75 | wrapper.matchesElement(
76 | ,
79 | ),
80 | ).to.equal(false);
81 | expect(
82 | wrapper.matchesElement(
83 | ,
86 | ),
87 | ).to.equal(false);
88 | expect(spy).to.have.property('callCount', 0);
89 | expect(spy2).to.have.property('callCount', 0);
90 | });
91 |
92 | it('matches a simple node', () => {
93 | class Test extends React.Component {
94 | render() {
95 | return test ;
96 | }
97 | }
98 | const wrapper = WrapRendered( );
99 | expect(wrapper.matchesElement(test )).to.equal(true);
100 | });
101 | });
102 | }
103 |
--------------------------------------------------------------------------------
/test/test/shared/methods/equals.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeEquals({ Wrap, WrapRendered }) {
5 | describe('.equals(node)', () => {
6 | it('allows matches on the root node', () => {
7 | const a =
;
8 | const b =
;
9 | const c =
;
10 |
11 | expect(Wrap(a).equals(b)).to.equal(true);
12 | expect(Wrap(a).equals(c)).to.equal(false);
13 | });
14 |
15 | it('does NOT allow matches on a nested node', () => {
16 | const wrapper = Wrap(
17 | ,
20 | );
21 | const b =
;
22 | expect(wrapper.equals(b)).to.equal(false);
23 | });
24 |
25 | it('matches composite components', () => {
26 | class Foo extends React.Component {
27 | render() {
28 | return
;
29 | }
30 | }
31 | const wrapper = Wrap(
32 |
33 |
34 |
,
35 | );
36 | const b = (
37 |
38 |
39 |
40 | );
41 | expect(wrapper.equals(b)).to.equal(true);
42 | });
43 |
44 | it('does not expand `node` content', () => {
45 | class Bar extends React.Component {
46 | render() {
47 | return
;
48 | }
49 | }
50 |
51 | class Foo extends React.Component {
52 | render() {
53 | return ;
54 | }
55 | }
56 |
57 | const wrapper = WrapRendered( );
58 | expect(wrapper.equals( )).to.equal(true);
59 | expect(wrapper.equals( )).to.equal(false);
60 | });
61 |
62 | describe('stateless function components (SFCs)', () => {
63 | it('matches composite SFCs', () => {
64 | const Foo = () =>
;
65 |
66 | const wrapper = Wrap(
67 |
68 |
69 |
,
70 | );
71 | const b = (
72 |
73 |
74 |
75 | );
76 | expect(wrapper.equals(b)).to.equal(true);
77 | });
78 |
79 | it('does not expand `node` content', () => {
80 | const Bar = () =>
;
81 |
82 | const Foo = () => ;
83 |
84 | const wrapper = WrapRendered( );
85 | expect(wrapper.equals( )).to.equal(true);
86 | expect(wrapper.equals( )).to.equal(false);
87 | });
88 | });
89 |
90 | it('flattens arrays of children to compare', () => {
91 | class TwoChildren extends React.Component {
92 | render() {
93 | return (
94 |
98 | );
99 | }
100 | }
101 |
102 | class TwoChildrenOneArrayed extends React.Component {
103 | render() {
104 | return (
105 |
109 | );
110 | }
111 | }
112 | const twoChildren = WrapRendered( );
113 | const twoChildrenOneArrayed = WrapRendered( );
114 |
115 | expect(twoChildren.equals(twoChildrenOneArrayed.getElement())).to.equal(true);
116 | expect(twoChildrenOneArrayed.equals(twoChildren.getElement())).to.equal(true);
117 | });
118 | });
119 | }
120 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useContext.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { describeIf, itIf } from '../../_helpers';
5 |
6 | import { useContext, useState, createContext } from '../../_helpers/react-compat';
7 |
8 | export default function describeUseContext({ Wrap, isShallow }) {
9 | describe('hooks: useContext', () => {
10 | describe('simple example', () => {
11 | const initialTitle = 'initialTitle';
12 | const TitleContext = createContext && createContext(initialTitle);
13 |
14 | function UiComponent() {
15 | const title = useContext(TitleContext);
16 | return {title}
;
17 | }
18 |
19 | const customTitle = 'CustomTitle';
20 |
21 | function App() {
22 | return (
23 |
24 |
25 |
26 | );
27 | }
28 |
29 | it('render ui component with initial context value', () => {
30 | const wrapper = Wrap( );
31 | expect(wrapper.text()).to.equal(initialTitle);
32 | });
33 |
34 | // TODO: useContext: enable when shallow dive supports createContext
35 | itIf(!isShallow, 'render ui component with value from outer provider', () => {
36 | const wrapper = Wrap( );
37 | const subWrapper = isShallow ? wrapper.dive().dive() : wrapper;
38 | expect(subWrapper.text()).to.equal(customTitle);
39 | });
40 | });
41 |
42 | // TODO: useContext: enable when shallow dive supports createContext
43 | describeIf(!isShallow, 'useContext: with Setting', () => {
44 | const initialState = 10;
45 | const context = createContext && createContext(null);
46 |
47 | function MyGrandChild() {
48 | const myContextVal = useContext(context);
49 |
50 | const increment = () => {
51 | myContextVal.setState(myContextVal.state + 1);
52 | };
53 |
54 | return (
55 |
56 |
57 | increment
58 |
59 | {myContextVal.state}
60 |
61 | );
62 | }
63 |
64 | function MyChild() {
65 | return (
66 |
67 |
68 |
69 | );
70 | }
71 |
72 | function App() {
73 | const [state, setState] = useState(initialState);
74 |
75 | return (
76 |
77 |
78 |
79 |
80 |
81 | );
82 | }
83 |
84 | it('test render, get and set context value ', () => {
85 | const wrapper = Wrap( );
86 |
87 | function getChild() {
88 | const child = wrapper.find(MyChild);
89 | return isShallow ? child.dive() : child;
90 | }
91 | function getGrandChild() {
92 | const grandchild = getChild().find(MyGrandChild);
93 | return isShallow ? grandchild.dive() : grandchild;
94 | }
95 | expect(getGrandChild().find('.grandChildState').debug()).to
96 | .equal(`
97 | ${String(initialState)}
98 | `);
99 |
100 | getGrandChild().find('button').props().onClick();
101 | wrapper.update();
102 | expect(getGrandChild().find('.grandChildState').debug()).to
103 | .equal(`
104 | ${String(initialState + 1)}
105 | `);
106 | });
107 | });
108 | });
109 | }
110 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/useState.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { useState, useEffect, Fragment } from '../../_helpers/react-compat';
7 |
8 | export default function describeUseState({ Wrap, isShallow }) {
9 | describe('hooks: useState', () => {
10 | function FooCounter({ initialCount: initial = 0 }) {
11 | const [count, setCount] = useState(+initial);
12 |
13 | return (
14 |
15 | setCount(count + 1)}>
16 | -
17 |
18 | {count}
19 | setCount(count - 1)}>
20 | +
21 |
22 |
23 | );
24 | }
25 |
26 | const initialCount = 5;
27 |
28 | it('initial render', () => {
29 | const wrapper = Wrap( );
30 | expect(wrapper.find('.counter').text()).to.equal(String(initialCount));
31 | });
32 |
33 | it('lets increment', () => {
34 | const wrapper = Wrap( );
35 |
36 | wrapper.find('.increment').props().onClick();
37 |
38 | expect(wrapper.find('.counter').text()).to.equal(String(initialCount + 1));
39 | });
40 |
41 | it('now decrement', () => {
42 | const wrapper = Wrap( );
43 |
44 | wrapper.find('.decrement').props().onClick();
45 |
46 | expect(wrapper.find('.counter').text()).to.equal(String(initialCount - 1));
47 | });
48 |
49 | it('handles useState', () => {
50 | function ComponentUsingStateHook() {
51 | const [count] = useState(0);
52 | return {count}
;
53 | }
54 |
55 | const wrapper = Wrap( );
56 |
57 | expect(wrapper.find('div').length).to.equal(1);
58 | expect(wrapper.find('div').text()).to.equal('0');
59 | });
60 |
61 | it('handles setState returned from useState', () => {
62 | function ComponentUsingStateHook() {
63 | const [count, setCount] = useState(0);
64 | return setCount(count + 1)}>{count}
;
65 | }
66 |
67 | const wrapper = Wrap( );
68 | const div = wrapper.find('div');
69 | const setCount = div.prop('onClick');
70 | setCount();
71 | wrapper.update();
72 |
73 | expect(wrapper.find('div').text()).to.equal('1');
74 | });
75 |
76 | describe('useState with willReceive prop effect / simulate getDerivedStateFromProp', () => {
77 | const newPropCount = 10;
78 |
79 | function FooCounterWithEffect({ initialCount: initial = 0 }) {
80 | const [count, setCount] = useState(+initial);
81 |
82 | useEffect(() => {
83 | setCount(initial);
84 | }, [initial]);
85 |
86 | return (
87 |
88 | {count}
89 |
90 | );
91 | }
92 |
93 | // TODO: fixme when useEffect works in the shallow renderer, see https://github.com/facebook/react/issues/15275
94 | itIf(!isShallow, 'initial render & new Props', () => {
95 | const wrapper = Wrap( );
96 | expect(wrapper.find('.counter').text()).to.equal(String(initialCount));
97 |
98 | wrapper.setProps({ initialCount: newPropCount });
99 | expect(wrapper.find('.counter').text()).to.equal(String(newPropCount));
100 | });
101 | });
102 | });
103 | }
104 |
--------------------------------------------------------------------------------
/test/test/shared/methods/invoke.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { useEffect, useState } from '../../_helpers/react-compat';
7 |
8 | export default function describeInvoke({ Wrap, WrapperName, isShallow }) {
9 | describe('.invoke(propName)(..args)', () => {
10 | class CounterButton extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = { count: 0 };
14 | }
15 |
16 | render() {
17 | const { count } = this.state;
18 | return (
19 |
20 | this.setState(({ count: oldCount }) => ({ count: oldCount + 1 }))}
23 | >
24 | {count}
25 |
26 |
27 | );
28 | }
29 | }
30 |
31 | class ClickableLink extends React.Component {
32 | render() {
33 | const { onClick } = this.props;
34 | return (
35 |
38 | );
39 | }
40 | }
41 |
42 | it('throws when pointing to a non-function prop', () => {
43 | const wrapper = Wrap(
);
44 |
45 | expect(() => wrapper.invoke('data-a')).to.throw(
46 | TypeError,
47 | `${WrapperName}::invoke() requires the name of a prop whose value is a function`,
48 | );
49 |
50 | expect(() => wrapper.invoke('does not exist')).to.throw(
51 | TypeError,
52 | `${WrapperName}::invoke() requires the name of a prop whose value is a function`,
53 | );
54 | });
55 |
56 | it('can update the state value', () => {
57 | const wrapper = Wrap( );
58 | expect(wrapper.state('count')).to.equal(0);
59 | wrapper.find('button').invoke('onClick')();
60 | expect(wrapper.state('count')).to.equal(1);
61 | });
62 |
63 | it('can return the handlers’ return value', () => {
64 | const sentinel = {};
65 | const spy = vi.fn().mockReturnValue(sentinel);
66 |
67 | const wrapper = Wrap( );
68 |
69 | const value = wrapper.find('a').invoke('onClick')();
70 | expect(value).to.equal(sentinel);
71 | expect(spy).to.have.property('callCount', 1);
72 | });
73 |
74 | it('can pass in arguments', () => {
75 | const spy = vi.fn();
76 |
77 | const wrapper = Wrap( );
78 |
79 | const a = {};
80 | const b = {};
81 | wrapper.find('a').invoke('onClick')(a, b);
82 | expect(spy).to.have.property('callCount', 1);
83 | const [[arg1, arg2]] = spy.mock.calls;
84 | expect(arg1).to.equal(a);
85 | expect(arg2).to.equal(b);
86 | });
87 |
88 | // TODO: enable when the shallow renderer fixes its bug
89 | itIf(!isShallow && true, 'works without explicit `act` wrapper', () => {
90 | function App() {
91 | const [counter, setCounter] = useState(0);
92 | const [result, setResult] = useState(0);
93 | useEffect(() => setResult(counter * 2), [counter]);
94 | return (
95 | setCounter((input) => input + 1)}>
96 | {result}
97 |
98 | );
99 | }
100 | const wrapper = Wrap( );
101 |
102 | const expected = ['0', '2', '4', '6'];
103 |
104 | const actual = [wrapper.find('button').text()].concat(
105 | Array.from({ length: 3 }, () => {
106 | wrapper.find('button').invoke('onClick')();
107 | wrapper.update();
108 |
109 | return wrapper.find('button').text();
110 | }),
111 | );
112 | expect(actual).to.eql(expected);
113 | });
114 | });
115 | }
116 |
--------------------------------------------------------------------------------
/test/test/shared/methods/html.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { Fragment } from '../../_helpers/react-compat';
5 |
6 | export default function describeHTML({ Wrap }) {
7 | describe('.html()', () => {
8 | it('returns html of straight DOM elements', () => {
9 | const wrapper = Wrap(
10 |
11 | Hello World!
12 |
,
13 | );
14 | expect(wrapper.html()).to.equal('Hello World!
');
15 | });
16 |
17 | it('renders out nested composite components', () => {
18 | class Foo extends React.Component {
19 | render() {
20 | return
;
21 | }
22 | }
23 | class Bar extends React.Component {
24 | render() {
25 | return (
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 | const wrapper = Wrap( );
33 | expect(wrapper.html()).to.equal('');
34 | expect(wrapper.find(Foo).html()).to.equal('
');
35 | });
36 |
37 | describe('stateless function components (SFCs)', () => {
38 | it('renders out nested composite components', () => {
39 | const Foo = () =>
;
40 | const Bar = () => (
41 |
42 |
43 |
44 | );
45 |
46 | const wrapper = Wrap( );
47 | expect(wrapper.html()).to.equal('');
48 | expect(wrapper.find(Foo).html()).to.equal('
');
49 | });
50 | });
51 |
52 | describe('Fragments', () => {
53 | class FragmentClassExample extends React.Component {
54 | render() {
55 | return (
56 |
57 |
58 | Foo
59 |
60 |
61 | Bar
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | const FragmentConstExample = () => (
69 |
70 |
71 | Foo
72 |
73 |
74 | Bar
75 |
76 |
77 | );
78 |
79 | class ClassChild extends React.Component {
80 | render() {
81 | return Class child
;
82 | }
83 | }
84 |
85 | function SFCChild() {
86 | return SFC child
;
87 | }
88 |
89 | class FragmentWithCustomChildClass extends React.Component {
90 | render() {
91 | return (
92 |
93 |
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | it('correctly renders html for both children for class', () => {
101 | const classWrapper = Wrap( );
102 | expect(classWrapper.html()).to.equal(
103 | 'Foo
Bar
',
104 | );
105 | });
106 |
107 | it('correctly renders html for both children for const', () => {
108 | const constWrapper = Wrap( );
109 | expect(constWrapper.html()).to.equal(
110 | 'Foo
Bar
',
111 | );
112 | });
113 |
114 | it('correctly renders html for custom component children', () => {
115 | const withChildrenWrapper = Wrap( );
116 | expect(withChildrenWrapper.html()).to.equal('Class child
SFC child
');
117 | });
118 | });
119 | });
120 | }
121 |
--------------------------------------------------------------------------------
/test/test/shared/methods/hostNodes.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeHostNodes({ Wrap, WrapRendered }) {
5 | describe('.hostNodes()', () => {
6 | it('strips out any non-hostNode', () => {
7 | class Foo extends React.Component {
8 | render() {
9 | return
;
10 | }
11 | }
12 | const wrapper = Wrap(
13 |
14 |
15 |
16 | ,
17 | );
18 |
19 | const foos = wrapper.find('main > .foo');
20 | expect(foos).to.have.lengthOf(2);
21 |
22 | const hostNodes = foos.hostNodes();
23 | expect(hostNodes).to.have.lengthOf(1);
24 |
25 | expect(hostNodes.is('div')).to.equal(true);
26 | expect(hostNodes.hasClass('foo')).to.equal(true);
27 | });
28 |
29 | it('does NOT allow matches on a nested node', () => {
30 | const wrapper = Wrap(
31 | ,
34 | );
35 | const b =
;
36 | expect(wrapper.equals(b)).to.equal(false);
37 | });
38 |
39 | it('matches composite components', () => {
40 | class Foo extends React.Component {
41 | render() {
42 | return
;
43 | }
44 | }
45 |
46 | const wrapper = Wrap(
47 |
48 |
49 |
,
50 | );
51 | const b = (
52 |
53 |
54 |
55 | );
56 | expect(wrapper.equals(b)).to.equal(true);
57 | });
58 |
59 | it('does not expand `node` content', () => {
60 | class Bar extends React.Component {
61 | render() {
62 | return
;
63 | }
64 | }
65 |
66 | class Foo extends React.Component {
67 | render() {
68 | return ;
69 | }
70 | }
71 |
72 | expect(WrapRendered( ).equals( )).to.equal(true);
73 | expect(WrapRendered( ).equals( )).to.equal(false);
74 | });
75 |
76 | describe('stateless function components (SFCs)', () => {
77 | it('matches composite SFCs', () => {
78 | const Foo = () =>
;
79 |
80 | const wrapper = Wrap(
81 |
82 |
83 |
,
84 | );
85 | const b = (
86 |
87 |
88 |
89 | );
90 | expect(wrapper.equals(b)).to.equal(true);
91 | });
92 |
93 | it('does not expand `node` content', () => {
94 | const Bar = () =>
;
95 |
96 | const Foo = () => ;
97 |
98 | expect(WrapRendered( ).equals( )).to.equal(true);
99 | expect(WrapRendered( ).equals( )).to.equal(false);
100 | });
101 | });
102 |
103 | it('flattens arrays of children to compare', () => {
104 | class TwoChildren extends React.Component {
105 | render() {
106 | return (
107 |
111 | );
112 | }
113 | }
114 |
115 | class TwoChildrenOneArrayed extends React.Component {
116 | render() {
117 | return (
118 |
122 | );
123 | }
124 | }
125 | const twoChildren = WrapRendered( );
126 | const twoChildrenOneArrayed = WrapRendered( );
127 |
128 | expect(twoChildren.equals(twoChildrenOneArrayed.getElement())).to.equal(true);
129 | expect(twoChildrenOneArrayed.equals(twoChildren.getElement())).to.equal(true);
130 | });
131 | });
132 | }
133 |
--------------------------------------------------------------------------------
/test/test/_helpers/index.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 | import { Memo } from 'react-is';
4 | import { compareNodeTypeOf } from '@wojtekmaj/enzyme-adapter-utils';
5 |
6 | /**
7 | * Simple wrapper around mocha describe which allows a boolean to be passed in first which
8 | * determines whether or not the test will be run
9 | */
10 | export function describeIf(test, title, fn) {
11 | if (typeof test !== 'boolean') {
12 | throw new TypeError(`a boolean is required, you passed a ${typeof test}`);
13 | }
14 |
15 | if (test) {
16 | describe(title, fn);
17 | } else {
18 | describe.skip(title, fn);
19 | }
20 | }
21 |
22 | describeIf.only = (test, title, fn) => {
23 | if (typeof test !== 'boolean') {
24 | throw new TypeError(`a boolean is required, you passed a ${typeof test}`);
25 | }
26 |
27 | if (test) {
28 | describe.only(title, fn);
29 | } else {
30 | describe.only('only:', () => {
31 | describe.skip(title, fn);
32 | });
33 | }
34 | };
35 |
36 | describeIf.skip = (test, title, fn) => {
37 | if (typeof test !== 'boolean') {
38 | throw new TypeError(`a boolean is required, you passed a ${typeof test}`);
39 | }
40 |
41 | describeIf(false, title, fn);
42 | };
43 |
44 | /**
45 | * Simple wrapper around mocha it which allows a boolean to be passed in first which
46 | * determines whether or not the test will be run
47 | */
48 | export function itIf(test, title, fn) {
49 | if (typeof test !== 'boolean') {
50 | throw new TypeError(`a boolean is required, you passed a ${typeof test}`);
51 | }
52 |
53 | if (test) {
54 | it(title, fn);
55 | } else {
56 | it.skip(title, fn);
57 | }
58 | }
59 |
60 | itIf.only = (test, title, fn) => {
61 | if (typeof test !== 'boolean') {
62 | throw new TypeError(`a boolean is required, you passed a ${typeof test}`);
63 | }
64 |
65 | if (test) {
66 | it.only(title, fn);
67 | } else {
68 | it.skip(title, fn);
69 | }
70 | };
71 |
72 | itIf.skip = (test, title, fn) => {
73 | if (typeof test !== 'boolean') {
74 | throw new TypeError(`a boolean is required, you passed a ${typeof test}`);
75 | }
76 |
77 | itIf(false, title, fn);
78 | };
79 |
80 | /**
81 | * Simple wrapper around mocha it which allows an array of possible values to test against.
82 | * Each test will be wrapped in a try/catch block to handle any errors.
83 | *
84 | * @param {Object[]} data
85 | * @param {String} message
86 | * @param {Function} factory
87 | */
88 | export function itWithData(data, message, factory) {
89 | data.forEach((testCase) => {
90 | it(`${message} ${testCase.message}`, () => factory(testCase));
91 | });
92 | }
93 |
94 | /**
95 | * React component used for testing.
96 | */
97 | class TestHelper extends React.Component {
98 | render() {
99 | return
;
100 | }
101 | }
102 |
103 | /**
104 | * Possible values for React render() checks.
105 | */
106 | export function generateEmptyRenderData() {
107 | return [
108 | // Returns true for empty
109 | { message: 'false', value: false, expectResponse: true },
110 | { message: 'null', value: null, expectResponse: true },
111 |
112 | // Returns false for empty, valid returns
113 | { message: 'React component', value: , expectResponse: false },
114 | { message: 'React element', value: , expectResponse: false },
115 | { message: 'React element', value: , expectResponse: false },
116 | ];
117 | }
118 |
119 | export function delay(ms) {
120 | return new Promise((resolve) => {
121 | setTimeout(resolve, ms);
122 | });
123 | }
124 |
125 | export function isMemo(type) {
126 | return compareNodeTypeOf(type, Memo);
127 | }
128 |
129 | export function argSpy() {
130 | const spy = vi.fn();
131 | spy(1);
132 | return spy;
133 | }
134 |
135 | export function expectArgs(spy, counter, args) {
136 | spy(counter);
137 | expect(spy.mock.calls).to.deep.equal([[counter], ...args, [counter]]);
138 | spy.mockReset();
139 | spy(counter + 1);
140 | }
141 |
--------------------------------------------------------------------------------
/test/test/shared/methods/context.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | import { itIf } from '../../_helpers';
6 |
7 | import { createClass } from '../../_helpers/react-compat';
8 |
9 | export default function describeContext({ Wrap, WrapperName, isShallow }) {
10 | describe('.context()', () => {
11 | const contextTypes = {
12 | name: PropTypes.string,
13 | };
14 | const SimpleComponent = createClass({
15 | contextTypes,
16 | render() {
17 | const { name } = this.context;
18 | return {name}
;
19 | },
20 | });
21 |
22 | function SimpleComponentSFC(props, { name }) {
23 | return {name}
;
24 | }
25 | SimpleComponentSFC.contextTypes = contextTypes;
26 |
27 | it('throws when not called on the root', () => {
28 | const context = { name: };
29 | const wrapper = Wrap( , { context });
30 | const main = wrapper.find('main');
31 | expect(main).to.have.lengthOf(1);
32 | expect(() => main.context()).to.throw(
33 | Error,
34 | `${WrapperName}::context() can only be called on the root`,
35 | );
36 | });
37 |
38 | itIf(isShallow, 'throws if it is called when wrapper didn’t include context', () => {
39 | const wrapper = Wrap( , { context: false });
40 | expect(() => wrapper.context()).to.throw(
41 | Error,
42 | `${WrapperName}::context() can only be called on a wrapper that was originally passed a context option`,
43 | );
44 | });
45 |
46 | it('throws on SFCs that lack an instance', () => {
47 | const context = { name: 'bob' };
48 | const wrapper = Wrap( , { context });
49 | expect(() => wrapper.context()).to.throw(
50 | Error,
51 | isShallow
52 | ? `${WrapperName}::context() can only be called on wrapped nodes that have a non-null instance`
53 | : `${WrapperName}::context() can only be called on components with instances`,
54 | );
55 | });
56 |
57 | it('works with no arguments', () => {
58 | const context = { name: 'foo' };
59 | const wrapper = Wrap( , { context });
60 | expect(wrapper.context()).to.eql(context);
61 | });
62 |
63 | it('works with a key name', () => {
64 | const context = { name: 'foo' };
65 | const wrapper = Wrap( , { context });
66 | expect(wrapper.context('name')).to.equal(context.name);
67 | });
68 |
69 | class RendersHTML extends React.Component {
70 | render() {
71 | return (
72 |
73 | hi
74 |
75 | );
76 | }
77 | }
78 |
79 | it('throws on non-instance', () => {
80 | const wrapper = Wrap( );
81 | const span = wrapper.find('span');
82 | expect(span).to.have.lengthOf(1);
83 | expect(() => span.context()).to.throw(Error);
84 | });
85 |
86 | class RendersChildren extends React.Component {
87 | render() {
88 | const { children } = this.props;
89 | return {children}
;
90 | }
91 | }
92 |
93 | it('throws on non-root', () => {
94 | const wrapper = Wrap(
95 |
96 |
97 | ,
98 | );
99 | const child = wrapper.find(RendersHTML);
100 | expect(child).to.have.lengthOf(1);
101 | expect(() => child.context()).to.throw(Error);
102 | });
103 |
104 | it('throws on an SFC without an instance', () => {
105 | function Bar() {
106 | return ;
107 | }
108 |
109 | const wrapper = Wrap(
110 |
111 |
112 | ,
113 | );
114 | const child = wrapper.find(Bar);
115 | expect(child).to.have.lengthOf(1);
116 | expect(() => child.context()).to.throw(Error);
117 | });
118 | });
119 | }
120 |
--------------------------------------------------------------------------------
/test/test/shared/methods/containsAnyMatchingElements.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeContainsAnyMatchingElements({ Wrap }) {
5 | describe('.containsAnyMatchingElements(nodes)', () => {
6 | it('matches on an array with at least one node that looks like a rendered node', () => {
7 | const spy1 = vi.fn();
8 | const spy2 = vi.fn();
9 | const wrapper = Wrap(
10 |
11 |
12 | Hello World
13 |
14 |
15 | Goodbye World
16 |
17 |
,
18 | );
19 | expect(
20 | wrapper.containsAnyMatchingElements([
21 | Bonjour le monde
,
22 | Goodbye World
,
23 | ]),
24 | ).to.equal(true);
25 | expect(
26 | wrapper.containsAnyMatchingElements([
27 |
28 | Bonjour le monde
29 |
,
30 | Goodbye World
,
31 | ]),
32 | ).to.equal(true);
33 | expect(
34 | wrapper.containsAnyMatchingElements([
35 | Bonjour le monde
,
36 |
37 | Goodbye World
38 |
,
39 | ]),
40 | ).to.equal(true);
41 | expect(
42 | wrapper.containsAnyMatchingElements([
43 |
44 | Bonjour le monde
45 |
,
46 |
47 | Goodbye World
48 |
,
49 | ]),
50 | ).to.equal(true);
51 | expect(
52 | wrapper.containsAnyMatchingElements([
53 | Bonjour le monde
,
54 |
55 | Goodbye World
56 |
,
57 | ]),
58 | ).to.equal(true);
59 | expect(
60 | wrapper.containsAnyMatchingElements([
61 | Bonjour le monde
,
62 |
63 | Goodbye World
64 |
,
65 | ]),
66 | ).to.equal(true);
67 | expect(
68 | wrapper.containsAnyMatchingElements([
69 |
70 | Bonjour le monde
71 |
,
72 | Goodbye World
,
73 | ]),
74 | ).to.equal(true);
75 | expect(
76 | wrapper.containsAnyMatchingElements([
77 |
78 | Bonjour le monde
79 |
,
80 | Goodbye World
,
81 | ]),
82 | ).to.equal(true);
83 | expect(spy1).to.have.property('callCount', 0);
84 | expect(spy2).to.have.property('callCount', 0);
85 | });
86 |
87 | it('does not match on an array with no nodes that look like a rendered node', () => {
88 | const spy1 = vi.fn();
89 | const spy2 = vi.fn();
90 | const wrapper = Wrap(
91 |
92 |
93 | Hello World
94 |
95 |
96 | Goodbye World
97 |
98 |
,
99 | );
100 | expect(
101 | wrapper.containsAnyMatchingElements([
102 |
103 | Bonjour le monde
104 |
,
105 | Au revoir le monde
,
106 | ]),
107 | ).to.equal(false);
108 | expect(spy1).to.have.property('callCount', 0);
109 | expect(spy2).to.have.property('callCount', 0);
110 | });
111 | });
112 | }
113 |
--------------------------------------------------------------------------------
/test/test/shared/methods/contains.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | export default function describeContains({ Wrap, WrapperName, isMount }) {
7 | describe('.contains(node)', () => {
8 | it('allows matches on the root node', () => {
9 | const a =
;
10 | const b =
;
11 | const c =
;
12 | const wrapper = Wrap(a);
13 | expect(wrapper.contains(b)).to.equal(true);
14 | expect(wrapper.contains(c)).to.equal(false);
15 | });
16 |
17 | it('allows matches on a nested node', () => {
18 | const wrapper = Wrap(
19 | ,
22 | );
23 | const b =
;
24 | expect(wrapper.contains(b)).to.equal(true);
25 | });
26 |
27 | it('matches composite components', () => {
28 | class Foo extends React.Component {
29 | render() {
30 | return
;
31 | }
32 | }
33 | const wrapper = Wrap(
34 |
35 |
36 |
,
37 | );
38 | const b = ;
39 | expect(wrapper.contains(b)).to.equal(true);
40 | });
41 |
42 | it('works with strings', () => {
43 | const wrapper = Wrap(foo
);
44 |
45 | expect(wrapper.contains('foo')).to.equal(true);
46 | expect(wrapper.contains('bar')).to.equal(false);
47 | });
48 |
49 | it('works with numbers', () => {
50 | const wrapper = Wrap({1}
);
51 |
52 | expect(wrapper.contains(1)).to.equal(true);
53 | expect(wrapper.contains(2)).to.equal(false);
54 | expect(wrapper.contains('1')).to.equal(false);
55 | });
56 |
57 | it('works with nested strings & numbers', () => {
58 | const wrapper = Wrap(
59 | ,
65 | );
66 |
67 | expect(wrapper.contains('foo')).to.equal(true);
68 | expect(wrapper.contains(foo
)).to.equal(true);
69 |
70 | expect(wrapper.contains(5)).to.equal(true);
71 | expect(wrapper.contains({5}
)).to.equal(true);
72 | });
73 |
74 | it('does something with arrays of nodes', () => {
75 | const wrapper = Wrap(
76 |
77 |
Hello
78 |
Goodbye
79 |
More
80 |
,
81 | );
82 | const fails = [wrong , Goodbye
];
83 |
84 | const passes1 = [Hello , Goodbye
];
85 | const passes2 = [Goodbye
, More ];
86 |
87 | expect(wrapper.contains(fails)).to.equal(false);
88 | expect(wrapper.contains(passes1)).to.equal(true);
89 | expect(wrapper.contains(passes2)).to.equal(true);
90 | });
91 |
92 | // FIXME: fix on mount
93 | itIf(!isMount, 'throws on invalid argument', () => {
94 | const wrapper = Wrap(
);
95 |
96 | expect(() => wrapper.contains({})).to.throw(
97 | Error,
98 | `${WrapperName}::contains() can only be called with a ReactElement (or an array of them), a string, or a number as an argument.`,
99 | );
100 | expect(() => wrapper.contains(() => ({}))).to.throw(
101 | Error,
102 | `${WrapperName}::contains() can only be called with a ReactElement (or an array of them), a string, or a number as an argument.`,
103 | );
104 | });
105 |
106 | describe('stateless function components (SFCs)', () => {
107 | it('matches composite components', () => {
108 | function Foo() {
109 | return
;
110 | }
111 |
112 | const wrapper = Wrap(
113 |
114 |
115 |
,
116 | );
117 | const b = ;
118 | expect(wrapper.contains(b)).to.equal(true);
119 | });
120 |
121 | it('matches composite components if rendered by function', () => {
122 | function Foo() {
123 | return
;
124 | }
125 | const renderStatelessComponent = () => ;
126 | const wrapper = Wrap({renderStatelessComponent()}
);
127 | const b = ;
128 | expect(wrapper.contains(b)).to.equal(true);
129 | });
130 | });
131 | });
132 | }
133 |
--------------------------------------------------------------------------------
/test/test/shared/methods/name.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 | import wrap from 'mocha-wrap';
4 |
5 | import getAdapter from 'enzyme/build/getAdapter';
6 |
7 | import { createClass } from '../../_helpers/react-compat';
8 |
9 | export default function describeName({ Wrap, WrapRendered }) {
10 | wrap()
11 | .withOverride(
12 | () => getAdapter(),
13 | 'displayNameOfNode',
14 | () => undefined,
15 | )
16 | .describe('.name()', () => {
17 | describe('node with displayName', () => {
18 | it('returns the displayName of the node', () => {
19 | class Foo extends React.Component {
20 | render() {
21 | return
;
22 | }
23 | }
24 |
25 | class FooWrapper extends React.Component {
26 | render() {
27 | return ;
28 | }
29 | }
30 |
31 | Foo.displayName = 'CustomWrapper';
32 |
33 | const wrapper = WrapRendered( );
34 | expect(wrapper.name()).to.equal('CustomWrapper');
35 | });
36 |
37 | describe('stateless function components (SFCs)', () => {
38 | it('returns the name of the node', () => {
39 | function SFC() {
40 | return
;
41 | }
42 | const SFCWrapper = () => ;
43 |
44 | SFC.displayName = 'CustomWrapper';
45 |
46 | const wrapper = WrapRendered( );
47 | expect(wrapper.name()).to.equal('CustomWrapper');
48 | });
49 | });
50 |
51 | describe('createClass', () => {
52 | it('returns the name of the node', () => {
53 | const Foo = createClass({
54 | displayName: 'CustomWrapper',
55 | render() {
56 | return
;
57 | },
58 | });
59 | const FooWrapper = createClass({
60 | render() {
61 | return ;
62 | },
63 | });
64 |
65 | const wrapper = WrapRendered( );
66 | expect(wrapper.name()).to.equal('CustomWrapper');
67 | });
68 | });
69 |
70 | wrap()
71 | .withOverride(
72 | () => getAdapter(),
73 | 'displayNameOfNode',
74 | () => vi.fn(),
75 | )
76 | .describe('adapter has `displayNameOfNode`', () => {
77 | it('delegates to the adapter’s `displayNameOfNode`', () => {
78 | class Foo extends React.Component {
79 | render() {
80 | return
;
81 | }
82 | }
83 | const stub = getAdapter().displayNameOfNode;
84 | const sentinel = {};
85 | stub.mockReturnValue(sentinel);
86 |
87 | const wrapper = Wrap( );
88 |
89 | expect(wrapper.name()).to.equal(sentinel);
90 |
91 | expect(stub).to.have.property('callCount', 1);
92 | const args = stub.mock.calls[0];
93 | expect(args).to.eql([wrapper.getNodeInternal()]);
94 | });
95 | });
96 | });
97 |
98 | describe('node without displayName', () => {
99 | it('returns the name of the node', () => {
100 | class Foo extends React.Component {
101 | render() {
102 | return
;
103 | }
104 | }
105 |
106 | class FooWrapper extends React.Component {
107 | render() {
108 | return ;
109 | }
110 | }
111 |
112 | const wrapper = WrapRendered( );
113 | expect(wrapper.name()).to.equal('Foo');
114 | });
115 |
116 | describe('stateless function components (SFCs)', () => {
117 | it('returns the name of the node', () => {
118 | function SFC() {
119 | return
;
120 | }
121 | const SFCWrapper = () => ;
122 |
123 | const wrapper = WrapRendered( );
124 | expect(wrapper.name()).to.equal('SFC');
125 | });
126 | });
127 | });
128 |
129 | describe('DOM node', () => {
130 | it('returns the name of the node', () => {
131 | const wrapper = Wrap(
);
132 | expect(wrapper.name()).to.equal('div');
133 | });
134 | });
135 | });
136 | }
137 |
--------------------------------------------------------------------------------
/test/test/staticRender.spec.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { render } from 'enzyme';
5 | import renderEntry from 'enzyme/render';
6 | import { fakeDynamicImport } from '@wojtekmaj/enzyme-adapter-utils';
7 |
8 | import './_helpers/setupAdapters';
9 | import { createClass, lazy } from './_helpers/react-compat';
10 |
11 | describe('render', () => {
12 | describe('top level entry points', () => {
13 | it('exports the same renderEntry and render', () => {
14 | expect(renderEntry).to.equal(render);
15 | });
16 | });
17 |
18 | describe('context', () => {
19 | it('can pass in context', () => {
20 | const SimpleComponent = createClass({
21 | contextTypes: {
22 | name: PropTypes.string,
23 | },
24 | render() {
25 | const { name } = this.context;
26 | return {name}
;
27 | },
28 | });
29 |
30 | const context = { name: 'foo' };
31 | const wrapper = render( , { context });
32 | expect(wrapper).to.have.lengthOf(1);
33 |
34 | expect(wrapper.is('div')).to.equal(true);
35 | expect(wrapper.text()).to.equal('foo');
36 |
37 | expect(String(wrapper)).to.equal('foo
');
38 | });
39 |
40 | it('can pass context to the child of mounted component', () => {
41 | const SimpleComponent = createClass({
42 | contextTypes: {
43 | name: PropTypes.string,
44 | },
45 | render() {
46 | const { name } = this.context;
47 | return {name} ;
48 | },
49 | });
50 | const ComplexComponent = createClass({
51 | render() {
52 | return (
53 |
54 |
55 |
56 | );
57 | },
58 | });
59 |
60 | const childContextTypes = {
61 | name: PropTypes.string.isRequired,
62 | };
63 | const context = { name: 'foo' };
64 | const wrapper = render( , { context, childContextTypes });
65 | expect(wrapper).to.have.lengthOf(1);
66 |
67 | expect(wrapper.is('div')).to.equal(true);
68 |
69 | const children = wrapper.children();
70 | expect(children).to.have.lengthOf(1);
71 | expect(children.is('span')).to.equal(true);
72 |
73 | expect(children.first().text()).to.equal('foo');
74 |
75 | expect(String(wrapper)).to.equal('foo
');
76 | expect(String(children)).to.equal('foo ');
77 | });
78 |
79 | it('does not throw if context is passed in but contextTypes is missing', () => {
80 | const SimpleComponent = createClass({
81 | render() {
82 | const { name } = this.context;
83 | return {name}
;
84 | },
85 | });
86 |
87 | const context = { name: 'foo' };
88 | expect(() => render( , { context })).not.to.throw();
89 | });
90 | });
91 |
92 | describe('rendering non-elements', () => {
93 | it('can render strings', () => {
94 | const StringComponent = createClass({
95 | render() {
96 | return 'foo';
97 | },
98 | });
99 |
100 | const getWrapper = (options) => render( , options);
101 | expect(getWrapper).to.not.throw();
102 |
103 | const wrapper = getWrapper();
104 | expect(wrapper.text()).to.equal('foo');
105 | expect(wrapper.html()).to.equal('foo');
106 | expect(String(wrapper)).to.equal('foo');
107 | expect(wrapper).to.have.lengthOf(1);
108 | });
109 |
110 | it('can render numbers', () => {
111 | const NumberComponent = createClass({
112 | render() {
113 | return 42;
114 | },
115 | });
116 |
117 | const getWrapper = (options) => render( , options);
118 | expect(getWrapper).to.not.throw();
119 |
120 | const wrapper = getWrapper();
121 | expect(wrapper.text()).to.equal('42');
122 | expect(wrapper.html()).to.equal('42');
123 | expect(String(wrapper)).to.equal('42');
124 | expect(wrapper).to.have.lengthOf(1);
125 | });
126 | });
127 |
128 | describe('suspense fallback option', () => {
129 | it('throws if options.suspenseFallback is specified', () => {
130 | class DynamicComponent extends React.Component {
131 | render() {
132 | return Dynamic Component
;
133 | }
134 | }
135 | const LazyComponent = lazy(fakeDynamicImport(DynamicComponent));
136 | expect(() => render( , { suspenseFallback: false })).to.throw();
137 | });
138 | });
139 | });
140 |
--------------------------------------------------------------------------------
/test/test/shared/methods/simulateError.jsx:
--------------------------------------------------------------------------------
1 | import { beforeEach, describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 | import wrap from 'mocha-wrap';
4 |
5 | import getAdapter from 'enzyme/build/getAdapter';
6 |
7 | import { sym } from 'enzyme/build/Utils';
8 |
9 | import { itIf } from '../../_helpers';
10 |
11 | export default function describeSimulateError({ Wrap, WrapRendered, isShallow }) {
12 | describe('.simulateError(error)', () => {
13 | class Div extends React.Component {
14 | render() {
15 | const { children } = this.props;
16 | return {children}
;
17 | }
18 | }
19 |
20 | class Spans extends React.Component {
21 | render() {
22 | return (
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
31 | class Nested extends React.Component {
32 | render() {
33 | return (
34 |
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | it('throws on host elements', () => {
42 | const wrapper = WrapRendered(
);
43 | expect(wrapper.is('div')).to.equal(true);
44 | expect(() => wrapper.simulateError()).to.throw();
45 | });
46 |
47 | it('throws on "not one" node', () => {
48 | const wrapper = Wrap( );
49 |
50 | const spans = wrapper.find('span');
51 | expect(spans).to.have.lengthOf(2);
52 | expect(() => spans.simulateError()).to.throw();
53 |
54 | const navs = wrapper.find('nav');
55 | expect(navs).to.have.lengthOf(0);
56 | expect(() => navs.simulateError()).to.throw();
57 | });
58 |
59 | it('throws when the renderer lacks `simulateError`', () => {
60 | const wrapper = Wrap( );
61 | delete wrapper[sym('__renderer__')].simulateError;
62 | expect(() => wrapper.simulateError()).to.throw();
63 | try {
64 | wrapper.simulateError();
65 | } catch (e) {
66 | expect(e).not.to.equal(undefined);
67 | }
68 | });
69 |
70 | wrap()
71 | .withOverride(
72 | () => getAdapter(),
73 | 'createRenderer',
74 | () => {
75 | const adapter = getAdapter();
76 | const original = adapter.createRenderer;
77 | return function deleteSimulateError() {
78 | const renderer = original.apply(this, arguments);
79 | delete renderer.simulateError;
80 | return renderer;
81 | };
82 | },
83 | )
84 | .it('throws when the adapter does not support simulateError', () => {
85 | const wrapper = WrapRendered( );
86 | expect(() => wrapper.simulateError()).to.throw(
87 | TypeError,
88 | 'your adapter does not support `simulateError`. Try upgrading it!',
89 | );
90 | });
91 |
92 | describe('calls through to renderer’s `simulateError`', () => {
93 | let hierarchy;
94 | beforeEach(() => {
95 | const wrapper = WrapRendered( );
96 | const stub = vi.fn().mockImplementation((_, __, e) => {
97 | throw e;
98 | });
99 | wrapper[sym('__renderer__')].simulateError = stub;
100 | const error = new Error('hi');
101 | expect(() => wrapper.simulateError(error)).to.throw(error);
102 | expect(stub).to.have.property('callCount', 1);
103 |
104 | const [args] = stub.mock.calls;
105 | expect(args).to.have.lengthOf(3);
106 | const [h, rootNode, actualError] = args;
107 | expect(actualError).to.equal(error);
108 | expect(rootNode).to.eql(wrapper[sym('__root__')].getNodeInternal());
109 |
110 | hierarchy = h;
111 | expect(hierarchy).not.to.have.lengthOf(0);
112 |
113 | const [divNode] = hierarchy;
114 | expect(divNode).to.contain.keys({
115 | type: Div,
116 | nodeType: 'class',
117 | rendered: {
118 | type: Spans,
119 | nodeType: 'class',
120 | rendered: null,
121 | },
122 | });
123 | });
124 |
125 | itIf(isShallow, 'calls through to renderer’s `simulateError`', () => {
126 | expect(hierarchy).to.have.lengthOf(1);
127 | });
128 |
129 | itIf(!isShallow, 'calls through to renderer’s `simulateError`', () => {
130 | expect(hierarchy).to.have.lengthOf(2);
131 | const [, spanNode] = hierarchy;
132 | expect(spanNode).to.contain.keys({
133 | type: Spans,
134 | nodeType: 'class',
135 | rendered: null,
136 | });
137 | });
138 | });
139 |
140 | it('returns the wrapper', () => {
141 | const wrapper = WrapRendered( );
142 | wrapper[sym('__renderer__')].simulateError = vi.fn();
143 | expect(wrapper.simulateError()).to.equal(wrapper);
144 | });
145 | });
146 | }
147 |
--------------------------------------------------------------------------------
/test/test/shared/methods/containsAllMatchingElements.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | export default function describeContainsAllMatchingElements({ Wrap }) {
5 | describe('.containsAllMatchingElements(nodes)', () => {
6 | it('throws TypeError if non-array passed in', () => {
7 | const wrapper = Wrap(Hello
);
8 |
9 | expect(() => wrapper.containsAllMatchingElements(Hello
)).to.throw(
10 | TypeError,
11 | 'nodes should be an Array',
12 | );
13 | });
14 |
15 | it('matches on array of nodes that each look like rendered nodes, with nested elements', () => {
16 | const wrapper = Wrap(
17 | ,
25 | );
26 |
27 | expect(wrapper.containsAllMatchingElements([Hello
, Goodbye
])).to.equal(true);
28 | });
29 |
30 | it('matches on an array of nodes that all look like one of the rendered nodes', () => {
31 | const spy1 = vi.fn();
32 | const spy2 = vi.fn();
33 | const wrapper = Wrap(
34 |
35 |
36 | Hello World
37 |
38 |
39 | Goodbye World
40 |
41 |
,
42 | );
43 | expect(
44 | wrapper.containsAllMatchingElements([Hello World
, Goodbye World
]),
45 | ).to.equal(true);
46 | expect(
47 | wrapper.containsAllMatchingElements([
48 |
49 | Hello World
50 |
,
51 | Goodbye World
,
52 | ]),
53 | ).to.equal(true);
54 | expect(
55 | wrapper.containsAllMatchingElements([
56 | Hello World
,
57 |
58 | Goodbye World
59 |
,
60 | ]),
61 | ).to.equal(true);
62 | expect(
63 | wrapper.containsAllMatchingElements([
64 |
65 | Hello World
66 |
,
67 |
68 | Goodbye World
69 |
,
70 | ]),
71 | ).to.equal(true);
72 | expect(
73 | wrapper.containsAllMatchingElements([
74 | Hello World
,
75 |
76 | Goodbye World
77 |
,
78 | ]),
79 | ).to.equal(true);
80 | expect(
81 | wrapper.containsAllMatchingElements([
82 | Hello World
,
83 |
84 | Goodbye World
85 |
,
86 | ]),
87 | ).to.equal(true);
88 | expect(
89 | wrapper.containsAllMatchingElements([
90 |
91 | Hello World
92 |
,
93 | Goodbye World
,
94 | ]),
95 | ).to.equal(true);
96 | expect(
97 | wrapper.containsAllMatchingElements([
98 |
99 | Hello World
100 |
,
101 | Goodbye World
,
102 | ]),
103 | ).to.equal(true);
104 | expect(spy1).to.have.property('callCount', 0);
105 | expect(spy2).to.have.property('callCount', 0);
106 | });
107 |
108 | it('does not match on nodes that do not all look like one of the rendered nodes', () => {
109 | const spy1 = vi.fn();
110 | const spy2 = vi.fn();
111 | const wrapper = Wrap(
112 |
113 |
114 | Hello World
115 |
116 |
117 | Goodbye World
118 |
119 |
,
120 | );
121 | expect(
122 | wrapper.containsAllMatchingElements([
123 |
124 | Hello World
125 |
,
126 |
127 | Bonjour le monde
128 |
,
129 | Goodbye World
,
130 | ]),
131 | ).to.equal(false);
132 | expect(spy1).to.have.property('callCount', 0);
133 | expect(spy2).to.have.property('callCount', 0);
134 | });
135 | });
136 | }
137 |
--------------------------------------------------------------------------------
/test/test/shared/methods/parents.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { createClass } from '../../_helpers/react-compat';
7 |
8 | export default function describeParents({ Wrap, isShallow }) {
9 | describe('.parents([selector])', () => {
10 | it('returns an array of current node’s ancestors', () => {
11 | const wrapper = Wrap(
12 | ,
22 | );
23 |
24 | const parents = wrapper.find('.baz').parents();
25 |
26 | expect(parents).to.have.lengthOf(3);
27 | expect(parents.at(0).hasClass('bar')).to.equal(true);
28 | expect(parents.at(1).hasClass('foo')).to.equal(true);
29 | expect(parents.at(2).hasClass('bax')).to.equal(true);
30 | });
31 |
32 | it('works for non-leaf nodes as well', () => {
33 | const wrapper = Wrap(
34 | ,
41 | );
42 |
43 | const parents = wrapper.find('.bar').parents();
44 |
45 | expect(parents).to.have.lengthOf(2);
46 | expect(parents.at(0).hasClass('foo')).to.equal(true);
47 | expect(parents.at(1).hasClass('bax')).to.equal(true);
48 | });
49 |
50 | it('optionally allows a selector', () => {
51 | const wrapper = Wrap(
52 | ,
59 | );
60 |
61 | const parents = wrapper.find('.baz').parents('.foo');
62 |
63 | expect(parents).to.have.lengthOf(2);
64 | expect(parents.at(0).hasClass('foo')).to.equal(true);
65 | expect(parents.at(1).hasClass('bax')).to.equal(true);
66 | });
67 |
68 | it('works when called sequentially on two sibling nodes', () => {
69 | class Test extends React.Component {
70 | render() {
71 | return (
72 |
80 | );
81 | }
82 | }
83 |
84 | const wrapper = Wrap( );
85 |
86 | const aChild = wrapper.find({ children: 'A child' });
87 | expect(aChild.debug()).to.equal(`
88 | A child
89 |
`);
90 | expect(aChild).to.have.lengthOf(1);
91 |
92 | const bChild = wrapper.find({ children: 'B child' });
93 | expect(bChild.debug()).to.equal(`
94 | B child
95 |
`);
96 | expect(bChild).to.have.lengthOf(1);
97 |
98 | const bChildParents = bChild.parents('.b');
99 | expect(bChildParents.debug()).to.equal(`
100 |
101 | B child
102 |
103 |
`);
104 | expect(bChildParents).to.have.lengthOf(1);
105 |
106 | const aChildParents = aChild.parents('.a');
107 | expect(aChildParents.debug()).to.equal(`
108 |
109 | A child
110 |
111 |
`);
112 | expect(aChildParents).to.have.lengthOf(1);
113 | });
114 |
115 | itIf(!isShallow, 'works with components in the tree', () => {
116 | const Foo = createClass({
117 | render() {
118 | return
;
119 | },
120 | });
121 | const wrapper = Wrap(
122 |
123 |
124 |
,
125 | );
126 | const rootDiv = wrapper.find('.root');
127 | expect(rootDiv).to.have.lengthOf(1);
128 | expect(rootDiv.hasClass('root')).to.equal(true);
129 | expect(rootDiv.hasClass('bar')).to.equal(false);
130 |
131 | const bar = rootDiv.find('.bar');
132 | expect(bar).to.have.lengthOf(1);
133 | expect(bar.parents('.root')).to.have.lengthOf(1);
134 | });
135 |
136 | itIf(!isShallow, 'finds parents up a tree through a custom component boundary', () => {
137 | class CustomForm extends React.Component {
138 | render() {
139 | const { children } = this.props;
140 | return ;
141 | }
142 | }
143 |
144 | const wrapper = Wrap(
145 |
146 |
147 |
148 |
149 |
,
150 | );
151 |
152 | const formDown = wrapper.find('form');
153 | expect(formDown).to.have.lengthOf(1);
154 |
155 | const input = wrapper.find('input');
156 | expect(input).to.have.lengthOf(1);
157 | const formUp = input.parents('form');
158 | expect(formUp).to.have.lengthOf(1);
159 | });
160 | });
161 | }
162 |
--------------------------------------------------------------------------------
/test/test/shared/hooks/custom.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 |
4 | import { describeIf } from '../../_helpers';
5 |
6 | import { useEffect, useState } from '../../_helpers/react-compat';
7 |
8 | export default function describeCustomHooks({ Wrap, isShallow }) {
9 | describe('hooks: custom', () => {
10 | describe('custom hook: useCounter', () => {
11 | function useCounter({ initialCount = 0, step = 1 } = {}) {
12 | const [count, setCount] = useState(initialCount);
13 | const increment = () => setCount((c) => c + step);
14 | const decrement = () => setCount((c) => c - step);
15 | return { count, increment, decrement };
16 | }
17 | // testing custom hooks with renderProps
18 | // may be we can think of adding in utils
19 | // will be repeated
20 | const Counter = ({ children, ...rest }) => children(useCounter(rest));
21 |
22 | function setupTest(props) {
23 | const returnVal = {};
24 | Wrap(
25 |
26 | {(val) => {
27 | Object.assign(returnVal, val);
28 | return null;
29 | }}
30 | ,
31 | );
32 | return returnVal;
33 | }
34 |
35 | it('useCounter', () => {
36 | const counterData = setupTest();
37 | counterData.increment();
38 | expect(counterData).to.have.property('count', 1);
39 | counterData.decrement();
40 | expect(counterData).to.have.property('count', 0);
41 | });
42 |
43 | it('useCounter with initialCount', () => {
44 | const counterData = setupTest({ initialCount: 2 });
45 | counterData.increment();
46 | expect(counterData).to.have.property('count', 3);
47 | counterData.decrement();
48 | expect(counterData).to.have.property('count', 2);
49 | });
50 |
51 | it('useCounter with step', () => {
52 | const counterData = setupTest({ step: 2 });
53 | counterData.increment();
54 | expect(counterData).to.have.property('count', 2);
55 | counterData.decrement();
56 | expect(counterData).to.have.property('count', 0);
57 | });
58 |
59 | it('useCounter with step and initialCount', () => {
60 | const counterData = setupTest({ step: 2, initialCount: 5 });
61 | counterData.increment();
62 | expect(counterData).to.have.property('count', 7);
63 | counterData.decrement();
64 | expect(counterData).to.have.property('count', 5);
65 | });
66 | });
67 |
68 | // todo: enable shallow when useEffect works in the shallow renderer. see https://github.com/facebook/react/issues/15275
69 | describeIf(!isShallow, 'custom hook: formInput invoke props', () => {
70 | function useFormInput(initialValue = '') {
71 | const [value, setValue] = useState(initialValue);
72 |
73 | return {
74 | value,
75 | onChange(e) {
76 | setValue(e.target.value);
77 | },
78 | };
79 | }
80 |
81 | function Input(props) {
82 | return (
83 |
84 |
85 |
86 | );
87 | }
88 |
89 | function ControlledInputWithEnhancedInput({ searchSomething }) {
90 | const search = useFormInput();
91 |
92 | useEffect(() => {
93 | searchSomething(search.value);
94 | }, [search.value]);
95 |
96 | return ;
97 | }
98 |
99 | function ControlledInputWithNativeInput({ searchSomething }) {
100 | const search = useFormInput();
101 |
102 | useEffect(() => {
103 | searchSomething(search.value);
104 | }, [search.value]);
105 |
106 | return ;
107 | }
108 |
109 | it('work with native input', () => {
110 | const spy = vi.fn();
111 | const wrapper = Wrap( );
112 | wrapper.find('input').invoke('onChange')({ target: { value: 'foo' } });
113 |
114 | expect(spy).toHaveBeenCalledTimes(2);
115 | expect(spy).toHaveBeenCalledWith('');
116 | expect(spy).toHaveBeenCalledWith('foo');
117 | });
118 |
119 | it('work with custom wrapped Input', () => {
120 | const spy = vi.fn();
121 | const wrapper = Wrap( );
122 | const input = wrapper.find('Input');
123 | input.invoke('onChange')({ target: { value: 'foo' } });
124 |
125 | expect(spy).toHaveBeenCalledTimes(2);
126 | expect(spy).toHaveBeenCalledWith('');
127 | expect(spy).toHaveBeenCalledWith('foo');
128 | });
129 |
130 | it('work with custom wrapped input', () => {
131 | const spy = vi.fn();
132 | const wrapper = Wrap( );
133 | const input = wrapper.find('input');
134 | input.invoke('onChange')({ target: { value: 'foo' } });
135 |
136 | expect(spy).toHaveBeenCalledTimes(2);
137 | expect(spy).toHaveBeenCalledWith('');
138 | expect(spy).toHaveBeenCalledWith('foo');
139 | });
140 | });
141 | });
142 | }
143 |
--------------------------------------------------------------------------------
/test/test/shared/methods/setContext.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | import { createClass } from '../../_helpers/react-compat';
6 |
7 | export default function describeSetContext({ Wrap, WrapperName, isShallow }) {
8 | describe('.setContext(newContext)', () => {
9 | const SimpleComponent = createClass({
10 | contextTypes: {
11 | name: PropTypes.string,
12 | },
13 | render() {
14 | const { name } = this.context;
15 | return {name}
;
16 | },
17 | });
18 |
19 | it('sets context for a component multiple times', () => {
20 | const context = { name: 'foo' };
21 | const wrapper = Wrap( , { context });
22 | expect(wrapper.text()).to.equal('foo');
23 | wrapper.setContext({ name: 'bar' });
24 | expect(wrapper.text()).to.equal('bar');
25 | wrapper.setContext({ name: 'baz' });
26 | expect(wrapper.text()).to.equal('baz');
27 | });
28 |
29 | it('throws if it is called when wrapper didn’t include context', () => {
30 | const wrapper = Wrap( );
31 | expect(() => wrapper.setContext({ name: 'bar' })).to.throw(
32 | Error,
33 | `${WrapperName}::setContext() can only be called on a wrapper that was originally passed a context option`,
34 | );
35 | });
36 |
37 | it('throws when not called on the root', () => {
38 | const context = { name: };
39 | const wrapper = Wrap( , { context });
40 | const main = wrapper.find('main');
41 | expect(main).to.have.lengthOf(1);
42 | expect(() => main.setContext()).to.throw(
43 | Error,
44 | `${WrapperName}::setContext() can only be called on the root`,
45 | );
46 | });
47 |
48 | describe('stateless functional components', () => {
49 | const SFC = (props, { name }) => {name}
;
50 | SFC.contextTypes = { name: PropTypes.string };
51 |
52 | it('sets context for a component multiple times', () => {
53 | const context = { name: 'foo' };
54 | const wrapper = Wrap( , { context });
55 | expect(wrapper.text()).to.equal('foo');
56 | wrapper.setContext({ name: 'bar' });
57 | expect(wrapper.text()).to.equal('bar');
58 | wrapper.setContext({ name: 'baz' });
59 | expect(wrapper.text()).to.equal('baz');
60 | });
61 |
62 | it('throws if it is called when shallow didn’t include context', () => {
63 | const wrapper = Wrap( );
64 | expect(() => wrapper.setContext({ name: 'bar' })).to.throw(
65 | Error,
66 | `${WrapperName}::setContext() can only be called on a wrapper that was originally passed a context option`,
67 | );
68 | });
69 | });
70 |
71 | it('calls componentWillReceiveProps when context is updated', () => {
72 | const spy = vi.fn();
73 | const updatedProps = { foo: 'baz' };
74 | class Foo extends React.Component {
75 | componentWillReceiveProps() {
76 | spy('componentWillReceiveProps');
77 | }
78 |
79 | render() {
80 | spy('render');
81 | const { foo } = this.context;
82 | return {foo}
;
83 | }
84 | }
85 | Foo.contextTypes = {
86 | foo: PropTypes.string,
87 | };
88 |
89 | const wrapper = Wrap( , {
90 | context: { foo: 'bar' },
91 | });
92 |
93 | wrapper.setContext(updatedProps);
94 |
95 | expect(spy.mock.calls).to.deep.equal([['render'], ['componentWillReceiveProps'], ['render']]);
96 | expect(wrapper.context('foo')).to.equal(updatedProps.foo);
97 |
98 | expect(wrapper.debug()).to.equal(
99 | isShallow
100 | ? `
101 | baz
102 |
`
103 | : `
104 |
105 | baz
106 |
107 | `,
108 | );
109 | });
110 |
111 | it('calls componentWillReceiveProps and UNSAFE_componentWillReceiveProps when context is updated', () => {
112 | const spy = vi.fn();
113 | const updatedProps = { foo: 'baz' };
114 | class Foo extends React.Component {
115 | componentWillReceiveProps() {
116 | spy('componentWillReceiveProps');
117 | }
118 |
119 | UNSAFE_componentWillReceiveProps() {
120 | spy('UNSAFE_componentWillReceiveProps');
121 | }
122 |
123 | render() {
124 | spy('render');
125 | const { foo } = this.context;
126 | return {foo}
;
127 | }
128 | }
129 | Foo.contextTypes = {
130 | foo: PropTypes.string,
131 | };
132 |
133 | const wrapper = Wrap( , {
134 | context: { foo: 'bar' },
135 | });
136 |
137 | wrapper.setContext(updatedProps);
138 |
139 | expect(spy.mock.calls).to.deep.equal([
140 | ['render'],
141 | ['componentWillReceiveProps'],
142 | ['UNSAFE_componentWillReceiveProps'],
143 | ['render'],
144 | ]);
145 | expect(wrapper.context('foo')).to.equal(updatedProps.foo);
146 |
147 | expect(wrapper.debug()).to.equal(
148 | isShallow
149 | ? `
150 | baz
151 |
`
152 | : `
153 |
154 | baz
155 |
156 | `,
157 | );
158 | });
159 | });
160 | }
161 |
--------------------------------------------------------------------------------
/test/test/shared/methods/state.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itIf } from '../../_helpers';
5 |
6 | import { createPortal } from '../../_helpers/react-compat';
7 |
8 | export default function describeState({ Wrap, WrapperName, isShallow, makeDOMElement }) {
9 | describe('.state([name])', () => {
10 | class HasFooState extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = { foo: 'foo' };
14 | }
15 |
16 | render() {
17 | const { foo } = this.state;
18 | return {foo}
;
19 | }
20 | }
21 |
22 | it('returns the state object', () => {
23 | const wrapper = Wrap( );
24 | expect(wrapper.state()).to.eql({ foo: 'foo' });
25 | });
26 |
27 | it('returns the current state after state transitions', () => {
28 | const wrapper = Wrap( );
29 | wrapper.setState({ foo: 'bar' });
30 | expect(wrapper.state()).to.eql({ foo: 'bar' });
31 | });
32 |
33 | it('allows a state property name be passed in as an argument', () => {
34 | const wrapper = Wrap( );
35 | expect(wrapper.state('foo')).to.equal('foo');
36 | });
37 |
38 | it('throws on host nodes', () => {
39 | const wrapper = Wrap(
40 |
41 |
42 |
,
43 | );
44 |
45 | expect(() => wrapper.state()).to.throw(
46 | Error,
47 | `${WrapperName}::state() can only be called on class components`,
48 | );
49 | });
50 |
51 | it('throws on Portals', () => {
52 | const containerDiv = makeDOMElement();
53 | const portal = createPortal(
, containerDiv);
54 |
55 | const wrapper = Wrap({portal}
);
56 | expect(() => wrapper.state()).to.throw(
57 | Error,
58 | `${WrapperName}::state() can only be called on class components`,
59 | );
60 | });
61 |
62 | describe('stateless function components (SFCs)', () => {
63 | it('throws on SFCs', () => {
64 | function FooSFC() {
65 | return
;
66 | }
67 |
68 | const wrapper = Wrap( );
69 | expect(() => wrapper.state()).to.throw(
70 | Error,
71 | `${WrapperName}::state() can only be called on class components`,
72 | );
73 | });
74 | });
75 |
76 | describe('child components', () => {
77 | class Child extends React.Component {
78 | constructor(...args) {
79 | super(...args);
80 | this.state = { state: 'a' };
81 | }
82 |
83 | render() {
84 | const { prop } = this.props;
85 | const { state } = this.state;
86 | return (
87 |
88 | {prop}
89 | {' - '}
90 | {state}
91 |
92 | );
93 | }
94 | }
95 |
96 | class Parent extends React.Component {
97 | constructor(...args) {
98 | super(...args);
99 | this.state = { childProp: 1 };
100 | }
101 |
102 | render() {
103 | const { childProp } = this.state;
104 | return ;
105 | }
106 | }
107 |
108 | it('gets the state of a stateful parent', () => {
109 | const wrapper = Wrap( );
110 |
111 | expect(wrapper.state()).to.eql({ childProp: 1 });
112 | });
113 |
114 | itIf(isShallow, 'can not get the state of the stateful child of a stateful root', () => {
115 | const wrapper = Wrap( );
116 |
117 | const child = wrapper.find(Child);
118 | expect(() => child.state()).to.throw(
119 | Error,
120 | `${WrapperName}::state() can only be called on the root`,
121 | );
122 | });
123 |
124 | itIf(!isShallow, 'gets the state of the stateful child of a stateful root', () => {
125 | const wrapper = Wrap( );
126 |
127 | const child = wrapper.find(Child);
128 | expect(child.state()).to.eql({ state: 'a' });
129 | });
130 |
131 | describe('stateless function components (SFCs)', () => {
132 | function StatelessParent(props) {
133 | return ;
134 | }
135 |
136 | itIf(isShallow, 'can not get the state of the stateful child of a stateless root', () => {
137 | const wrapper = Wrap( );
138 |
139 | const child = wrapper.find(Child);
140 | expect(() => child.state()).to.throw(
141 | Error,
142 | `${WrapperName}::state() can only be called on the root`,
143 | );
144 | });
145 |
146 | itIf(!isShallow, 'gets the state of the stateful child of a stateless root', () => {
147 | const wrapper = Wrap( );
148 |
149 | const child = wrapper.find(Child);
150 | expect(child.state()).to.eql({ state: 'a' });
151 | });
152 | });
153 | });
154 |
155 | it('throws when called on a wrapper of multiple nodes', () => {
156 | const wrapper = Wrap(
157 |
158 |
159 |
160 |
161 |
,
162 | );
163 | const spans = wrapper.find('span');
164 | expect(spans).to.have.lengthOf(3);
165 | expect(() => spans.state()).to.throw(
166 | Error,
167 | isShallow
168 | ? 'ShallowWrapper::state() can only be called on the root'
169 | : `${WrapperName}::getNode() can only be called when wrapping one node`,
170 | );
171 | });
172 | });
173 | }
174 |
--------------------------------------------------------------------------------
/test/test/shared/methods/props.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { delay, itIf } from '../../_helpers';
5 |
6 | import sloppyReturnThis from '../../_helpers/untranspiledSloppyReturnThis';
7 |
8 | export default function describeProps({ Wrap, WrapRendered, isMount }) {
9 | describe('.props()', () => {
10 | it('returns the props object', () => {
11 | const fn = () => ({});
12 | const wrapper = Wrap(
13 | ,
17 | );
18 |
19 | expect(wrapper.props().className).to.equal('bax');
20 | expect(wrapper.props().onClick).to.equal(fn);
21 | expect(wrapper.props().id).to.equal('fooId');
22 | });
23 |
24 | it('is allowed to be used on an inner node', () => {
25 | const fn = () => ({});
26 | const wrapper = Wrap(
27 | ,
31 | );
32 |
33 | expect(wrapper.find('.baz').props().onClick).to.equal(fn);
34 | expect(wrapper.find('.foo').props().id).to.equal('fooId');
35 | });
36 |
37 | class Foo extends React.Component {
38 | render() {
39 | const { bar, foo } = this.props;
40 | return
;
41 | }
42 | }
43 |
44 | itIf(isMount, 'called on root should return props of root node', () => {
45 | const wrapper = Wrap( );
46 |
47 | expect(wrapper.props()).to.eql({ bar: 'bye', foo: 'hi' });
48 | });
49 |
50 | it('returns props of root rendered node', () => {
51 | const wrapper = WrapRendered( );
52 |
53 | expect(wrapper.props()).to.eql({ className: 'bye', id: 'hi' });
54 | });
55 |
56 | describe('stateless function components (SFCs)', () => {
57 | const FooSFC = ({ bar, foo }) =>
;
58 |
59 | itIf(isMount, 'called on root should return props of root node', () => {
60 | const wrapper = Wrap( );
61 |
62 | expect(wrapper.props()).to.eql({ bar: 'bye', foo: 'hi' });
63 | });
64 |
65 | it('returns props of root rendered node', () => {
66 | const wrapper = WrapRendered( );
67 |
68 | expect(wrapper.props()).to.eql({ className: 'bye', id: 'hi' });
69 | });
70 |
71 | const SloppyReceiver = sloppyReturnThis((receiver, props = { NO_PROPS: true }, context) => (
72 |
78 | ));
79 |
80 | const StrictReceiver = function SFC(props = { NO_PROPS: true }, context) {
81 | return (
82 |
88 | );
89 | };
90 |
91 | it('does not provide a `this` to a sloppy-mode SFC', () => {
92 | const wrapper = WrapRendered( );
93 | expect(wrapper.props()).to.be.an('object').that.has.all.keys({
94 | 'data-is-global': true,
95 | 'data-is-undefined': false,
96 | });
97 | });
98 |
99 | it('does not provide a `this` to a strict-mode SFC', () => {
100 | const wrapper = WrapRendered( );
101 | expect(wrapper.props()).to.be.an('object').that.has.all.keys({
102 | 'data-is-global': false,
103 | 'data-is-undefined': true,
104 | });
105 | });
106 | });
107 |
108 | describe('props in async handler', () => {
109 | class TestComponent extends React.Component {
110 | constructor(props) {
111 | super(props);
112 | this.state = { counter: 1 };
113 | this.handleClick = this.handleClick.bind(this);
114 | }
115 |
116 | handleClick() {
117 | return delay(100).then(
118 | () =>
119 | new Promise((resolve) => {
120 | this.setState({ counter: 2 }, () => {
121 | resolve();
122 | });
123 | }),
124 | );
125 | }
126 |
127 | render() {
128 | const { counter } = this.state;
129 | return (
130 |
131 |
132 |
133 | );
134 | }
135 | }
136 |
137 | class TestSubComponent extends React.Component {
138 | render() {
139 | const { counter } = this.props;
140 | return {counter}
;
141 | }
142 | }
143 |
144 | it('child component props should update after call to setState in async handler', () => {
145 | const wrapper = Wrap( );
146 | expect(wrapper.find(TestSubComponent).props()).to.eql({ id: 'childDiv', counter: 1 });
147 | const promise = wrapper.find('#parentDiv').props().onClick();
148 | expect(wrapper.find(TestSubComponent).props()).to.eql({ id: 'childDiv', counter: 1 });
149 | return promise.then(() => {
150 | wrapper.update();
151 | expect(wrapper.find(TestSubComponent).props()).to.eql({ id: 'childDiv', counter: 2 });
152 | });
153 | });
154 | });
155 | });
156 | }
157 |
--------------------------------------------------------------------------------
/test/test/shared/methods/isEmptyRender.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import React from 'react';
3 |
4 | import { itWithData, generateEmptyRenderData, itIf } from '../../_helpers';
5 |
6 | import { createClass, memo } from '../../_helpers/react-compat';
7 |
8 | export default function describeIsEmptyRender({ Wrap, WrapRendered, isShallow }) {
9 | describe('.isEmptyRender()', () => {
10 | class RenderChildren extends React.Component {
11 | render() {
12 | const { children } = this.props;
13 | return children;
14 | }
15 | }
16 |
17 | class RenderNull extends React.Component {
18 | render() {
19 | return null;
20 | }
21 | }
22 |
23 | const emptyRenderValues = generateEmptyRenderData();
24 |
25 | itWithData(emptyRenderValues, 'when a React createClass component returns: ', (data) => {
26 | const Foo = createClass({
27 | render() {
28 | return data.value;
29 | },
30 | });
31 | const wrapper = Wrap( );
32 | expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
33 | });
34 |
35 | itWithData(emptyRenderValues, 'when an ES2015 class component returns: ', (data) => {
36 | class Foo extends React.Component {
37 | render() {
38 | return data.value;
39 | }
40 | }
41 | const wrapper = Wrap( );
42 | expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
43 | });
44 |
45 | describe('nested nodes', () => {
46 | it(`returns ${!isShallow} for nested elements that return null`, () => {
47 | const wrapper = Wrap(
48 |
49 |
50 | ,
51 | );
52 |
53 | expect(wrapper.isEmptyRender()).to.equal(!isShallow);
54 | });
55 |
56 | it('returns false for multiple nested elements that all return null', () => {
57 | const wrapper = Wrap(
58 |
59 |
60 | ,
61 | );
62 |
63 | expect(wrapper.isEmptyRender()).to.equal(false);
64 | });
65 |
66 | it('returns false for multiple nested elements where one fringe returns a non null value', () => {
67 | const wrapper = Wrap(
68 |
69 | Hello
70 | ,
71 | );
72 |
73 | expect(wrapper.isEmptyRender()).to.equal(false);
74 | });
75 |
76 | it('returns false for multiple nested elements that all return null', () => {
77 | const wrapper = Wrap(
78 |
79 |
80 |
81 |
82 |
83 |
84 | ,
85 | );
86 |
87 | expect(wrapper.isEmptyRender()).to.equal(false);
88 | });
89 |
90 | it('returns false for multiple nested elements where one fringe returns a non null value', () => {
91 | const wrapper = Wrap(
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Hello
105 |
106 |
107 | ,
108 | );
109 |
110 | expect(wrapper.isEmptyRender()).to.equal(false);
111 | });
112 |
113 | it(`returns ${!isShallow} for multiple nested elements where all values are null`, () => {
114 | const wrapper = Wrap(
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | ,
130 | );
131 |
132 | expect(wrapper.isEmptyRender()).to.equal(!isShallow);
133 | });
134 | });
135 |
136 | it('does not return true for HTML elements', () => {
137 | const wrapper = Wrap(
);
138 | expect(wrapper.isEmptyRender()).to.equal(false);
139 | });
140 |
141 | describe('stateless function components (SFCs)', () => {
142 | itWithData(emptyRenderValues, 'when a component returns: ', (data) => {
143 | function Foo() {
144 | return data.value;
145 | }
146 | const wrapper = Wrap( );
147 | expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
148 | });
149 | });
150 |
151 | itIf(isShallow, 'returns false for > 1 elements', () => {
152 | class RendersThree extends React.Component {
153 | render() {
154 | return (
155 |
156 |
157 |
158 |
159 |
160 | );
161 | }
162 | }
163 |
164 | const wrapper = WrapRendered( );
165 | const elements = wrapper.find(RenderNull);
166 | expect(elements).to.have.lengthOf(3);
167 | expect(elements.isEmptyRender()).to.equal(false);
168 | });
169 |
170 | it('works on a memoized functional component', () => {
171 | const Component = memo(() => null);
172 | const wrapper = Wrap( );
173 | expect(wrapper.debug()).to.equal(isShallow ? '' : ' ');
174 | expect(wrapper.isEmptyRender()).to.equal(true);
175 | });
176 | });
177 | }
178 |
--------------------------------------------------------------------------------
/test/test/shared/methods/renderProp.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest';
2 | import React from 'react';
3 | import wrap from 'mocha-wrap';
4 |
5 | import getAdapter from 'enzyme/build/getAdapter';
6 |
7 | export default function describeRenderProp({ Wrap, WrapRendered, WrapperName }) {
8 | wrap()
9 | .withConsoleThrows()
10 | .describe('.renderProp()', () => {
11 | class Foo extends React.Component {
12 | render() {
13 | return
;
14 | }
15 | }
16 | class Bar extends React.Component {
17 | render() {
18 | const { render: r } = this.props;
19 | return {typeof r === 'function' && r()}
;
20 | }
21 | }
22 | class RendersBar extends React.Component {
23 | render() {
24 | return ;
25 | }
26 | }
27 |
28 | it('returns a wrapper around the node returned from the render prop', () => {
29 | const wrapperA = Wrap(
30 |
31 |
(
33 |
34 |
35 |
36 | )}
37 | />
38 | ,
39 | );
40 | const renderPropWrapperA = wrapperA.find(Bar).renderProp('render')();
41 | expect(renderPropWrapperA.find(Foo)).to.have.lengthOf(1);
42 |
43 | const wrapperB = Wrap(
44 |
45 | } />
46 |
,
47 | );
48 | const renderPropWrapperB = wrapperB.find(Bar).renderProp('render')();
49 | expect(renderPropWrapperB.find(Foo)).to.have.lengthOf(1);
50 |
51 | const stub = vi.fn().mockReturnValue(
);
52 | const wrapperC = Wrap(
53 |
54 |
55 |
,
56 | );
57 | stub.mockClear();
58 | wrapperC.find(Bar).renderProp('render')('one', 'two');
59 | expect(stub.mock.calls).to.deep.equal([['one', 'two']]);
60 | });
61 |
62 | it('throws on a non-string prop name', () => {
63 | const wrapper = Wrap( {}} />);
64 | expect(() => wrapper.renderProp()).to.throw(
65 | TypeError,
66 | `${WrapperName}::renderProp(): \`propName\` must be a string`,
67 | );
68 | });
69 |
70 | it('throws on a missing prop', () => {
71 | const wrapper = Wrap( {}} />);
72 | expect(() => wrapper.renderProp('nope')).to.throw(
73 | Error,
74 | `${WrapperName}::renderProp(): no prop called “nope“ found`,
75 | );
76 | });
77 |
78 | it('throws on a non-function render prop value', () => {
79 | const wrapper = Wrap( );
80 | expect(() => wrapper.renderProp('render')).to.throw(
81 | TypeError,
82 | `${WrapperName}::renderProp(): expected prop “render“ to contain a function, but it holds “object“`,
83 | );
84 | });
85 |
86 | it('throws on host elements', () => {
87 | class Div extends React.Component {
88 | render() {
89 | const { children } = this.props;
90 | return {children}
;
91 | }
92 | }
93 |
94 | const wrapper = WrapRendered(
);
95 | expect(wrapper.is('div')).to.equal(true);
96 | expect(() => wrapper.renderProp('foo')).to.throw();
97 | });
98 |
99 | wrap()
100 | .withOverride(
101 | () => getAdapter(),
102 | 'wrap',
103 | () => undefined,
104 | )
105 | .it('throws with a react adapter that lacks a `.wrap`', () => {
106 | const wrapper = Wrap(
107 |
108 |
(
110 |
111 |
112 |
113 | )}
114 | />
115 | ,
116 | );
117 | expect(() => wrapper.find(Bar).renderProp('render')).to.throw(RangeError);
118 | });
119 |
120 | describe('allows non-nodes', () => {
121 | function MyComponent({ val }) {
122 | return x} />;
123 | }
124 |
125 | function ComponentWithRenderProp({ val, r }) {
126 | return r(val);
127 | }
128 |
129 | it('works with strings', () => {
130 | const wrapper = Wrap( );
131 |
132 | wrapper.find(ComponentWithRenderProp).renderProp('r')('foo');
133 |
134 | wrapper.find(ComponentWithRenderProp).renderProp('r')('');
135 | });
136 |
137 | it('works with numbers', () => {
138 | const wrapper = Wrap( );
139 |
140 | wrapper.find(ComponentWithRenderProp).renderProp('r')(42);
141 |
142 | wrapper.find(ComponentWithRenderProp).renderProp('r')(0);
143 |
144 | wrapper.find(ComponentWithRenderProp).renderProp('r')(NaN);
145 | });
146 |
147 | it('works with null', () => {
148 | const wrapper = Wrap( );
149 |
150 | wrapper.find(ComponentWithRenderProp).renderProp('r')(null);
151 | });
152 |
153 | // FIXME: figure out how to test this reliably
154 | it.skip('throws with undefined', () => {
155 | const wrapper = Wrap( );
156 |
157 | expect(() => wrapper.find(ComponentWithRenderProp).renderProp('r')(undefined)).to.throw();
158 | });
159 |
160 | it('works with arrays', () => {
161 | const wrapper = Wrap( );
162 |
163 | wrapper.find(ComponentWithRenderProp).renderProp('r')([]);
164 |
165 | wrapper.find(ComponentWithRenderProp).renderProp('r')(['a']);
166 |
167 | wrapper.find(ComponentWithRenderProp).renderProp('r')([Infinity]);
168 | });
169 |
170 | it('works with false', () => {
171 | const wrapper = Wrap( );
172 |
173 | wrapper.find(ComponentWithRenderProp).renderProp('r')(false);
174 | });
175 |
176 | it('throws with true', () => {
177 | const wrapper = Wrap( );
178 |
179 | expect(() =>
180 | wrapper.find(ComponentWithRenderProp).renderProp('r')(true).Wrap(),
181 | ).to.throw();
182 | });
183 | });
184 | });
185 | }
186 |
--------------------------------------------------------------------------------
/src/findCurrentFiberUsingSlowPath.js:
--------------------------------------------------------------------------------
1 | // Extracted from https://github.com/facebook/react/blob/a724a3b578dce77d427bef313102a4d0e978d9b4/packages/react-reconciler/src/ReactFiberTreeReflection.js
2 |
3 | const HostRoot = 3;
4 |
5 | const Placement = 0b0000000000000000000000010;
6 | const Hydrating = 0b0000000000001000000000000;
7 | const NoFlags = 0b0000000000000000000000000;
8 |
9 | function getNearestMountedFiber(fiber) {
10 | let node = fiber;
11 | let nearestMounted = fiber;
12 | if (!fiber.alternate) {
13 | // If there is no alternate, this might be a new tree that isn't inserted
14 | // yet. If it is, then it will have a pending insertion effect on it.
15 | let nextNode = node;
16 | do {
17 | node = nextNode;
18 | if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
19 | // This is an insertion or in-progress hydration. The nearest possible
20 | // mounted fiber is the parent but we need to continue to figure out
21 | // if that one is still mounted.
22 | nearestMounted = node.return;
23 | }
24 | nextNode = node.return;
25 | } while (nextNode);
26 | } else {
27 | while (node.return) {
28 | node = node.return;
29 | }
30 | }
31 | if (node.tag === HostRoot) {
32 | // TODO: Check if this was a nested HostRoot when used with
33 | // renderContainerIntoSubtree.
34 | return nearestMounted;
35 | }
36 | // If we didn't hit the root, that means that we're in an disconnected tree
37 | // that has been unmounted.
38 | return null;
39 | }
40 |
41 | function findCurrentFiberUsingSlowPath(fiber) {
42 | const alternate = fiber.alternate;
43 | if (!alternate) {
44 | // If there is no alternate, then we only need to check if it is mounted.
45 | const nearestMounted = getNearestMountedFiber(fiber);
46 |
47 | if (nearestMounted === null) {
48 | throw new Error('Unable to find node on an unmounted component.');
49 | }
50 |
51 | if (nearestMounted !== fiber) {
52 | return null;
53 | }
54 | return fiber;
55 | }
56 | // If we have two possible branches, we'll walk backwards up to the root
57 | // to see what path the root points to. On the way we may hit one of the
58 | // special cases and we'll deal with them.
59 | let a = fiber;
60 | let b = alternate;
61 | // eslint-disable-next-line no-constant-condition
62 | while (true) {
63 | const parentA = a.return;
64 | if (parentA === null) {
65 | // We're at the root.
66 | break;
67 | }
68 | const parentB = parentA.alternate;
69 | if (parentB === null) {
70 | // There is no alternate. This is an unusual case. Currently, it only
71 | // happens when a Suspense component is hidden. An extra fragment fiber
72 | // is inserted in between the Suspense fiber and its children. Skip
73 | // over this extra fragment fiber and proceed to the next parent.
74 | const nextParent = parentA.return;
75 | if (nextParent !== null) {
76 | a = b = nextParent;
77 | continue;
78 | }
79 | // If there's no parent, we're at the root.
80 | break;
81 | }
82 |
83 | // If both copies of the parent fiber point to the same child, we can
84 | // assume that the child is current. This happens when we bailout on low
85 | // priority: the bailed out fiber's child reuses the current child.
86 | if (parentA.child === parentB.child) {
87 | let child = parentA.child;
88 | while (child) {
89 | if (child === a) {
90 | // We've determined that A is the current branch.
91 | return fiber;
92 | }
93 | if (child === b) {
94 | // We've determined that B is the current branch.
95 | return alternate;
96 | }
97 | child = child.sibling;
98 | }
99 |
100 | // We should never have an alternate for any mounting node. So the only
101 | // way this could possibly happen is if this was unmounted, if at all.
102 | throw new Error('Unable to find node on an unmounted component.');
103 | }
104 |
105 | if (a.return !== b.return) {
106 | // The return pointer of A and the return pointer of B point to different
107 | // fibers. We assume that return pointers never criss-cross, so A must
108 | // belong to the child set of A.return, and B must belong to the child
109 | // set of B.return.
110 | a = parentA;
111 | b = parentB;
112 | } else {
113 | // The return pointers point to the same fiber. We'll have to use the
114 | // default, slow path: scan the child sets of each parent alternate to see
115 | // which child belongs to which set.
116 | //
117 | // Search parent A's child set
118 | let didFindChild = false;
119 | let child = parentA.child;
120 | while (child) {
121 | if (child === a) {
122 | didFindChild = true;
123 | a = parentA;
124 | b = parentB;
125 | break;
126 | }
127 | if (child === b) {
128 | didFindChild = true;
129 | b = parentA;
130 | a = parentB;
131 | break;
132 | }
133 | child = child.sibling;
134 | }
135 | if (!didFindChild) {
136 | // Search parent B's child set
137 | child = parentB.child;
138 | while (child) {
139 | if (child === a) {
140 | didFindChild = true;
141 | a = parentB;
142 | b = parentA;
143 | break;
144 | }
145 | if (child === b) {
146 | didFindChild = true;
147 | b = parentB;
148 | a = parentA;
149 | break;
150 | }
151 | child = child.sibling;
152 | }
153 |
154 | if (!didFindChild) {
155 | throw new Error(
156 | 'Child was not found in either parent set. This indicates a bug ' +
157 | 'in React related to the return pointer. Please file an issue.',
158 | );
159 | }
160 | }
161 | }
162 |
163 | if (a.alternate !== b) {
164 | throw new Error(
165 | "Return fibers should always be each others' alternates. " +
166 | 'This error is likely caused by a bug in React. Please file an issue.',
167 | );
168 | }
169 | }
170 |
171 | // If the root is not a host container, we're in a disconnected tree. I.e.
172 | // unmounted.
173 | if (a.tag !== HostRoot) {
174 | throw new Error('Unable to find node on an unmounted component.');
175 | }
176 |
177 | if (a.stateNode?.current === a) {
178 | // We've determined that A is the current branch.
179 | return fiber;
180 | }
181 | // Otherwise B has to be current branch.
182 | return alternate;
183 | }
184 |
185 | module.exports = findCurrentFiberUsingSlowPath;
186 |
--------------------------------------------------------------------------------