-
Notifications
You must be signed in to change notification settings - Fork 310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add automatic mock for angular entities (#2908) #2953
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,7 +111,7 @@ | |
"github-files-fetcher": "^1.6.0", | ||
"glob": "^10.4.5", | ||
"husky": "^9.1.7", | ||
"jest": "^29.7.0", | ||
"jest": "^30.0.0-alpha.7", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is only available in Jest 30, would you pls change merge target branch of this repo to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides, there are other Jest deps which need to be bumped together too. I think making a separate commit or PR to support Jest 30 first is necessary before this change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I described it in the TODO section. |
||
"jsdom": "^26.0.0", | ||
"pinst": "^3.0.0", | ||
"prettier": "^2.8.8", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
const { | ||
ɵɵdefineComponent, | ||
ɵɵdefineInjectable, | ||
ɵɵdefineDirective, | ||
ɵɵdefinePipe, | ||
ɵɵdefineNgModule, | ||
} = require('@angular/core'); | ||
|
||
const cache = new Map(); | ||
|
||
function stubInjectable(target, { providedIn }) { | ||
target.ɵprov = ɵɵdefineInjectable({ | ||
token: target, | ||
providedIn, | ||
factory: () => new target(), | ||
}); | ||
} | ||
|
||
function stubComponent(target, actual) { | ||
const { selectors, exportAs, standalone, signals, ngContentSelectors, changeDetection } = actual; | ||
|
||
cache.set( | ||
actual, | ||
(target.ɵcmp = | ||
cache.get(actual) ?? | ||
ɵɵdefineComponent({ | ||
type: target, | ||
selectors, | ||
inputs: {}, | ||
outputs: {}, | ||
exportAs, | ||
standalone, | ||
signals, | ||
decls: 0, | ||
vars: 0, | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
template: () => {}, | ||
ngContentSelectors, | ||
changeDetection, | ||
})) | ||
); | ||
} | ||
|
||
function stubDirective(target, { selectors, exportAs, standalone, signals }) { | ||
target.ɵdir = ɵɵdefineDirective({ | ||
type: target, | ||
selectors, | ||
inputs: {}, | ||
outputs: {}, | ||
exportAs, | ||
standalone, | ||
signals, | ||
}); | ||
} | ||
|
||
function stubPipe(target, { name, pure, standalone }) { | ||
target.ɵpipe = ɵɵdefinePipe({ | ||
name, | ||
type: target, | ||
pure, | ||
standalone, | ||
}); | ||
} | ||
|
||
jest.onGenerateMock((modulePath, moduleMock) => { | ||
const moduleActual = jest.requireActual(modulePath); | ||
|
||
function* walk(obj, walkedNodes = [], path = []) { | ||
if (!obj || (typeof obj !== 'function' && typeof obj !== 'object') || walkedNodes.includes(obj)) { | ||
return; | ||
} | ||
|
||
for (const key of Object.getOwnPropertyNames(obj)) { | ||
if (typeof key === 'string' && key.startsWith('ɵ')) { | ||
const pathFunction = (root) => { | ||
return path.reduce((acc, k) => acc?.[k], root); | ||
}; | ||
|
||
yield [key, pathFunction]; | ||
|
||
continue; | ||
} | ||
|
||
yield* walk(obj[key], [...walkedNodes, obj], [...path, key]); | ||
} | ||
} | ||
|
||
function stubRecursive(mock, actual) { | ||
for (const [key, getParent] of walk(actual)) { | ||
const parentMock = getParent(mock); | ||
const parentActual = getParent(actual); | ||
const valueActual = parentActual[key]; | ||
|
||
if (!parentMock || !valueActual) { | ||
continue; | ||
} | ||
|
||
switch (key) { | ||
case `ɵfac`: { | ||
parentMock[key] = () => new parentMock(); | ||
break; | ||
} | ||
case `ɵprov`: { | ||
stubInjectable(parentMock, valueActual); | ||
break; | ||
} | ||
case `ɵcmp`: { | ||
stubComponent(parentMock, valueActual); | ||
break; | ||
} | ||
case `ɵdir`: { | ||
stubDirective(parentMock, valueActual); | ||
break; | ||
} | ||
case `ɵpipe`: { | ||
stubPipe(parentMock, valueActual); | ||
break; | ||
} | ||
case `ɵmod`: { | ||
parentMock[key] = ɵɵdefineNgModule({ | ||
type: parentMock, | ||
imports: valueActual.imports.map((actual) => { | ||
const mock = jest.fn(); | ||
Check failure on line 123 in setup-env/mock-transformer.js
|
||
|
||
stubRecursive(mock, actual); | ||
|
||
return mock; | ||
}), | ||
exports: valueActual.exports.map((actual) => { | ||
const mock = jest.fn(); | ||
Check failure on line 130 in setup-env/mock-transformer.js
|
||
|
||
stubRecursive(mock, actual); | ||
|
||
return mock; | ||
}), | ||
declarations: valueActual.declarations.map((actual) => { | ||
const mock = jest.fn(); | ||
|
||
stubRecursive(mock, actual); | ||
|
||
return mock; | ||
}), | ||
}); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
stubRecursive(moduleMock, moduleActual); | ||
|
||
return moduleMock; | ||
}); |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,5 +1,6 @@ | ||||
require('zone.js'); | ||||
require('zone.js/testing'); | ||||
require('../mock-transformer'); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not everyone would want this enabled by default, we should provide a flag to enable it via options provided in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides, this file is only for testbed setup, we shouldn’t add mock here. It’s better that we provide a utility function which can be imported in a Jest setup file which is owned by the users. For example
Or any names which fit the purpose :) |
||||
|
||||
const { getTestBed } = require('@angular/core/testing'); | ||||
const { | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
require('../mock-transformer'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same comment like we have in |
||
|
||
const { provideExperimentalZonelessChangeDetection, NgModule, ErrorHandler } = require('@angular/core'); | ||
const { getTestBed } = require('@angular/core/testing'); | ||
const { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you pls revert this?