1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[PM-9809] attachments v2 refactor (#10142)

* update attachments v2 view. using download attachment component. remove excess code. Refactor location of attachments v2
This commit is contained in:
Jason Ng
2024-07-23 13:27:39 -04:00
committed by GitHub
parent decc7a3031
commit 6041c460b7
19 changed files with 126 additions and 196 deletions

View File

@@ -0,0 +1,273 @@
import { Component, Input } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { mock } from "jest-mock-extended";
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 { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ButtonComponent, ToastService } from "@bitwarden/components";
import { DownloadAttachmentComponent } from "@bitwarden/vault";
import { CipherAttachmentsComponent } from "./cipher-attachments.component";
import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component";
@Component({
standalone: true,
selector: "app-download-attachment",
template: "",
})
class MockDownloadAttachmentComponent {
@Input() attachment: AttachmentView;
@Input() cipher: CipherView;
}
describe("CipherAttachmentsComponent", () => {
let component: CipherAttachmentsComponent;
let fixture: ComponentFixture<CipherAttachmentsComponent>;
const showToast = jest.fn();
const cipherView = {
id: "5555-444-3333",
type: CipherType.Login,
name: "Test Login",
login: {
username: "username",
password: "password",
},
} as CipherView;
const cipherDomain = {
decrypt: () => cipherView,
};
const cipherServiceGet = jest.fn().mockResolvedValue(cipherDomain);
const saveAttachmentWithServer = jest.fn().mockResolvedValue(cipherDomain);
beforeEach(async () => {
cipherServiceGet.mockClear();
showToast.mockClear();
saveAttachmentWithServer.mockClear().mockResolvedValue(cipherDomain);
await TestBed.configureTestingModule({
imports: [CipherAttachmentsComponent],
providers: [
{
provide: CipherService,
useValue: {
get: cipherServiceGet,
saveAttachmentWithServer,
getKeyForCipherKeyDecryption: () => Promise.resolve(null),
},
},
{
provide: ToastService,
useValue: {
showToast,
},
},
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: LogService, useValue: mock<LogService>() },
{ provide: ConfigService, useValue: mock<ConfigService>() },
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
],
})
.overrideComponent(CipherAttachmentsComponent, {
remove: {
imports: [DownloadAttachmentComponent],
},
add: {
imports: [MockDownloadAttachmentComponent],
},
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CipherAttachmentsComponent);
component = fixture.componentInstance;
component.cipherId = "5555-444-3333" as CipherId;
component.submitBtn = {} as ButtonComponent;
fixture.detectChanges();
});
it("fetches cipherView using `cipherId`", async () => {
await component.ngOnInit();
expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333");
expect(component.cipher).toEqual(cipherView);
});
it("sets testids for automation testing", () => {
const attachment = {
id: "1234-5678",
fileName: "test file.txt",
sizeName: "244.2 KB",
} as AttachmentView;
component.cipher.attachments = [attachment];
fixture.detectChanges();
const fileName = fixture.debugElement.query(By.css('[data-testid="file-name"]'));
const fileSize = fixture.debugElement.query(By.css('[data-testid="file-size"]'));
expect(fileName.nativeElement.textContent).toEqual(attachment.fileName);
expect(fileSize.nativeElement.textContent).toEqual(attachment.sizeName);
});
describe("bitSubmit", () => {
beforeEach(() => {
component.submitBtn.disabled = undefined;
component.submitBtn.loading = undefined;
});
it("updates sets initial state of the submit button", async () => {
await component.ngOnInit();
expect(component.submitBtn.disabled).toBe(true);
});
it("sets submitBtn loading state", () => {
component.bitSubmit.loading = true;
expect(component.submitBtn.loading).toBe(true);
component.bitSubmit.loading = false;
expect(component.submitBtn.loading).toBe(false);
});
it("sets submitBtn disabled state", () => {
component.bitSubmit.disabled = true;
expect(component.submitBtn.disabled).toBe(true);
component.bitSubmit.disabled = false;
expect(component.submitBtn.disabled).toBe(false);
});
});
describe("attachmentForm", () => {
let file: File;
beforeEach(() => {
component.submitBtn.disabled = undefined;
file = new File([""], "attachment.txt", { type: "text/plain" });
const inputElement = fixture.debugElement.query(By.css("input[type=file]"));
// Set the file value of the input element
Object.defineProperty(inputElement.nativeElement, "files", {
value: [file],
writable: false,
});
// Trigger change event, for event listeners
inputElement.nativeElement.dispatchEvent(new InputEvent("change"));
});
it("sets value of `file` control when input changes", () => {
expect(component.attachmentForm.controls.file.value.name).toEqual(file.name);
});
it("updates disabled state of submit button", () => {
expect(component.submitBtn.disabled).toBe(false);
});
});
describe("submit", () => {
describe("error", () => {
it("shows error toast if no file is selected", async () => {
await component.submit();
expect(showToast).toHaveBeenCalledWith({
variant: "error",
title: "errorOccurred",
message: "selectFile",
});
});
it("shows error toast if file size is greater than 500MB", async () => {
component.attachmentForm.controls.file.setValue({
size: 524288001,
} as File);
await component.submit();
expect(showToast).toHaveBeenCalledWith({
variant: "error",
title: "errorOccurred",
message: "maxFileSize",
});
});
});
describe("success", () => {
const file = { size: 524287999 } as File;
beforeEach(() => {
component.attachmentForm.controls.file.setValue(file);
});
it("calls `saveAttachmentWithServer`", async () => {
await component.submit();
expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file);
});
it("resets form and input values", async () => {
await component.submit();
const fileInput = fixture.debugElement.query(By.css("input[type=file]"));
expect(fileInput.nativeElement.value).toEqual("");
expect(component.attachmentForm.controls.file.value).toEqual(null);
});
it("shows success toast", async () => {
await component.submit();
expect(showToast).toHaveBeenCalledWith({
variant: "success",
title: null,
message: "attachmentSaved",
});
});
it('emits "onUploadSuccess"', async () => {
const emitSpy = jest.spyOn(component.onUploadSuccess, "emit");
await component.submit();
expect(emitSpy).toHaveBeenCalled();
});
});
});
describe("removeAttachment", () => {
const attachment = { id: "1234-5678" } as AttachmentView;
beforeEach(() => {
component.cipher.attachments = [attachment];
fixture.detectChanges();
});
it("removes attachment from cipher", () => {
const deleteAttachmentComponent = fixture.debugElement.query(
By.directive(DeleteAttachmentComponent),
).componentInstance as DeleteAttachmentComponent;
deleteAttachmentComponent.onDeletionSuccess.emit();
expect(component.cipher.attachments).toEqual([]);
});
});
});