1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-09 09:51:02 +01:00
bitwarden-browser/libs/common/spec/observable-tracker.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

87 lines
2.5 KiB
TypeScript
Raw Normal View History

Ps/pm 5965/better config polling (#8325) * Create tracker that can await until expected observables are received. * Test dates are almost equal * Remove unused class method * Allow for updating active account in accout service fake * Correct observable tracker behavior Clarify documentation * Transition config service to state provider Updates the config fetching behavior to be lazy and ensure that any emitted value has been updated if older than a configurable value (statically compiled). If desired, config fetching can be ensured fresh through an async. * Update calls to config service in DI and bootstrapping * Migrate account server configs * Fix global config fetching * Test migration rollback * Adhere to implementation naming convention * Adhere to abstract class naming convention * Complete config abstraction rename * Remove unnecessary cli config service * Fix builds * Validate observable does not complete * Use token service to determine authed or unauthed config pull * Remove superfluous factory config * Name describe blocks after the thing they test * Remove implementation documentation Unfortunately the experience when linking to external documentation is quite poor. Instead of following the link and retrieving docs, you get a link that can be clicked to take you out of context to the docs. No link _does_ retrieve docs, but lacks indication in the implementation that documentation exists at all. On the balance, removing the link is the better experience. * Fix storybook
2024-03-27 18:03:09 +01:00
import { Observable, Subscription, firstValueFrom, throwError, timeout } from "rxjs";
/** Test class to enable async awaiting of observable emissions */
export class ObservableTracker<T> {
private subscription: Subscription;
emissions: T[] = [];
constructor(private observable: Observable<T>) {
this.emissions = this.trackEmissions(observable);
}
/** Unsubscribes from the observable */
unsubscribe() {
this.subscription.unsubscribe();
}
/**
* Awaits the next emission from the observable, or throws if the timeout is exceeded
* @param msTimeout The maximum time to wait for another emission before throwing
*/
async expectEmission(msTimeout = 50) {
await firstValueFrom(
this.observable.pipe(
timeout({
first: msTimeout,
with: () => throwError(() => new Error("Timeout exceeded waiting for another emission.")),
}),
),
);
}
/** Awaits until the the total number of emissions observed by this tracker equals or exceeds {@link count}
* @param count The number of emissions to wait for
*/
async pauseUntilReceived(count: number, msTimeout = 50): Promise<T[]> {
for (let i = 0; i < count - this.emissions.length; i++) {
await this.expectEmission(msTimeout);
}
return this.emissions;
}
private trackEmissions<T>(observable: Observable<T>): T[] {
const emissions: T[] = [];
this.subscription = observable.subscribe((value) => {
switch (value) {
case undefined:
case null:
emissions.push(value);
return;
default:
// process by type
break;
}
switch (typeof value) {
case "string":
case "number":
case "boolean":
emissions.push(value);
break;
case "symbol":
// Cheating types to make symbols work at all
emissions.push(value.toString() as T);
break;
default: {
emissions.push(clone(value));
}
}
});
return emissions;
}
}
function clone(value: any): any {
if (global.structuredClone != undefined) {
return structuredClone(value);
} else {
return JSON.parse(JSON.stringify(value));
}
}
/** A test helper that builds an @see{@link ObservableTracker}, which can be used to assert things about the
* emissions of the given observable
* @param observable The observable to track
*/
export function subscribeTo<T>(observable: Observable<T>) {
return new ObservableTracker(observable);
}