1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-04 01:23:57 +00:00

[PM-17820] - Browser/Web - update button and label state in username generator (#13189)

* add event handling for username generator

* fix specs. change function name to not be of an event type

* update specs

* rename function

* revert name change

* fix spec

* bubble algorithmSelected up to generator components. add disabled button tests

* add typeSelected event

* revert addition of onType.

* apply same logic in onAlgorithmSelected to web and desktop
This commit is contained in:
Jordan Aasen
2025-03-03 11:44:34 -08:00
committed by GitHub
parent d01f0c6bc4
commit 13213585b2
9 changed files with 205 additions and 97 deletions

View File

@@ -3,13 +3,14 @@
slot="header"
[backAction]="close"
showBackButton
[pageTitle]="title"
[pageTitle]="titleKey | i18n"
></popup-header>
<vault-cipher-form-generator
[type]="params.type"
[uri]="uri"
(valueGenerated)="onValueGenerated($event)"
(algorithmSelected)="onAlgorithmSelected($event)"
></vault-cipher-form-generator>
<popup-footer slot="footer">
@@ -19,6 +20,7 @@
buttonType="primary"
(click)="selectValue()"
data-testid="select-button"
[disabled]="!(selectButtonText && generatedValue)"
>
{{ selectButtonText }}
</button>

View File

@@ -1,15 +1,18 @@
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { mock, MockProxy } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { AlgorithmInfo } from "@bitwarden/generator-core";
import { CipherFormGeneratorComponent } from "@bitwarden/vault";
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
import {
GeneratorDialogAction,
GeneratorDialogParams,
GeneratorDialogResult,
VaultGeneratorDialogComponent,
@@ -21,8 +24,9 @@ import {
standalone: true,
})
class MockCipherFormGenerator {
@Input() type: "password" | "username";
@Input() uri: string;
@Input() type: "password" | "username" = "password";
@Output() algorithmSelected: EventEmitter<AlgorithmInfo> = new EventEmitter();
@Input() uri: string = "";
@Output() valueGenerated = new EventEmitter<string>();
}
@@ -53,34 +57,87 @@ describe("VaultGeneratorDialogComponent", () => {
fixture = TestBed.createComponent(VaultGeneratorDialogComponent);
component = fixture.componentInstance;
});
it("should create", () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it("should use the appropriate text based on generator type", () => {
expect(component["title"]).toBe("passwordGenerator");
expect(component["selectButtonText"]).toBe("useThisPassword");
dialogData.type = "username";
fixture = TestBed.createComponent(VaultGeneratorDialogComponent);
component = fixture.componentInstance;
expect(component["title"]).toBe("usernameGenerator");
expect(component["selectButtonText"]).toBe("useThisUsername");
it("should show password generator title", () => {
const header = fixture.debugElement.query(By.css("popup-header")).componentInstance;
expect(header.pageTitle).toBe("passwordGenerator");
});
it("should close the dialog with the generated value when the user selects it", () => {
component["generatedValue"] = "generated-value";
it("should pass type to cipher form generator", () => {
const generator = fixture.debugElement.query(
By.css("vault-cipher-form-generator"),
).componentInstance;
expect(generator.type).toBe("password");
});
fixture.nativeElement.querySelector("button[data-testid='select-button']").click();
it("should enable select button when value is generated", () => {
component.onAlgorithmSelected({ useGeneratedValue: "Test" } as any);
component.onValueGenerated("test-password");
fixture.detectChanges();
const button = fixture.debugElement.query(
By.css("[data-testid='select-button']"),
).nativeElement;
expect(button.disabled).toBe(false);
});
it("should disable the button if no value has been generated", () => {
const generator = fixture.debugElement.query(
By.css("vault-cipher-form-generator"),
).componentInstance;
generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any);
fixture.detectChanges();
const button = fixture.debugElement.query(
By.css("[data-testid='select-button']"),
).nativeElement;
expect(button.disabled).toBe(true);
});
it("should disable the button if no algorithm is selected", () => {
const generator = fixture.debugElement.query(
By.css("vault-cipher-form-generator"),
).componentInstance;
generator.valueGenerated.emit("test-password");
fixture.detectChanges();
const button = fixture.debugElement.query(
By.css("[data-testid='select-button']"),
).nativeElement;
expect(button.disabled).toBe(true);
});
it("should update button text when algorithm is selected", () => {
component.onAlgorithmSelected({ useGeneratedValue: "Use This Password" } as any);
fixture.detectChanges();
const button = fixture.debugElement.query(
By.css("[data-testid='select-button']"),
).nativeElement;
expect(button.textContent.trim()).toBe("Use This Password");
});
it("should close with generated value when selected", () => {
component.onAlgorithmSelected({ useGeneratedValue: "Test" } as any);
component.onValueGenerated("test-password");
fixture.detectChanges();
fixture.debugElement.query(By.css("[data-testid='select-button']")).nativeElement.click();
expect(mockDialogRef.close).toHaveBeenCalledWith({
action: "selected",
generatedValue: "generated-value",
action: GeneratorDialogAction.Selected,
generatedValue: "test-password",
});
});
it("should close with canceled action when dismissed", () => {
fixture.debugElement.query(By.css("popup-header")).componentInstance.backAction();
expect(mockDialogRef.close).toHaveBeenCalledWith({
action: GeneratorDialogAction.Canceled,
});
});
});

View File

@@ -7,6 +7,8 @@ import { Component, Inject } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ButtonModule, DialogService } from "@bitwarden/components";
import { AlgorithmInfo } from "@bitwarden/generator-core";
import { I18nPipe } from "@bitwarden/ui-common";
import { CipherFormGeneratorComponent } from "@bitwarden/vault";
import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component";
@@ -39,13 +41,12 @@ export enum GeneratorDialogAction {
CommonModule,
CipherFormGeneratorComponent,
ButtonModule,
I18nPipe,
],
})
export class VaultGeneratorDialogComponent {
protected title = this.i18nService.t(this.isPassword ? "passwordGenerator" : "usernameGenerator");
protected selectButtonText = this.i18nService.t(
this.isPassword ? "useThisPassword" : "useThisUsername",
);
protected selectButtonText: string | undefined;
protected titleKey = this.isPassword ? "passwordGenerator" : "usernameGenerator";
/**
* Whether the dialog is generating a password/passphrase. If false, it is generating a username.
@@ -92,6 +93,16 @@ export class VaultGeneratorDialogComponent {
this.generatedValue = value;
}
onAlgorithmSelected = (selected?: AlgorithmInfo) => {
if (selected) {
this.selectButtonText = selected.useGeneratedValue;
} else {
// default to email
this.selectButtonText = this.i18nService.t("useThisEmail");
}
this.generatedValue = undefined;
};
/**
* Opens the vault generator dialog in a full screen dialog.
*/