Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using multiple Mixin classes with Angular annotation @Component leads to failing test using jest framework #66

Open
TRUSTMEIMJEDI opened this issue Jan 3, 2024 · 3 comments

Comments

@TRUSTMEIMJEDI
Copy link

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.

@tannerntannern
Copy link
Owner

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 @components at all? Perhaps that was just left out of your example for simplicity?

@TRUSTMEIMJEDI
Copy link
Author

TRUSTMEIMJEDI commented Jan 5, 2024

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.
image

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.

@TRUSTMEIMJEDI
Copy link
Author

@tannerntannern Did you see my last replay? ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants