1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-20 11:24:07 +00:00

[PM-26704] Vault List Item Ordering for Extension (#18853)

* shows all/filtered ciphers in allItems instead of the ones that haven't been bubbled up into autofill or favorites

* removes remainingCiphers$ remnants

* updates loading$ observable logic

* updates loading$ test
This commit is contained in:
Jackson Engstrom
2026-02-18 14:34:17 -08:00
committed by GitHub
parent c90b4ded33
commit d1250cf5a4
4 changed files with 4 additions and 56 deletions

View File

@@ -127,7 +127,7 @@
<!--Change the title header when a filter is applied-->
<app-vault-list-items-container
[title]="((numberOfAppliedFilters$ | async) === 0 ? 'allItems' : 'items') | i18n"
[ciphers]="(remainingCiphers$ | async) || []"
[ciphers]="(filteredCiphers$ | async) || []"
id="allItems"
disableSectionMargin
collapsibleKey="allItems"

View File

@@ -164,7 +164,6 @@ export class VaultComponent implements OnInit, OnDestroy {
protected filteredCiphers$ = this.vaultPopupItemsService.filteredCiphers$;
protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$;
protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$;
protected allFilters$ = this.vaultPopupListFiltersService.allFilters$;
protected cipherCount$ = this.vaultPopupItemsService.cipherCount$;
protected hasPremium$ = this.activeUserId$.pipe(

View File

@@ -370,37 +370,6 @@ describe("VaultPopupItemsService", () => {
});
});
describe("remainingCiphers$", () => {
beforeEach(() => {
searchService.isSearchable.mockImplementation(async (text) => text.length > 2);
});
it("should exclude autofill and favorite ciphers", (done) => {
service.remainingCiphers$.subscribe((ciphers) => {
// 2 autofill ciphers, 2 favorite ciphers = 6 remaining ciphers to show
expect(ciphers.length).toBe(6);
done();
});
});
it("should filter remainingCiphers$ down to search term", (done) => {
const cipherList = Object.values(allCiphers);
const searchText = "Login";
searchService.searchCiphers.mockImplementation(async () => {
return cipherList.filter((cipher) => {
return cipher.name.includes(searchText);
});
});
service.remainingCiphers$.subscribe((ciphers) => {
// There are 6 remaining ciphers but only 2 with "Login" in the name
expect(ciphers.length).toBe(2);
done();
});
});
});
describe("emptyVault$", () => {
it("should return true if there are no ciphers", (done) => {
cipherServiceMock.cipherListViews$.mockReturnValue(of([]));
@@ -493,8 +462,8 @@ describe("VaultPopupItemsService", () => {
// Start tracking loading$ emissions
tracked = new ObservableTracker(service.loading$);
// Track remainingCiphers$ to make cipher observables active
trackedCiphers = new ObservableTracker(service.remainingCiphers$);
// Track favoriteCiphers$ to make cipher observables active
trackedCiphers = new ObservableTracker(service.favoriteCiphers$);
});
it("should initialize with true first", async () => {

View File

@@ -2,7 +2,6 @@ import { inject, Injectable, NgZone } from "@angular/core";
import { toObservable } from "@angular/core/rxjs-interop";
import {
combineLatest,
concatMap,
distinctUntilChanged,
distinctUntilKeyChanged,
filter,
@@ -242,31 +241,12 @@ export class VaultPopupItemsService {
shareReplay({ refCount: false, bufferSize: 1 }),
);
/**
* List of all remaining ciphers that are not currently suggested for autofill or marked as favorite.
* Ciphers are sorted by name.
*/
remainingCiphers$: Observable<PopupCipherViewLike[]> = this.favoriteCiphers$.pipe(
concatMap(
(
favoriteCiphers, // concatMap->of is used to make withLatestFrom lazy to avoid race conditions with autoFillCiphers$
) =>
of(favoriteCiphers).pipe(withLatestFrom(this._filteredCipherList$, this.autoFillCiphers$)),
),
map(([favoriteCiphers, ciphers, autoFillCiphers]) =>
ciphers.filter(
(cipher) => !autoFillCiphers.includes(cipher) && !favoriteCiphers.includes(cipher),
),
),
shareReplay({ refCount: false, bufferSize: 1 }),
);
/**
* Observable that indicates whether the service is currently loading ciphers.
*/
loading$: Observable<boolean> = merge(
this._ciphersLoading$.pipe(map(() => true)),
this.remainingCiphers$.pipe(map(() => false)),
this.favoriteCiphers$.pipe(map(() => false)),
).pipe(startWith(true), distinctUntilChanged(), shareReplay({ refCount: false, bufferSize: 1 }));
/** Observable that indicates whether there is search text present.