Skip to content

Commit

Permalink
feat(MockRender): supports Self providers #3053
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Jul 14, 2022
1 parent 105fbc1 commit c455e25
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 25 deletions.
2 changes: 1 addition & 1 deletion e2e/a10/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:jasmine:es2015:ivy": "ng test --ts-config ./tsconfig.es2015ivy.spec.json --progress=false",
"test:jasmine:es2015:no-ivy": "ng test --ts-config ./tsconfig.es2015noivy.spec.json --progress=false",
"test:jasmine:debug": "ng test -- --watch --browsers Chrome",
"test:jest": "npm run test:jest:es5:ivy && npm run test:jest:es5:no-ivy &&npm run test:jest:es2015:ivy &&npm run test:jest:es2015:no-ivy",
"test:jest": "npm run test:jest:es5:ivy && npm run test:jest:es5:no-ivy && npm run test:jest:es2015:ivy && npm run test:jest:es2015:no-ivy",
"test:jest:es5:ivy": "jest -i --config jest.es5ivy.js",
"test:jest:es5:no-ivy": "jest -i --config jest.es5noivy.js",
"test:jest:es2015:ivy": "jest -i --config jest.es2015ivy.js",
Expand Down
2 changes: 1 addition & 1 deletion e2e/a11/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:jasmine:es2015:ivy": "ng test --ts-config ./tsconfig.es2015ivy.spec.json --progress=false",
"test:jasmine:es2015:no-ivy": "ng test --ts-config ./tsconfig.es2015noivy.spec.json --progress=false",
"test:jasmine:debug": "ng test -- --watch --browsers Chrome",
"test:jest": "npm run test:jest:es5:ivy && npm run test:jest:es5:no-ivy &&npm run test:jest:es2015:ivy &&npm run test:jest:es2015:no-ivy",
"test:jest": "npm run test:jest:es5:ivy && npm run test:jest:es5:no-ivy && npm run test:jest:es2015:ivy && npm run test:jest:es2015:no-ivy",
"test:jest:es5:ivy": "jest -i --config jest.es5ivy.js",
"test:jest:es5:no-ivy": "jest -i --config jest.es5noivy.js",
"test:jest:es2015:ivy": "jest -i --config jest.es2015ivy.js",
Expand Down
2 changes: 1 addition & 1 deletion e2e/a9/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:jasmine:es2015:ivy": "ng test --ts-config ./tsconfig.es2015ivy.spec.json --progress=false",
"test:jasmine:es2015:no-ivy": "ng test --ts-config ./tsconfig.es2015noivy.spec.json --progress=false",
"test:jasmine:debug": "ng test -- --watch --browsers Chrome",
"test:jest": "npm run test:jest:es5:ivy && npm run test:jest:es5:no-ivy &&npm run test:jest:es2015:ivy &&npm run test:jest:es2015:no-ivy",
"test:jest": "npm run test:jest:es5:ivy && npm run test:jest:es5:no-ivy && npm run test:jest:es2015:ivy && npm run test:jest:es2015:no-ivy",
"test:jest:es5:ivy": "jest -i --config jest.es5ivy.js",
"test:jest:es5:no-ivy": "jest -i --config jest.es5noivy.js",
"test:jest:es2015:ivy": "jest -i --config jest.es2015ivy.js",
Expand Down
20 changes: 18 additions & 2 deletions libs/ng-mocks/src/lib/mock-render/func.create-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const generateWrapperOutput =
instance[prop] = event;
};

