enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Re-installing enzyme-adapter-react-16@1.15.2 breaks mount as sub-dependency resolution isn't locked #2483

Closed PaulSearcy closed 3 years ago

PaulSearcy commented 3 years ago

Current behavior

( I am aware npm i library@version releases the lock on the resolved sub-dependencies)

Re-installing enzyme-adapter-react-16@1.15.2 via npm i enzyme-adapter-react-16@1.15.2 causes any test with mount to fail.

In addition outputs a stack trace that exceeds the maximum call stack.

Error: Uncaught [RangeError: Maximum call stack size exceeded]
    at reportException (/ams/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
    at innerInvokeEventListeners (/ams/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:346:9)
    at invokeEventListeners (/ams/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
    at HTMLUnknownElementImpl._dispatch (/ams/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
    at HTMLUnknownElementImpl.dispatchEvent (/ams/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)
    at HTMLUnknownElement.dispatchEvent (/ams/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:224:25)
    at Object.invokeGuardedCallbackDev (/ams/node_modules/react-dom/cjs/react-dom.development.js:237:16)
    at invokeGuardedCallback (/ams/node_modules/react-dom/cjs/react-dom.development.js:292:31)
    at beginWork$1 (/ams/node_modules/react-dom/cjs/react-dom.development.js:23234:7)
    at performUnitOfWork (/ams/node_modules/react-dom/cjs/react-dom.development.js:22188:12)
    at workLoopSync (/ams/node_modules/react-dom/cjs/react-dom.development.js:22161:22)
    at performSyncWorkOnRoot (/ams/node_modules/react-dom/cjs/react-dom.development.js:21787:9)
    at scheduleUpdateOnFiber (/ams/node_modules/react-dom/cjs/react-dom.development.js:21219:7)
    at updateContainer (/ams/node_modules/react-dom/cjs/react-dom.development.js:24407:3)
    at /ams/node_modules/react-dom/cjs/react-dom.development.js:24792:7
    at unbatchedUpdates (/ams/node_modules/react-dom/cjs/react-dom.development.js:21934:12)
    at legacyRenderSubtreeIntoContainer (/ams/node_modules/react-dom/cjs/react-dom.development.js:24791:5)
    at Object.render (/ams/node_modules/react-dom/cjs/react-dom.development.js:24874:10)
    at fn (/ams/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:437:26)
    at /ams/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:354:37
    at batchedUpdates$1 (/ams/node_modules/react-dom/cjs/react-dom.development.js:21887:12)
    at Object.act (/ams/node_modules/react-dom/cjs/react-dom-test-utils.development.js:929:14)
    at wrapAct (/ams/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:354:13)
    at Object.render (/ams/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:423:16)
    at new ReactWrapper (/ams/node_modules/enzyme/src/ReactWrapper.js:115:16)
    at mount (/ams/node_modules/enzyme/src/mount.js:10:10)
    at Context.<anonymous> (<removed file name as code isn't open source>)
    at callFnAsync (/ams/node_modules/mocha/lib/runnable.js:385:21)
    at Hook.Runnable.run (/ams/node_modules/mocha/lib/runnable.js:329:7)
    at next (/ams/node_modules/mocha/lib/runner.js:475:10)
    at Immediate._onImmediate (/ams/node_modules/mocha/lib/runner.js:520:5)
    at processImmediate (internal/timers.js:458:21) RangeError: Maximum call stack size exceeded
    at new WrapperComponent (/ams/node_modules/enzyme-adapter-utils/src/createMountWrapper.jsx:49:5)
    at Object.construct (/ams/node_modules/harmony-reflect/reflect.js:2044:12)
    at WrapperComponent._createSuperInternal (/ams/node_modules/enzyme-adapter-utils/build/createMountWrapper.js:38:283)

    /// These three lines infinitely repeat until call stack reaches maximum
    at new WrapperComponent (/ams/node_modules/enzyme-adapter-utils/src/createMountWrapper.jsx:50:7)
    at Object.construct (/ams/node_modules/harmony-reflect/reflect.js:2044:12)
    at WrapperComponent._createSuperInternal (/ams/node_modules/enzyme-adapter-utils/build/createMountWrapper.js:38:283)

Working package-lock.json and npm ls for enzyme-adapter-react-16@1.15.2

    "enzyme-adapter-react-16": {
      "version": "1.15.2",
      "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz",
      "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==",
      "dev": true,
      "requires": {
        "enzyme-adapter-utils": "^1.13.0",
        "enzyme-shallow-equal": "^1.0.1",
        "has": "^1.0.3",
        "object.assign": "^4.1.0",
        "object.values": "^1.1.1",
        "prop-types": "^15.7.2",
        "react-is": "^16.12.0",
        "react-test-renderer": "^16.0.0-0",
        "semver": "^5.7.0"
      },
      "dependencies": {
        "react-is": {
          "version": "16.13.0",
          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
          "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==",
          "dev": true
        }
      }
    },
├─┬ enzyme-adapter-react-16@1.15.2
│ ├─┬ enzyme-adapter-utils@1.13.0
│ │ ├─┬ airbnb-prop-types@2.15.0
│ │ │ ├─┬ array.prototype.find@2.1.1
│ │ │ │ ├── define-properties@1.1.3 deduped
│ │ │ │ └── es-abstract@1.17.4 deduped
│ │ │ ├── function.prototype.name@1.1.2 deduped
│ │ │ ├── has@1.0.3 deduped
│ │ │ ├─┬ is-regex@1.0.4
│ │ │ │ └── has@1.0.3 deduped
│ │ │ ├── object-is@1.0.2 deduped
│ │ │ ├── object.assign@4.1.0 deduped
│ │ │ ├── object.entries@1.1.1 deduped
│ │ │ ├── prop-types@15.7.2 deduped
│ │ │ ├─┬ prop-types-exact@1.2.0
│ │ │ │ ├── has@1.0.3 deduped
│ │ │ │ ├── object.assign@4.1.0 deduped
│ │ │ │ └── reflect.ownkeys@0.2.0
│ │ │ └── react-is@16.9.0 deduped
│ │ ├── function.prototype.name@1.1.2 deduped
│ │ ├── object.assign@4.1.0 deduped
│ │ ├── object.fromentries@2.0.2 deduped
│ │ ├── prop-types@15.7.2 deduped
│ │ └── semver@5.7.1 deduped
│ ├── enzyme-shallow-equal@1.0.1 deduped
│ ├── has@1.0.3 deduped
│ ├── object.assign@4.1.0 deduped
│ ├── object.values@1.1.1 deduped
│ ├── prop-types@15.7.2 deduped
│ ├── react-is@16.13.0
│ ├─┬ react-test-renderer@16.13.0
│ │ ├── object-assign@4.1.1 deduped
│ │ ├── prop-types@15.7.2 deduped
│ │ ├── react-is@16.9.0 deduped
│ │ └── scheduler@0.19.0 deduped
│ └── semver@5.7.1 deduped

Broken package-lock.json and npm ls for enzyme-adapter-react-16@1.15.2 after running npm i enzyme-adapter-react-16@1.15.2

    "enzyme-adapter-react-16": {
      "version": "1.15.2",
      "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz",
      "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==",
      "dev": true,
      "requires": {
        "enzyme-adapter-utils": "^1.13.0",
        "enzyme-shallow-equal": "^1.0.1",
        "has": "^1.0.3",
        "object.assign": "^4.1.0",
        "object.values": "^1.1.1",
        "prop-types": "^15.7.2",
        "react-is": "^16.12.0",
        "react-test-renderer": "^16.0.0-0",
        "semver": "^5.7.0"
      },
      "dependencies": {
        "react-is": {
          "version": "16.13.1",
          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
          "dev": true
        }
      }
    },
+-- enzyme-adapter-react-16@1.15.2
| +-- enzyme-adapter-utils@1.13.1
| | +-- airbnb-prop-types@2.16.0
| | | +-- array.prototype.find@2.1.1
| | | | +-- define-properties@1.1.3 deduped
| | | | `-- es-abstract@1.17.4 deduped
| | | +-- function.prototype.name@1.1.2 deduped
| | | +-- is-regex@1.1.1
| | | | `-- has-symbols@1.0.1
| | | +-- object-is@1.1.4
| | | | +-- call-bind@1.0.0
| | | | | +-- function-bind@1.1.1 deduped
| | | | | `-- get-intrinsic@1.0.1
| | | | |   +-- function-bind@1.1.1 deduped
| | | | |   +-- has@1.0.3 deduped
| | | | |   `-- has-symbols@1.0.1
| | | | `-- define-properties@1.1.3 deduped
| | | +-- object.assign@4.1.0 deduped
| | | +-- object.entries@1.1.3
| | | | +-- call-bind@1.0.0 deduped
| | | | +-- define-properties@1.1.3 deduped
| | | | +-- es-abstract@1.18.0-next.1
| | | | | +-- es-to-primitive@1.2.1 deduped
| | | | | +-- function-bind@1.1.1 deduped
| | | | | +-- has@1.0.3 deduped
| | | | | +-- has-symbols@1.0.1 deduped
| | | | | +-- is-callable@1.2.2
| | | | | +-- is-negative-zero@2.0.1
| | | | | +-- is-regex@1.1.1 deduped
| | | | | +-- object-inspect@1.9.0
| | | | | +-- object-keys@1.1.1 deduped
| | | | | +-- object.assign@4.1.2
| | | | | | +-- call-bind@1.0.0 deduped
| | | | | | +-- define-properties@1.1.3 deduped
| | | | | | +-- has-symbols@1.0.1 deduped
| | | | | | `-- object-keys@1.1.1 deduped
| | | | | +-- string.prototype.trimend@1.0.3
| | | | | | +-- call-bind@1.0.0 deduped
| | | | | | `-- define-properties@1.1.3 deduped
| | | | | `-- string.prototype.trimstart@1.0.3
| | | | |   +-- call-bind@1.0.0 deduped
| | | | |   `-- define-properties@1.1.3 deduped
| | | | `-- has@1.0.3 deduped
| | | +-- prop-types@15.7.2 deduped
| | | +-- prop-types-exact@1.2.0
| | | | +-- has@1.0.3 deduped
| | | | +-- object.assign@4.1.0 deduped
| | | | `-- reflect.ownkeys@0.2.0
| | | `-- react-is@16.13.1
| | +-- function.prototype.name@1.1.2 deduped
| | +-- object.assign@4.1.0 deduped
| | +-- object.fromentries@2.0.2 deduped
| | +-- prop-types@15.7.2 deduped
| | `-- semver@5.7.1 deduped
| +-- enzyme-shallow-equal@1.0.1 deduped
| +-- has@1.0.3 deduped
| +-- object.assign@4.1.0 deduped
| +-- object.values@1.1.1 deduped
| +-- prop-types@15.7.2 deduped
| +-- UNMET PEER DEPENDENCY react@^16.14.0
| +-- react-is@16.13.1
| +-- react-test-renderer@16.14.0
| | +-- object-assign@4.1.1 deduped
| | +-- prop-types@15.7.2 deduped
| | +-- react-is@16.9.0 deduped
| | `-- scheduler@0.19.1
| |   +-- loose-envify@1.4.0 deduped
| |   `-- object-assign@4.1.1 deduped
| `-- semver@5.7.1 deduped

Expected behavior

Sub-dependencies to be locked to exact version or something reasonable. As to not have to worry about re-installing the exact version creating a breaking change downstream.

I believe the culprit may be sub-dep react-test-renderer getting bumped to 16.14.0, but have already spent 4+ hours debugging and have to put this task down for now.

Your environment

Mocha + JSDom + Cha-Enzyme + Enzyme

API

Version

library version
enzyme ^3.11.0
react ^16.13.0
react-dom ^16.13.0
react-test-renderer 16.13.0 OR 16.14.0 (since sub-dep are not locked)
adapter (below)

Adapter

ljharb commented 3 years ago

react-test-renderer, along with react and react-dom, generally must always match the same major and minor version. In this case, the latest v16 of react-test-renderer is supposed to work on every version of react 16, and thus enzyme.

If you explicitly dev-depend, in your app, on react-test-renderer, can you determine which versions pass and which fail? We currently have explicit tests for v16.7 and v16.8 of the renderer, with the v16 adapter, but nothing past that, since there's been no breakage reports since then.

(I'm pretty sure "locked" isn't the correct term here; if react-test-renderer started breaking anything within the v16 major, that's a bug in react-test-renderer, and either it would need to be fixed, or enzyme would need to work around it, or enzyme would need to alter the dependency range to exclude the broken versions)

PaulSearcy commented 3 years ago

If you explicitly dev-depend, in your app, on react-test-renderer, can you determine which versions pass and which fail?

I don't, it's a dependency installed by enzyme-adapter-react-16@1.15.2 according to the require section generated in package-lock.json.

"react-test-renderer": "^16.0.0-0",

Forgot to include this earlier. Here is the dev-dep and dep of the package.json.

"dependencies": {
    "@babel/runtime": "^7.8.7",
    "babel-plugin-react-css-modules": "^5.2.6",
    "content-disposition": "^0.5.2",
    "cookie-parser": "^1.4.5",
    "core-js": "^3.6.5",
    "dotenv": "^8.2.0",
    "downloadjs": "^1.4.7",
    "express": "^4.17.1",
    "fuse.js": "^3.0.5",
    "hardpass": "^0.1.4",
    "js-cookie": "^2.1.4",
    "lodash": "^4.17.15",
    "moment-timezone": "^0.5.28",
    "node-fetch": "^2.6.0",
    "papaparse": "^5.1.1",
    "pdfmake": "^0.1.65",
    "prop-types": "^15.6.0",
    "query-string": "^4.3.4",
    "react": "^16.13.0",
    "react-csv": "^1.1.2",
    "react-dom": "^16.13.0",
    "react-dropzone": "^10.2.1",
    "react-ga": "^3.1.2",
    "react-joyride": "^2.2.1",
    "react-markdown": "^3.3.0",
    "react-notification-system": "^0.2.17",
    "react-notification-system-redux": "^2.0.1",
    "react-password-strength": "^2.3.1",
    "react-redux": "^7.2.0",
    "react-router": "^5.1.2",
    "react-router-dom": "^5.1.2",
    "react-select": "^1.2.1",
    "react-svg-inline": "^2.0.0",
    "react-table": "6.7.4",
    "redux": "^4.0.5",
    "redux-thunk": "^2.3.0",
    "spin": "0.0.1",
    "stacktrace-js": "^2.0.2"
  },
  "devDependencies": {
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.8.7",
    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
    "@babel/plugin-proposal-optional-chaining": "^7.8.3",
    "@babel/plugin-transform-classes": "^7.8.6",
    "@babel/plugin-transform-runtime": "^7.8.3",
    "@babel/preset-env": "^7.10.4",
    "@babel/preset-react": "^7.8.3",
    "@babel/register": "^7.8.6",
    "@speechanddebate/eslint-config-nsda": "^1.0.5",
    "@testing-library/react": "^10.0.1",
    "autoprefixer": "^6.5.3",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^8.0.6",
    "babel-plugin-add-module-exports": "^0.2.1",
    "babel-plugin-module-resolver": "^4.0.0",
    "babel-preset-react-optimize": "^1.0.1",
    "chai": "*",
    "chai-enzyme": "^1.0.0-beta.1",
    "chromedriver": "^78.0.1",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.4.2",
    "dotenv-webpack": "^1.7.0",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.2",
    "eslint": "^6.8.0",
    "eslint-import-resolver-babel-module": "^5.1.2",
    "eslint-plugin-import": "^2.20.1",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-react": "^7.19.0",
    "geckodriver": "^1.19.1",
    "html-webpack-plugin": "^4.0.3",
    "jsdom": "^16.2.1",
    "jsdom-global": "^3.0.2",
    "mini-css-extract-plugin": "^0.9.0",
    "mocha": "^8.0.1",
    "mock-css-modules": "^1.0.0",
    "nightwatch": "^1.3.4",
    "nock": "^11.9.1",
    "node-localstorage": "^1.3.1",
    "nyc": "^14.1.1",
    "react-hot-loader": "^4.12.19",
    "react-router-test-context": "^0.1.0",
    "redux-devtools": "^3.5.0",
    "redux-mock-store": "^1.5.4",
    "require-hacker": "^3.0.0",
    "sinon": "^9.0.1",
    "style-loader": "^1.1.3",
    "stylelint": "^12.0.1",
    "stylelint-config-standard": "^16.0.0",
    "svg-inline-loader": "^0.8.2",
    "url-loader": "^2.3.0",
    "webpack": "^4.42.0",
    "webpack-bundle-analyzer": "^3.6.1",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  },

Correct me if I'm wrong, as this is the first time doing a deep dive into module resolution, but doesn't the '^' mean that the minor or patch be resolved to the latest of either when installing?

In that for this project I'm working on when the adapter was installed react-test-renderer resolved to 16.3.0 which was the latest minor version at the time.
Now when I install the adapter again at the specific version react-test-renderer resolves to 16.4.0 since that is the latest minor version now.

I'm not 100% sure that react-test-renderer is the sub-dependency that is the exact cause. Since multiple dependencies of enzyme-adapter-react-16@1.15.2 have a range of minor or patch versions they can land on depending on when npm i enzyme-adapter-react-16@1.15.2 was run.

What this means for me/project is that I'll have to make a note in the project to never re-install or update the adapter. Since our tests now rely on the library not only on the version but a version resolved at a specific date in the past.

By "locked" I mean a library and it's dependencies (sub-dependencies of the project) written into package-lock.json. So that future installs will get that exact dependency tree rather then going the npm resolution algorithm again and potentially producing a different tree. (If any of the sub dependencies have changed between when one dev npm i and the second npm i)

When you npm i library@version you release that "lock" on that specific library and it re-installs that library and goes through the resolution algorithm. This is different then just npm i at the root level of a project.

You probably already know this, but wanted to give context while I clarified.

ljharb commented 3 years ago

@PaulSearcy i'm asking you to add an explicit devDep on react-test-renderer, and by doing so, figure out the true cause of the issue.

You should be able to update the adapter any time you like. No package "locks" it's transitive dependencies; lockfiles are only ever for a top-level app.

In this specific case, with react-dom and react both at v16.13, and react-test-renderer at the latest, if something's broken, i'd suspect your test is broken, not react or enzyme. If I can get a reproducible test case, though, I'd be more than happy to look into it.

ljharb commented 3 years ago

@PaulSearcy were you able to figure this out?