1
0
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:
Andreas Coroiu
2022-10-10 16:04:29 +02:00
committed by GitHub
parent 96c99058c4
commit bb4f063fe7
32 changed files with 955 additions and 65 deletions

View 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);
});
});

View 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);
}