From dfb597c236876e8c7438d4a6dae37d7dde9b7868 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 22 Dec 2025 11:04:14 -0500 Subject: [PATCH] [PM-24015] Handle Send form empty password field properly (#17911) --- .../options/send-options.component.spec.ts | 67 +++++++++++++++++++ .../options/send-options.component.ts | 32 ++++++--- .../components/send-form.component.ts | 5 -- 3 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts new file mode 100644 index 00000000000..6724bb324c3 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.spec.ts @@ -0,0 +1,67 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { CredentialGeneratorService } from "@bitwarden/generator-core"; + +import { SendFormContainer } from "../../send-form-container"; + +import { SendOptionsComponent } from "./send-options.component"; + +describe("SendOptionsComponent", () => { + let component: SendOptionsComponent; + let fixture: ComponentFixture; + const mockSendFormContainer = mock(); + const mockAccountService = mock(); + + beforeAll(() => { + mockAccountService.activeAccount$ = of({ id: "myTestAccount" } as Account); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SendOptionsComponent], + declarations: [], + providers: [ + { provide: SendFormContainer, useValue: mockSendFormContainer }, + { provide: DialogService, useValue: mock() }, + { provide: SendApiService, useValue: mock() }, + { provide: PolicyService, useValue: mock() }, + { provide: I18nService, useValue: mock() }, + { provide: ToastService, useValue: mock() }, + { provide: CredentialGeneratorService, useValue: mock() }, + { provide: AccountService, useValue: mockAccountService }, + { provide: PlatformUtilsService, useValue: mock() }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(SendOptionsComponent); + component = fixture.componentInstance; + component.config = { areSendsAllowed: true, mode: "add", sendType: SendType.Text }; + fixture.detectChanges(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should emit a null password when password textbox is empty", async () => { + const newSend = {} as SendView; + mockSendFormContainer.patchSend.mockImplementation((updateFn) => updateFn(newSend)); + component.sendOptionsForm.patchValue({ password: "testing" }); + expect(newSend.password).toBe("testing"); + component.sendOptionsForm.patchValue({ password: "" }); + expect(newSend.password).toBe(null); + }); +}); diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 2ddb10dc80b..ae8706a375e 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -4,7 +4,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; -import { BehaviorSubject, firstValueFrom, map, switchMap } from "rxjs"; +import { BehaviorSubject, firstValueFrom, map, switchMap, tap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -12,6 +12,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { pin } from "@bitwarden/common/tools/rx"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -112,18 +113,27 @@ export class SendOptionsComponent implements OnInit { this.disableHideEmail = disableHideEmail; }); - this.sendOptionsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { - this.sendFormContainer.patchSend((send) => { - Object.assign(send, { - maxAccessCount: value.maxAccessCount, - accessCount: value.accessCount, - password: value.password, - hideEmail: value.hideEmail, - notes: value.notes, + this.sendOptionsForm.valueChanges + .pipe( + tap((value) => { + if (Utils.isNullOrWhitespace(value.password)) { + value.password = null; + } + }), + takeUntilDestroyed(), + ) + .subscribe((value) => { + this.sendFormContainer.patchSend((send) => { + Object.assign(send, { + maxAccessCount: value.maxAccessCount, + accessCount: value.accessCount, + password: value.password, + hideEmail: value.hideEmail, + notes: value.notes, + }); + return send; }); - return send; }); - }); } generatePassword = async () => { diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts index fcd3b0cb7ea..0471ed90eef 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts @@ -18,7 +18,6 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { @@ -227,10 +226,6 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send return; } - if (Utils.isNullOrWhitespace(this.updatedSendView.password)) { - this.updatedSendView.password = null; - } - this.toastService.showToast({ variant: "success", title: null,