diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 52d70e8652a..50577472120 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { importProvidersFrom, signal } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; import { action } from "@storybook/addon-actions"; import { applicationConfig, @@ -225,6 +226,14 @@ export default { getFeatureFlag: () => Promise.resolve(false), }, }, + { + provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: {}, + }, + }, + }, ], }), componentWrapperDecorator( diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts index 58a9b8f3965..af39ea96c16 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts @@ -83,4 +83,24 @@ describe("AddEditCustomFieldDialogComponent", () => { expect.objectContaining({ value: FieldType.Linked }), ); }); + + it("does not filter out 'Hidden' field type when 'disallowHiddenField' is false", () => { + dialogData.disallowHiddenField = false; + fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent); + component = fixture.componentInstance; + + expect(component.fieldTypeOptions).toContainEqual( + expect.objectContaining({ value: FieldType.Hidden }), + ); + }); + + it("filers out 'Hidden' field type when 'disallowHiddenField' is true", () => { + dialogData.disallowHiddenField = true; + fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent); + component = fixture.componentInstance; + + expect(component.fieldTypeOptions).not.toContainEqual( + expect.objectContaining({ value: FieldType.Hidden }), + ); + }); }); diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts index bdf5345672d..72bdf5dca1a 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts @@ -25,6 +25,7 @@ export type AddEditCustomFieldDialogData = { cipherType: CipherType; /** When provided, dialog will display edit label variants */ editLabelConfig?: { index: number; label: string }; + disallowHiddenField?: boolean; }; @Component({ @@ -68,6 +69,9 @@ export class AddEditCustomFieldDialogComponent { this.variant = data.editLabelConfig ? "edit" : "add"; this.fieldTypeOptions = this.fieldTypeOptions.filter((option) => { + if (this.data.disallowHiddenField && option.value === FieldType.Hidden) { + return false; + } // Filter out the Linked field type for Secure Notes if (this.data.cipherType === CipherType.SecureNote) { return option.value !== FieldType.Linked; diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html index 3bce3c5f385..1305bcdae05 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html @@ -89,7 +89,7 @@ bitIconButton="bwi-pencil-square" class="tw-self-center tw-mt-2" data-testid="edit-custom-field-button" - *ngIf="!isPartialEdit" + *ngIf="canEdit(field.value.type)" > diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts index fb9664594ed..ced8763f895 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts @@ -45,7 +45,9 @@ describe("CustomFieldsComponent", () => { announce = jest.fn().mockResolvedValue(null); patchCipher = jest.fn(); originalCipherView = new CipherView(); - config = {} as CipherFormConfig; + config = { + collections: [], + } as CipherFormConfig; await TestBed.configureTestingModule({ imports: [CustomFieldsComponent], @@ -463,5 +465,91 @@ describe("CustomFieldsComponent", () => { // "reorder boolean label to position 4 of 4" expect(announce).toHaveBeenCalledWith("reorderFieldDown boolean label 4 4", "assertive"); }); + + it("hides reorder buttons when in partial edit mode", () => { + originalCipherView.fields = mockFieldViews; + config.mode = "partial-edit"; + + component.ngOnInit(); + fixture.detectChanges(); + + toggleItems = fixture.debugElement.queryAll( + By.css('button[data-testid="reorder-toggle-button"]'), + ); + + expect(toggleItems).toHaveLength(0); + }); + }); + + it("shows all reorders button when in edit mode and viewPassword is true", () => { + originalCipherView.fields = mockFieldViews; + originalCipherView.viewPassword = true; + config.mode = "edit"; + + component.ngOnInit(); + fixture.detectChanges(); + + const toggleItems = fixture.debugElement.queryAll( + By.css('button[data-testid="reorder-toggle-button"]'), + ); + expect(toggleItems).toHaveLength(4); + }); + + it("shows all reorder buttons except for hidden fields when in edit mode and viewPassword is false", () => { + originalCipherView.fields = mockFieldViews; + originalCipherView.viewPassword = false; + config.mode = "edit"; + + component.ngOnInit(); + fixture.detectChanges(); + + const toggleItems = fixture.debugElement.queryAll( + By.css('button[data-testid="reorder-toggle-button"]'), + ); + + expect(toggleItems).toHaveLength(3); + }); + + describe("edit button", () => { + it("hides the edit button when in partial-edit mode", () => { + originalCipherView.fields = mockFieldViews; + config.mode = "partial-edit"; + + component.ngOnInit(); + fixture.detectChanges(); + + const editButtons = fixture.debugElement.queryAll( + By.css('button[data-testid="edit-custom-field-button"]'), + ); + expect(editButtons).toHaveLength(0); + }); + + it("shows all the edit buttons when in edit mode and viewPassword is true", () => { + originalCipherView.fields = mockFieldViews; + originalCipherView.viewPassword = true; + config.mode = "edit"; + + component.ngOnInit(); + fixture.detectChanges(); + + const editButtons = fixture.debugElement.queryAll( + By.css('button[data-testid="edit-custom-field-button"]'), + ); + expect(editButtons).toHaveLength(4); + }); + + it("shows all the edit buttons except for hidden fields when in edit mode and viewPassword is false", () => { + originalCipherView.fields = mockFieldViews; + originalCipherView.viewPassword = false; + config.mode = "edit"; + + component.ngOnInit(); + fixture.detectChanges(); + + const editButtons = fixture.debugElement.queryAll( + By.css('button[data-testid="edit-custom-field-button"]'), + ); + expect(editButtons).toHaveLength(3); + }); }); }); diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts index dd3fd8c24a8..49e9e109b74 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts @@ -116,6 +116,8 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { /** Emits when a new custom field should be focused */ private focusOnNewInput$ = new Subject(); + disallowHiddenField?: boolean; + destroyed$: DestroyRef; FieldType = FieldType; @@ -141,6 +143,13 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { return this.customFieldsForm.controls.fields as FormArray; } + canEdit(type: FieldType): boolean { + return ( + !this.isPartialEdit && + (type !== FieldType.Hidden || this.cipherFormContainer.originalCipherView?.viewPassword) + ); + } + ngOnInit() { const linkedFieldsOptionsForCipher = this.getLinkedFieldsOptionsForCipher(); const optionsArray = Array.from(linkedFieldsOptionsForCipher?.entries() ?? []); @@ -210,6 +219,7 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { /** Opens the add/edit custom field dialog */ openAddEditCustomFieldDialog(editLabelConfig?: AddEditCustomFieldDialogData["editLabelConfig"]) { + const { cipherType, mode, originalCipher } = this.cipherFormContainer.config; this.dialogRef = this.dialogService.open( AddEditCustomFieldDialogComponent, { @@ -217,8 +227,9 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { addField: this.addField.bind(this), updateLabel: this.updateLabel.bind(this), removeField: this.removeField.bind(this), - cipherType: this.cipherFormContainer.config.cipherType, + cipherType, editLabelConfig, + disallowHiddenField: mode === "edit" && !originalCipher.viewPassword, }, }, );