ngx-translate/core

Testing of standalone components

Viktor-Ivliev opened this issue · 5 comments

I need to test the correctness of translations for a component, which I can't do due to translation moc issues.
component:

<div class="row form-group">
  <label class="control-label col-4">
    {{ 'form.fields.example_status' | translate }}
  </label>
  <div class="col-8">
    <ng-select
      [clearable]="false"
      [(ngModel)]="report.template.example_filter.status"
      appTooltip="{{ 'form.fields.tooltip.example_status' | translate }}"
      data-tippy-placement='right'
      [disabled]="report.template.example_filter.exclude_unsubscribed"
    >
      <ng-option value="">
        {{ 'form.example_statuses.all' | translate }}
      </ng-option>
      <ng-option *ngFor="let status of statuses" [value]="status">
        {{ 'form.example_statuses.' + status | translate }}
      </ng-option>
    </ng-select>
    <app-form-errors
      *ngIf="errors['template.example_filter.status']"
      class="field-errors"
      [errors]="errors['template.example_filter.status']"
    ></app-form-errors>
  </div>
</div>
import { Component, Input } from '@angular/core';

import { CommonModule } from '@angular/common';
import { SharedModule } from '@app/shared/shared.module';

import { Report } from '@app/example/report';

@Component({
  standalone: true,
  selector: 'app-example-status-filter',
  templateUrl: './example-status-filter.component.html',
  imports: [CommonModule, SharedModule]
})

export class ExampleStatusFilterComponent {
  @Input({ required: true }) report!: Report;
  @Input({ required: true }) errors = {};

  statuses: string[] = ['enabled', 'disabled', 'opted_out'];
}

Test:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NgSelectComponent } from '@ng-select/ng-select';
import { of } from "rxjs";
import { TranslateLoader, TranslateFakeLoader, TranslateModule, TranslateService } from '@ngx-translate/core';

import { ExampleStatusFilterComponent } from './example-status-filter.component';

import { ReportsServiceStub } from '@app/example/report/services/reports.service.spec';

class FakeLoader implements TranslateFakeLoader {
  public getTranslation(_) {
    return of(require('src/assets/i18n/example/reports/en.json'));
  }
}

describe('ExampleStatusFilterComponent', () => {
  let component: ExampleStatusFilterComponent;
  let fixture: ComponentFixture<ExampleStatusFilterComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        ExampleStatusFilterComponent,
        TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: FakeLoader } }) // and forChild
      ]
    }).compileComponents();

    // TestBed.inject(TranslateService).use('en');

    fixture = TestBed.createComponent(ExampleStatusFilterComponent);
    // TestBed.inject(TranslateService).use('en');

    component = fixture.componentInstance;
    component.report = ReportsServiceStub._items[0];
    fixture.detectChanges();
  });

  it('shows all statuses', (done) => {
    fixture.whenStable().then(() => {
      const statuses = fixture.debugElement.query(By.directive(NgSelectComponent));

      expect(statuses.componentInstance.itemsList.items.map((item) => item.label)).toEqual(
        ['All', 'Enabled', 'Disabled', 'Other Translate']
      );

      done()
    });
  });
});

The test is dropping because the translations haven't pulled up.

But if you do this: TestBed.inject(TranslateService).use('en');
I can see the getTranslation(_) visit but it has no effect on pipe and tests fail too.

I use separate translation sets for different models and everything is fine in the final project, here's an example:

import { HttpBackend } from '@angular/common/http';
import { TranslateLoader, TranslateModule, TranslateModuleConfig } from '@ngx-translate/core';
import { MultiTranslateHttpLoader } from 'ngx-translate-multi-http-loader';

export function TranslationLoaderFactory(http: HttpBackend) {
  return new MultiTranslateHttpLoader(http, [
    '/frontend/assets/i18n/example/reports/',
    '/frontend/assets/i18n/shared/'
  ]);
}

const config: TranslateModuleConfig = {
  loader: {
    provide: TranslateLoader,
    useFactory: TranslationLoaderFactory,
    deps: [HttpBackend]
  },
  isolate: true
};

export const ReportsTranslate = TranslateModule.forChild(config);

Previously before switching to standalone I used this:

TranslateTestingModule.withTranslations({
    en: require('src/assets/i18n/example/reports/en.json')
})

And the tests worked, but when switching to standalone it feels like translations are compiled before the test module is created, where providers for translations are defined.

"@angular/core": "18.2.12",
"@ngx-translate/core": "15.0.0",

Same here. but in my project I am testing only if the translation keys appears and they are correct. Unfortunately, after converting some components to standalone, nothing is rendered, even if I use
{ provide: I18NEXT_SERVICE, useClass: TranslateServiceStub }, and mock
t(key: string | Array<string>, options?: Object): string | any { return key; }

I18NEXT_SERVICE ???

@adam-ai-97
I'm thinking of trying to migrate to the next version. Seems like it would be a problem to fix here.
https://ngx-translate.org/getting-started/migration-guide/
Or try to use the standard one, but I don't like it:
https://angular.dev/guide/i18n

This is how I configure the tests for the TranslatePipe.
It works with stand alone components and properly switches the translations.
However you should use a fake translation loader to avoid it trying to load the JSONs using HTTP.