diff --git a/src/useBehavior.test.tsx b/src/useBehavior.test.tsx new file mode 100644 index 00000000..3e5c1ac1 --- /dev/null +++ b/src/useBehavior.test.tsx @@ -0,0 +1,111 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { render } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { catchError, of, tap, Subject, map } from "rxjs"; + +import { type Behavior } from "./state/Behavior"; +import { useBehavior } from "./useBehavior"; +import { ObservableScope } from "./state/ObservableScope"; + +function TestComponent({ + behavior$, + shouldThrow = false, + children, +}: { + behavior$: Behavior; + shouldThrow?: boolean; + children: React.ReactNode; +}): React.ReactNode { + if (shouldThrow) throw Error("Test error"); + + const value = useBehavior(behavior$); + + return ( +
+ value: {value} + {children} +
+ ); +} + +describe("useBehavior", () => { + let scope: ObservableScope; + beforeEach(() => (scope = new ObservableScope())); + afterEach(() => scope.end()); + + it("reference test", () => { + expect(() => + render( + + Test + , + ), + ).not.toThrow(); + }); + it("should return the correct behavior", () => { + expect(() => + render( + + Test + , + ), + ).toThrow("Test error"); + }); + + it("useBehavior forwards the throw in obs computation", () => { + const throwingOperation = (): string => { + throw new Error("Test error"); + return "a"; + }; + + expect(() => { + const s$ = new Subject(); + const b$ = scope.behavior(s$, ""); + s$.next("test"); + s$.next(throwingOperation()); + render(Test); + }).toThrow("Test error"); + }); + it("useBehavior does not throw in caught obs computation", () => { + function errorOnE(s: string): string { + if (s === "E") throw new Error("Test error"); + return s + "CheckedForE"; + } + + console.log("hello"); + const s$ = new Subject(); + const b$ = scope.behavior(s$.pipe(map(errorOnE)), "START"); + + const inheritForThrowCatch$ = scope.behavior( + b$.pipe( + tap((v) => console.log("tap-" + v)), + catchError((err) => { + console.log("caught error" + err); + return of("I caught an error and converted it to sth nicer"); + }), + ), + ); + + s$.next("test"); + s$.next("E"); + s$.next("after error"); + + expect(() => { + render( + Test, + ); + }).not.toThrow(); + expect(() => { + render(Test); + }).toThrow(); + }); +}); diff --git a/src/useBehavior.ts b/src/useBehavior.ts index 14761a4a..2f759b3c 100644 --- a/src/useBehavior.ts +++ b/src/useBehavior.ts @@ -15,7 +15,12 @@ import { type Behavior } from "./state/Behavior"; export function useBehavior(behavior: Behavior): T { const subscribe = useCallback( (onChange: () => void) => { - const s = behavior.subscribe(onChange); + const s = behavior.subscribe({ + next: onChange, + error: (e) => { + throw e; + }, + }); return (): void => s.unsubscribe(); }, [behavior],