Compare commits

..

3 Commits

Author SHA1 Message Date
Guido D'Orsi
b426342c02 fix(useCoState): return null when a coValue is not found 2025-01-29 18:12:56 +01:00
Guido D'Orsi
064d19b48c chore: move jazz-react-core version up to 0.9.19 2025-01-27 09:27:18 +01:00
Guido D'Orsi
3ea269f4d8 chore: package json formatting 2025-01-27 09:27:00 +01:00
19 changed files with 240 additions and 69 deletions

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Export the coValue loading config to reduce the timeout on tests

View File

@@ -0,0 +1,8 @@
---
"jazz-react-core": minor
"jazz-svelte": minor
"jazz-tools": minor
"jazz-vue": minor
---
Return null when a coValue is not found

View File

@@ -7,7 +7,7 @@ import QRCode from "qrcode";
import { Button, useToast } from "../basicComponents";
export function ShareButton({ petPost }: { petPost?: PetPost }) {
export function ShareButton({ petPost }: { petPost?: PetPost | null }) {
const [existingInviteLink, setExistingInviteLink] = useState<string>();
const { toast } = useToast();

View File

@@ -10,7 +10,7 @@ export function InviteButton<T extends CoValue>({
value,
valueHint,
}: {
value?: T;
value?: T | null;
valueHint?: string;
}) {
const [existingInviteLink, setExistingInviteLink] = useState<string>();

View File

@@ -27,7 +27,6 @@
"version": "0.9.19",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^1.29.0",
"@types/jest": "^29.5.3",
"typescript": "~5.6.2",
"vitest": "1.5.3"
},

View File

@@ -4,8 +4,10 @@ import { RawCoID } from "./ids.js";
import { logger } from "./logger.js";
import { PeerID } from "./sync.js";
export const CO_VALUE_LOADING_MAX_RETRIES = 5;
export const CO_VALUE_LOADING_TIMEOUT = 30_000;
export const CO_VALUE_LOADING_CONFIG = {
MAX_RETRIES: 5,
TIMEOUT: 30_000,
};
export class CoValueUnknownState {
type = "unknown" as const;
@@ -227,7 +229,7 @@ export class CoValueState {
this.getCoValue(),
runWithRetry(
() => doLoad(peersWithRetry),
CO_VALUE_LOADING_MAX_RETRIES,
CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
),
]);
}
@@ -313,7 +315,7 @@ async function loadCoValueFromPeers(
peerId: peer.id,
});
}
}, CO_VALUE_LOADING_TIMEOUT);
}, CO_VALUE_LOADING_CONFIG.TIMEOUT);
await coValueEntry.state.waitForPeer(peer.id);
clearTimeout(timeout);
}

View File

