mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-06 23:51:28 +01:00
[PM-16245] delete btn in browser edit item (#12876)
* send the user back to vault after deleting from edit view * [PM-17443] Navigation After Deletion (#13023) * navigate to vault tab after cipher deletion --------- Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com>
This commit is contained in:
parent
caf3e77d1c
commit
2d488a8e68
@ -26,5 +26,15 @@
|
||||
<button bitButton type="submit" form="cipherForm" buttonType="primary" #submitBtn>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
slot="end"
|
||||
*ngIf="canDeleteCipher$ | async"
|
||||
[bitAction]="delete"
|
||||
type="button"
|
||||
buttonType="danger"
|
||||
bitIconButton="bwi-trash"
|
||||
[appA11yTitle]="'delete' | i18n"
|
||||
></button>
|
||||
</popup-footer>
|
||||
</popup-page>
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||
import {
|
||||
CipherFormConfig,
|
||||
@ -40,7 +42,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
const buildConfigResponse = { originalCipher: {} } as CipherFormConfig;
|
||||
const buildConfig = jest.fn((mode: CipherFormMode) =>
|
||||
Promise.resolve({ mode, ...buildConfigResponse }),
|
||||
Promise.resolve({ ...buildConfigResponse, mode }),
|
||||
);
|
||||
const queryParams$ = new BehaviorSubject({});
|
||||
const disable = jest.fn();
|
||||
@ -55,9 +57,10 @@ describe("AddEditV2Component", () => {
|
||||
back.mockClear();
|
||||
collect.mockClear();
|
||||
|
||||
addEditCipherInfo$ = new BehaviorSubject(null);
|
||||
addEditCipherInfo$ = new BehaviorSubject<AddEditCipherInfo | null>(null);
|
||||
cipherServiceMock = mock<CipherService>();
|
||||
cipherServiceMock.addEditCipherInfo$ = addEditCipherInfo$.asObservable();
|
||||
cipherServiceMock.addEditCipherInfo$ =
|
||||
addEditCipherInfo$.asObservable() as Observable<AddEditCipherInfo>;
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddEditV2Component],
|
||||
@ -71,6 +74,13 @@ describe("AddEditV2Component", () => {
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{ provide: CipherService, useValue: cipherServiceMock },
|
||||
{ provide: EventCollectionService, useValue: { collect } },
|
||||
{ provide: LogService, useValue: mock<LogService>() },
|
||||
{
|
||||
provide: CipherAuthorizationService,
|
||||
useValue: {
|
||||
canDeleteCipher$: jest.fn().mockReturnValue(true),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideProvider(CipherFormConfigService, {
|
||||
@ -92,7 +102,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("add");
|
||||
expect(buildConfig.mock.lastCall![0]).toBe("add");
|
||||
expect(component.config.mode).toBe("add");
|
||||
}));
|
||||
|
||||
@ -101,7 +111,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("clone");
|
||||
expect(buildConfig.mock.lastCall![0]).toBe("clone");
|
||||
expect(component.config.mode).toBe("clone");
|
||||
}));
|
||||
|
||||
@ -111,7 +121,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("edit");
|
||||
expect(buildConfig.mock.lastCall![0]).toBe("edit");
|
||||
expect(component.config.mode).toBe("edit");
|
||||
}));
|
||||
|
||||
@ -121,7 +131,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("edit");
|
||||
expect(buildConfig.mock.lastCall![0]).toBe("edit");
|
||||
expect(component.config.mode).toBe("partial-edit");
|
||||
}));
|
||||
});
|
||||
@ -218,7 +228,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
tick();
|
||||
|
||||
expect(component.config.initialValues.username).toBe("identity-username");
|
||||
expect(component.config.initialValues!.username).toBe("identity-username");
|
||||
}));
|
||||
|
||||
it("overrides query params with `addEditCipherInfo` values", fakeAsync(() => {
|
||||
@ -231,7 +241,7 @@ describe("AddEditV2Component", () => {
|
||||
|
||||
tick();
|
||||
|
||||
expect(component.config.initialValues.name).toBe("AddEditCipherName");
|
||||
expect(component.config.initialValues!.name).toBe("AddEditCipherName");
|
||||
}));
|
||||
|
||||
it("clears `addEditCipherInfo` after initialization", fakeAsync(() => {
|
||||
@ -326,4 +336,30 @@ describe("AddEditV2Component", () => {
|
||||
expect(back).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("delete", () => {
|
||||
it("dialogService openSimpleDialog called when deleteBtn is hit", async () => {
|
||||
const dialogSpy = jest
|
||||
.spyOn(component["dialogService"], "openSimpleDialog")
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await component.delete();
|
||||
expect(dialogSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call deleteCipher when user confirms deletion", async () => {
|
||||
const deleteCipherSpy = jest.spyOn(component as any, "deleteCipher");
|
||||
jest.spyOn(component["dialogService"], "openSimpleDialog").mockResolvedValue(true);
|
||||
|
||||
await component.delete();
|
||||
expect(deleteCipherSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("navigates to vault tab after deletion", async () => {
|
||||
jest.spyOn(component["dialogService"], "openSimpleDialog").mockResolvedValue(true);
|
||||
await component.delete();
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith(["/tabs/vault"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,18 +5,27 @@ import { Component, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { firstValueFrom, map, switchMap } from "rxjs";
|
||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||
import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
SearchModule,
|
||||
IconButtonModule,
|
||||
DialogService,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
CipherFormConfig,
|
||||
CipherFormConfigService,
|
||||
@ -131,11 +140,13 @@ export type AddEditQueryParams = Partial<Record<keyof QueryParams, string>>;
|
||||
CipherFormModule,
|
||||
AsyncActionsModule,
|
||||
PopOutComponent,
|
||||
IconButtonModule,
|
||||
],
|
||||
})
|
||||
export class AddEditV2Component implements OnInit {
|
||||
headerText: string;
|
||||
config: CipherFormConfig;
|
||||
canDeleteCipher$: Observable<boolean>;
|
||||
|
||||
get loading() {
|
||||
return this.config == null;
|
||||
@ -165,6 +176,10 @@ export class AddEditV2Component implements OnInit {
|
||||
private router: Router,
|
||||
private cipherService: CipherService,
|
||||
private eventCollectionService: EventCollectionService,
|
||||
private logService: LogService,
|
||||
private toastService: ToastService,
|
||||
private dialogService: DialogService,
|
||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||
) {
|
||||
this.subscribeToParams();
|
||||
}
|
||||
@ -281,6 +296,10 @@ export class AddEditV2Component implements OnInit {
|
||||
}
|
||||
|
||||
if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) {
|
||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(
|
||||
config.originalCipher,
|
||||
);
|
||||
|
||||
await this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientViewed,
|
||||
config.originalCipher.id,
|
||||
@ -337,6 +356,43 @@ export class AddEditV2Component implements OnInit {
|
||||
return this.i18nService.t(partOne, this.i18nService.t("typeSshKey"));
|
||||
}
|
||||
}
|
||||
|
||||
delete = async () => {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "deleteItem" },
|
||||
content: {
|
||||
key: "deleteItemConfirmation",
|
||||
},
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.deleteCipher();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.router.navigate(["/tabs/vault"]);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("deletedItem"),
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
protected deleteCipher() {
|
||||
return this.config.originalCipher.deletedDate
|
||||
? this.cipherService.deleteWithServer(this.config.originalCipher.id)
|
||||
: this.cipherService.softDeleteWithServer(this.config.originalCipher.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user