[PM-8334] Sort ciphers after autofill (#9511)

* [PM-8334] Add localData$ to CipherService and watch it for updates

* Fix leftover tw-fixed class

* [PM-8334] Fix tests
This commit is contained in:
Shane Melton 2024-06-05 08:10:36 -07:00 committed by GitHub
parent 1aaa88a64d
commit 1cfbcf4ee0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 3 deletions

View File

@ -6,6 +6,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums";
import { ObservableTracker } from "@bitwarden/common/spec";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
@ -50,7 +51,8 @@ describe("VaultPopupItemsService", () => {
cipherList[3].favorite = true;
cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList);
cipherServiceMock.ciphers$ = new BehaviorSubject(null).asObservable();
cipherServiceMock.ciphers$ = new BehaviorSubject(null);
cipherServiceMock.localData$ = new BehaviorSubject(null);
searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers);
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
ciphers.filter((c) => ["0", "1"].includes(c.id)),
@ -123,6 +125,34 @@ describe("VaultPopupItemsService", () => {
});
});
it("should update cipher list when cipherService.ciphers$ emits", async () => {
const tracker = new ObservableTracker(service.autoFillCiphers$);
await tracker.expectEmission();
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
await tracker.expectEmission();
// Should only emit twice
expect(tracker.emissions.length).toBe(2);
await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded");
});
it("should update cipher list when cipherService.localData$ emits", async () => {
const tracker = new ObservableTracker(service.autoFillCiphers$);
await tracker.expectEmission();
(cipherServiceMock.localData$ as BehaviorSubject<any>).next(null);
await tracker.expectEmission();
// Should only emit twice
expect(tracker.emissions.length).toBe(2);
await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded");
});
describe("autoFillCiphers$", () => {
it("should return empty array if there is no current tab", (done) => {
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(null);

View File

@ -5,6 +5,7 @@ import {
distinctUntilKeyChanged,
from,
map,
merge,
Observable,
of,
shareReplay,
@ -78,10 +79,12 @@ export class VaultPopupItemsService {
* Observable that contains the list of all decrypted ciphers.
* @private
*/
private _cipherList$: Observable<PopupCipherView[]> = this.cipherService.ciphers$.pipe(
private _cipherList$: Observable<PopupCipherView[]> = merge(
this.cipherService.ciphers$,
this.cipherService.localData$,
).pipe(
runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())),
map((ciphers) => Object.values(ciphers)),
switchMap((ciphers) =>
combineLatest([
this.organizationService.organizations$,

View File

@ -1,5 +1,7 @@
import { Observable } from "rxjs";
import { LocalData } from "@bitwarden/common/vault/models/data/local.data";
import { UriMatchStrategySetting } from "../../models/domain/domain-service";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { CipherId, CollectionId, OrganizationId } from "../../types/guid";
@ -14,6 +16,7 @@ import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
export abstract class CipherService {
cipherViews$: Observable<Record<CipherId, CipherView>>;
ciphers$: Observable<Record<CipherId, CipherData>>;
localData$: Observable<Record<CipherId, LocalData>>;
/**
* An observable monitoring the add/edit cipher info saved to memory.
*/