From c7fc9b88fccc15b93d99f03c60444ab1c7e79204 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 3 Jul 2025 17:35:50 -0400 Subject: [PATCH] [PM-23197] update cipherService to return decCiphers (#15433) * update cipherService to return decCiphers, update input to use signal, refactor observable, update spec --- .../src/vault/services/cipher.service.ts | 2 +- .../new-item-nudge.component.html | 2 +- .../new-item-nudge.component.spec.ts | 59 ++++++++++--------- .../new-item-nudge.component.ts | 53 ++++++++--------- 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index b4f79b2467e..a1727fd7a1d 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -383,7 +383,7 @@ export class CipherService implements CipherServiceAbstraction { const decCiphers = await this.getDecryptedCiphers(userId); if (decCiphers != null && decCiphers.length !== 0) { await this.reindexCiphers(userId); - return await this.getDecryptedCiphers(userId); + return decCiphers; } const decrypted = await this.decryptCiphers(await this.getAll(userId), userId); diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html index 5cd1246fd36..58dc181f826 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html @@ -1,4 +1,4 @@ - + { let component: NewItemNudgeComponent; + let componentRef: ComponentRef; let fixture: ComponentFixture; let i18nService: MockProxy; - let accountService: MockProxy; let nudgesService: MockProxy; + const accountService: FakeAccountService = mockAccountServiceWith("test-user-id" as UserId); beforeEach(async () => { i18nService = mock({ t: (key: string) => key }); - accountService = mock(); nudgesService = mock(); await TestBed.configureTestingModule({ @@ -37,7 +40,8 @@ describe("NewItemNudgeComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(NewItemNudgeComponent); component = fixture.componentInstance; - component.configType = null; // Set to null for initial state + componentRef = fixture.componentRef; + componentRef.setInput("configType", null); // Set a default type for testing fixture.detectChanges(); }); @@ -46,13 +50,11 @@ describe("NewItemNudgeComponent", () => { }); it("should set nudge title and body for CipherType.Login type", async () => { - component.configType = CipherType.Login; - accountService.activeAccount$ = of({ id: "test-user-id" as UserId } as Account); - jest.spyOn(component, "checkHasSpotlightDismissed").mockResolvedValue(true); - - await component.ngOnInit(); - - expect(component.showNewItemSpotlight).toBe(true); + componentRef.setInput("configType", CipherType.Login); + fixture.detectChanges(); + component.showNewItemSpotlight$.subscribe((value) => { + expect(value).toEqual(true); + }); expect(component.nudgeTitle).toBe("newLoginNudgeTitle"); expect(component.nudgeBody).toBe( "newLoginNudgeBodyOne newLoginNudgeBodyBold newLoginNudgeBodyTwo", @@ -61,39 +63,38 @@ describe("NewItemNudgeComponent", () => { }); it("should set nudge title and body for CipherType.Card type", async () => { - component.configType = CipherType.Card; - accountService.activeAccount$ = of({ id: "test-user-id" as UserId } as Account); - jest.spyOn(component, "checkHasSpotlightDismissed").mockResolvedValue(true); - - await component.ngOnInit(); - - expect(component.showNewItemSpotlight).toBe(true); + componentRef.setInput("configType", CipherType.Card); + fixture.detectChanges(); + component.showNewItemSpotlight$.subscribe((value) => { + expect(value).toEqual(true); + }); expect(component.nudgeTitle).toBe("newCardNudgeTitle"); expect(component.nudgeBody).toBe("newCardNudgeBody"); expect(component.dismissalNudgeType).toBe(NudgeType.NewCardItemStatus); }); it("should not show anything if spotlight has been dismissed", async () => { - component.configType = CipherType.Identity; - accountService.activeAccount$ = of({ id: "test-user-id" as UserId } as Account); - jest.spyOn(component, "checkHasSpotlightDismissed").mockResolvedValue(false); - - await component.ngOnInit(); - - expect(component.showNewItemSpotlight).toBe(false); + componentRef.setInput("configType", CipherType.Identity); + fixture.detectChanges(); + component.showNewItemSpotlight$.subscribe((value) => { + expect(value).toEqual(false); + }); expect(component.dismissalNudgeType).toBe(NudgeType.NewIdentityItemStatus); }); it("should set showNewItemSpotlight to false when user dismisses spotlight", async () => { - component.showNewItemSpotlight = true; + component.showNewItemSpotlight$ = of(true); component.dismissalNudgeType = NudgeType.NewLoginItemStatus; - component.activeUserId = "test-user-id" as UserId; + const activeUserId = "test-user-id" as UserId; + component.activeUserId$ = of(activeUserId); const dismissSpy = jest.spyOn(nudgesService, "dismissNudge").mockResolvedValue(); await component.dismissNewItemSpotlight(); - expect(component.showNewItemSpotlight).toBe(false); - expect(dismissSpy).toHaveBeenCalledWith(NudgeType.NewLoginItemStatus, component.activeUserId); + component.showNewItemSpotlight$.subscribe((value) => { + expect(value).toEqual(false); + }); + expect(dismissSpy).toHaveBeenCalledWith(NudgeType.NewLoginItemStatus, activeUserId); }); }); diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts index 79defc271cf..eccf8f65715 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -1,6 +1,7 @@ -import { NgIf } from "@angular/common"; -import { Component, Input, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { AsyncPipe, NgIf } from "@angular/common"; +import { Component, input } from "@angular/core"; +import { toObservable } from "@angular/core/rxjs-interop"; +import { combineLatest, firstValueFrom, map, of, switchMap } from "rxjs"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; @@ -13,12 +14,17 @@ import { CipherType } from "@bitwarden/sdk-internal"; @Component({ selector: "vault-new-item-nudge", templateUrl: "./new-item-nudge.component.html", - imports: [NgIf, SpotlightComponent], + imports: [NgIf, SpotlightComponent, AsyncPipe], }) -export class NewItemNudgeComponent implements OnInit { - @Input({ required: true }) configType: CipherType | null = null; - activeUserId: UserId | null = null; - showNewItemSpotlight: boolean = false; +export class NewItemNudgeComponent { + configType = input.required(); + activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); + showNewItemSpotlight$ = combineLatest([ + this.activeUserId$, + toObservable(this.configType).pipe(map((cipherType) => this.mapToNudgeType(cipherType))), + ]).pipe( + switchMap(([userId, nudgeType]) => this.nudgesService.showNudgeSpotlight$(nudgeType, userId)), + ); nudgeTitle: string = ""; nudgeBody: string = ""; dismissalNudgeType: NudgeType | null = null; @@ -29,10 +35,8 @@ export class NewItemNudgeComponent implements OnInit { private nudgesService: NudgesService, ) {} - async ngOnInit() { - this.activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - - switch (this.configType) { + mapToNudgeType(cipherType: CipherType | null): NudgeType { + switch (cipherType) { case CipherType.Login: { const nudgeBodyOne = this.i18nService.t("newLoginNudgeBodyOne"); const nudgeBodyBold = this.i18nService.t("newLoginNudgeBodyBold"); @@ -40,25 +44,25 @@ export class NewItemNudgeComponent implements OnInit { this.dismissalNudgeType = NudgeType.NewLoginItemStatus; this.nudgeTitle = this.i18nService.t("newLoginNudgeTitle"); this.nudgeBody = `${nudgeBodyOne} ${nudgeBodyBold} ${nudgeBodyTwo}`; - break; + return NudgeType.NewLoginItemStatus; } case CipherType.Card: this.dismissalNudgeType = NudgeType.NewCardItemStatus; this.nudgeTitle = this.i18nService.t("newCardNudgeTitle"); this.nudgeBody = this.i18nService.t("newCardNudgeBody"); - break; + return NudgeType.NewCardItemStatus; case CipherType.Identity: this.dismissalNudgeType = NudgeType.NewIdentityItemStatus; this.nudgeTitle = this.i18nService.t("newIdentityNudgeTitle"); this.nudgeBody = this.i18nService.t("newIdentityNudgeBody"); - break; + return NudgeType.NewIdentityItemStatus; case CipherType.SecureNote: this.dismissalNudgeType = NudgeType.NewNoteItemStatus; this.nudgeTitle = this.i18nService.t("newNoteNudgeTitle"); this.nudgeBody = this.i18nService.t("newNoteNudgeBody"); - break; + return NudgeType.NewNoteItemStatus; case CipherType.SshKey: { const sshPartOne = this.i18nService.t("newSshNudgeBodyOne"); @@ -67,25 +71,18 @@ export class NewItemNudgeComponent implements OnInit { this.dismissalNudgeType = NudgeType.NewSshItemStatus; this.nudgeTitle = this.i18nService.t("newSshNudgeTitle"); this.nudgeBody = `${sshPartOne} ${sshPartTwo}`; - break; + return NudgeType.NewSshItemStatus; } default: throw new Error("Unsupported cipher type"); } - this.showNewItemSpotlight = await this.checkHasSpotlightDismissed( - this.dismissalNudgeType as NudgeType, - this.activeUserId, - ); } async dismissNewItemSpotlight() { - if (this.dismissalNudgeType && this.activeUserId) { - await this.nudgesService.dismissNudge(this.dismissalNudgeType, this.activeUserId as UserId); - this.showNewItemSpotlight = false; + const activeUserId = await firstValueFrom(this.activeUserId$); + if (this.dismissalNudgeType && activeUserId) { + await this.nudgesService.dismissNudge(this.dismissalNudgeType, activeUserId as UserId); + this.showNewItemSpotlight$ = of(false); } } - - async checkHasSpotlightDismissed(nudgeType: NudgeType, userId: UserId): Promise { - return await firstValueFrom(this.nudgesService.showNudgeSpotlight$(nudgeType, userId)); - } }