const generateWrapper = ({ bindings, options, inputs }: any) => {
const generateWrapperComponent = ({ bindings, options, inputs }: any) => {
class MockRenderComponent {
public constructor() {
coreDefineProperty(this, '__ngMocksOutput', generateWrapperOutput(this));
Expand All @@ -45,6 +45,16 @@ const generateWrapper = ({ bindings, options, inputs }: any) => {
return MockRenderComponent;
};

const generateWrapperDirective = ({ selector, options }: any) => {
class MockRenderDirective {}
Directive({
selector,
providers: options.providers,
})(MockRenderDirective);

return MockRenderDirective;
};

const getCache = () => {
const caches: Array<Type<any> & Record<'cacheKey', any[]>> = ngMocksUniverse.config.get('MockRenderCaches') ?? [];
if (caches.length === 0) {
Expand Down Expand Up @@ -102,9 +112,15 @@ export default (
viewProviders: flags.viewProviders,
};

ctor = generateWrapper({ ...meta, bindings, options });
ctor = generateWrapperComponent({ ...meta, bindings, options });
coreDefineProperty(ctor, 'cacheKey', cacheKey);
coreDefineProperty(ctor, 'tpl', mockTemplate);

if (meta.selector && options.providers) {
const dir = generateWrapperDirective({ ...meta, bindings, options });
coreDefineProperty(ctor, 'providers', dir);
}

caches.unshift(ctor as any);
caches.splice(ngMocksUniverse.global.get('mockRenderCacheSize') ?? coreConfig.mockRenderCacheSize);

Expand Down
44 changes: 25 additions & 19 deletions libs/ng-mocks/src/lib/mock-render/mock-render-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,27 +110,33 @@ const flushTestBed = (flags: Record<string, any>): void => {
}
};

const generateFactoryInstall = (ctor: AnyType<any>, options: IMockRenderFactoryOptions) => () => {
const testBed: TestBed & {
_compiler?: {
const generateFactoryInstall =
(ctor: AnyType<any> & { providers?: AnyType<any> }, options: IMockRenderFactoryOptions) => () => {
const testBed: TestBed & {
_compiler?: {
declarations?: Array<AnyType<any>>;
};
_declarations?: Array<AnyType<any>>;
declarations?: Array<AnyType<any>>;
};
_declarations?: Array<AnyType<any>>;
declarations?: Array<AnyType<any>>;
} = getTestBed();
// istanbul ignore next
const declarations = testBed._compiler?.declarations || testBed.declarations || testBed._declarations;
if (!declarations || declarations.indexOf(ctor) === -1) {
flushTestBed(options);
try {
TestBed.configureTestingModule({
declarations: [ctor],
});
} catch (error) {
handleFixtureError(error);
} = getTestBed();
// istanbul ignore next
const existing = testBed._compiler?.declarations || testBed.declarations || testBed._declarations;
if (!existing || existing.indexOf(ctor) === -1) {
flushTestBed(options);
try {
const declarations: Array<AnyType<any>> = [];
if (ctor.providers) {
declarations.push(ctor.providers);
}
declarations.push(ctor);
TestBed.configureTestingModule({
declarations,
});
} catch (error) {
handleFixtureError(error);
}
}
}
};
};

const generateFactory = (
componentCtor: Type<any> & { tpl?: string },
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"s:test:jest": "node --version",
"s:test:min": "node --version",
"s:test:nx": "P=e2e/nx/apps/a-nx/src/test && rm -Rf $P && mkdir -p $P && cp -R tests $P && cp -R examples $P",
"test:e2e": "npm run test:a5 && npm run test:a6 && npm run test:a7 &&npm run test:a8 && npm run test:a9 && npm run test:a10 && npm run test:a11 && npm run test:a12 && npm run test:a13 && npm run test:a14 && npm run test:jasmine && npm run test:jest && npm run test:min && npm run test:nx",
"test:e2e": "npm run test:a5 && npm run test:a6 && npm run test:a7 && npm run test:a8 && npm run test:a9 && npm run test:a10 && npm run test:a11 && npm run test:a12 && npm run test:a13 && npm run test:a14 && npm run test:jasmine && npm run test:jest && npm run test:min && npm run test:nx",
"test:a5": "npm run test:a5es5 && npm run test:a5es2015",
"test:a5es5": "cd e2e/a5es5 && npm run test",
"test:a5es2015": "cd e2e/a5es2015 && npm run test",
Expand Down
96 changes: 96 additions & 0 deletions tests/issue-3053/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
Component,
Directive,
Injectable,
Self,
VERSION,
} from '@angular/core';

import {
MockBuilder,
MockProvider,
MockRender,
ngMocks,
} from 'ng-mocks';

@Injectable()
class TargetService {
echo() {
return this.constructor.name;
}
}

@Directive({
selector: 'target',
})
class TargetDirective {
constructor(@Self() public service: TargetService) {}
}

@Component({
selector: 'target',
template: ``,
})
class TargetComponent {
constructor(@Self() public service: TargetService) {}
}

// @see https://github.com/help-me-mom/ng-mocks/issues/3053
// MockRender should create a directive which provides the desired services on @Self level.
describe('issue-3053', () => {
describe('Directive:default', () => {
beforeEach(() => MockBuilder(TargetDirective, TargetService));

it('throws because of missing service', () => {
expect(() => MockRender(TargetDirective)).toThrowError(
/No provider for TargetService|NOT_FOUND \[TargetService]/,
);
});
});

describe('Directive:providers', () => {
beforeEach(() => MockBuilder(TargetDirective, TargetService));

it('renders with self provider', () => {
expect(() =>
MockRender(TargetDirective, null, {
providers: [MockProvider(TargetService)],
}),
).not.toThrow();

const target = ngMocks.findInstance(TargetDirective);
expect(target.service).toBeDefined();
});
});

if (Number.parseInt(VERSION.major, 10) <= 12) {
// Before Angular 13, directives are injected after components.
// This breaks dependency tree, therefore we should skip those tests.
return;
}

describe('Component:default', () => {
beforeEach(() => MockBuilder(TargetComponent, TargetService));

it('throws because of missing service', () => {
expect(() => MockRender(TargetComponent)).toThrowError(
/No provider for TargetService|NOT_FOUND \[TargetService]/,
);
});
});

describe('Component:providers', () => {
beforeEach(() => MockBuilder(TargetComponent, TargetService));

it('renders with self provider', () => {
expect(() =>
MockRender(TargetComponent, null, {
providers: [MockProvider(TargetService)],
}),
).not.toThrow();

const target = ngMocks.findInstance(TargetComponent);
expect(target.service).toBeDefined();
});
});
});

0 comments on commit c455e25

Please sign in to comment.