From ffc9022f5450c70602e5449f06bbd177e35f1108 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:24:07 -0500 Subject: [PATCH] [PM-10424][PM-10425] Extension Refresh - Copy success toast (#10353) * add option to pass toast variant into copy-click directive * refactor copy toast to use success variant * add tests for copy-click directive * swap `success` to be the default toast variant --- .../directives/copy-click.directive.spec.ts | 90 +++++++++++++++++++ .../src/directives/copy-click.directive.ts | 33 ++++++- 2 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 libs/angular/src/directives/copy-click.directive.spec.ts diff --git a/libs/angular/src/directives/copy-click.directive.spec.ts b/libs/angular/src/directives/copy-click.directive.spec.ts new file mode 100644 index 00000000000..a64b6658b3a --- /dev/null +++ b/libs/angular/src/directives/copy-click.directive.spec.ts @@ -0,0 +1,90 @@ +import { Component, ElementRef, ViewChild } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; + +import { CopyClickDirective } from "./copy-click.directive"; + +@Component({ + template: ` + + + + `, +}) +class TestCopyClickComponent { + @ViewChild("noToast") noToastButton: ElementRef; + @ViewChild("infoToast") infoToastButton: ElementRef; + @ViewChild("successToast") successToastButton: ElementRef; +} + +describe("CopyClickDirective", () => { + let fixture: ComponentFixture; + const copyToClipboard = jest.fn(); + const showToast = jest.fn(); + + beforeEach(async () => { + copyToClipboard.mockClear(); + showToast.mockClear(); + + await TestBed.configureTestingModule({ + declarations: [CopyClickDirective, TestCopyClickComponent], + providers: [ + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: PlatformUtilsService, useValue: { copyToClipboard } }, + { provide: ToastService, useValue: { showToast } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(TestCopyClickComponent); + fixture.detectChanges(); + }); + + it("copies the the value for all variants of toasts ", () => { + const noToastButton = fixture.componentInstance.noToastButton.nativeElement; + + noToastButton.click(); + expect(copyToClipboard).toHaveBeenCalledWith("no toast shown"); + + const infoToastButton = fixture.componentInstance.infoToastButton.nativeElement; + + infoToastButton.click(); + expect(copyToClipboard).toHaveBeenCalledWith("info toast shown"); + + const successToastButton = fixture.componentInstance.successToastButton.nativeElement; + + successToastButton.click(); + expect(copyToClipboard).toHaveBeenCalledWith("success toast shown"); + }); + + it("does not show a toast when showToast is not present", () => { + const noToastButton = fixture.componentInstance.noToastButton.nativeElement; + + noToastButton.click(); + expect(showToast).not.toHaveBeenCalled(); + }); + + it("shows a success toast when showToast is present", () => { + const successToastButton = fixture.componentInstance.successToastButton.nativeElement; + + successToastButton.click(); + expect(showToast).toHaveBeenCalledWith({ + message: "copySuccessful", + title: null, + variant: "success", + }); + }); + + it("shows the toast variant when set with showToast", () => { + const infoToastButton = fixture.componentInstance.infoToastButton.nativeElement; + + infoToastButton.click(); + expect(showToast).toHaveBeenCalledWith({ + message: "copySuccessful", + title: null, + variant: "info", + }); + }); +}); diff --git a/libs/angular/src/directives/copy-click.directive.ts b/libs/angular/src/directives/copy-click.directive.ts index cee2bdde4e8..0d764c95edb 100644 --- a/libs/angular/src/directives/copy-click.directive.ts +++ b/libs/angular/src/directives/copy-click.directive.ts @@ -1,14 +1,17 @@ -import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { Directive, HostListener, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService } from "@bitwarden/components"; +import { ToastVariant } from "@bitwarden/components/src/toast/toast.component"; @Directive({ selector: "[appCopyClick]", }) export class CopyClickDirective { + private _showToast = false; + private toastVariant: ToastVariant = "success"; + constructor( private platformUtilsService: PlatformUtilsService, private toastService: ToastService, @@ -16,14 +19,36 @@ export class CopyClickDirective { ) {} @Input("appCopyClick") valueToCopy = ""; - @Input({ transform: coerceBooleanProperty }) showToast?: boolean; + + /** + * When set without a value, a success toast will be shown when the value is copied + * @example + * ```html + * + * ``` + * When set with a value, a toast with the specified variant will be shown when the value is copied + * + * @example + * ```html + * + * ``` + */ + @Input() set showToast(value: ToastVariant | "") { + // When the `showToast` is set without a value, an empty string will be passed + if (value === "") { + this._showToast = true; + } else { + this._showToast = true; + this.toastVariant = value; + } + } @HostListener("click") onClick() { this.platformUtilsService.copyToClipboard(this.valueToCopy); - if (this.showToast) { + if (this._showToast) { this.toastService.showToast({ - variant: "info", + variant: this.toastVariant, title: null, message: this.i18nService.t("copySuccessful"), });