import { type AutomergeUrl, type DocHandle, type PeerId, Repo, } from "@automerge/automerge-repo" import {render, renderHook, waitFor} from "@solidjs/testing-library" import {afterEach, describe, expect, it, vi} from "vitest" import useDocHandle from "../src/useDocHandle.js" import {RepoContext} from "../src/context.js" import { createEffect, createSignal, on, Suspense, untrack, type ParentComponent, } from "solid-js" import type {UseDocHandleOptions} from "../src/types.js" interface ExampleDoc { foo: string } function getRepoWrapper(repo: Repo): ParentComponent { return props => ( {props.children} ) } describe("useDocHandle", () => { afterEach(() => { document.body.innerHTML = "" }) const repo = new Repo({ peerId: "bob" as PeerId, }) function setup() { const handleA = repo.create() handleA.change(doc => (doc.foo = "A")) const handleB = repo.create() handleB.change(doc => (doc.foo = "B")) return { repo, handleA, handleB, wrapper: getRepoWrapper(repo), } } const Component = (props: { url: AutomergeUrl | undefined onHandle: (handle: DocHandle | undefined) => void options?: UseDocHandleOptions }) => { const handle = useDocHandle( () => props.url, untrack(() => props.options) ) createEffect( on([handle], () => { props.onHandle(handle()) }) ) return ( fallback}> ) } it("loads a handle", async () => { const {handleA, wrapper} = setup() const onHandle = vi.fn() render(() => , { wrapper, }) await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA)) }) it("throws if called without any kinda repo", async () => { const {handleA} = setup() const onHandle = vi.fn() expect(() => render(() => , {}) ).toThrowErrorMatchingInlineSnapshot( `[Error: use outside requires options.repo]` ) }) it("works without a context if given a repo in options", async () => { const {handleA} = setup() const onHandle = vi.fn() render( () => ( ), {} ) await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA)) }) it("returns undefined when no url given", async () => { const {wrapper} = setup() const onHandle = vi.fn() render(() => , {wrapper}) await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined)) }) it("updates the handle when the url changes", async () => { const {handleA, handleB, wrapper} = setup() const onHandle = vi.fn() const [url, updateURL] = createSignal(undefined) let hookResult = renderHook(useDocHandle, { initialProps: [url], wrapper, }) let componentResult = render( () => , {wrapper} ) let button = componentResult.getByRole("button") // set url to doc A updateURL(handleA.url) await waitFor(() => expect(hookResult.result.latest).toBe(handleA)) await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA)) await waitFor(() => expect(button).toHaveTextContent(handleA.url)) // set url to doc B updateURL(handleB.url) await waitFor(() => expect(hookResult.result.latest?.url).toBe(handleB.url)) await waitFor(() => expect(button).toHaveTextContent(handleB.url)) // set url to undefined updateURL(undefined) await waitFor(() => expect(hookResult.result.latest?.url).toBe(undefined)) await waitFor(() => expect(button).toHaveTextContent("🕯️🕯️🕯️🕯️")) }) it("does not return undefined after the url is updated", async () => { const {wrapper, handleA, handleB} = setup() const onHandle = vi.fn() const [url, updateURL] = createSignal(handleA.url) render(() => , {wrapper}) await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA)) const onHandle2 = vi.fn() // set url to doc B updateURL(handleB.url) await waitFor(() => expect(onHandle2).not.toHaveBeenCalledWith(undefined)) }) it("does not return a handle for a different url after the url is updated", async () => { const {wrapper, handleA, handleB} = setup() const onHandle = vi.fn() const [url, updateURL] = createSignal(handleA.url) render(() => , {wrapper}) await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA)) const onHandle2 = vi.fn() // set url to doc B updateURL(handleB.url) await waitFor(() => expect(onHandle2).not.toHaveBeenCalledWith(handleA)) }) })