mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-17 20:31:50 +01:00
[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:
parent
1aaa88a64d
commit
1cfbcf4ee0
@ -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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { ProductType } from "@bitwarden/common/enums";
|
import { ProductType } from "@bitwarden/common/enums";
|
||||||
|
import { ObservableTracker } from "@bitwarden/common/spec";
|
||||||
import { CipherId } from "@bitwarden/common/types/guid";
|
import { CipherId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||||
@ -50,7 +51,8 @@ describe("VaultPopupItemsService", () => {
|
|||||||
cipherList[3].favorite = true;
|
cipherList[3].favorite = true;
|
||||||
|
|
||||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList);
|
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);
|
searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers);
|
||||||
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
|
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
|
||||||
ciphers.filter((c) => ["0", "1"].includes(c.id)),
|
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$", () => {
|
describe("autoFillCiphers$", () => {
|
||||||
it("should return empty array if there is no current tab", (done) => {
|
it("should return empty array if there is no current tab", (done) => {
|
||||||
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(null);
|
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(null);
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
distinctUntilKeyChanged,
|
distinctUntilKeyChanged,
|
||||||
from,
|
from,
|
||||||
map,
|
map,
|
||||||
|
merge,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
@ -78,10 +79,12 @@ export class VaultPopupItemsService {
|
|||||||
* Observable that contains the list of all decrypted ciphers.
|
* Observable that contains the list of all decrypted ciphers.
|
||||||
* @private
|
* @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
|
runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular
|
||||||
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())),
|
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())),
|
||||||
map((ciphers) => Object.values(ciphers)),
|
|
||||||
switchMap((ciphers) =>
|
switchMap((ciphers) =>
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.organizationService.organizations$,
|
this.organizationService.organizations$,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { LocalData } from "@bitwarden/common/vault/models/data/local.data";
|
||||||
|
|
||||||
import { UriMatchStrategySetting } from "../../models/domain/domain-service";
|
import { UriMatchStrategySetting } from "../../models/domain/domain-service";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { CipherId, CollectionId, OrganizationId } from "../../types/guid";
|
import { CipherId, CollectionId, OrganizationId } from "../../types/guid";
|
||||||
@ -14,6 +16,7 @@ import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
|
|||||||
export abstract class CipherService {
|
export abstract class CipherService {
|
||||||
cipherViews$: Observable<Record<CipherId, CipherView>>;
|
cipherViews$: Observable<Record<CipherId, CipherView>>;
|
||||||
ciphers$: Observable<Record<CipherId, CipherData>>;
|
ciphers$: Observable<Record<CipherId, CipherData>>;
|
||||||
|
localData$: Observable<Record<CipherId, LocalData>>;
|
||||||
/**
|
/**
|
||||||
* An observable monitoring the add/edit cipher info saved to memory.
|
* An observable monitoring the add/edit cipher info saved to memory.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user