@@ -76,6 +76,7 @@ import {
type Value = JsonValue | AnyRawCoValue;
import { CO_VALUE_LOADING_CONFIG } from "./coValueState.js";
import { logger } from "./logger.js";
import { getPriorityFromHeader } from "./priority.js";
import { FileSystem } from "./storage/FileSystem.js";
@@ -103,6 +104,7 @@ export const cojsonInternals = {
getGroupDependentKeyList,
getGroupDependentKey,
disablePermissionErrors,
CO_VALUE_LOADING_CONFIG,
};
export {

View File

@@ -1,7 +1,7 @@
import { describe, expect, test, vi } from "vitest";
import { PeerState } from "../PeerState";
import { CoValueCore } from "../coValueCore";
import { CO_VALUE_LOADING_MAX_RETRIES, CoValueState } from "../coValueState";
import { CO_VALUE_LOADING_CONFIG, CoValueState } from "../coValueState";
import { RawCoID } from "../ids";
import { Peer } from "../sync";
@@ -92,18 +92,18 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
// Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
);
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
);
expect(state.state.type).toBe("unavailable");
await expect(state.getCoValue()).resolves.toBe("unavailable");
@@ -145,8 +145,8 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
// Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
@@ -154,7 +154,7 @@ describe("CoValueState", () => {
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
);
expect(state.state.type).toBe("unavailable");
await expect(state.getCoValue()).resolves.toBe("unavailable");
@@ -194,8 +194,8 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
// Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
@@ -203,7 +203,7 @@ describe("CoValueState", () => {
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
);
expect(state.state.type).toBe("unavailable");
await expect(state.getCoValue()).resolves.toEqual("unavailable");
@@ -244,8 +244,8 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES + 1; i++) {
// Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES + 1; i++) {
await vi.runAllTimersAsync();
}
@@ -278,8 +278,8 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
// Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
@@ -327,7 +327,7 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
@@ -372,7 +372,7 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers([peer1, peer2]);
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
@@ -421,7 +421,7 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers([peer1, peer2]);
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
@@ -449,7 +449,7 @@ describe("CoValueState", () => {
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers([peer1]);
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES * 2; i++) {
for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES * 2; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;

View File

@@ -4,10 +4,7 @@
"type": "module",
"main": "./dist/app.js",
"types": "./dist/app.d.ts",
"files": [
"dist/**",
"src"
],
"files": ["dist/**", "src"],
"scripts": {
"dev": "vite build --watch",
"build": "rm -rf ./dist && tsc --sourceMap --outDir dist",

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-core",
"version": "0.8.60",
"version": "0.9.19",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -30,7 +30,7 @@ export function useCoState<V extends CoValue, D>(
Schema: CoValueClass<V>,
id: ID<V> | undefined,
depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
): DeeplyLoaded<V, D> | undefined {
): DeeplyLoaded<V, D> | undefined | null {
const context = useJazzContext();
const [observable] = React.useState(() =>
@@ -39,14 +39,19 @@ export function useCoState<V extends CoValue, D>(
}),
);
const value = React.useSyncExternalStore<DeeplyLoaded<V, D> | undefined>(
const value = React.useSyncExternalStore<
DeeplyLoaded<V, D> | undefined | null
>(
React.useCallback(
(callback) => {
if (!id) return () => {};
const agent = "me" in context ? context.me : context.guest;
return observable.subscribe(Schema, id, agent, depth, callback);
return observable.subscribe(Schema, id, agent, depth, callback, () => {
console.log("unavailable");
callback();
});
},
[Schema, id, context],
),
@@ -54,6 +59,8 @@ export function useCoState<V extends CoValue, D>(
() => observable.getCurrentValue(),
);
console.log("value", value);
return value;
}
@@ -64,10 +71,10 @@ export function createUseAccountHooks<Acc extends Account>() {
};
function useAccount<D extends DepthsIn<Acc>>(
depth: D,
): { me: DeeplyLoaded<Acc, D> | undefined; logOut: () => void };
): { me: DeeplyLoaded<Acc, D> | undefined | null; logOut: () => void };
function useAccount<D extends DepthsIn<Acc>>(
depth?: D,
): { me: Acc | DeeplyLoaded<Acc, D> | undefined; logOut: () => void } {
): { me: Acc | DeeplyLoaded<Acc, D> | undefined | null; logOut: () => void } {
const context = useJazzContext<Acc>();
if (!("me" in context)) {
@@ -89,10 +96,12 @@ export function createUseAccountHooks<Acc extends Account>() {
};
function useAccountOrGuest<D extends DepthsIn<Acc>>(
depth: D,
): { me: DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent };
): { me: DeeplyLoaded<Acc, D> | undefined | null | AnonymousJazzAgent };
function useAccountOrGuest<D extends DepthsIn<Acc>>(
depth?: D,
): { me: Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent } {
): {
me: Acc | DeeplyLoaded<Acc, D> | undefined | null | AnonymousJazzAgent;
} {
const context = useJazzContext<Acc>();
const contextMe = "me" in context ? context.me : undefined;

View File

@@ -1,10 +1,24 @@
// @vitest-environment happy-dom
import { cojsonInternals } from "cojson";
import { CoMap, co } from "jazz-tools";
import { describe, expect, it } from "vitest";
import { beforeEach, describe, expect, it } from "vitest";
import { useCoState } from "../index.js";
import { createJazzTestAccount } from "../testing.js";
import { act, renderHook } from "./testUtils.js";
import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
import { act, renderHook, waitFor } from "./testUtils.js";
beforeEach(async () => {
await setupJazzTestSync();
await createJazzTestAccount({
isCurrentActiveAccount: true,
});
});
beforeEach(() => {
cojsonInternals.CO_VALUE_LOADING_CONFIG.MAX_RETRIES = 1;
cojsonInternals.CO_VALUE_LOADING_CONFIG.TIMEOUT = 1;
});
describe("useCoState", () => {
it("should return the correct value", async () => {
@@ -116,4 +130,31 @@ describe("useCoState", () => {
expect(result.current?.value).toBe("123");
expect(result.current?.nested?.value).toBe("456");
});
it("should return null if the coValue is not found", async () => {
class TestMap extends CoMap {
value = co.string;
}
const map = TestMap.create({
value: "123",
});
const account = await createJazzTestAccount({
isCurrentActiveAccount: true,
});
const { result } = renderHook(
() => useCoState(TestMap, (map.id + "123") as any, {}),
{
account,
},
);
expect(result.current).toBeUndefined();
await waitFor(() => {
expect(result.current).toBeNull();
});
});
});

View File

@@ -47,7 +47,7 @@ export type RegisteredAccount = Register extends { Account: infer Acc }
export function useAccount(): { me: RegisteredAccount; logOut: () => void };
export function useAccount<D extends DepthsIn<RegisteredAccount>>(
depth: D
): { me: DeeplyLoaded<RegisteredAccount, D> | undefined; logOut: () => void };
): { me: DeeplyLoaded<RegisteredAccount, D> | undefined | null; logOut: () => void };
/**
* Use the current account with a optional depth.
* @param depth - The depth.
@@ -55,7 +55,7 @@ export function useAccount<D extends DepthsIn<RegisteredAccount>>(
*/
export function useAccount<D extends DepthsIn<RegisteredAccount>>(
depth?: D
): { me: RegisteredAccount | DeeplyLoaded<RegisteredAccount, D> | undefined; logOut: () => void } {
): { me: RegisteredAccount | DeeplyLoaded<RegisteredAccount, D> | undefined | null; logOut: () => void } {
const ctx = getJazzContext<RegisteredAccount>();
if (!ctx?.current) {
throw new Error('useAccount must be used within a JazzProvider');
@@ -98,7 +98,7 @@ export function useAccount<D extends DepthsIn<RegisteredAccount>>(
export function useAccountOrGuest(): { me: RegisteredAccount | AnonymousJazzAgent };
export function useAccountOrGuest<D extends DepthsIn<RegisteredAccount>>(
depth: D
): { me: DeeplyLoaded<RegisteredAccount, D> | undefined | AnonymousJazzAgent };
): { me: DeeplyLoaded<RegisteredAccount, D> | undefined | null | AnonymousJazzAgent };
/**
* Use the current account or guest with a optional depth.
* @param depth - The depth.
@@ -106,7 +106,7 @@ export function useAccountOrGuest<D extends DepthsIn<RegisteredAccount>>(
*/
export function useAccountOrGuest<D extends DepthsIn<RegisteredAccount>>(
depth?: D
): { me: RegisteredAccount | DeeplyLoaded<RegisteredAccount, D> | undefined | AnonymousJazzAgent } {
): { me: RegisteredAccount | DeeplyLoaded<RegisteredAccount, D> | undefined | null | AnonymousJazzAgent } {
const ctx = getJazzContext<RegisteredAccount>();
if (!ctx?.current) {
@@ -153,12 +153,12 @@ export function useCoState<V extends CoValue, D extends DepthsIn<V> = []>(
id: ID<V> | undefined,
depth: D = [] as D
): {
current?: DeeplyLoaded<V, D>;
current?: DeeplyLoaded<V, D> | null;
} {
const ctx = getJazzContext<RegisteredAccount>();
// Create state and a stable observable
let state = $state.raw<DeeplyLoaded<V, D> | undefined>(undefined);
let state = $state.raw<DeeplyLoaded<V, D> | undefined | null>(undefined);
// Effect to handle subscription
$effect(() => {
@@ -178,7 +178,9 @@ export function useCoState<V extends CoValue, D extends DepthsIn<V> = []>(
// Get current value from our stable observable
state = value;
},
undefined,
() => {
state = null;
},
true
);
});

View File

@@ -1,9 +1,18 @@
import { render } from "@testing-library/svelte";
import { Account, CoMap, co, type CoValue, type CoValueClass, type DepthsIn } from "jazz-tools";
import { render, waitFor } from "@testing-library/svelte";
import { Account, CoMap, co, cojsonInternals, type CoValue, type CoValueClass, type DepthsIn } from "jazz-tools";
import { describe, expect, it } from "vitest";
import { createJazzTestAccount, createJazzTestContext } from "../testing.js";
import { createJazzTestAccount, createJazzTestContext, setupJazzTestSync } from "../testing.js";
import UseCoState from "./components/useCoState.svelte";
beforeEach(async () => {
await setupJazzTestSync();
});
beforeEach(() => {
cojsonInternals.CO_VALUE_LOADING_CONFIG.MAX_RETRIES = 1;
cojsonInternals.CO_VALUE_LOADING_CONFIG.TIMEOUT = 1;
});
function setup<T extends CoValue>(options: {
account: Account;
map: T;
@@ -148,4 +157,34 @@ describe("useCoState", () => {
expect(result.current?.value).toBe("123");
expect(result.current?.nested?.value).toBe("456");
});
});
it("should return null if the coValue is not found", async () => {
class TestMap extends CoMap {
value = co.string;
}
const unreachableAccount = await createJazzTestAccount({
});
const map = TestMap.create({
value: "123",
}, unreachableAccount);
unreachableAccount._raw.core.node.gracefulShutdown();
const account = await createJazzTestAccount({
isCurrentActiveAccount: true,
});
const result = setup({
account,
map,
});
expect(result.current).toBeUndefined();
await waitFor(() => {
expect(result.current).toBeNull();
});
});
});

View File

@@ -301,7 +301,7 @@ export function subscribeToCoValue<V extends CoValue, Depth>(
export function createCoValueObservable<V extends CoValue, Depth>(options?: {
syncResolution?: boolean;
}) {
let currentValue: DeeplyLoaded<V, Depth> | undefined = undefined;
let currentValue: DeeplyLoaded<V, Depth> | undefined | null = undefined;
let subscriberCount = 0;
function subscribe(
@@ -323,7 +323,10 @@ export function createCoValueObservable<V extends CoValue, Depth>(options?: {
currentValue = value;
listener();
},
onUnavailable,
() => {
currentValue = null;
onUnavailable?.();
},
options?.syncResolution,
);

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, onTestFinished, vi } from "vitest";
import { beforeEach, describe, expect, it, onTestFinished, vi } from "vitest";
import {
Account,
CoFeed,
@@ -7,12 +7,15 @@ import {
FileStream,
Group,
co,
cojsonInternals,
} from "../index.web.js";
import {
type DepthsIn,
ID,
createCoValueObservable,
subscribeToCoValue,
} from "../internal.js";
import { setupJazzTestSync } from "../testing.js";
import { setupAccount, waitFor } from "./utils.js";
class ChatRoom extends CoMap {
@@ -43,6 +46,15 @@ function createMessage(me: Account | Group, text: string) {
);
}
beforeEach(async () => {
await setupJazzTestSync();
});
beforeEach(() => {
cojsonInternals.CO_VALUE_LOADING_CONFIG.MAX_RETRIES = 1;
cojsonInternals.CO_VALUE_LOADING_CONFIG.TIMEOUT = 1;
});
describe("subscribeToCoValue", () => {
it("subscribes to a CoMap", async () => {
const { me, meOnSecondPeer } = await setupAccount();
@@ -368,4 +380,25 @@ describe("createCoValueObservable", () => {
unsubscribe();
expect(observable.getCurrentValue()).toBeUndefined();
});
it("should return null if the coValue is not found", async () => {
const { meOnSecondPeer } = await setupAccount();
const observable = createCoValueObservable<TestMap, DepthsIn<TestMap>>();
const unsubscribe = observable.subscribe(
TestMap,
"co_z123" as ID<TestMap>,
meOnSecondPeer,
{},
() => {},
);
expect(observable.getCurrentValue()).toBeUndefined();
await waitFor(() => {
expect(observable.getCurrentValue()).toBeNull();
});
unsubscribe();
});
});

View File

@@ -52,13 +52,13 @@ export function createUseAccountComposables<Acc extends Account>() {
function useAccount<D extends DepthsIn<Acc>>(
depth: D,
): {
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined>;
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | null>;
logOut: () => void;
};
function useAccount<D extends DepthsIn<Acc>>(
depth?: D,
): {
me: ComputedRef<Acc | DeeplyLoaded<Acc, D> | undefined>;
me: ComputedRef<Acc | DeeplyLoaded<Acc, D> | undefined | null>;
logOut: () => void;
} {
const context = useJazzContext();
@@ -100,13 +100,15 @@ export function createUseAccountComposables<Acc extends Account>() {
function useAccountOrGuest<D extends DepthsIn<Acc>>(
depth: D,
): {
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent>;
me: ComputedRef<
DeeplyLoaded<Acc, D> | undefined | null | AnonymousJazzAgent
>;
};
function useAccountOrGuest<D extends DepthsIn<Acc>>(
depth?: D,
): {
me: ComputedRef<
Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent
Acc | DeeplyLoaded<Acc, D> | undefined | null | AnonymousJazzAgent
>;
} {
const context = useJazzContext();
@@ -155,8 +157,8 @@ export function useCoState<V extends CoValue, D>(
Schema: CoValueClass<V>,
id: MaybeRef<ID<V> | undefined>,
depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
): Ref<DeeplyLoaded<V, D> | undefined> {
const state: ShallowRef<DeeplyLoaded<V, D> | undefined> =
): Ref<DeeplyLoaded<V, D> | undefined | null> {
const state: ShallowRef<DeeplyLoaded<V, D> | undefined | null> =
shallowRef(undefined);
const context = useJazzContext();
@@ -184,7 +186,9 @@ export function useCoState<V extends CoValue, D>(
(value) => {
state.value = value;
},
undefined,
() => {
state.value = null;
},
true,
);
},

View File

@@ -1,10 +1,19 @@
// @vitest-environment happy-dom
import { CoMap, co } from "jazz-tools";
import { createJazzTestAccount } from "jazz-tools/testing";
import { describe, expect, it } from "vitest";
import { CoMap, co, cojsonInternals } from "jazz-tools";
import { createJazzTestAccount, setupJazzTestSync } from "jazz-tools/testing";
import { beforeEach, describe, expect, it } from "vitest";
import { useCoState } from "../index.js";
import { withJazzTestSetup } from "./testUtils.js";
import { waitFor, withJazzTestSetup } from "./testUtils.js";
beforeEach(async () => {
await setupJazzTestSync();
});
beforeEach(() => {
cojsonInternals.CO_VALUE_LOADING_CONFIG.MAX_RETRIES = 1;
cojsonInternals.CO_VALUE_LOADING_CONFIG.TIMEOUT = 1;
});
describe("useCoState", () => {
it("should return the correct value", async () => {
@@ -124,4 +133,25 @@ describe("useCoState", () => {
expect(result.value?.content).toBe("123");
expect(result.value?.nested?.content).toBe("456");
});
it("should return null if the coValue is not found", async () => {
class TestMap extends CoMap {
content = co.string;
}
const account = await createJazzTestAccount();
const [result] = withJazzTestSetup(
() => useCoState(TestMap, "co_z123", {}),
{
account,
},
);
expect(result.value).toBeUndefined();
await waitFor(() => {
expect(result.value).toBeNull();
});
});
});

3
pnpm-lock.yaml generated
View File

@@ -1432,9 +1432,6 @@ importers:
'@opentelemetry/sdk-metrics':
specifier: ^1.29.0
version: 1.30.0(@opentelemetry/api@1.9.0)
'@types/jest':
specifier: ^29.5.3
version: 29.5.14
typescript:
specifier: ~5.6.2
version: 5.6.3