1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 18:53:29 +00:00

[PM-26688][PM-27710] Delay skeletons from showing + search (#17394)

* add custom operator for loading skeleton delays

* add `isCipherSearching$` observable to search service

* prevent vault skeleton from showing immediately

* add skeleton for search + delay to sends

* update fade-in-out component selector

* add fade-in-out component for generic use

* address memory leak by using defer to encapsulate `skeletonShownAt`

* add missing provider
This commit is contained in:
Nick Krantz
2025-11-20 08:26:47 -06:00
committed by GitHub
parent 9e6d0cce35
commit b00987180d
11 changed files with 295 additions and 38 deletions

View File

@@ -0,0 +1,109 @@
import { BehaviorSubject } from "rxjs";
import { skeletonLoadingDelay } from "./skeleton-loading.operator";
describe("skeletonLoadingDelay", () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.clearAllTimers();
jest.useRealTimers();
});
it("returns false immediately when starting with false", () => {
const source$ = new BehaviorSubject<boolean>(false);
const results: boolean[] = [];
source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value));
expect(results).toEqual([false]);
});
it("waits 1 second before returning true when starting with true", () => {
const source$ = new BehaviorSubject<boolean>(true);
const results: boolean[] = [];
source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value));
expect(results).toEqual([]);
jest.advanceTimersByTime(999);
expect(results).toEqual([]);
jest.advanceTimersByTime(1);
expect(results).toEqual([true]);
});
it("cancels if source becomes false before show delay completes", () => {
const source$ = new BehaviorSubject<boolean>(true);
const results: boolean[] = [];
source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value));
jest.advanceTimersByTime(500);
source$.next(false);
expect(results).toEqual([false]);
jest.advanceTimersByTime(1000);
expect(results).toEqual([false]);
});
it("delays hiding if minimum display time has not elapsed", () => {
const source$ = new BehaviorSubject<boolean>(true);
const results: boolean[] = [];
source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value));
jest.advanceTimersByTime(1000);
expect(results).toEqual([true]);
source$.next(false);
expect(results).toEqual([true]);
jest.advanceTimersByTime(1000);
expect(results).toEqual([true, false]);
});
it("handles rapid true->false->true transitions", () => {
const source$ = new BehaviorSubject<boolean>(true);
const results: boolean[] = [];
source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value));
jest.advanceTimersByTime(500);
expect(results).toEqual([]);
source$.next(false);
expect(results).toEqual([false]);
source$.next(true);
jest.advanceTimersByTime(999);
expect(results).toEqual([false]);
jest.advanceTimersByTime(1);
expect(results).toEqual([false, true]);
});
it("allows for custom timings", () => {
const source$ = new BehaviorSubject<boolean>(true);
const results: boolean[] = [];
source$.pipe(skeletonLoadingDelay(1000, 2000)).subscribe((value) => results.push(value));
jest.advanceTimersByTime(1000);
expect(results).toEqual([true]);
source$.next(false);
jest.advanceTimersByTime(1999);
expect(results).toEqual([true]);
jest.advanceTimersByTime(1);
expect(results).toEqual([true, false]);
});
});