Search code examples
javascriptangulartestingangular2-directives

Can't bind to the input of a standalone angular directive in unit test


I'm trying to write a test for a directive that modifies the Angular CDK table. To test this I made a test component decalred in my *.spec.ts file, where I setup a simple table and add my directive.

However when run, on fixture.detectChanges() the component throws two errors. One is that the input property is unknown, the second is from the directive sayingthe required input is not set.

Can't bind to 'sortableColumns' since it isn't a known property of 'table' (used in the 'TestComponent' component template)

ERROR RuntimeError: NG0950: Input is required but no value is available yet.

The test component looks like so (I have removed some logic for brevity):

@Component({
  standalone: true,
  imports: [CdkTableModule, SortableTableDirective],
  template: ` <table cdk-table sortableTable [dataSource]="dataSource" [sortableColumns]="sortableColumns">
    <ng-container cdkColumnDef="columnA">
      <th cdk-header-cell *cdkHeaderCellDef>The First Column</th>
      <td cdk-cell *cdkCellDef="let element">{{ element.columnA }}</td>
    </ng-container>
    <ng-container cdkColumnDef="columnB">
      <th cdk-header-cell *cdkHeaderCellDef>The Second Column</th>
      <td cdk-cell *cdkCellDef="let element">{{ element.columnB }}</td>
    </ng-container>
    <ng-container cdkColumnDef="columnC">
      <th cdk-header-cell *cdkHeaderCellDef>The Third Column</th>
      <td cdk-cell *cdkCellDef="let element">{{ element.columnC }}</td>
    </ng-container>
    <tr cdk-header-row *cdkHeaderRowDef="displayedColumns"></tr>
    <tr cdk-row *cdkRowDef="let row; columns: displayedColumns"></tr>
  </table>`,
})
class TestComponent {
  dataSource = //data-source creation etc removed for brevity
  sortableColumns = ['columnA', 'columnB'];
  displayedColumns = ['columnA', 'columnB', 'columnC'];
}

The test is setup as follows. The table variable gets a value despite an error being thrown on the fixture setup. When I later want to check that the directive has run however, it has not. I have tried with and without the compileComponents function, it does not make a difference.

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [SortableTableDirective, TestComponent, CdkTableModule],
    }).compileComponents();
    fixture = TestBed.createComponent(TestComponent);

    fixture.detectChanges();

    table = fixture.debugElement.query(By.directive(SortableTableDirective));
  });

The directive itself is fairly simple from a dependency standpoint, not depending on anything outside of some core angular things

@Directive({
  selector: 'table[sortableTable]',
  standalone: true,
})
export class SortableTableDirective<T> implements OnDestroy {
  dataSource = input.required<SortableDatasource<T>>();
  sortableColumns = input.required<string[]>();

  public constructor(
    private renderer: Renderer2,
    private elRef: ElementRef,
    private containerRef: ViewContainerRef,
  ) {
    // code removed
  }

The cdk-table directive works and runs as it should, with its created elements all being available in the fixture. The code runs perfectly fine "live".

Edit: Further testing seem to indicate the problem is with the signal inputs. Replacing signal inputs with @Input solves the binding problem. Wether it is a bug or I am doing something wrong is still unclear.

Edit2: We use jest for running the tests. Using karma + jasmine seems to also work in a minimum-replication project I tried making.


Solution

  • Found the answer via this github issue https://github.com/angular/angular/issues/54013

    Short of it: the version of jest-preset-angular we were using was outdated and did not use the correct JIT transform. Updating to 14.0.3 resolved the issue and thest tests now run as they should.