mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-40748 Bug fixes for translation and storage services (#11551)
* AAE-40748 Various i18n and storage bug fixes * refactor: update NoopTranslateModule to use provideHttpClient and provideHttpClientTesting Replaced HttpClientTestingModule with provideHttpClient and provideHttpClientTesting for improved dependency injection in the NoopTranslateModule. * refactor: update FileUploadErrorPipe tests to use TestBed for dependency injection Replaced direct instantiation of FileUploadErrorPipe with TestBed configuration, utilizing NoopTranslateModule for improved testing practices. * test: enhance StorageService and UserPreferencesService tests for improved initialization logic Added tests for UserPreferencesService to verify locale and pagination initialization from storage and config. Updated StorageService tests to restore original localStorage after each test for better isolation. * refactor: simplify TranslationService by removing direct StorageService dependency
This commit is contained in:
@@ -15,14 +15,19 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NoopTranslationService } from '@alfresco/adf-core';
|
import { NoopTranslateModule } from '@alfresco/adf-core';
|
||||||
import { FileUploadErrorPipe } from './file-upload-error.pipe';
|
import { FileUploadErrorPipe } from './file-upload-error.pipe';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
describe('FileUploadErrorPipe', () => {
|
describe('FileUploadErrorPipe', () => {
|
||||||
let pipe: FileUploadErrorPipe;
|
let pipe: FileUploadErrorPipe;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pipe = new FileUploadErrorPipe(new NoopTranslationService());
|
TestBed.configureTestingModule({
|
||||||
|
imports: [NoopTranslateModule],
|
||||||
|
providers: [FileUploadErrorPipe]
|
||||||
|
});
|
||||||
|
pipe = TestBed.inject(FileUploadErrorPipe);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return generic message when error code is null', () => {
|
it('should return generic message when error code is null', () => {
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ describe('StorageService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with memory storage', () => {
|
describe('with memory storage', () => {
|
||||||
|
const originalLocalStorage = window.localStorage;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
Object.defineProperty(window, 'localStorage', {
|
Object.defineProperty(window, 'localStorage', {
|
||||||
value: undefined,
|
value: undefined,
|
||||||
@@ -130,6 +132,13 @@ describe('StorageService', () => {
|
|||||||
storage.clear();
|
storage.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.defineProperty(window, 'localStorage', {
|
||||||
|
value: originalLocalStorage,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to get a property from storage', () => {
|
it('should be able to get a property from storage', () => {
|
||||||
storage.setItem(key, value);
|
storage.setItem(key, value);
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,79 @@ describe('UserPreferencesService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('initialization', () => {
|
||||||
|
it('should use setWithoutStore for locale when it is already in storage', () => {
|
||||||
|
const spySetWithoutStore = spyOn(preferences, 'setWithoutStore').and.callThrough();
|
||||||
|
const spySet = spyOn(preferences, 'set').and.callThrough();
|
||||||
|
|
||||||
|
storage.setItem('GUEST__locale', 'fr');
|
||||||
|
appConfig.config.locale = 'en';
|
||||||
|
|
||||||
|
preferences.setStoragePrefix(null);
|
||||||
|
|
||||||
|
expect(preferences.locale).toBe('fr');
|
||||||
|
expect(spySetWithoutStore).toHaveBeenCalledWith(UserPreferenceValues.Locale, 'fr');
|
||||||
|
expect(spySet).not.toHaveBeenCalledWith(UserPreferenceValues.Locale, jasmine.any(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use set for locale when it is in config but not in storage', () => {
|
||||||
|
const spySet = spyOn(preferences, 'set').and.callThrough();
|
||||||
|
|
||||||
|
appConfig.config.locale = 'de';
|
||||||
|
|
||||||
|
preferences.setStoragePrefix(null);
|
||||||
|
|
||||||
|
expect(preferences.locale).toBe('de');
|
||||||
|
expect(spySet).toHaveBeenCalledWith(UserPreferenceValues.Locale, 'de');
|
||||||
|
expect(storage.getItem('GUEST__locale')).toBe('de');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use defaults and setWithoutStore for locale when neither storage nor config has values', () => {
|
||||||
|
const spySetWithoutStore = spyOn(preferences, 'setWithoutStore').and.callThrough();
|
||||||
|
const spySet = spyOn(preferences, 'set').and.callThrough();
|
||||||
|
|
||||||
|
delete appConfig.config.locale;
|
||||||
|
spyOn(translate, 'getBrowserCultureLang').and.returnValue(null);
|
||||||
|
|
||||||
|
preferences.setStoragePrefix(null);
|
||||||
|
|
||||||
|
expect(preferences.locale).toBe('en'); // default
|
||||||
|
expect(spySetWithoutStore).toHaveBeenCalledWith(UserPreferenceValues.Locale, 'en');
|
||||||
|
expect(spySet).not.toHaveBeenCalledWith(UserPreferenceValues.Locale, jasmine.any(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use setWithoutStore for pagination when it is already in storage', () => {
|
||||||
|
const spySetWithoutStore = spyOn(preferences, 'setWithoutStore').and.callThrough();
|
||||||
|
const spySet = spyOn(preferences, 'set').and.callThrough();
|
||||||
|
|
||||||
|
storage.setItem('GUEST__paginationSize', '50');
|
||||||
|
storage.setItem('GUEST__supportedPageSizes', JSON.stringify([10, 20, 50]));
|
||||||
|
appConfig.config.pagination = { size: 10, supportedPageSizes: [5, 10] };
|
||||||
|
|
||||||
|
preferences.setStoragePrefix(null);
|
||||||
|
|
||||||
|
expect(preferences.paginationSize).toBe(50);
|
||||||
|
expect(preferences.supportedPageSizes).toEqual([10, 20, 50]);
|
||||||
|
expect(spySetWithoutStore).toHaveBeenCalledWith(UserPreferenceValues.PaginationSize, 50);
|
||||||
|
expect(spySetWithoutStore).toHaveBeenCalledWith(UserPreferenceValues.SupportedPageSizes, JSON.stringify([10, 20, 50]));
|
||||||
|
expect(spySet).not.toHaveBeenCalledWith(UserPreferenceValues.PaginationSize, jasmine.any(Number));
|
||||||
|
expect(spySet).not.toHaveBeenCalledWith(UserPreferenceValues.SupportedPageSizes, jasmine.any(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use set for pagination when it is in config but not in storage', () => {
|
||||||
|
const spySet = spyOn(preferences, 'set').and.callThrough();
|
||||||
|
|
||||||
|
appConfig.config.pagination = { size: 15, supportedPageSizes: [5, 15] };
|
||||||
|
|
||||||
|
preferences.setStoragePrefix(null);
|
||||||
|
|
||||||
|
expect(preferences.paginationSize).toBe(15);
|
||||||
|
expect(spySet).toHaveBeenCalledWith(UserPreferenceValues.PaginationSize, 15);
|
||||||
|
expect(spySet).toHaveBeenCalledWith(UserPreferenceValues.SupportedPageSizes, JSON.stringify([5, 15]));
|
||||||
|
expect(storage.getItem('GUEST__paginationSize')).toBe('15');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('with language config', () => {
|
describe('with language config', () => {
|
||||||
it('should store default textOrientation based on language', () => {
|
it('should store default textOrientation based on language', () => {
|
||||||
appConfig.config.languages = [
|
appConfig.config.languages = [
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class UserPreferencesService {
|
|||||||
expandedSidenav: true
|
expandedSidenav: true
|
||||||
};
|
};
|
||||||
|
|
||||||
private userPreferenceStatus: any = this.defaults;
|
private userPreferenceStatus: any = { ...this.defaults };
|
||||||
private onChangeSubject: BehaviorSubject<any>;
|
private onChangeSubject: BehaviorSubject<any>;
|
||||||
onChange: Observable<any>;
|
onChange: Observable<any>;
|
||||||
|
|
||||||
@@ -140,19 +140,48 @@ export class UserPreferencesService {
|
|||||||
|
|
||||||
private initUserPreferenceStatus() {
|
private initUserPreferenceStatus() {
|
||||||
this.initUserLanguage();
|
this.initUserLanguage();
|
||||||
this.set(UserPreferenceValues.PaginationSize, this.paginationSize);
|
this.initPaginationPreferences();
|
||||||
this.set(UserPreferenceValues.SupportedPageSizes, JSON.stringify(this.supportedPageSizes));
|
}
|
||||||
|
|
||||||
|
private initPaginationPreferences() {
|
||||||
|
// Check if values are already in storage
|
||||||
|
const storedPaginationSize = this.get(UserPreferenceValues.PaginationSize);
|
||||||
|
const storedSupportedPageSizes = this.get(UserPreferenceValues.SupportedPageSizes);
|
||||||
|
|
||||||
|
if (storedPaginationSize) {
|
||||||
|
// Already in storage - just update in-memory state
|
||||||
|
this.setWithoutStore(UserPreferenceValues.PaginationSize, Number(storedPaginationSize));
|
||||||
|
} else {
|
||||||
|
// Not in storage - get from config and save
|
||||||
|
const paginationSize = this.appConfig.get('pagination.size', this.defaults.paginationSize);
|
||||||
|
this.set(UserPreferenceValues.PaginationSize, paginationSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedSupportedPageSizes) {
|
||||||
|
// Already in storage - just update in-memory state
|
||||||
|
this.setWithoutStore(UserPreferenceValues.SupportedPageSizes, storedSupportedPageSizes);
|
||||||
|
} else {
|
||||||
|
// Not in storage - get from config and save
|
||||||
|
const supportedPageSizes = this.appConfig.get('pagination.supportedPageSizes', this.defaults.supportedPageSizes);
|
||||||
|
this.set(UserPreferenceValues.SupportedPageSizes, JSON.stringify(supportedPageSizes));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initUserLanguage() {
|
private initUserLanguage() {
|
||||||
if (this.locale || this.appConfig.get<string>(UserPreferenceValues.Locale)) {
|
const storedLocale = this.get(UserPreferenceValues.Locale);
|
||||||
const locale = this.locale || this.getDefaultLocale();
|
const configLocale = this.appConfig.get<string>(UserPreferenceValues.Locale);
|
||||||
|
|
||||||
this.set(UserPreferenceValues.Locale, locale);
|
if (storedLocale) {
|
||||||
this.set('textOrientation', this.getLanguageByKey(locale).direction || 'ltr');
|
// Locale already in storage - just update in-memory state, don't re-save
|
||||||
|
this.setWithoutStore(UserPreferenceValues.Locale, storedLocale);
|
||||||
|
this.setWithoutStore('textOrientation', this.getLanguageByKey(storedLocale).direction || 'ltr');
|
||||||
|
} else if (configLocale) {
|
||||||
|
// Locale from config but not in storage - save to storage
|
||||||
|
this.set(UserPreferenceValues.Locale, configLocale);
|
||||||
|
this.set('textOrientation', this.getLanguageByKey(configLocale).direction || 'ltr');
|
||||||
} else {
|
} else {
|
||||||
const locale = this.locale || this.getDefaultLocale();
|
// No locale anywhere - use default, don't save to storage
|
||||||
|
const locale = this.getDefaultLocale();
|
||||||
this.setWithoutStore(UserPreferenceValues.Locale, locale);
|
this.setWithoutStore(UserPreferenceValues.Locale, locale);
|
||||||
this.setWithoutStore('textOrientation', this.getLanguageByKey(locale).direction || 'ltr');
|
this.setWithoutStore('textOrientation', this.getLanguageByKey(locale).direction || 'ltr');
|
||||||
}
|
}
|
||||||
@@ -298,7 +327,7 @@ export class UserPreferencesService {
|
|||||||
* @returns locale name
|
* @returns locale name
|
||||||
*/
|
*/
|
||||||
get locale(): string {
|
get locale(): string {
|
||||||
return this.get(UserPreferenceValues.Locale);
|
return this.get(UserPreferenceValues.Locale) || this.getDefaultLocale();
|
||||||
}
|
}
|
||||||
|
|
||||||
set locale(value: string) {
|
set locale(value: string) {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
import { EventEmitter, Injectable } from '@angular/core';
|
import { EventEmitter, Injectable } from '@angular/core';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { TranslationService } from '../translation/translation.service';
|
|
||||||
|
|
||||||
export interface LangChangeEvent {
|
export interface LangChangeEvent {
|
||||||
lang: string;
|
lang: string;
|
||||||
@@ -26,7 +25,7 @@ export interface LangChangeEvent {
|
|||||||
|
|
||||||
/** @deprecated use `NoopTranslateModule` instead */
|
/** @deprecated use `NoopTranslateModule` instead */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TranslationMock implements TranslationService {
|
export class TranslationMock {
|
||||||
defaultLang: string = 'en';
|
defaultLang: string = 'en';
|
||||||
userLang: string;
|
userLang: string;
|
||||||
customLoader: any;
|
customLoader: any;
|
||||||
|
|||||||
@@ -16,14 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter, Injectable, NgModule } from '@angular/core';
|
import { EventEmitter, Injectable, NgModule } from '@angular/core';
|
||||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
import { provideTranslateService, TranslateLoader } from '@ngx-translate/core';
|
import { provideTranslateService, TranslateLoader } from '@ngx-translate/core';
|
||||||
import { TranslationService } from '../translation/translation.service';
|
import { TranslationService } from '../translation/translation.service';
|
||||||
import { LangChangeEvent } from '../mock';
|
import { LangChangeEvent } from '../mock';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class NoopTranslationService implements TranslationService {
|
export class NoopTranslationService {
|
||||||
defaultLang: string = 'en';
|
defaultLang: string = 'en';
|
||||||
userLang: string;
|
userLang: string;
|
||||||
customLoader: any;
|
customLoader: any;
|
||||||
@@ -49,8 +50,9 @@ export class NoopTranslationService implements TranslationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [HttpClientTestingModule],
|
|
||||||
providers: [
|
providers: [
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
{ provide: TranslationService, useClass: NoopTranslationService },
|
{ provide: TranslationService, useClass: NoopTranslationService },
|
||||||
provideTranslateService({
|
provideTranslateService({
|
||||||
loader: {
|
loader: {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { effect, Inject, Injectable, InjectionToken, Optional } from '@angular/c
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { TranslateLoaderService } from './translate-loader.service';
|
import { TranslateLoaderService } from './translate-loader.service';
|
||||||
import { UserPreferencesService } from '../common/services/user-preferences.service';
|
import { UserPreferencesService, UserPreferenceValues } from '../common/services/user-preferences.service';
|
||||||
|
|
||||||
export const TRANSLATION_PROVIDER = new InjectionToken('Injection token for translation providers.');
|
export const TRANSLATION_PROVIDER = new InjectionToken('Injection token for translation providers.');
|
||||||
|
|
||||||
@@ -71,13 +71,21 @@ export class TranslationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use effect to reactively update translations when locale signal changes
|
// Try to read locale from storage synchronously to apply it before components render
|
||||||
// Note: This is a singleton service, so no cleanup needed
|
const storedLocale = userPreferencesService.get(UserPreferenceValues.Locale);
|
||||||
|
|
||||||
|
if (storedLocale) {
|
||||||
|
// Apply stored locale immediately during construction
|
||||||
|
this.userLang = storedLocale;
|
||||||
|
this.loadTranslation(storedLocale, this.defaultLang);
|
||||||
|
}
|
||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const locale = userPreferencesService.localeSignal();
|
const locale = userPreferencesService.localeSignal();
|
||||||
if (locale) {
|
|
||||||
|
if (locale && locale !== this.userLang) {
|
||||||
this.userLang = locale;
|
this.userLang = locale;
|
||||||
this.use(this.userLang);
|
this.loadTranslation(locale, this.defaultLang);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,7 +136,7 @@ export class TranslationService {
|
|||||||
onTranslationChanged(lang: string): void {
|
onTranslationChanged(lang: string): void {
|
||||||
this.translate.onTranslationChange.next({
|
this.translate.onTranslationChange.next({
|
||||||
lang,
|
lang,
|
||||||
translations: this.customLoader.getFullTranslationJSON(lang)
|
translations: this.customLoader.getFullTranslationJSON?.(lang) ?? {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user