Skip to content

Commit

Permalink
feat(MockRender): supports Self providers help-me-mom#3053
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Jul 13, 2022
1 parent 6ba1d9e commit ace0b95
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 21 deletions.
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 MockRenderDirectives {}
Directive({
selector,
providers: options.providers,
})(MockRenderDirectives);

return MockRenderDirectives;
};

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
43 changes: 24 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,32 @@ 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 declarations = testBed._compiler?.declarations || testBed.declarations || testBed._declarations;
if (!declarations || declarations.indexOf(ctor) === -1) {
flushTestBed(options);
try {
const declarations: Array<AnyType<any>> = [ctor];
if (ctor.providers) {
declarations.push(ctor.providers);
}
TestBed.configureTestingModule({
declarations,
});
} catch (error) {
handleFixtureError(error);
}
}
}
};
};

const generateFactory = (
componentCtor: Type<any> & { tpl?: string },
Expand Down
78 changes: 78 additions & 0 deletions tests/issue-3053/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
Component,
Directive,
Injectable,
Self,
} from '@angular/core';

import { MockBuilder, MockProvider, MockRender } 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: `{{ service.echo() }}`,
})
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/,
);
});
});

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

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

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

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

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

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

0 comments on commit ace0b95

Please sign in to comment.