test_runner: add exports option to mock.module#61727
test_runner: add exports option to mock.module#61727Han5991 wants to merge 3 commits intonodejs:mainfrom
Conversation
|
Review requested:
|
3d95c0c to
fa99ada
Compare
|
Related: #58443 (I haven't read through this PR yet though) |
I was working on it with that in mind. |
73c4cf1 to
715c12c
Compare
|
Yes, sounds good go me! It would be ideal (but not required or anything) to land this with a userland migration ready to go. I think it could be done fairly easily. Cc @Ceres6 this should make your Jest β node:test migration easier π |
|
Thanks for the feedback. I will scope this PR to Step 2 of the plan: introduce |
Codecov Reportβ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #61727 +/- ##
==========================================
- Coverage 89.73% 89.72% -0.01%
==========================================
Files 675 676 +1
Lines 204525 206034 +1509
Branches 39310 39499 +189
==========================================
+ Hits 183529 184865 +1336
- Misses 13287 13302 +15
- Partials 7709 7867 +158
π New features to boost your workflow:
|
715c12c to
424675e
Compare
Add options.exports support in mock.module() and normalize option shapes through a shared exports path. Keep defaultExport and namedExports as aliases, emit runtime deprecation warnings for legacy options, and update docs and tests, including output fixtures and coverage snapshots. Refs: nodejs#58443
424675e to
0498de3
Compare
| if ('exports' in options) { | ||
| validateObject(options.exports, 'options.exports'); | ||
| copyOwnProperties(options.exports, moduleExports); | ||
| } |
There was a problem hiding this comment.
Since last-wins, I think the "new" should win (and be moved below namedExports and defaultExport copying).
Or maybe when exports exists, the others should be ignored? I feel like there's some kind of back-version compatibility thing here. @ljharb thoughts?
| copyOwnProperties(options.namedExports, moduleExports); | ||
| } | ||
|
|
||
| if ('defaultExport' in options) { | ||
| emitLegacyMockOptionWarning('defaultExport'); | ||
| moduleExports.default = options.defaultExport; |
There was a problem hiding this comment.
namedExports copies the descriptors, but defaultExport does direct assignment. Why the difference?
There was a problem hiding this comment.
The difference is intentional β namedExports copies descriptors of the properties of the provided object (so getters/setters are preserved per export slot), while defaultExport receives the value itself, not an
object whose property structure matters. Copying ObjectGetOwnPropertyDescriptor(options, 'defaultExport') is semantically equivalent to direct assignment in practice. That said, if you'd prefer we align the two
for visual consistency, happy to apply ObjectDefineProperty for defaultExport as well.
There was a problem hiding this comment.
I think that's only true for the original module. MockModuleOptions.defaultExport could be a getter:
mock.module('β¦', {
get defaultExport() { return mock.fn() },
});But I see you already made the switch to ObjectDefineProperty + ObjectGetOwnPropertyDescriptor.
Could you please add a test-case to cover this scenario so nobody comes along in future and unwittingly breaks that?
Rename the named export in the mock.module() example from `fn` to `foo` for improved readability.
- Unify internal export representation into a single `moduleExports` object, removing the split into `defaultExport`, `hasDefaultExport`, and `namedExports`. - DRY up `emitLegacyMockOptionWarning` with a lookup map instead of a switch statement with separate flag variables. - Use `ObjectDefineProperty` for `defaultExport` option to be consistent with descriptor copying in `namedExports`.
JakobJingleheimer
left a comment
There was a problem hiding this comment.
I think this is looking pretty good :)
| copyOwnProperties(options.namedExports, moduleExports); | ||
| } | ||
|
|
||
| if ('defaultExport' in options) { | ||
| emitLegacyMockOptionWarning('defaultExport'); | ||
| moduleExports.default = options.defaultExport; |
There was a problem hiding this comment.
I think that's only true for the original module. MockModuleOptions.defaultExport could be a getter:
mock.module('β¦', {
get defaultExport() { return mock.fn() },
});But I see you already made the switch to ObjectDefineProperty + ObjectGetOwnPropertyDescriptor.
Could you please add a test-case to cover this scenario so nobody comes along in future and unwittingly breaks that?
| This option is deprecated and will be removed in a future major release. | ||
| Prefer `options.exports.default`. |
There was a problem hiding this comment.
Do we need to wait for a future major release? the feature is experimental and may not follow Semver?
There was a problem hiding this comment.
True. This could probably be more vague, like "a later version".
| function emitLegacyMockOptionWarning(option) { | ||
| if (warnedLegacyOptions[option]) return; | ||
| warnedLegacyOptions[option] = true; | ||
| process.emitWarning( | ||
| `mock.module(): options.${option} is deprecated. ` + | ||
| `Use ${kLegacyOptionReplacements[option]} instead.`, | ||
| 'DeprecationWarning', | ||
| ); | ||
| } |
There was a problem hiding this comment.
lib/internal/util provides a deprecateProperty utility. Can't we use that?
Summary
mock.module(..., { exports })and normalize option shapes through a shared exports pathdefaultExportandnamedExportsas aliases, and emit runtimeDeprecationWarningwhen legacy options are usedLegacy Option Mixing Behavior
options.exportswith legacy options is currently allowed.Scope
defaultExport+namedExportsintoexportsΒ #58443 plan.Testing
make lint-jspython3 tools/test.py test/parallel/test-runner-module-mocking.jspython3 tools/test.py test/test-runner/test-output-coverage-with-mock.mjs test/test-runner/test-output-typescript-coverage.mjsRefs: #58443