Using multiple Mixin classes with Angular annotation @Component leads to failing test using jest framework
Opened this issue · 3 comments
I have angular 17 app made using nx repo
Code in runtime works fine even if my app is powered by TS 5.2.2
For simplicity, I made below code:
import { Mixin } from 'ts-mixer';
import { FilterComponent } from './extensions/filter.component';
import { SortComponent } from './extensions/sort.component';
import { SettingsComponent } from './extensions/settings.component';
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
standalone: true,
selector: 'ng-mf1-table',
templateUrl: 'table.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TableComponent extends Mixin(FilterComponent, SortComponent, SettingsComponent) {
constructor() {
super();
}
getTest(): string {
return `${this.getFilter()}_${this.getSort()}_${this.getSettings()}`;
}
}
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
standalone: true,
selector: 'ng-mf1-sort',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SortComponent {
getSort(): string {
return 'sort';
}
}
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
standalone: true,
selector: 'ng-mf1-settings',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingsComponent {
getSettings(): string {
return 'settings';
}
}
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
standalone: true,
selector: 'ng-mf1-filter',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FilterComponent {
getFilter(): string {
return 'filter';
}
}
When having multiple Mixin classes with @component annotation below test fails
import { TableComponent } from './table.component';
describe('TableComponent', () => {
it('test1', () => {
const table = new TableComponent();
expect(table.getTest()).toEqual('filter_sort_settings');
});
});
with the following error:
● Test suite failed to run
TypeError: Cannot redefine property: __annotations__
at Function.defineProperties (<anonymous>)
11 | changeDetection: ChangeDetectionStrategy.OnPush,
12 | })
> 13 | export class TableComponent extends Mixin(FilterComponent, SortComponent, SettingsComponent) {
| ^
14 |
15 | constructor() {
16 | super();
at copyProps (../../node_modules/ts-mixer/dist/cjs/util.js:12:12)
at hardMixProtos (../../node_modules/ts-mixer/dist/cjs/util.js:69:39)
at Mixin (../../node_modules/ts-mixer/dist/cjs/mixins.js:38:36)
at Object.<anonymous> (src/app/test/table.component.ts:13:42)
at Object.<anonymous> (src/app/test/table.component.spec.ts:1:1)
if TableComponent extends multiple classes and only one has @component annotation everything works fine, problem appears when at least 2 classes have this annotation.
The weird thing is that when "zone.js": "~0.14.0" is changed to "zone.js": "~0.11.0" there are no problem but Angular 17 support only zone.js >= 14.
I'm not sure if this problem is related to Jest/TestBed or ts-mixer because everything works fine, except testing.
I'm not that familiar with Angular, but it's not clear to me how UI components could be "mixed". Don't they have to be composed in order to specify where they sit in the DOM tree?
Also, SortComponent
, SettingsComponent
and FilterComponent
don't have a template, so do they need to be @component
s at all? Perhaps that was just left out of your example for simplicity?
Base components don't have to be annotated with @Component
but when using angular features like @Input
, @Output
or using the DI injection mechanism then those base classes must have annotation @Component
or @Directive
when a base class has no template is recommended to use @Directive
annotation, but a problem is that even by annotation @Directive
there is still the same issue.
I went with a debugger through the code and I could not find any difference in the details of the same objects when have zone.js in version ~0.11.0 and ~0.14.0. By version ~0.14.0 of zone.js the difference is that on a second call of method hardMixProtos
to copy static fields has inside call for method copyProps
which tries to define property __annotation__
that is already existing in dest object because first call of hardMixProtos
already defined that property.
Add extra value __annotations__
to excluded properties in the second call of method hardMixProtos
like that
Object.setPrototypeOf(
MixedClass,
settings.staticsStrategy === 'copy'
? hardMixProtos(constructors, null, ['prototype', '__annotations__'])
: proxyMix(constructors, Function.prototype)
fixes the problem.
As I mentioned before, running the angular app is not an issue, but when trying to run the Jest test for that component with or without using TestBed is an issue. I can share with you an example code.
@tannerntannern Did you see my last replay? ;)