mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +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:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<vault-cipher-form-generator
|
||||
[type]="data.type"
|
||||
(valueGenerated)="onCredentialGenerated($event)"
|
||||
[onAlgorithmSelected]="onAlgorithmSelected"
|
||||
(algorithmSelected)="onAlgorithmSelected($event)"
|
||||
/>
|
||||
<bit-item>
|
||||
<button
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import {
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
@@ -44,16 +45,17 @@ export class CredentialGeneratorDialogComponent {
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: CredentialGeneratorParams,
|
||||
private dialogService: DialogService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
onAlgorithmSelected = (selected?: AlgorithmInfo) => {
|
||||
if (selected) {
|
||||
this.buttonLabel = selected.useGeneratedValue;
|
||||
} else {
|
||||
// clear the credential value when the user is
|
||||
// selecting the credential generation algorithm
|
||||
this.credentialValue = undefined;
|
||||
// default to email
|
||||
this.buttonLabel = this.i18nService.t("useThisEmail");
|
||||
}
|
||||
this.credentialValue = undefined;
|
||||
};
|
||||
|
||||
applyCredentials = () => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<bit-dialog dialogSize="default" background="alt">
|
||||
<span bitDialogTitle>
|
||||
{{ title }}
|
||||
{{ titleKey | i18n }}
|
||||
</span>
|
||||
<ng-container bitDialogContent>
|
||||
<vault-cipher-form-generator
|
||||
[type]="params.type"
|
||||
[uri]="uri"
|
||||
(valueGenerated)="onValueGenerated($event)"
|
||||
(algorithmSelected)="onAlgorithmSelected($event)"
|
||||
disableMargin
|
||||
></vault-cipher-form-generator>
|
||||
</ng-container>
|
||||
@@ -17,8 +18,9 @@
|
||||
buttonType="primary"
|
||||
(click)="selectValue()"
|
||||
data-testid="select-button"
|
||||
[disabled]="!(buttonLabel && generatedValue)"
|
||||
>
|
||||
{{ selectButtonText }}
|
||||
{{ buttonLabel }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Input, Output, EventEmitter } 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { AlgorithmInfo } from "@bitwarden/generator-core";
|
||||
import { CipherFormGeneratorComponent } from "@bitwarden/vault";
|
||||
|
||||
import {
|
||||
WebVaultGeneratorDialogAction,
|
||||
WebVaultGeneratorDialogComponent,
|
||||
WebVaultGeneratorDialogParams,
|
||||
WebVaultGeneratorDialogResult,
|
||||
} from "./web-generator-dialog.component";
|
||||
|
||||
@Component({
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
standalone: true,
|
||||
})
|
||||
class MockCipherFormGenerator {
|
||||
@Input() type: "password" | "username";
|
||||
@Input() type: "password" | "username" = "password";
|
||||
@Output() algorithmSelected: EventEmitter<AlgorithmInfo> = new EventEmitter();
|
||||
@Input() uri?: string;
|
||||
@Output() valueGenerated = new EventEmitter<string>();
|
||||
}
|
||||
@@ -30,35 +31,20 @@ class MockCipherFormGenerator {
|
||||
describe("WebVaultGeneratorDialogComponent", () => {
|
||||
let component: WebVaultGeneratorDialogComponent;
|
||||
let fixture: ComponentFixture<WebVaultGeneratorDialogComponent>;
|
||||
|
||||
let dialogRef: MockProxy<DialogRef<any>>;
|
||||
let dialogRef: MockProxy<DialogRef<WebVaultGeneratorDialogResult>>;
|
||||
let mockI18nService: MockProxy<I18nService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
dialogRef = mock<DialogRef<any>>();
|
||||
dialogRef = mock<DialogRef<WebVaultGeneratorDialogResult>>();
|
||||
mockI18nService = mock<I18nService>();
|
||||
|
||||
const mockDialogData: WebVaultGeneratorDialogParams = { type: "password" };
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule, WebVaultGeneratorDialogComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: DialogRef,
|
||||
useValue: dialogRef,
|
||||
},
|
||||
{
|
||||
provide: DIALOG_DATA,
|
||||
useValue: mockDialogData,
|
||||
},
|
||||
{
|
||||
provide: I18nService,
|
||||
useValue: mockI18nService,
|
||||
},
|
||||
{
|
||||
provide: PlatformUtilsService,
|
||||
useValue: mock<PlatformUtilsService>(),
|
||||
},
|
||||
{ provide: DialogRef, useValue: dialogRef },
|
||||
{ provide: DIALOG_DATA, useValue: { type: "password" } },
|
||||
{ provide: I18nService, useValue: mockI18nService },
|
||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||
],
|
||||
})
|
||||
.overrideComponent(WebVaultGeneratorDialogComponent, {
|
||||
@@ -72,38 +58,73 @@ describe("WebVaultGeneratorDialogComponent", () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("initializes without errors", () => {
|
||||
fixture.detectChanges();
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("closes the dialog with 'canceled' result when close is called", () => {
|
||||
const closeSpy = jest.spyOn(dialogRef, "close");
|
||||
it("should enable button when value and algorithm are selected", () => {
|
||||
const generator = fixture.debugElement.query(
|
||||
By.css("vault-cipher-form-generator"),
|
||||
).componentInstance;
|
||||
|
||||
(component as any).close();
|
||||
generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any);
|
||||
generator.valueGenerated.emit("test-password");
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(closeSpy).toHaveBeenCalledWith({
|
||||
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 close with selected value when confirmed", () => {
|
||||
const generator = fixture.debugElement.query(
|
||||
By.css("vault-cipher-form-generator"),
|
||||
).componentInstance;
|
||||
generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any);
|
||||
generator.valueGenerated.emit("test-password");
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.debugElement.query(By.css("[data-testid='select-button']")).nativeElement.click();
|
||||
|
||||
expect(dialogRef.close).toHaveBeenCalledWith({
|
||||
action: WebVaultGeneratorDialogAction.Selected,
|
||||
generatedValue: "test-password",
|
||||
});
|
||||
});
|
||||
|
||||
it("should close with canceled action when dismissed", () => {
|
||||
component["close"]();
|
||||
expect(dialogRef.close).toHaveBeenCalledWith({
|
||||
action: WebVaultGeneratorDialogAction.Canceled,
|
||||
});
|
||||
});
|
||||
|
||||
it("closes the dialog with 'selected' result when selectValue is called", () => {
|
||||
const closeSpy = jest.spyOn(dialogRef, "close");
|
||||
const generatedValue = "generated-value";
|
||||
component.onValueGenerated(generatedValue);
|
||||
|
||||
(component as any).selectValue();
|
||||
|
||||
expect(closeSpy).toHaveBeenCalledWith({
|
||||
action: WebVaultGeneratorDialogAction.Selected,
|
||||
generatedValue: generatedValue,
|
||||
});
|
||||
});
|
||||
|
||||
it("updates generatedValue when onValueGenerated is called", () => {
|
||||
const generatedValue = "new-generated-value";
|
||||
component.onValueGenerated(generatedValue);
|
||||
|
||||
expect((component as any).generatedValue).toBe(generatedValue);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
|
||||
import { AlgorithmInfo } from "@bitwarden/generator-core";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
import { CipherFormGeneratorComponent } from "@bitwarden/vault";
|
||||
|
||||
export interface WebVaultGeneratorDialogParams {
|
||||
@@ -27,13 +29,11 @@ export enum WebVaultGeneratorDialogAction {
|
||||
selector: "web-vault-generator-dialog",
|
||||
templateUrl: "./web-generator-dialog.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, CipherFormGeneratorComponent, ButtonModule, DialogModule],
|
||||
imports: [CommonModule, CipherFormGeneratorComponent, ButtonModule, DialogModule, I18nPipe],
|
||||
})
|
||||
export class WebVaultGeneratorDialogComponent {
|
||||
protected title = this.i18nService.t(this.isPassword ? "passwordGenerator" : "usernameGenerator");
|
||||
protected selectButtonText = this.i18nService.t(
|
||||
this.isPassword ? "useThisPassword" : "useThisUsername",
|
||||
);
|
||||
protected titleKey = this.isPassword ? "passwordGenerator" : "usernameGenerator";
|
||||
protected buttonLabel: string | undefined;
|
||||
|
||||
/**
|
||||
* Whether the dialog is generating a password/passphrase. If false, it is generating a username.
|
||||
@@ -80,6 +80,16 @@ export class WebVaultGeneratorDialogComponent {
|
||||
this.generatedValue = value;
|
||||
}
|
||||
|
||||
onAlgorithmSelected = (selected?: AlgorithmInfo) => {
|
||||
if (selected) {
|
||||
this.buttonLabel = selected.useGeneratedValue;
|
||||
} else {
|
||||
// default to email
|
||||
this.buttonLabel = this.i18nService.t("useThisEmail");
|
||||
}
|
||||
this.generatedValue = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the vault generator dialog.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
@@ -18,9 +16,6 @@ import { AlgorithmInfo, GeneratedCredential } from "@bitwarden/generator-core";
|
||||
imports: [CommonModule, GeneratorModule],
|
||||
})
|
||||
export class CipherFormGeneratorComponent {
|
||||
@Input()
|
||||
onAlgorithmSelected: (selected: AlgorithmInfo) => void;
|
||||
|
||||
@Input()
|
||||
uri: string = "";
|
||||
|
||||
@@ -28,17 +23,25 @@ export class CipherFormGeneratorComponent {
|
||||
* The type of generator form to show.
|
||||
*/
|
||||
@Input({ required: true })
|
||||
type: "password" | "username";
|
||||
type: "password" | "username" = "password";
|
||||
|
||||
/** Removes bottom margin of internal sections */
|
||||
@Input({ transform: coerceBooleanProperty }) disableMargin = false;
|
||||
|
||||
@Output()
|
||||
algorithmSelected = new EventEmitter<AlgorithmInfo>();
|
||||
|
||||
/**
|
||||
* Emits an event when a new value is generated.
|
||||
*/
|
||||
@Output()
|
||||
valueGenerated = new EventEmitter<string>();
|
||||
|
||||
/** Event handler for when an algorithm is selected */
|
||||
onAlgorithmSelected = (selected: AlgorithmInfo) => {
|
||||
this.algorithmSelected.emit(selected);
|
||||
};
|
||||
|
||||
/** Event handler for both generation components */
|
||||
onCredentialGenerated = (generatedCred: GeneratedCredential) => {
|
||||
this.valueGenerated.emit(generatedCred.credential);
|
||||
|
||||
Reference in New Issue
Block a user