Error behavior example

This commit is contained in:
Timo K
2025-11-17 19:20:50 +01:00
parent 2e2c799f72
commit 256c53236b
2 changed files with 117 additions and 1 deletions

111
src/useBehavior.test.tsx Normal file
View File

@@ -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<string>;
shouldThrow?: boolean;
children: React.ReactNode;
}): React.ReactNode {
if (shouldThrow) throw Error("Test error");
const value = useBehavior(behavior$);
return (
<div>
value: {value}
{children}
</div>
);
}
describe("useBehavior", () => {
let scope: ObservableScope;
beforeEach(() => (scope = new ObservableScope()));
afterEach(() => scope.end());
it("reference test", () => {
expect(() =>
render(
<TestComponent behavior$={scope.behavior(of("test"))}>
Test
</TestComponent>,
),
).not.toThrow();
});
it("should return the correct behavior", () => {
expect(() =>
render(
<TestComponent
shouldThrow={true}
behavior$={scope.behavior(of("test"))}
>
Test
</TestComponent>,
),
).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<string>();
const b$ = scope.behavior(s$, "");
s$.next("test");
s$.next(throwingOperation());
render(<TestComponent behavior$={b$}>Test</TestComponent>);
}).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<string>();
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(
<TestComponent behavior$={inheritForThrowCatch$}>Test</TestComponent>,
);
}).not.toThrow();
expect(() => {
render(<TestComponent behavior$={b$}>Test</TestComponent>);
}).toThrow();
});
});

View File

@@ -15,7 +15,12 @@ import { type Behavior } from "./state/Behavior";
export function useBehavior<T>(behavior: Behavior<T>): 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],