mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 15:23:33 +00:00
[EC-558] Reflecting async progress on buttons and forms (#3548)
* [EC-556] feat: convert button into component * [EC-556] feat: implement loading state * [EC-556] feat: remove loading from submit button * [EC-556] fix: add missing import * [EC-556] fix: disabling button using regular attribute * [EC-556] feat: implement bitFormButton * [EC-556] feat: use bitFormButton in submit button * [EC-556] fix: missing import * [EC-558] chore: rename file to match class name * [EC-558] feat: allow skipping bitButton on form buttons * [EC-558]: only show spinner on submit button * [EC-558] feat: add new bit async directive * [EC-558] feat: add functionToObservable util * [EC-558] feat: implement bitAction directive * [EC-558] refactor: simplify bitSubmit using functionToObservable * [EC-558] feat: connect bit action with form button * [EC-558] feat: execute function immediately to allow for form validation * [EC-558] feat: disable form on loading * [EC-558] chore: remove duplicate types * [EC-558] feat: move validation service to common * [EC-558] feat: add error handling using validation service * [EC-558] feat: add support for icon button * [EC-558] fix: icon button hover border styles * [EC-558] chore: refactor icon button story to show all styles * [EC-558] fix: better align loading spinner to middle * [EC-558] fix: simplify try catch * [EC-558] chore: reorganize async actions * [EC-558] chore: rename stories * [EC-558] docs: add documentation * [EC-558] feat: decouple buttons and form buttons * [EC-558] chore: rename button like abstraction * [EC-558] chore: remove null check * [EC-558] docs: add jsdocs to directives * [EC-558] fix: switch abs imports to relative * [EC-558] chore: add async actions module to web shared module * [EC-558] chore: remove unecessary null check * [EC-558] chore: apply suggestions from code review Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * [EC-558] fix: whitespaces * [EC-558] feat: dont disable form by default * [EC-558] fix: bug where form could be submit during a previous submit * [EC-558] feat: remove ability to disable form Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
103
libs/components/src/utils/function-to-observable.spec.ts
Normal file
103
libs/components/src/utils/function-to-observable.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { lastValueFrom, Observable, of, throwError } from "rxjs";
|
||||
|
||||
import { functionToObservable } from "./function-to-observable";
|
||||
|
||||
describe("functionToObservable", () => {
|
||||
it("should execute function when calling", () => {
|
||||
const func = jest.fn();
|
||||
|
||||
functionToObservable(func);
|
||||
|
||||
expect(func).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not subscribe when calling", () => {
|
||||
let hasSubscribed = false;
|
||||
const underlyingObservable = new Observable(() => {
|
||||
hasSubscribed = true;
|
||||
});
|
||||
const funcReturningObservable = () => underlyingObservable;
|
||||
|
||||
functionToObservable(funcReturningObservable);
|
||||
|
||||
expect(hasSubscribed).toBe(false);
|
||||
});
|
||||
|
||||
it("should subscribe to underlying when subscribing to outer", () => {
|
||||
let hasSubscribed = false;
|
||||
const underlyingObservable = new Observable(() => {
|
||||
hasSubscribed = true;
|
||||
});
|
||||
const funcReturningObservable = () => underlyingObservable;
|
||||
const outerObservable = functionToObservable(funcReturningObservable);
|
||||
|
||||
outerObservable.subscribe();
|
||||
|
||||
expect(hasSubscribed).toBe(true);
|
||||
});
|
||||
|
||||
it("should return value when using sync function", async () => {
|
||||
const value = Symbol();
|
||||
const func = () => value;
|
||||
const observable = functionToObservable(func);
|
||||
|
||||
const result = await lastValueFrom(observable);
|
||||
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
|
||||
it("should return value when using async function", async () => {
|
||||
const value = Symbol();
|
||||
const func = () => Promise.resolve(value);
|
||||
const observable = functionToObservable(func);
|
||||
|
||||
const result = await lastValueFrom(observable);
|
||||
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
|
||||
it("should return value when using observable", async () => {
|
||||
const value = Symbol();
|
||||
const func = () => of(value);
|
||||
const observable = functionToObservable(func);
|
||||
|
||||
const result = await lastValueFrom(observable);
|
||||
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
|
||||
it("should throw error when using sync function", async () => {
|
||||
const error = new Error();
|
||||
const func = () => {
|
||||
throw error;
|
||||
};
|
||||
const observable = functionToObservable(func);
|
||||
|
||||
let thrown: unknown;
|
||||
observable.subscribe({ error: (err: unknown) => (thrown = err) });
|
||||
|
||||
expect(thrown).toBe(thrown);
|
||||
});
|
||||
|
||||
it("should return value when using async function", async () => {
|
||||
const error = new Error();
|
||||
const func = () => Promise.reject(error);
|
||||
const observable = functionToObservable(func);
|
||||
|
||||
let thrown: unknown;
|
||||
observable.subscribe({ error: (err: unknown) => (thrown = err) });
|
||||
|
||||
expect(thrown).toBe(thrown);
|
||||
});
|
||||
|
||||
it("should return value when using observable", async () => {
|
||||
const error = new Error();
|
||||
const func = () => throwError(() => error);
|
||||
const observable = functionToObservable(func);
|
||||
|
||||
let thrown: unknown;
|
||||
observable.subscribe({ error: (err: unknown) => (thrown = err) });
|
||||
|
||||
expect(thrown).toBe(thrown);
|
||||
});
|
||||
});
|
||||
27
libs/components/src/utils/function-to-observable.ts
Normal file
27
libs/components/src/utils/function-to-observable.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { from, Observable, of, throwError } from "rxjs";
|
||||
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
export type FunctionReturningAwaitable =
|
||||
| (() => unknown)
|
||||
| (() => Promise<unknown>)
|
||||
| (() => Observable<unknown>);
|
||||
|
||||
export function functionToObservable(func: FunctionReturningAwaitable): Observable<unknown> {
|
||||
let awaitable: unknown;
|
||||
try {
|
||||
awaitable = func();
|
||||
} catch (error) {
|
||||
return throwError(() => error);
|
||||
}
|
||||
|
||||
if (Utils.isPromise(awaitable)) {
|
||||
return from(awaitable);
|
||||
}
|
||||
|
||||
if (awaitable instanceof Observable) {
|
||||
return awaitable;
|
||||
}
|
||||
|
||||
return of(awaitable);
|
||||
}
|
||||
Reference in New Issue
Block a user