mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 06:43:35 +00:00
Billing/pm 22625/tax id should not be saved when hidden (#15905)
* fix: add logic for ngOnUpdate * fix: use markAsTouched function * tests: add component tests
This commit is contained in:
@@ -0,0 +1,262 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { SimpleChange } from "@angular/core";
|
||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { ReactiveFormsModule } from "@angular/forms";
|
||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { SelectModule, FormFieldModule, BitSubmitDirective } from "@bitwarden/components";
|
||||||
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
|
|
||||||
|
import { ManageTaxInformationComponent } from "./manage-tax-information.component";
|
||||||
|
|
||||||
|
describe("ManageTaxInformationComponent", () => {
|
||||||
|
let sut: ManageTaxInformationComponent;
|
||||||
|
let fixture: ComponentFixture<ManageTaxInformationComponent>;
|
||||||
|
let mockTaxService: MockProxy<TaxServiceAbstraction>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
mockTaxService = mock();
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ManageTaxInformationComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: TaxServiceAbstraction, useValue: mockTaxService },
|
||||||
|
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
SelectModule,
|
||||||
|
FormFieldModule,
|
||||||
|
BitSubmitDirective,
|
||||||
|
I18nPipe,
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ManageTaxInformationComponent);
|
||||||
|
sut = fixture.componentInstance;
|
||||||
|
fixture.autoDetectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates successfully", () => {
|
||||||
|
expect(sut).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize with all values empty in startWith", async () => {
|
||||||
|
// Arrange
|
||||||
|
sut.startWith = {
|
||||||
|
country: "",
|
||||||
|
postalCode: "",
|
||||||
|
taxId: "",
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const startWithValue = sut.startWith;
|
||||||
|
expect(startWithValue.line1).toHaveLength(0);
|
||||||
|
expect(startWithValue.line2).toHaveLength(0);
|
||||||
|
expect(startWithValue.city).toHaveLength(0);
|
||||||
|
expect(startWithValue.state).toHaveLength(0);
|
||||||
|
expect(startWithValue.postalCode).toHaveLength(0);
|
||||||
|
expect(startWithValue.country).toHaveLength(0);
|
||||||
|
expect(startWithValue.taxId).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update the tax information protected state when form is updated", async () => {
|
||||||
|
// Arrange
|
||||||
|
const line1Value = "123 Street";
|
||||||
|
const line2Value = "Apt. 5";
|
||||||
|
const cityValue = "New York";
|
||||||
|
const stateValue = "NY";
|
||||||
|
const countryValue = "USA";
|
||||||
|
const postalCodeValue = "123 Street";
|
||||||
|
|
||||||
|
sut.startWith = {
|
||||||
|
country: countryValue,
|
||||||
|
postalCode: "",
|
||||||
|
taxId: "",
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
};
|
||||||
|
sut.showTaxIdField = false;
|
||||||
|
mockTaxService.isCountrySupported.mockResolvedValue(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sut.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const line1: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='line1']",
|
||||||
|
);
|
||||||
|
const line2: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='line2']",
|
||||||
|
);
|
||||||
|
const city: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='city']",
|
||||||
|
);
|
||||||
|
const state: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='state']",
|
||||||
|
);
|
||||||
|
const postalCode: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='postalCode']",
|
||||||
|
);
|
||||||
|
|
||||||
|
line1.value = line1Value;
|
||||||
|
line2.value = line2Value;
|
||||||
|
city.value = cityValue;
|
||||||
|
state.value = stateValue;
|
||||||
|
postalCode.value = postalCodeValue;
|
||||||
|
|
||||||
|
line1.dispatchEvent(new Event("input"));
|
||||||
|
line2.dispatchEvent(new Event("input"));
|
||||||
|
city.dispatchEvent(new Event("input"));
|
||||||
|
state.dispatchEvent(new Event("input"));
|
||||||
|
postalCode.dispatchEvent(new Event("input"));
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
// Assert that the internal tax information reflects the form
|
||||||
|
const taxInformation = sut.getTaxInformation();
|
||||||
|
expect(taxInformation.line1).toBe(line1Value);
|
||||||
|
expect(taxInformation.line2).toBe(line2Value);
|
||||||
|
expect(taxInformation.city).toBe(cityValue);
|
||||||
|
expect(taxInformation.state).toBe(stateValue);
|
||||||
|
expect(taxInformation.postalCode).toBe(postalCodeValue);
|
||||||
|
expect(taxInformation.country).toBe(countryValue);
|
||||||
|
expect(taxInformation.taxId).toHaveLength(0);
|
||||||
|
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue);
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show address fields except postal code if country is not supported for taxes", async () => {
|
||||||
|
// Arrange
|
||||||
|
const countryValue = "UNKNOWN";
|
||||||
|
sut.startWith = {
|
||||||
|
country: countryValue,
|
||||||
|
postalCode: "",
|
||||||
|
taxId: "",
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
};
|
||||||
|
sut.showTaxIdField = false;
|
||||||
|
mockTaxService.isCountrySupported.mockResolvedValue(false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sut.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const line1: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='line1']",
|
||||||
|
);
|
||||||
|
const line2: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='line2']",
|
||||||
|
);
|
||||||
|
const city: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='city']",
|
||||||
|
);
|
||||||
|
const state: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='state']",
|
||||||
|
);
|
||||||
|
const postalCode: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='postalCode']",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(line1).toBeNull();
|
||||||
|
expect(line2).toBeNull();
|
||||||
|
expect(city).toBeNull();
|
||||||
|
expect(state).toBeNull();
|
||||||
|
//Should be visible
|
||||||
|
expect(postalCode).toBeTruthy();
|
||||||
|
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue);
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show the tax id field if showTaxIdField is set to false", async () => {
|
||||||
|
// Arrange
|
||||||
|
const countryValue = "USA";
|
||||||
|
sut.startWith = {
|
||||||
|
country: countryValue,
|
||||||
|
postalCode: "",
|
||||||
|
taxId: "",
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
sut.showTaxIdField = false;
|
||||||
|
mockTaxService.isCountrySupported.mockResolvedValue(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sut.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const taxId: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='taxId']",
|
||||||
|
);
|
||||||
|
expect(taxId).toBeNull();
|
||||||
|
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue);
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clear the tax id field if showTaxIdField is set to false after being true", async () => {
|
||||||
|
// Arrange
|
||||||
|
const countryValue = "USA";
|
||||||
|
const taxIdValue = "A12345678";
|
||||||
|
|
||||||
|
sut.startWith = {
|
||||||
|
country: countryValue,
|
||||||
|
postalCode: "",
|
||||||
|
taxId: taxIdValue,
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
};
|
||||||
|
sut.showTaxIdField = true;
|
||||||
|
|
||||||
|
mockTaxService.isCountrySupported.mockResolvedValue(true);
|
||||||
|
await sut.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const initialTaxIdValue = fixture.nativeElement.querySelector(
|
||||||
|
"input[formControlName='taxId']",
|
||||||
|
).value;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.showTaxIdField = false;
|
||||||
|
sut.ngOnChanges({ showTaxIdField: new SimpleChange(true, false, false) });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const taxId = fixture.nativeElement.querySelector("input[formControlName='taxId']");
|
||||||
|
expect(taxId).toBeNull();
|
||||||
|
|
||||||
|
const taxInformation = sut.getTaxInformation();
|
||||||
|
expect(taxInformation.taxId).toBeNull();
|
||||||
|
expect(initialTaxIdValue).toEqual(taxIdValue);
|
||||||
|
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue);
|
||||||
|
expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
SimpleChanges,
|
||||||
|
} from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { Subject, takeUntil } from "rxjs";
|
||||||
import { debounceTime } from "rxjs/operators";
|
import { debounceTime } from "rxjs/operators";
|
||||||
@@ -13,7 +22,7 @@ import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/model
|
|||||||
templateUrl: "./manage-tax-information.component.html",
|
templateUrl: "./manage-tax-information.component.html",
|
||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class ManageTaxInformationComponent implements OnInit, OnDestroy {
|
export class ManageTaxInformationComponent implements OnInit, OnDestroy, OnChanges {
|
||||||
@Input() startWith: TaxInformation;
|
@Input() startWith: TaxInformation;
|
||||||
@Input() onSubmit?: (taxInformation: TaxInformation) => Promise<void>;
|
@Input() onSubmit?: (taxInformation: TaxInformation) => Promise<void>;
|
||||||
@Input() showTaxIdField: boolean = true;
|
@Input() showTaxIdField: boolean = true;
|
||||||
@@ -56,7 +65,7 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit = async () => {
|
submit = async () => {
|
||||||
this.formGroup.markAllAsTouched();
|
this.markAllAsTouched();
|
||||||
if (this.formGroup.invalid) {
|
if (this.formGroup.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -65,7 +74,7 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
validate(): boolean {
|
validate(): boolean {
|
||||||
this.formGroup.markAllAsTouched();
|
this.markAllAsTouched();
|
||||||
return this.formGroup.valid;
|
return this.formGroup.valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +151,13 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
// Clear the value of the tax-id if states have been changed in the parent component
|
||||||
|
if (!changes.showTaxIdField.currentValue) {
|
||||||
|
this.formGroup.controls.taxId.setValue(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
|
|||||||
Reference in New Issue
Block a user