mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 15:23:33 +00:00
[PM-10247] Browser Refresh - Fix save credential banner (#10520)
* [PM-10247] Prioritize initialValues when initiating the CipherForm child forms * [PM-10247] Fetch the addEditCipherInfo when opening the cipher form in Browser and override any initialValues if present * [PM-10247] Fix item details section tests * [PM-10247] Add login details section test * [PM-10247] Add autofill options tests * [PM-10247] Undo webpack config change * [PM-10247] Fix failing tests * [PM-10247] Add additional tests for addEditCipherInfo
This commit is contained in:
@@ -1,14 +1,22 @@
|
|||||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { mock } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { CipherFormConfig, CipherFormConfigService, CipherFormMode } from "@bitwarden/vault";
|
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||||
|
import {
|
||||||
|
CipherFormConfig,
|
||||||
|
CipherFormConfigService,
|
||||||
|
CipherFormMode,
|
||||||
|
OptionalInitialValues,
|
||||||
|
} from "@bitwarden/vault";
|
||||||
|
|
||||||
import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service";
|
import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service";
|
||||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||||
@@ -25,6 +33,8 @@ jest.mock("qrcode-parser", () => {});
|
|||||||
describe("AddEditV2Component", () => {
|
describe("AddEditV2Component", () => {
|
||||||
let component: AddEditV2Component;
|
let component: AddEditV2Component;
|
||||||
let fixture: ComponentFixture<AddEditV2Component>;
|
let fixture: ComponentFixture<AddEditV2Component>;
|
||||||
|
let addEditCipherInfo$: BehaviorSubject<AddEditCipherInfo | null>;
|
||||||
|
let cipherServiceMock: MockProxy<CipherService>;
|
||||||
|
|
||||||
const buildConfigResponse = { originalCipher: {} } as CipherFormConfig;
|
const buildConfigResponse = { originalCipher: {} } as CipherFormConfig;
|
||||||
const buildConfig = jest.fn((mode: CipherFormMode) =>
|
const buildConfig = jest.fn((mode: CipherFormMode) =>
|
||||||
@@ -41,6 +51,10 @@ describe("AddEditV2Component", () => {
|
|||||||
navigate.mockClear();
|
navigate.mockClear();
|
||||||
back.mockClear();
|
back.mockClear();
|
||||||
|
|
||||||
|
addEditCipherInfo$ = new BehaviorSubject(null);
|
||||||
|
cipherServiceMock = mock<CipherService>();
|
||||||
|
cipherServiceMock.addEditCipherInfo$ = addEditCipherInfo$.asObservable();
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [AddEditV2Component],
|
imports: [AddEditV2Component],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -51,6 +65,7 @@ describe("AddEditV2Component", () => {
|
|||||||
{ provide: Router, useValue: { navigate } },
|
{ provide: Router, useValue: { navigate } },
|
||||||
{ provide: ActivatedRoute, useValue: { queryParams: queryParams$ } },
|
{ provide: ActivatedRoute, useValue: { queryParams: queryParams$ } },
|
||||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||||
|
{ provide: CipherService, useValue: cipherServiceMock },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideProvider(CipherFormConfigService, {
|
.overrideProvider(CipherFormConfigService, {
|
||||||
@@ -107,6 +122,72 @@ describe("AddEditV2Component", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("addEditCipherInfo initialization", () => {
|
||||||
|
it("populates config.initialValues with `addEditCipherInfo` values", fakeAsync(() => {
|
||||||
|
const addEditCipherInfo = {
|
||||||
|
cipher: {
|
||||||
|
name: "test",
|
||||||
|
folderId: "folder1",
|
||||||
|
organizationId: "org1",
|
||||||
|
type: CipherType.Login,
|
||||||
|
login: {
|
||||||
|
password: "password",
|
||||||
|
username: "username",
|
||||||
|
uris: [{ uri: "https://example.com" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
collectionIds: ["col1", "col2"],
|
||||||
|
} as AddEditCipherInfo;
|
||||||
|
addEditCipherInfo$.next(addEditCipherInfo);
|
||||||
|
queryParams$.next({});
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(component.config.initialValues).toEqual({
|
||||||
|
name: "test",
|
||||||
|
folderId: "folder1",
|
||||||
|
organizationId: "org1",
|
||||||
|
password: "password",
|
||||||
|
username: "username",
|
||||||
|
loginUri: "https://example.com",
|
||||||
|
collectionIds: ["col1", "col2"],
|
||||||
|
} as OptionalInitialValues);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("populates config.initialValues.username when `addEditCipherInfo` is an Identity", fakeAsync(() => {
|
||||||
|
addEditCipherInfo$.next({
|
||||||
|
cipher: { type: CipherType.Identity, identity: { username: "identity-username" } },
|
||||||
|
} as AddEditCipherInfo);
|
||||||
|
queryParams$.next({});
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(component.config.initialValues.username).toBe("identity-username");
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("overrides query params with `addEditCipherInfo` values", fakeAsync(() => {
|
||||||
|
addEditCipherInfo$.next({
|
||||||
|
cipher: { name: "AddEditCipherName" },
|
||||||
|
} as AddEditCipherInfo);
|
||||||
|
queryParams$.next({
|
||||||
|
name: "QueryParamName",
|
||||||
|
});
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(component.config.initialValues.name).toBe("AddEditCipherName");
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("clears `addEditCipherInfo` after initialization", fakeAsync(() => {
|
||||||
|
addEditCipherInfo$.next({ cipher: { name: "test" } } as AddEditCipherInfo);
|
||||||
|
queryParams$.next({});
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(cipherServiceMock.setAddEditCipherInfo).toHaveBeenCalledTimes(1);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
describe("onCipherSaved", () => {
|
describe("onCipherSaved", () => {
|
||||||
it("disables warning when in popout", async () => {
|
it("disables warning when in popout", async () => {
|
||||||
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValueOnce(true);
|
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValueOnce(true);
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import { firstValueFrom, map, switchMap } from "rxjs";
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
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 { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||||
import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components";
|
import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components";
|
||||||
import {
|
import {
|
||||||
CipherFormConfig,
|
CipherFormConfig,
|
||||||
@@ -18,6 +20,7 @@ import {
|
|||||||
CipherFormMode,
|
CipherFormMode,
|
||||||
CipherFormModule,
|
CipherFormModule,
|
||||||
DefaultCipherFormConfigService,
|
DefaultCipherFormConfigService,
|
||||||
|
OptionalInitialValues,
|
||||||
TotpCaptureService,
|
TotpCaptureService,
|
||||||
} from "@bitwarden/vault";
|
} from "@bitwarden/vault";
|
||||||
|
|
||||||
@@ -156,6 +159,7 @@ export class AddEditV2Component implements OnInit {
|
|||||||
private popupCloseWarningService: PopupCloseWarningService,
|
private popupCloseWarningService: PopupCloseWarningService,
|
||||||
private popupRouterCacheService: PopupRouterCacheService,
|
private popupRouterCacheService: PopupRouterCacheService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private cipherService: CipherService,
|
||||||
) {
|
) {
|
||||||
this.subscribeToParams();
|
this.subscribeToParams();
|
||||||
}
|
}
|
||||||
@@ -255,7 +259,21 @@ export class AddEditV2Component implements OnInit {
|
|||||||
config.mode = "partial-edit";
|
config.mode = "partial-edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setInitialValuesFromParams(params, config);
|
config.initialValues = this.setInitialValuesFromParams(params);
|
||||||
|
|
||||||
|
// The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form
|
||||||
|
// Attempt to fetch them here and overwrite the initialValues if present
|
||||||
|
const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$);
|
||||||
|
|
||||||
|
if (cachedCipherInfo != null) {
|
||||||
|
// Cached cipher info has priority over queryParams
|
||||||
|
config.initialValues = {
|
||||||
|
...config.initialValues,
|
||||||
|
...mapAddEditCipherInfoToInitialValues(cachedCipherInfo),
|
||||||
|
};
|
||||||
|
// Be sure to clear the "cached" cipher info, so it doesn't get used again
|
||||||
|
await this.cipherService.setAddEditCipherInfo(null);
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}),
|
}),
|
||||||
@@ -266,26 +284,27 @@ export class AddEditV2Component implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialValuesFromParams(params: QueryParams, config: CipherFormConfig) {
|
setInitialValuesFromParams(params: QueryParams) {
|
||||||
config.initialValues = {};
|
const initialValues = {} as OptionalInitialValues;
|
||||||
if (params.folderId) {
|
if (params.folderId) {
|
||||||
config.initialValues.folderId = params.folderId;
|
initialValues.folderId = params.folderId;
|
||||||
}
|
}
|
||||||
if (params.organizationId) {
|
if (params.organizationId) {
|
||||||
config.initialValues.organizationId = params.organizationId;
|
initialValues.organizationId = params.organizationId;
|
||||||
}
|
}
|
||||||
if (params.collectionId) {
|
if (params.collectionId) {
|
||||||
config.initialValues.collectionIds = [params.collectionId];
|
initialValues.collectionIds = [params.collectionId];
|
||||||
}
|
}
|
||||||
if (params.uri) {
|
if (params.uri) {
|
||||||
config.initialValues.loginUri = params.uri;
|
initialValues.loginUri = params.uri;
|
||||||
}
|
}
|
||||||
if (params.username) {
|
if (params.username) {
|
||||||
config.initialValues.username = params.username;
|
initialValues.username = params.username;
|
||||||
}
|
}
|
||||||
if (params.name) {
|
if (params.name) {
|
||||||
config.initialValues.name = params.name;
|
initialValues.name = params.name;
|
||||||
}
|
}
|
||||||
|
return initialValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHeader(mode: CipherFormMode, type: CipherType) {
|
setHeader(mode: CipherFormMode, type: CipherType) {
|
||||||
@@ -303,3 +322,63 @@ export class AddEditV2Component implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to map the old AddEditCipherInfo to the new OptionalInitialValues type used by the CipherForm
|
||||||
|
* @param cipherInfo
|
||||||
|
*/
|
||||||
|
const mapAddEditCipherInfoToInitialValues = (
|
||||||
|
cipherInfo: AddEditCipherInfo | null,
|
||||||
|
): OptionalInitialValues => {
|
||||||
|
const initialValues: OptionalInitialValues = {};
|
||||||
|
|
||||||
|
if (cipherInfo == null) {
|
||||||
|
return initialValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipherInfo.collectionIds != null) {
|
||||||
|
initialValues.collectionIds = cipherInfo.collectionIds as CollectionId[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipherInfo.cipher == null) {
|
||||||
|
return initialValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher = cipherInfo.cipher;
|
||||||
|
|
||||||
|
if (cipher.folderId != null) {
|
||||||
|
initialValues.folderId = cipher.folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipher.organizationId != null) {
|
||||||
|
initialValues.organizationId = cipher.organizationId as OrganizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipher.name != null) {
|
||||||
|
initialValues.name = cipher.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipher.type === CipherType.Login) {
|
||||||
|
const login = cipher.login;
|
||||||
|
|
||||||
|
if (login != null) {
|
||||||
|
if (login.uris != null && login.uris.length > 0) {
|
||||||
|
initialValues.loginUri = login.uris[0].uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (login.username != null) {
|
||||||
|
initialValues.username = login.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (login.password != null) {
|
||||||
|
initialValues.password = login.password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipher.type === CipherType.Identity && cipher.identity?.username != null) {
|
||||||
|
initialValues.username = cipher.identity.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialValues;
|
||||||
|
};
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type OptionalInitialValues = {
|
|||||||
collectionIds?: CollectionId[];
|
collectionIds?: CollectionId[];
|
||||||
loginUri?: string;
|
loginUri?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
password?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,7 +59,8 @@ type BaseCipherFormConfig = {
|
|||||||
originalCipher?: Cipher;
|
originalCipher?: Cipher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional initial values for the form when creating a new cipher. Useful when creating a cipher in a filtered view.
|
* Optional initial values for the form when opening the cipher form.
|
||||||
|
* Useful when creating a new cipher in a filtered view or modifying a cipher with values from another source (e.g. the notification bar in Browser)
|
||||||
*/
|
*/
|
||||||
initialValues?: OptionalInitialValues;
|
initialValues?: OptionalInitialValues;
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,47 @@ describe("AutofillOptionsComponent", () => {
|
|||||||
expect(component.autofillOptionsForm.value.autofillOnPageLoad).toEqual(null);
|
expect(component.autofillOptionsForm.value.autofillOnPageLoad).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("initializes 'autoFillOptionsForm' with initialValues when editing an existing cipher", () => {
|
||||||
|
cipherFormContainer.config.initialValues = { loginUri: "https://new-website.com" };
|
||||||
|
const existingLogin = new LoginUriView();
|
||||||
|
existingLogin.uri = "https://example.com";
|
||||||
|
existingLogin.match = UriMatchStrategy.Exact;
|
||||||
|
|
||||||
|
(cipherFormContainer.originalCipherView as CipherView) = new CipherView();
|
||||||
|
cipherFormContainer.originalCipherView.login = {
|
||||||
|
autofillOnPageLoad: true,
|
||||||
|
uris: [existingLogin],
|
||||||
|
} as LoginView;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.autofillOptionsForm.value.uris).toEqual([
|
||||||
|
{ uri: "https://example.com", matchDetection: UriMatchStrategy.Exact },
|
||||||
|
{ uri: "https://new-website.com", matchDetection: null },
|
||||||
|
]);
|
||||||
|
expect(component.autofillOptionsForm.value.autofillOnPageLoad).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initializes 'autoFillOptionsForm' with initialValues without duplicating an existing URI", () => {
|
||||||
|
cipherFormContainer.config.initialValues = { loginUri: "https://example.com" };
|
||||||
|
const existingLogin = new LoginUriView();
|
||||||
|
existingLogin.uri = "https://example.com";
|
||||||
|
existingLogin.match = UriMatchStrategy.Exact;
|
||||||
|
|
||||||
|
(cipherFormContainer.originalCipherView as CipherView) = new CipherView();
|
||||||
|
cipherFormContainer.originalCipherView.login = {
|
||||||
|
autofillOnPageLoad: true,
|
||||||
|
uris: [existingLogin],
|
||||||
|
} as LoginView;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.autofillOptionsForm.value.uris).toEqual([
|
||||||
|
{ uri: "https://example.com", matchDetection: UriMatchStrategy.Exact },
|
||||||
|
]);
|
||||||
|
expect(component.autofillOptionsForm.value.autofillOnPageLoad).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("initializes 'autoFillOptionsForm' with an empty URI when creating a new cipher", () => {
|
it("initializes 'autoFillOptionsForm' with an empty URI when creating a new cipher", () => {
|
||||||
cipherFormContainer.config.initialValues = null;
|
cipherFormContainer.config.initialValues = null;
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,20 @@ export class AutofillOptionsComponent implements OnInit {
|
|||||||
this.autofillOptionsForm.patchValue({
|
this.autofillOptionsForm.patchValue({
|
||||||
autofillOnPageLoad: existingLogin.autofillOnPageLoad,
|
autofillOnPageLoad: existingLogin.autofillOnPageLoad,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.cipherFormContainer.config.initialValues?.loginUri) {
|
||||||
|
// Avoid adding the same uri again if it already exists
|
||||||
|
if (
|
||||||
|
existingLogin.uris?.findIndex(
|
||||||
|
(uri) => uri.uri === this.cipherFormContainer.config.initialValues.loginUri,
|
||||||
|
) === -1
|
||||||
|
) {
|
||||||
|
this.addUri({
|
||||||
|
uri: this.cipherFormContainer.config.initialValues.loginUri,
|
||||||
|
matchDetection: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initNewCipher() {
|
private initNewCipher() {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export class IdentitySectionComponent implements OnInit {
|
|||||||
firstName: identity.firstName,
|
firstName: identity.firstName,
|
||||||
middleName: identity.middleName,
|
middleName: identity.middleName,
|
||||||
lastName: identity.lastName,
|
lastName: identity.lastName,
|
||||||
username: identity.username,
|
username: this.cipherFormContainer.config.initialValues?.username ?? identity.username,
|
||||||
company: identity.company,
|
company: identity.company,
|
||||||
ssn: identity.ssn,
|
ssn: identity.ssn,
|
||||||
passportNumber: identity.passportNumber,
|
passportNumber: identity.passportNumber,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
|||||||
|
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
|
|
||||||
@@ -104,6 +105,43 @@ describe("ItemDetailsSectionComponent", () => {
|
|||||||
expect(updatedCipher.favorite).toBe(true);
|
expect(updatedCipher.favorite).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it("should prioritize initialValues when editing an existing cipher ", fakeAsync(async () => {
|
||||||
|
component.config.allowPersonalOwnership = true;
|
||||||
|
component.config.organizations = [{ id: "org1" } as Organization];
|
||||||
|
component.config.collections = [
|
||||||
|
{ id: "col1", name: "Collection 1", organizationId: "org1" } as CollectionView,
|
||||||
|
{ id: "col2", name: "Collection 2", organizationId: "org1" } as CollectionView,
|
||||||
|
];
|
||||||
|
component.originalCipherView = {
|
||||||
|
name: "cipher1",
|
||||||
|
organizationId: "org1",
|
||||||
|
folderId: "folder1",
|
||||||
|
collectionIds: ["col1"],
|
||||||
|
favorite: true,
|
||||||
|
} as CipherView;
|
||||||
|
|
||||||
|
component.config.initialValues = {
|
||||||
|
name: "new-name",
|
||||||
|
folderId: "new-folder",
|
||||||
|
organizationId: "bad-org" as OrganizationId, // Should not be set in edit mode
|
||||||
|
collectionIds: ["col2" as CollectionId],
|
||||||
|
};
|
||||||
|
|
||||||
|
await component.ngOnInit();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(cipherFormProvider.patchCipher).toHaveBeenCalled();
|
||||||
|
const patchFn = cipherFormProvider.patchCipher.mock.lastCall[0];
|
||||||
|
|
||||||
|
const updatedCipher = patchFn(new CipherView());
|
||||||
|
|
||||||
|
expect(updatedCipher.name).toBe("new-name");
|
||||||
|
expect(updatedCipher.organizationId).toBe("org1");
|
||||||
|
expect(updatedCipher.folderId).toBe("new-folder");
|
||||||
|
expect(updatedCipher.collectionIds).toEqual(["col2"]);
|
||||||
|
expect(updatedCipher.favorite).toBe(true);
|
||||||
|
}));
|
||||||
|
|
||||||
it("should disable organizationId control if ownership change is not allowed", async () => {
|
it("should disable organizationId control if ownership change is not allowed", async () => {
|
||||||
component.config.allowPersonalOwnership = false;
|
component.config.allowPersonalOwnership = false;
|
||||||
component.config.organizations = [{ id: "org1" } as Organization];
|
component.config.organizations = [{ id: "org1" } as Organization];
|
||||||
|
|||||||
@@ -190,9 +190,9 @@ export class ItemDetailsSectionComponent implements OnInit {
|
|||||||
|
|
||||||
private async initFromExistingCipher() {
|
private async initFromExistingCipher() {
|
||||||
this.itemDetailsForm.setValue({
|
this.itemDetailsForm.setValue({
|
||||||
name: this.originalCipherView.name,
|
name: this.initialValues?.name ?? this.originalCipherView.name,
|
||||||
organizationId: this.originalCipherView.organizationId,
|
organizationId: this.originalCipherView.organizationId, // We do not allow changing ownership of an existing cipher.
|
||||||
folderId: this.originalCipherView.folderId,
|
folderId: this.initialValues?.folderId ?? this.originalCipherView.folderId,
|
||||||
collectionIds: [],
|
collectionIds: [],
|
||||||
favorite: this.originalCipherView.favorite,
|
favorite: this.originalCipherView.favorite,
|
||||||
});
|
});
|
||||||
@@ -208,7 +208,10 @@ export class ItemDetailsSectionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateCollectionOptions(this.originalCipherView.collectionIds as CollectionId[]);
|
await this.updateCollectionOptions(
|
||||||
|
this.initialValues?.collectionIds ??
|
||||||
|
(this.originalCipherView.collectionIds as CollectionId[]),
|
||||||
|
);
|
||||||
|
|
||||||
if (this.partialEdit) {
|
if (this.partialEdit) {
|
||||||
this.itemDetailsForm.disable();
|
this.itemDetailsForm.disable();
|
||||||
|
|||||||
@@ -125,6 +125,29 @@ describe("LoginDetailsSectionComponent", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("initializes 'loginDetailsForm' with initialValues that override any original cipher view values", async () => {
|
||||||
|
(cipherFormContainer.originalCipherView as CipherView) = {
|
||||||
|
viewPassword: true,
|
||||||
|
login: {
|
||||||
|
password: "original-password",
|
||||||
|
username: "original-username",
|
||||||
|
totp: "original-totp",
|
||||||
|
} as LoginView,
|
||||||
|
} as CipherView;
|
||||||
|
cipherFormContainer.config.initialValues = {
|
||||||
|
username: "new-username",
|
||||||
|
password: "new-password",
|
||||||
|
};
|
||||||
|
|
||||||
|
await component.ngOnInit();
|
||||||
|
|
||||||
|
expect(component.loginDetailsForm.value).toEqual({
|
||||||
|
username: "new-username",
|
||||||
|
password: "new-password",
|
||||||
|
totp: "original-totp",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("viewHiddenFields", () => {
|
describe("viewHiddenFields", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(cipherFormContainer.originalCipherView as CipherView) = {
|
(cipherFormContainer.originalCipherView as CipherView) = {
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ export class LoginDetailsSectionComponent implements OnInit {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get initialValues() {
|
||||||
|
return this.cipherFormContainer.config.initialValues;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cipherFormContainer: CipherFormContainer,
|
private cipherFormContainer: CipherFormContainer,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
@@ -139,8 +143,8 @@ export class LoginDetailsSectionComponent implements OnInit {
|
|||||||
|
|
||||||
private initFromExistingCipher(existingLogin: LoginView) {
|
private initFromExistingCipher(existingLogin: LoginView) {
|
||||||
this.loginDetailsForm.patchValue({
|
this.loginDetailsForm.patchValue({
|
||||||
username: existingLogin.username,
|
username: this.initialValues?.username ?? existingLogin.username,
|
||||||
password: existingLogin.password,
|
password: this.initialValues?.password ?? existingLogin.password,
|
||||||
totp: existingLogin.totp,
|
totp: existingLogin.totp,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -154,8 +158,8 @@ export class LoginDetailsSectionComponent implements OnInit {
|
|||||||
|
|
||||||
private async initNewCipher() {
|
private async initNewCipher() {
|
||||||
this.loginDetailsForm.patchValue({
|
this.loginDetailsForm.patchValue({
|
||||||
username: this.cipherFormContainer.config.initialValues?.username || "",
|
username: this.initialValues?.username || "",
|
||||||
password: "",
|
password: this.initialValues?.password || "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user