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"),
});