diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.html b/lib/core/src/lib/form/components/widgets/amount/amount.widget.html index 12aba25f05..6727eb3d12 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.html +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.html @@ -12,7 +12,9 @@
@if ( (field.name || field?.required) && !field.leftLabels) { {{field.name | translate }} } - {{ currency }}  + @if(!enableDisplayBasedOnLocale) { + {{ currency }}  + } + (focus)="amountWidgetOnFocus()" + (blur)="amountWidgetOnBlur()" + />
diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts b/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts index 61f733f2e9..cefdc3a74e 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts @@ -23,6 +23,7 @@ import { FormModel } from '../core/form.model'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; +import { of } from 'rxjs'; describe('AmountWidgetComponent', () => { let loader: HarnessLoader; @@ -76,6 +77,137 @@ describe('AmountWidgetComponent', () => { expect(widget.placeholder).toBe('1234'); }); + it('it should return locale based on browser', () => { + const returnedLanguages: string[] = ['en-GB', 'en-US', 'en', 'de-DE', 'pl']; + const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({ + languages: returnedLanguages + } as any); + const locale = widget.getLocale(); + + expect(locale).toBe(returnedLanguages[0]); + expect(mockLanguages).toHaveBeenCalled(); + }); + + it('it should return default locale if browser does not return valid value', () => { + const defaultLocale = 'en-US'; + spyOnProperty(window, 'navigator').and.returnValue({ + languages: undefined + } as any); + const locale = widget.getLocale(); + + expect(locale).toBe(defaultLocale); + }); + + it('should set initial values when enableDisplayBasedOnLocale is enabled', () => { + const returnedLanguages: string[] = ['en-GB']; + spyOnProperty(window, 'navigator').and.returnValue({ + languages: returnedLanguages + } as any); + widget.field = new FormFieldModel(null, { id: 1, name: 'test', value: 25, currency: 'USD' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = 'USD'; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('$25'); + expect(widget.decimalProperty).toBe('1.0-0'); + expect(widget.locale).toBe(returnedLanguages[0]); + expect(widget.valueAsNumber).toBe(25); + }); + + it('should set initial values with correct currency', () => { + const returnedLanguages: string[] = ['en-GB']; + spyOnProperty(window, 'navigator').and.returnValue({ + languages: returnedLanguages + } as any); + widget.field = new FormFieldModel(null, { id: 2, name: 'test', value: 25, currency: 'GBP' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = 'GBP'; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('£25'); + expect(widget.decimalProperty).toBe('1.0-0'); + }); + + it('should set initial values with correct currency icon', () => { + const returnedLanguages: string[] = ['en-GB']; + spyOnProperty(window, 'navigator').and.returnValue({ + languages: returnedLanguages + } as any); + widget.field = new FormFieldModel(null, { id: 2, name: 'test', value: 25, currency: '¥' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = '¥'; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('¥25'); + expect(widget.decimalProperty).toBe('1.0-0'); + }); + + it('should set initial values without currency', () => { + const returnedLanguages: string[] = ['en-GB']; + spyOnProperty(window, 'navigator').and.returnValue({ + languages: returnedLanguages + } as any); + widget.field = new FormFieldModel(null, { id: 3, name: 'test', value: 25, currency: '' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = ''; + widget.currencyDisplay = ''; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('25'); + expect(widget.decimalProperty).toBe('1.0-0'); + }); + + it('should set initial values when enableDisplayBasedOnLocale is disabled', () => { + widget.field = new FormFieldModel(null, { id: 4, name: 'test', value: 25, enableFractions: false, className: '' }); + widget.enableDisplayBasedOnLocale = false; + widget.setInitialValues(); + + expect(widget.amountWidgetValue.toString()).toBe('25'); + }); + + it('should transform value from number to string', () => { + widget.enableDisplayBasedOnLocale = true; + widget.valueAsNumber = 123456; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe('123456'); + + widget.valueAsNumber = 123456.11; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe('123456.11'); + + widget.valueAsNumber = 0; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe('0'); + + widget.valueAsNumber = undefined; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe(null); + }); + + it('should update field.value on change', () => { + widget.field = new FormFieldModel(null, { id: 5, name: 'test', value: 25 }); + const mockValue = '1234.12'; + widget.amountWidgetValue = mockValue; + widget.onFieldChangedAmountWidget(); + + expect(widget.field.value).toBe(mockValue); + }); + + it('should transform values on blur', () => { + widget.enableDisplayBasedOnLocale = true; + widget.amountWidgetValue = '1234.56'; + widget.amountWidgetOnBlur(); + + expect(widget.valueAsNumber).toBe(1234.56); + expect(widget.amountWidgetValue).toBe('$1,234.56'); + + widget.amountWidgetValue = ''; + widget.amountWidgetOnBlur(); + + expect(widget.valueAsNumber).toBe(null); + expect(widget.amountWidgetValue).toBe(null); + }); + describe('when tooltip is set', () => { beforeEach(() => { widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { @@ -327,6 +459,189 @@ describe('AmountWidgetComponent - rendering', () => { expect(asterisk.textContent).toEqual('*'); }); }); + + describe('Test widget with different setting for enableDisplayBasedOnLocale', () => { + beforeEach(() => { + TestBed.resetTestingModule(); + }); + + describe('set module for enableDisplayBasedOnLocale = true', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'TestAmount1', + name: 'Test Amount', + type: 'amount', + currency: 'USD', + enableFractions: true, + value: '1234.55' + }); + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AmountWidgetComponent], + providers: [{ provide: ADF_AMOUNT_SETTINGS, useValue: { enableDisplayBasedOnLocale: true } }] + }); + fixture = TestBed.createComponent(AmountWidgetComponent); + widget = fixture.componentInstance; + + fixture.componentRef.setInput('field', mockField); + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + fixture.detectChanges(); + }); + + it('should not display prefix with currency when enableDisplayBasedOnLocale = true', async () => { + const field = await testingUtils.getMatFormField(); + expect(await field.getPrefixText()).toBe(''); + }); + + it('should call method on focus and change input value', async () => { + const focusSpy = spyOn(widget, 'amountWidgetOnFocus').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeFocus = await field.getValue(); + await field.focus(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe('1234.55'); + expect(fieldValueBeforeFocus).toBe('$1,234.55'); + expect(focusSpy).toHaveBeenCalled(); + expect(fieldValue).toBe('1234.55'); + }); + + it('should transform value on blur', async () => { + const newValue = '456789'; + const blurSpy = spyOn(widget, 'amountWidgetOnBlur').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeBlur = await field.getValue(); + await field.setValue(newValue); + await field.blur(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe(newValue); + expect(fieldValueBeforeBlur).toBe('$1,234.55'); + expect(blurSpy).toHaveBeenCalled(); + expect(widget.valueAsNumber).toBe(parseFloat(newValue)); + expect(widget.amountWidgetValue).toBe('$456,789.00'); + expect(fieldValue).toBe('$456,789.00'); + }); + }); + describe('set module for enableDisplayBasedOnLocale = false', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'TestAmount1', + name: 'Test Amount', + type: 'amount', + currency: 'USD', + enableFractions: true, + value: '1234.55' + }); + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AmountWidgetComponent], + providers: [{ provide: ADF_AMOUNT_SETTINGS, useValue: { enableDisplayBasedOnLocale: false } }] + }); + fixture = TestBed.createComponent(AmountWidgetComponent); + widget = fixture.componentInstance; + + fixture.componentRef.setInput('field', mockField); + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + fixture.detectChanges(); + }); + + it('should display prefix with currency when enableDisplayBasedOnLocale = true', async () => { + const field = await testingUtils.getMatFormField(); + expect(await field.getPrefixText()).toBe('USD'); + }); + + it('should call method on focus and not change input value', async () => { + const focusSpy = spyOn(widget, 'amountWidgetOnFocus').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeFocus = await field.getValue(); + await field.focus(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe('1234.55'); + expect(widget.valueAsNumber).toBeUndefined(); + expect(fieldValueBeforeFocus).toBe('1234.55'); + expect(focusSpy).toHaveBeenCalled(); + expect(fieldValue).toBe('1234.55'); + }); + + it('should call method on blur and not change input value', async () => { + const newValue = '456789'; + const blurSpy = spyOn(widget, 'amountWidgetOnBlur').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeBlur = await field.getValue(); + await field.setValue(newValue); + await field.blur(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe(newValue); + expect(widget.valueAsNumber).toBeUndefined(); + expect(fieldValueBeforeBlur).toBe('1234.55'); + expect(blurSpy).toHaveBeenCalled(); + expect(widget.valueAsNumber).toBeUndefined(); + expect(widget.amountWidgetValue).toBe('456789'); + expect(fieldValue).toBe('456789'); + }); + }); + }); + + describe('Test widget with ADF_AMOUNT_SETTINGS as observable', () => { + beforeEach(() => { + TestBed.resetTestingModule(); + }); + + describe('set module for enableDisplayBasedOnLocale = true', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'TestAmount1', + name: 'Test Amount', + type: 'amount', + currency: 'USD', + enableFractions: true, + value: '1234.55' + }); + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AmountWidgetComponent], + providers: [{ provide: ADF_AMOUNT_SETTINGS, useValue: of({ enableDisplayBasedOnLocale: true }) }] + }); + fixture = TestBed.createComponent(AmountWidgetComponent); + widget = fixture.componentInstance; + const returnedLanguages: string[] = ['en-GB', 'en-US', 'en', 'de-DE', 'pl']; + spyOnProperty(window, 'navigator').and.returnValue({ + languages: returnedLanguages + } as any); + fixture.componentRef.setInput('field', mockField); + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + fixture.detectChanges(); + }); + + it('should set enableDisplayBasedOnLocale to true', () => { + expect(widget.enableDisplayBasedOnLocale).toBeTrue(); + expect(widget.decimalProperty).toBe('1.2-2'); + expect(widget.locale).toBe('en-GB'); + expect(widget.valueAsNumber).toBe('1234.55'); + expect(widget.amountWidgetValue).toBe('$1,234.55'); + }); + + it('should not display prefix with currency when enableDisplayBasedOnLocale = true', async () => { + const field = await testingUtils.getMatFormField(); + expect(await field.getPrefixText()).toBe(''); + }); + }); + }); }); describe('AmountWidgetComponent settings', () => { diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts b/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts index 1a8e5bb014..de27d53c53 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts @@ -17,7 +17,7 @@ /* eslint-disable @angular-eslint/component-selector */ -import { NgIf } from '@angular/common'; +import { CurrencyPipe, NgIf } from '@angular/common'; import { Component, OnInit, ViewEncapsulation, InjectionToken, Inject, Optional } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -26,12 +26,15 @@ import { TranslatePipe } from '@ngx-translate/core'; import { FormService } from '../../../services/form.service'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; +import { isObservable, Observable } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export interface AmountWidgetSettings { showReadonlyPlaceholder: boolean; + enableDisplayBasedOnLocale: boolean; } -export const ADF_AMOUNT_SETTINGS = new InjectionToken('adf-amount-settings'); +export const ADF_AMOUNT_SETTINGS = new InjectionToken | AmountWidgetSettings>('adf-amount-settings'); @Component({ selector: 'amount-widget', @@ -49,13 +52,23 @@ export const ADF_AMOUNT_SETTINGS = new InjectionToken('adf '(select)': 'event($event)' }, imports: [MatFormFieldModule, MatInputModule, FormsModule, ErrorWidgetComponent, TranslatePipe, NgIf], + providers: [CurrencyPipe], encapsulation: ViewEncapsulation.None }) export class AmountWidgetComponent extends WidgetComponent implements OnInit { static DEFAULT_CURRENCY: string = '$'; private showPlaceholder = true; + amountWidgetValue: string; currency: string = AmountWidgetComponent.DEFAULT_CURRENCY; + currencyDisplay: string | boolean = 'symbol'; + decimalProperty: string; + enableDisplayBasedOnLocale: boolean; + locale: string; + notShowDecimalDigits = '1.0-0'; + showDecimalDigits = '1.2-2'; + showReadonlyPlaceholder: boolean; + valueAsNumber: number; get placeholder(): string { return this.showPlaceholder ? this.field.placeholder : ''; @@ -63,22 +76,97 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { constructor( public formService: FormService, - @Inject(ADF_AMOUNT_SETTINGS) - @Optional() - private settings: AmountWidgetSettings + @Optional() @Inject(ADF_AMOUNT_SETTINGS) settings: Observable | AmountWidgetSettings, + private currencyPipe: CurrencyPipe ) { super(formService); + if (isObservable(settings)) { + settings.pipe(takeUntilDestroyed()).subscribe((data: AmountWidgetSettings) => { + this.updateSettingsBasedProperties(data); + }); + } else { + this.updateSettingsBasedProperties(settings); + } } ngOnInit() { if (this.field) { if (this.field.currency) { this.currency = this.field.currency; + } else { + if (this.enableDisplayBasedOnLocale) { + this.currency = ''; + this.currencyDisplay = ''; + } } if (this.field.readOnly) { - this.showPlaceholder = this.settings?.showReadonlyPlaceholder; + this.showPlaceholder = this.showReadonlyPlaceholder; } + this.setInitialValues(); } } + + amountWidgetOnBlur(): void { + if (this.enableDisplayBasedOnLocale) { + if (this.amountWidgetValue) { + this.valueAsNumber = parseFloat(this.amountWidgetValue); + this.amountWidgetValue = this.currencyPipe.transform( + this.amountWidgetValue, + this.currency, + this.currencyDisplay, + this.decimalProperty + ); + } else { + this.valueAsNumber = null; + this.amountWidgetValue = null; + } + } + this.markAsTouched(); + } + + amountWidgetOnFocus(): void { + if (this.enableDisplayBasedOnLocale) { + const hasValue = this.valueAsNumber === 0 || this.valueAsNumber; + this.amountWidgetValue = hasValue ? this.valueAsNumber.toString() : null; + } + } + + getLocale(): string { + const defaultLocale = 'en-US'; + if (typeof window?.navigator === 'undefined') { + return defaultLocale; + } + const wn = window.navigator as Navigator; + let lang = wn.languages ? wn.languages[0] : defaultLocale; + lang = lang || wn.language; + return lang; + } + + onFieldChangedAmountWidget(): void { + this.field.value = this.amountWidgetValue; + super.onFieldChanged(this.field); + } + + setInitialValues(): void { + if (this.enableDisplayBasedOnLocale) { + this.decimalProperty = this.field.enableFractions ? this.showDecimalDigits : this.notShowDecimalDigits; + this.locale = this.getLocale(); + this.valueAsNumber = this.field.value; + this.amountWidgetValue = this.currencyPipe.transform( + this.field.value, + this.currency, + this.currencyDisplay, + this.decimalProperty, + this.locale + ); + } else { + this.amountWidgetValue = this.field.value; + } + } + + updateSettingsBasedProperties(data: AmountWidgetSettings): void { + this.enableDisplayBasedOnLocale = data?.enableDisplayBasedOnLocale ?? false; + this.showReadonlyPlaceholder = data?.showReadonlyPlaceholder; + } }