Skip to content

Commit

Permalink
fix(I18NextEagerPipe): ensure changing PipeOptions returns correct tr…
Browse files Browse the repository at this point in the history
…anslated value a not cached one with different PipeOptions but same key
  • Loading branch information
wawyed committed Feb 26, 2021
1 parent e23ab0d commit 4a6d375
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 6 deletions.
14 changes: 12 additions & 2 deletions src/I18NextEagerPipe.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ChangeDetectorRef, Inject, OnDestroy, Pipe, PipeTransform } from '@angular/core';
import { I18NextPipe } from './I18NextPipe';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { I18NEXT_NAMESPACE, I18NEXT_SCOPE, I18NEXT_SERVICE } from './I18NEXT_TOKENS';
import { I18NextPipe } from './I18NextPipe';
import { ITranslationService } from './ITranslationService';
import { PipeOptions } from './models';

Expand All @@ -13,6 +13,7 @@ import { PipeOptions } from './models';
export class I18NextEagerPipe extends I18NextPipe implements PipeTransform, OnDestroy {

private lastKey: string;
private lastOptions: PipeOptions;
private lastValue: string;

private ngUnsubscribe: Subject<any> = new Subject();
Expand All @@ -31,12 +32,21 @@ constructor(
.subscribe(() => {
this.cd.markForCheck();
});
}
private hasKeyChanged(key: string | string[]): boolean {
return !this.lastKey || this.lastKey !== key;
}

private hasOptionsChanged(options: PipeOptions): boolean {
return this.lastOptions !== options;
}

public transform(key: string | string[], options?: PipeOptions): string {
const newKey = this.translateI18Next.language + '|' + JSON.stringify(key);
if (!this.lastKey || this.lastKey !== newKey) {

if (this.hasKeyChanged(newKey) || this.hasOptionsChanged(options)) {
this.lastKey = newKey;
this.lastOptions = options;
this.lastValue = super.transform(key, options);
}
return this.lastValue;
Expand Down
8 changes: 4 additions & 4 deletions tests/mocks/MockTranslationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,20 @@ export class MockI18NextService implements ITranslationService {
});
}

public t(key: string | string[], options?: any): string {
public t = jasmine.createSpy('MockI18NextService.t').and.callFake((key: string | string[], options?: any): string => {
if (key instanceof Array)
return key.length > 0 ? key[0] : '';
return key;
}
});

public format(value: string, format: string, lng: string): string {
public format = jasmine.createSpy('MockI18NextService.format').and.callFake((value: string, format: string, lng: string): string => {
if (!value)
return value;
if (format === 'cap') {
return value[0].toUpperCase() + value.substring(1);
}
return value;
}
});

public changeLanguage(lng: string): Promise<any> {
return new Promise<any>(
Expand Down
166 changes: 166 additions & 0 deletions tests/pipes/I18NextEagerPipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { ChangeDetectorRef } from '@angular/core';
import { ITranslationEvents, PipeOptions } from '../../src';
import { I18NextEagerPipe } from '../../src/I18NextEagerPipe';
import { BehaviorSubject } from 'rxjs';
import { MockI18NextService } from '../mocks/MockTranslationService';

describe('I18NextEagerPipe', () => {
let pipe: I18NextEagerPipe,
languageChangedSubject: BehaviorSubject<string>,
changeDetector: ChangeDetectorRef,
service: MockI18NextService;

beforeEach(() => {
service = new MockI18NextService();
service.language = 'en';
languageChangedSubject = new BehaviorSubject<string>('en');

const scope = 'scope';
const ns = 'ns';
service.events = { languageChanged: languageChangedSubject } as ITranslationEvents;

changeDetector = jasmine.createSpyObj<ChangeDetectorRef>(['markForCheck']);
pipe = new I18NextEagerPipe(service, ns, scope, changeDetector);
});

it('should create the pipe', () => {
expect(pipe).toBeTruthy();
});

describe('when called with key and options', () => {
let result: string;
let myOptions: PipeOptions;

beforeEach(() => {
myOptions = { myValue: 'value1' };

result = pipe.transform('myKey', myOptions);
});

it('should call the translate service', () => {
expect(service.t).toHaveBeenCalledWith([ 'ns:scope.myKey', 'scope.myKey', 'ns:myKey', 'myKey' ], { myValue: 'value1' });
});

it('should return the correct result', () => {
expect(result).toBe('ns:scope.myKey');
});

describe('when the language changes', () => {
beforeEach(() => {
languageChangedSubject.next('es');
service.language = 'es';
});

it('should mark for check so it triggers the pipe transform', () => {
expect(changeDetector.markForCheck).toHaveBeenCalled();
});

describe('when the pipe gets triggered by change detection', () => {
beforeEach(() => {
service.t.calls.reset();
result = pipe.transform('myKey', myOptions);
});

it('should call the translate service', () => {
expect(service.t).toHaveBeenCalledWith([ 'ns:scope.myKey', 'scope.myKey', 'ns:myKey', 'myKey' ], { myValue: 'value1' });
});

it('should return the new result', () => {
expect(result).toBe('ns:scope.myKey');
});
});
});


describe('when called with same key and options', () => {
beforeEach(() => {
service.t.calls.reset();
result = pipe.transform('myKey', myOptions);
});

it('should not call the translate service', () => {
expect(service.t).not.toHaveBeenCalled();
});

it('should return the previously cached result', () => {
expect(result).toBe('ns:scope.myKey');
});
});

describe('when called with same key but different options', () => {
beforeEach(() => {
service.t.calls.reset();
result = pipe.transform('myKey', { myValue: 'value2' });
});

it('should call the translate service', () => {
expect(service.t).toHaveBeenCalledWith([ 'ns:scope.myKey', 'scope.myKey', 'ns:myKey', 'myKey' ], { myValue: 'value2' });
});

it('should return the new result', () => {
expect(result).toBe('ns:scope.myKey');
});
});

describe('when called with different key but same options', () => {
beforeEach(() => {
service.t.calls.reset();
result = pipe.transform('myKey2', myOptions);
});

it('should call the translate service', () => {
expect(service.t).toHaveBeenCalledWith([ 'ns:scope.myKey2', 'scope.myKey2', 'ns:myKey2', 'myKey2' ], { myValue: 'value1' });
});

it('should return the new result', () => {
expect(result).toBe('ns:scope.myKey2');
});
});
});

describe('when called with only with key', () => {
let result: string;

beforeEach(() => {
result = pipe.transform('myKey');
});

it('should call the translate service', () => {
expect(service.t).toHaveBeenCalledWith([ 'ns:scope.myKey', 'scope.myKey', 'ns:myKey', 'myKey' ], {});
});

it('should return the correct result', () => {
expect(result).toBe('ns:scope.myKey');
});

describe('when called with same key', () => {
beforeEach(() => {
service.t.calls.reset();
result = pipe.transform('myKey');
});

it('should not call the translate service', () => {
expect(service.t).not.toHaveBeenCalled();
});

it('should return the previously cached result', () => {
expect(result).toBe('ns:scope.myKey');
});
});

describe('when called with different key', () => {
beforeEach(() => {
service.t.calls.reset();
result = pipe.transform('myKey2');
});

it('should call the translate service', () => {
expect(service.t).toHaveBeenCalledWith([ 'ns:scope.myKey2', 'scope.myKey2', 'ns:myKey2', 'myKey2' ], {});
});

it('should return the new result', () => {
expect(result).toBe('ns:scope.myKey2');
});
});
});
});

0 comments on commit 4a6d375

Please sign in to comment.