1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-22756] Send minimizeOnCopy message during copy on Desktop platform (#15232)

* [PM-22756] Send minimizeOnCopy message during copy on Desktop platform

* [PM-22756] Introduce optional CopyClickListener pattern

* [PM-22756] Introduce CopyService that wraps PlatformUtilsService.copyToClipboard to allow scoped implementations

* [PM-22756] Introduce DesktopVaultCopyService that sends the minimizeOnCopy message

* [PM-22756] Remove leftover onCopy method

* [PM-22756] Fix failing tests

* [PM-22756] Revert CopyService solution

* [PM-22756] Cleanup

* [PM-22756] Update test

* [PM-22756] Cleanup leftover test changes

(cherry picked from commit e8f53fe9b7)
This commit is contained in:
Shane Melton
2025-06-18 14:44:21 -07:00
committed by Shane
parent bd734b962d
commit 381f528c53
3 changed files with 43 additions and 14 deletions

View File

@@ -13,8 +13,6 @@ import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rx
import { filter, map, take } from "rxjs/operators";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service";
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -43,6 +41,8 @@ import {
DialogService,
ItemModule,
ToastService,
CopyClickListener,
COPY_CLICK_LISTENER,
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import {
@@ -111,9 +111,13 @@ const BroadcasterSubscriptionId = "VaultComponent";
useClass: DesktopPremiumUpgradePromptService,
},
{ provide: CipherFormGenerationService, useClass: DesktopCredentialGenerationService },
{
provide: COPY_CLICK_LISTENER,
useExisting: VaultV2Component,
},
],
})
export class VaultV2Component implements OnInit, OnDestroy {
export class VaultV2Component implements OnInit, OnDestroy, CopyClickListener {
@ViewChild(VaultItemsV2Component, { static: true })
vaultItemsComponent: VaultItemsV2Component | null = null;
@ViewChild(VaultFilterComponent, { static: true })
@@ -152,14 +156,12 @@ export class VaultV2Component implements OnInit, OnDestroy {
),
);
private modal: ModalRef | null = null;
private componentIsDestroyed$ = new Subject<boolean>();
constructor(
private route: ActivatedRoute,
private router: Router,
private i18nService: I18nService,
private modalService: ModalService,
private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
@@ -356,6 +358,13 @@ export class VaultV2Component implements OnInit, OnDestroy {
}
}
/**
* Handler for Vault level CopyClickDirectives to send the minimizeOnCopy message
*/
onCopy() {
this.messagingService.send("minimizeOnCopy");
}
async viewCipher(cipher: CipherView) {
if (await this.shouldReprompt(cipher, "view")) {
return;

View File

@@ -1,10 +1,11 @@
import { Component, ElementRef, ViewChild } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "../";
import { ToastService, CopyClickListener, COPY_CLICK_LISTENER } from "../";
import { CopyClickDirective } from "./copy-click.directive";
@@ -34,10 +35,12 @@ describe("CopyClickDirective", () => {
let fixture: ComponentFixture<TestCopyClickComponent>;
const copyToClipboard = jest.fn();
const showToast = jest.fn();
const copyClickListener = mock<CopyClickListener>();
beforeEach(async () => {
copyToClipboard.mockClear();
showToast.mockClear();
copyClickListener.onCopy.mockClear();
await TestBed.configureTestingModule({
imports: [TestCopyClickComponent],
@@ -55,6 +58,7 @@ describe("CopyClickDirective", () => {
},
{ provide: PlatformUtilsService, useValue: { copyToClipboard } },
{ provide: ToastService, useValue: { showToast } },
{ provide: COPY_CLICK_LISTENER, useValue: copyClickListener },
],
}).compileComponents();
@@ -92,7 +96,6 @@ describe("CopyClickDirective", () => {
successToastButton.click();
expect(showToast).toHaveBeenCalledWith({
message: "copySuccessful",
title: null,
variant: "success",
});
});
@@ -103,7 +106,6 @@ describe("CopyClickDirective", () => {
infoToastButton.click();
expect(showToast).toHaveBeenCalledWith({
message: "copySuccessful",
title: null,
variant: "info",
});
});
@@ -115,8 +117,15 @@ describe("CopyClickDirective", () => {
expect(showToast).toHaveBeenCalledWith({
message: "valueCopied Content",
title: null,
variant: "success",
});
});
it("should call copyClickListener.onCopy when value is copied", () => {
const successToastButton = fixture.componentInstance.successToastButton.nativeElement;
successToastButton.click();
expect(copyClickListener.onCopy).toHaveBeenCalledWith("success toast shown");
});
});

View File

@@ -1,12 +1,19 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, HostListener, Input } from "@angular/core";
import { Directive, HostListener, Input, InjectionToken, Inject, Optional } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService, ToastVariant } from "../";
/**
* Listener that can be provided to receive copy events to allow for customized behavior.
*/
export interface CopyClickListener {
onCopy(value: string): void;
}
export const COPY_CLICK_LISTENER = new InjectionToken<CopyClickListener>("CopyClickListener");
@Directive({
selector: "[appCopyClick]",
})
@@ -18,6 +25,7 @@ export class CopyClickDirective {
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
private i18nService: I18nService,
@Optional() @Inject(COPY_CLICK_LISTENER) private copyListener?: CopyClickListener,
) {}
@Input("appCopyClick") valueToCopy = "";
@@ -26,7 +34,7 @@ export class CopyClickDirective {
* When set, the toast displayed will show `<valueLabel> copied`
* instead of the default messaging.
*/
@Input() valueLabel: string;
@Input() valueLabel?: string;
/**
* When set without a value, a success toast will be shown when the value is copied
@@ -54,6 +62,10 @@ export class CopyClickDirective {
@HostListener("click") onClick() {
this.platformUtilsService.copyToClipboard(this.valueToCopy);
if (this.copyListener) {
this.copyListener.onCopy(this.valueToCopy);
}
if (this._showToast) {
const message = this.valueLabel
? this.i18nService.t("valueCopied", this.valueLabel)
@@ -61,7 +73,6 @@ export class CopyClickDirective {
this.toastService.showToast({
variant: this.toastVariant,
title: null,
message,
});
}