Skip to content

Commit

Permalink
perf: cc duplicates
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Nov 23, 2020
1 parent a2b98db commit 8553dcb
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 220 deletions.
4 changes: 2 additions & 2 deletions lib/common/decorate.inputs.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Input } from '@angular/core';

import { Type } from './core.types';
import { AnyType } from './core.types';

// Looks like an A9 bug, that queries from @Component aren't processed.
// Also we have to pass prototype, not the class.
// The same issue happens with outputs, but time to time
// (when I restart tests with refreshing browser manually).
// https://github.com/ike18t/ng-mocks/issues/109
export default function (cls: Type<any>, inputs?: string[], exclude?: string[]) {
export default function (cls: AnyType<any>, inputs?: string[], exclude?: string[]) {
/* istanbul ignore else */
if (inputs) {
for (const input of inputs) {
Expand Down
4 changes: 2 additions & 2 deletions lib/common/decorate.outputs.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Output } from '@angular/core';

import { Type } from './core.types';
import { AnyType } from './core.types';

// Looks like an A9 bug, that queries from @Component aren't processed.
// Also we have to pass prototype, not the class.
// The same issue happens with outputs, but time to time
// (when I restart tests with refreshing browser manually).
// https://github.com/ike18t/ng-mocks/issues/109
export default function (cls: Type<any>, outputs?: string[]) {
export default function (cls: AnyType<any>, outputs?: string[]) {
/* istanbul ignore else */
if (outputs) {
for (const output of outputs) {
Expand Down
4 changes: 2 additions & 2 deletions lib/common/decorate.queries.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ContentChild, ContentChildren, Query, ViewChild, ViewChildren } from '@angular/core';

import { Type } from './core.types';
import { AnyType } from './core.types';

// Looks like an A9 bug, that queries from @Component aren't processed.
// Also we have to pass prototype, not the class.
// The same issue happens with outputs, but time to time
// (when I restart tests with refreshing browser manually).
// https://github.com/ike18t/ng-mocks/issues/109
export default function (cls: Type<any>, queries?: { [key: string]: Query }) {
export default function (cls: AnyType<any>, queries?: { [key: string]: Query }) {
/* istanbul ignore else */
if (queries) {
for (const key of Object.keys(queries)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/common/mock-of.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* tslint:disable variable-name */

import { Type } from './core.types';
import { AnyType } from './core.types';
import { ngMocksMockConfig } from './mock';

// This helps with debugging in the browser. Decorating mock classes with this
Expand All @@ -9,7 +9,7 @@ import { ngMocksMockConfig } from './mock';
// by name (which will now include the original class' name.
// Additionally, if we set breakpoints, we can inspect the actual class being
// replaced with a mock copy by looking into the 'mockOf' property on the class.
export const MockOf = (mockClass: Type<any>, config?: ngMocksMockConfig) => (constructor: Type<any>) => {
export const MockOf = (mockClass: AnyType<any>, config?: ngMocksMockConfig) => (constructor: AnyType<any>) => {
Object.defineProperties(constructor, {
mockOf: { value: mockClass },
name: { value: `MockOf${mockClass.name}` },
Expand Down
31 changes: 5 additions & 26 deletions lib/mock-builder/mock-builder-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { MockService } from '../mock-service/mock-service';

import extractDep from './mock-builder-promise.extract-dep';
import skipDep from './mock-builder-promise.skip-dep';
import { MockBuilderStash } from './mock-builder-stash';
import { IMockBuilder, IMockBuilderConfig, IMockBuilderResult } from './types';

const defaultMock = {}; // simulating Symbol
Expand All @@ -35,6 +36,7 @@ export class MockBuilderPromise implements IMockBuilder {
protected mockDef: Set<Type<any> | InjectionToken<any>> = new Set();
protected providerDef: Map<Type<any> | InjectionToken<any>, Provider> = new Map();
protected replaceDef: Set<Type<any> | InjectionToken<any>> = new Set();
protected stash: MockBuilderStash = new MockBuilderStash();

public beforeCompileComponents(callback: (testBed: typeof TestBed) => void): this {
this.beforeCC.add(callback);
Expand All @@ -43,30 +45,9 @@ export class MockBuilderPromise implements IMockBuilder {
}

public build(): NgModule {
const backup = {
builtDeclarations: ngMocksUniverse.builtDeclarations,
builtProviders: ngMocksUniverse.builtProviders,
cacheDeclarations: ngMocksUniverse.cacheDeclarations,
cacheProviders: ngMocksUniverse.cacheProviders,
config: ngMocksUniverse.config,
flags: ngMocksUniverse.flags,
touches: ngMocksUniverse.touches,
};
this.stash.backup();
ngMocksUniverse.flags.add('cachePipe');

ngMocksUniverse.builtDeclarations = new Map();
ngMocksUniverse.builtProviders = new Map();
ngMocksUniverse.cacheDeclarations = new Map();
ngMocksUniverse.cacheProviders = new Map();
ngMocksUniverse.config = new Map();
ngMocksUniverse.flags = new Set([
'cacheComponent',
'cacheDirective',
'cacheModule',
'cachePipe',
'cacheProvider',
'correctModuleExports',
]);
ngMocksUniverse.touches = new Set();
ngMocksUniverse.config.set('multi', new Set()); // collecting multi flags of providers.
ngMocksUniverse.config.set('deps', new Set()); // collecting all deps of providers.
ngMocksUniverse.config.set('depsSkip', new Set()); // collecting all declarations of kept modules.
Expand Down Expand Up @@ -387,9 +368,7 @@ export class MockBuilderPromise implements IMockBuilder {
overrides.set(value, override);
}

for (const key of Object.keys(backup)) {
ngMocksUniverse[key] = (backup as any)[key];
}
this.stash.restore();

return {
declarations,
Expand Down
32 changes: 32 additions & 0 deletions lib/mock-builder/mock-builder-stash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import config from '../common/core.config';
import ngMocksUniverse from '../common/ng-mocks-universe';

export class MockBuilderStash {
protected data: Record<keyof any, any> = {};

public backup(): void {
this.data = {
builtDeclarations: ngMocksUniverse.builtDeclarations,
builtProviders: ngMocksUniverse.builtProviders,
cacheDeclarations: ngMocksUniverse.cacheDeclarations,
cacheProviders: ngMocksUniverse.cacheProviders,
config: ngMocksUniverse.config,
flags: ngMocksUniverse.flags,
touches: ngMocksUniverse.touches,
};

ngMocksUniverse.builtDeclarations = new Map();
ngMocksUniverse.builtProviders = new Map();
ngMocksUniverse.cacheDeclarations = new Map();
ngMocksUniverse.cacheProviders = new Map();
ngMocksUniverse.config = new Map();
ngMocksUniverse.flags = new Set(config.flags);
ngMocksUniverse.touches = new Set();
}

public restore(): void {
for (const key of Object.keys(this.data)) {
ngMocksUniverse[key] = (this.data as any)[key];
}
}
}
95 changes: 15 additions & 80 deletions lib/mock-component/mock-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,20 @@ import {
AfterContentInit,
ChangeDetectorRef,
Component,
forwardRef,
Injector,
Provider,
Query,
TemplateRef,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { getTestBed } from '@angular/core/testing';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';

import { flatten } from '../common/core.helpers';
import { directiveResolver } from '../common/core.reflect';
import { Type } from '../common/core.types';
import decorateInputs from '../common/decorate.inputs';
import decorateOutputs from '../common/decorate.outputs';
import decorateQueries from '../common/decorate.queries';
import { getMockedNgDefOf } from '../common/func.get-mocked-ng-def-of';
import { MockControlValueAccessor } from '../common/mock-control-value-accessor';
import { MockOf } from '../common/mock-of';
import ngMocksUniverse from '../common/ng-mocks-universe';
import mockServiceHelper from '../mock-service/helper';
import decorateDeclaration from '../mock/decorate-declaration';

import { MockedComponent } from './types';

Expand Down Expand Up @@ -89,73 +81,8 @@ export function MockComponent<TComponent>(component: Type<TComponent>): Type<Moc
}
}

const options: Component = {
exportAs,
providers: [
{
provide: component,
useExisting: (() => {
const value: Type<any> & { __ngMocksSkip?: boolean } = forwardRef(() => ComponentMock);
value.__ngMocksSkip = true;

return value;
})(),
},
],
selector,
template,
};

const resolutions = new Map();
const resolveProvider = (def: Provider) => mockServiceHelper.resolveProvider(def, resolutions);

let setNgValueAccessor: undefined | boolean;
for (const providerDef of flatten(providers || [])) {
const provide =
providerDef && typeof providerDef === 'object' && providerDef.provide ? providerDef.provide : providerDef;
if (options.providers && provide === NG_VALIDATORS) {
options.providers.push({
multi: true,
provide,
useExisting: (() => {
const value: Type<any> & { __ngMocksSkip?: boolean } = forwardRef(() => ComponentMock);
value.__ngMocksSkip = true;

return value;
})(),
});
continue;
}
if (setNgValueAccessor === undefined && options.providers && provide === NG_VALUE_ACCESSOR) {
setNgValueAccessor = false;
options.providers.push({
multi: true,
provide,
useExisting: (() => {
const value: Type<any> & { __ngMocksSkip?: boolean } = forwardRef(() => ComponentMock);
value.__ngMocksSkip = true;

return value;
})(),
});
continue;
}

const mock = resolveProvider(providerDef);
/* istanbul ignore else */
if (options.providers && mock) {
options.providers.push(mock);
}
}
if (setNgValueAccessor === undefined) {
setNgValueAccessor =
mockServiceHelper.extractMethodsFromPrototype(component.prototype).indexOf('writeValue') !== -1;
}

const config = ngMocksUniverse.config.get(component);

@Component(options)
@MockOf(component, { outputs, setNgValueAccessor })
class ComponentMock extends MockControlValueAccessor implements AfterContentInit {
/* istanbul ignore next */
public constructor(changeDetector: ChangeDetectorRef, injector: Injector) {
Expand Down Expand Up @@ -208,13 +135,21 @@ export function MockComponent<TComponent>(component: Type<TComponent>): Type<Moc
};
}
}
(ComponentMock as any).parameters = [ChangeDetectorRef, Injector];

/* istanbul ignore else */
if (queries) {
decorateInputs(ComponentMock, inputs, Object.keys(queries));
}
decorateOutputs(ComponentMock, outputs);
decorateQueries(ComponentMock, queries);
const mockMeta = {
inputs,
outputs,
providers,
queries,
};
const mockParams = {
exportAs,
selector,
template,
};
const options = decorateDeclaration(component, ComponentMock, mockMeta, mockParams);
Component(options)(ComponentMock);

/* istanbul ignore else */
if (ngMocksUniverse.flags.has('cacheComponent')) {
Expand Down
Loading

0 comments on commit 8553dcb

Please sign in to comment.