Skip to content

Commit 411842c

Browse files
committed
fix(core): ignoring host bindings in mocks #1427
1 parent c01d591 commit 411842c

File tree

5 files changed

+107
-47
lines changed

5 files changed

+107
-47
lines changed

karma.conf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export default (config: Config) => {
102102
reporters: ['dots', ...(process.env.WITH_COVERAGE === undefined ? [] : ['junit', 'coverage-istanbul'])],
103103
singleRun: true,
104104
webpack: {
105-
devtool: 'inline-source-map',
105+
devtool: 'eval-source-map',
106106
module: {
107107
rules: [
108108
...(process.env.WITH_COVERAGE === undefined

libs/ng-mocks/src/lib/mock/decorate-declaration.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// tslint:disable cyclomatic-complexity
22

3-
import { Component, Directive, HostBinding, HostListener, Provider, ViewChild } from '@angular/core';
3+
import { Component, Directive, Provider, ViewChild } from '@angular/core';
44

55
import { AnyType } from '../common/core.types';
66
import decorateInputs from '../common/decorate.inputs';
@@ -65,16 +65,18 @@ export default <T extends Component | Directive>(
6565
config.queryScanKeys = decorateQueries(mock, meta.queries);
6666

6767
config.hostBindings = [];
68-
for (const [key, ...args] of meta.hostBindings || []) {
69-
HostBinding(...args)(mock.prototype, key);
68+
for (const [key] of meta.hostBindings || []) {
69+
// mock declarations should not have side effects based on host bindings.
70+
// HostBinding(...args)(mock.prototype, key);
7071
if (config.hostBindings.indexOf(key) === -1) {
7172
config.hostBindings.push(key);
7273
}
7374
}
7475

7576
config.hostListeners = [];
76-
for (const [key, ...args] of meta.hostListeners || []) {
77-
HostListener(...args)(mock.prototype, key);
77+
for (const [key] of meta.hostListeners || []) {
78+
// mock declarations should not have side effects based on host bindings.
79+
// HostListener(...args)(mock.prototype, key);
7880
if (config.hostListeners.indexOf(key) === -1) {
7981
config.hostListeners.push(key);
8082
}

e2e/a13/src/e2e/double-declarations/fixtures.ts renamed to tests/double-declarations/fixtures.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ export class DivCls {
5757
selector: 'base2',
5858
})
5959
export class BaseCls {
60-
@ContentChild(DivCls) public contentChildBase?: DivCls;
61-
@ContentChildren(DivCls) public contentChildrenBase?: QueryList<DivCls>;
60+
@ContentChild(DivCls, {} as any) public contentChildBase?: DivCls;
61+
@ContentChildren(DivCls, {} as any) public contentChildrenBase?: QueryList<DivCls>;
6262

6363
@HostBinding('attr.base1') public hostBase1: any;
6464
@HostBinding('attr.base2') public hostBase2: any;
@@ -75,9 +75,6 @@ export class BaseCls {
7575
@Input() public propBase1: EventEmitter<void> | string = '';
7676
@Output() public propBase2 = new EventEmitter<void>();
7777

78-
@ViewChild(DivCls) public viewChildBase?: DivCls;
79-
@ViewChildren(DivCls) public viewChildrenBase?: QueryList<DivCls>;
80-
8178
@HostListener('focus') public hostBaseHandler3() {
8279
this.hostBase3 = 'base3';
8380
}
@@ -92,8 +89,8 @@ export class BaseCls {
9289
template: `override2<ng-content></ng-content>`,
9390
})
9491
export class OverrideCls extends BaseCls {
95-
@ContentChild(DivCls) public contentChildOverride?: DivCls;
96-
@ContentChildren(DivCls) public contentChildrenOverride?: QueryList<DivCls>;
92+
@ContentChild(DivCls, {} as any) public contentChildOverride?: DivCls;
93+
@ContentChildren(DivCls, {} as any) public contentChildrenOverride?: QueryList<DivCls>;
9794

9895
@HostBinding('attr.override2') public hostBase2: any;
9996
@HostBinding('attr.override1') public hostOverride1: any;
@@ -107,9 +104,6 @@ export class OverrideCls extends BaseCls {
107104

108105
@Output() public propOverride2 = new EventEmitter<void>();
109106

110-
@ContentChild(DivCls) public viewChildBase?: DivCls;
111-
@ContentChildren(DivCls) public viewChildrenBase?: QueryList<DivCls>;
112-
113107
@HostListener('click') public hostBaseHandler3() {
114108
this.hostOverride3 = 'override3';
115109
}

e2e/a13/src/e2e/double-declarations/test.spec.ts renamed to tests/double-declarations/test.spec.ts

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { EventEmitter, HostBinding } from '@angular/core';
1+
// tslint:disable cyclomatic-complexity
2+
3+
import { EventEmitter } from '@angular/core';
24
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';
35

46
import {
@@ -141,10 +143,18 @@ describe('double-declarations', () => {
141143

142144
// renders component
143145
const html = ngMocks.formatHtml(fixture);
144-
expect(html).toContain('base1="base1"');
145-
expect(html).toContain('base2="override2"');
146-
expect(html).toContain('override2="override2"');
147-
expect(html).toContain('override1="override1"');
146+
if (contextName === 'real') {
147+
expect(html).toContain('base1="base1"');
148+
expect(html).toContain('base2="override2"');
149+
expect(html).toContain('override2="override2"');
150+
expect(html).toContain('override1="override1"');
151+
} else {
152+
// but doesn't not render host bindings in mock declarations.
153+
expect(html).not.toContain('base1="base1"');
154+
expect(html).not.toContain('base2="override2"');
155+
expect(html).not.toContain('override2="override2"');
156+
expect(html).not.toContain('override1="override1"');
157+
}
148158
});
149159

150160
it('fails on override2', () => {
@@ -194,9 +204,7 @@ describe('double-declarations', () => {
194204
MockRender(
195205
`<override1
196206
[prop1]="'prop1'"
197-
[prop2alias]="'prop2alias'"
198207
[override2alias]="'override2alias'"
199-
[prop3alias]="'prop3alias'"
200208
[override3alias]="'override3alias'"
201209
[propBase1]="'propBase1'"
202210
[propOverride1]="'propOverride1'"
@@ -224,9 +232,9 @@ describe('double-declarations', () => {
224232
const instance = ngMocks.findInstance(OverrideCls);
225233
(instance.prop1 as EventEmitter<void>).emit();
226234
expect(data.value).toEqual('prop1');
227-
(instance.propBase2 as EventEmitter<void>).emit();
235+
instance.propBase2.emit();
228236
expect(data.value).toEqual('propBase2');
229-
(instance.propOverride2 as EventEmitter<void>).emit();
237+
instance.propOverride2.emit();
230238
expect(data.value).toEqual('propOverride2');
231239
});
232240

@@ -241,9 +249,10 @@ describe('double-declarations', () => {
241249
});
242250
expect(triggers).toEqual(0);
243251
ngMocks.trigger(instanceEl, 'focus');
244-
expect(triggers).toEqual(1);
252+
// host listeners are not triggered in mock declarations
253+
expect(triggers).toEqual(contextName === 'real' ? 1 : 0);
245254
ngMocks.trigger(instanceEl, 'click');
246-
expect(triggers).toEqual(2);
255+
expect(triggers).toEqual(contextName === 'real' ? 2 : 0);
247256
});
248257

249258
it('respects content injections', () => {
@@ -253,36 +262,60 @@ describe('double-declarations', () => {
253262
);
254263
const instance = ngMocks.findInstance(OverrideCls);
255264

256-
expect(instance.contentChildBase?.prop).toEqual(1);
257-
expect(instance.contentChildrenBase?.first.prop).toEqual(1);
258-
expect(instance.contentChildrenBase?.length).toEqual(1);
259-
260-
expect(instance.contentChildOverride?.prop).toEqual(1);
261265
expect(
262-
instance.contentChildrenOverride?.first.prop,
266+
instance.contentChildBase &&
267+
instance.contentChildBase.prop,
268+
).toEqual(1);
269+
expect(
270+
instance.contentChildrenBase &&
271+
instance.contentChildrenBase.first.prop,
272+
).toEqual(1);
273+
expect(
274+
instance.contentChildrenBase &&
275+
instance.contentChildrenBase.length,
263276
).toEqual(1);
264-
expect(instance.contentChildrenOverride?.length).toEqual(1);
265277

266-
// looks like parent views wins
267-
expect(instance.viewChildBase).toBeUndefined();
268-
expect(instance.viewChildrenBase?.length).toEqual(0);
278+
expect(
279+
instance.contentChildOverride &&
280+
instance.contentChildOverride.prop,
281+
).toEqual(1);
282+
expect(
283+
instance.contentChildrenOverride &&
284+
instance.contentChildrenOverride.first.prop,
285+
).toEqual(1);
286+
expect(
287+
instance.contentChildrenOverride &&
288+
instance.contentChildrenOverride.length,
289+
).toEqual(1);
269290

270291
fixture.componentInstance.value = 2;
271292
fixture.detectChanges();
272293

273-
expect(instance.contentChildBase?.prop).toEqual(2);
274-
expect(instance.contentChildrenBase?.first.prop).toEqual(2);
275-
expect(instance.contentChildrenBase?.length).toEqual(1);
276-
277-
expect(instance.contentChildOverride?.prop).toEqual(2);
278294
expect(
279-
instance.contentChildrenOverride?.first.prop,
295+
instance.contentChildBase &&
296+
instance.contentChildBase.prop,
297+
).toEqual(2);
298+
expect(
299+
instance.contentChildrenBase &&
300+
instance.contentChildrenBase.first.prop,
280301
).toEqual(2);
281-
expect(instance.contentChildrenOverride?.length).toEqual(1);
302+
expect(
303+
instance.contentChildrenBase &&
304+
instance.contentChildrenBase.length,
305+
).toEqual(1);
282306

283-
// looks like parent views wins
284-
expect(instance.viewChildBase).toBeUndefined();
285-
expect(instance.viewChildrenBase?.length).toEqual(0);
307+
expect(
308+
instance.contentChildOverride &&
309+
instance.contentChildOverride.prop,
310+
).toEqual(2);
311+
expect(
312+
instance.contentChildrenOverride &&
313+
instance.contentChildrenOverride.first.prop,
314+
).toEqual(2);
315+
expect(
316+
instance.contentChildrenOverride &&
317+
instance.contentChildrenOverride.length,
318+
).toEqual(1);
286319
});
287320
});
288321
});

tests/issue-1427/test.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Component, HostBinding, HostListener } from '@angular/core';
2+
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';
3+
4+
@Component({
5+
selector: 'target',
6+
template: '{{ id }}',
7+
})
8+
export class TargetComponent {
9+
@HostBinding() public id = `custom-form`;
10+
@HostListener('click') public click = () => undefined;
11+
}
12+
13+
describe('issue-1427', () => {
14+
beforeEach(() => MockBuilder(null, TargetComponent));
15+
16+
it('ignores host bindings in mock declarations', () => {
17+
const fixture = MockRender(TargetComponent);
18+
19+
// HostBinding with id doesn't cause a side effect.
20+
expect(ngMocks.formatHtml(fixture)).toEqual('<target></target>');
21+
22+
// HostListener doesn't cause a side effect.
23+
expect(
24+
fixture.point.componentInstance.click,
25+
).not.toHaveBeenCalled();
26+
ngMocks.trigger(fixture.point, 'click');
27+
expect(
28+
fixture.point.componentInstance.click,
29+
).not.toHaveBeenCalled();
30+
});
31+
});

0 commit comments

Comments
 (0)