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