diff --git a/packages/automerge-repo-react-hooks/package.json b/packages/automerge-repo-react-hooks/package.json index b28bf8b5b..e877dc661 100644 --- a/packages/automerge-repo-react-hooks/package.json +++ b/packages/automerge-repo-react-hooks/package.json @@ -23,6 +23,7 @@ }, "devDependencies": { "@testing-library/react": "^14.0.0", + "@testing-library/react-hooks": "8.0.1", "jsdom": "^22.1.0", "rollup-plugin-visualizer": "^5.9.3", "vite-plugin-dts": "^3.6.4" diff --git a/packages/automerge-repo-react-hooks/src/useDocument.ts b/packages/automerge-repo-react-hooks/src/useDocument.ts index 88b0b2666..de51ae6fe 100644 --- a/packages/automerge-repo-react-hooks/src/useDocument.ts +++ b/packages/automerge-repo-react-hooks/src/useDocument.ts @@ -3,22 +3,30 @@ import { AutomergeUrl, DocHandleChangePayload } from "@automerge/automerge-repo" import { useEffect, useState } from "react" import { useRepo } from "./useRepo.js" -/** A hook which returns a document identified by a URL and a function to change the document. +/** A hook which returns a document identified by a URL and a function to change the document. * * @returns a tuple of the document and a function to change the document. * The document will be `undefined` if the document is not available in storage or from any peers - * + * * @remarks - * This requires a {@link RepoContext} to be provided by a parent component. + * This requires a {@link RepoContext} to be provided by a parent component. * */ -export function useDocument(documentUrl?: AutomergeUrl): [Doc | undefined, (changeFn: ChangeFn) => void] { +export function useDocument( + documentUrl?: AutomergeUrl +): [Doc | undefined, (changeFn: ChangeFn) => void] { const [doc, setDoc] = useState>() const repo = useRepo() const handle = documentUrl ? repo.find(documentUrl) : null useEffect(() => { - if (!handle) return + if (!handle) { + if (doc) { + setDoc(undefined) + } + + return + } handle.doc().then(v => setDoc(v)) diff --git a/packages/automerge-repo-react-hooks/test/useDocument.test.tsx b/packages/automerge-repo-react-hooks/test/useDocument.test.tsx new file mode 100644 index 000000000..953b5fd6a --- /dev/null +++ b/packages/automerge-repo-react-hooks/test/useDocument.test.tsx @@ -0,0 +1,91 @@ +import { PeerId, Repo, AutomergeUrl } from "@automerge/automerge-repo" +import { DummyStorageAdapter } from "@automerge/automerge-repo/test/helpers/DummyStorageAdapter" +import { describe, it } from "vitest" +import { RepoContext } from "../src/useRepo" +import { useDocument } from "../src/useDocument" +import { renderHook } from "@testing-library/react-hooks" +import React, { useState } from "react" +import assert from "assert" + +interface ExampleDoc { + foo: string +} + +function getRepoWrapper(repo: Repo) { + return ({ children }) => ( + {children} + ) +} + +describe("useDocument", () => { + const repo = new Repo({ + peerId: "bob" as PeerId, + network: [], + storage: new DummyStorageAdapter(), + }) + + 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), + } + } + + it("should load a document", async () => { + const { handleA, wrapper } = setup() + + const { result, waitForNextUpdate } = renderHook( + () => useDocument(handleA.url), + { wrapper } + ) + + await waitForNextUpdate() + + const [doc] = result.current + + assert.deepStrictEqual(doc, { foo: "A" }) + }) + + it("should update if the url changes", async () => { + const { wrapper, handleA, handleB } = setup() + + const { result, waitForNextUpdate } = renderHook( + () => { + const [url, setUrl] = useState() + const [doc] = useDocument(url) + + return { + setUrl, + doc, + } + }, + { wrapper } + ) + + // initially doc is undefined + assert.deepStrictEqual(result.current.doc, undefined) + + // set url to doc A + result.current.setUrl(handleA.url) + await waitForNextUpdate() + assert.deepStrictEqual(result.current.doc, { foo: "A" }) + + // set url to doc B + result.current.setUrl(handleB.url) + await waitForNextUpdate() + assert.deepStrictEqual(result.current.doc, { foo: "B" }) + + // set url to undefined + result.current.setUrl(undefined) + await waitForNextUpdate() + assert.deepStrictEqual(result.current.doc, undefined) + }) +}) diff --git a/packages/automerge-repo-react-hooks/src/useRepo.test.tsx b/packages/automerge-repo-react-hooks/test/useRepo.test.tsx similarity index 89% rename from packages/automerge-repo-react-hooks/src/useRepo.test.tsx rename to packages/automerge-repo-react-hooks/test/useRepo.test.tsx index b6366d246..f42a09223 100644 --- a/packages/automerge-repo-react-hooks/src/useRepo.test.tsx +++ b/packages/automerge-repo-react-hooks/test/useRepo.test.tsx @@ -1,6 +1,6 @@ import { renderHook } from "@testing-library/react" -import { describe, expect, test, vi } from 'vitest' -import { RepoContext, useRepo } from "./useRepo.js" +import { describe, expect, test, vi } from "vitest" +import { RepoContext, useRepo } from "../src/useRepo.js" import { Repo } from "@automerge/automerge-repo" import React from "react" diff --git a/yarn.lock b/yarn.lock index 4db5be0e5..8dbd45fb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1350,6 +1350,14 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/react-hooks@8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-boundary "^3.1.0" + "@testing-library/react@^14.0.0": version "14.1.0" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.1.0.tgz#01d64915111db99b50f8361d51d7217606805989" @@ -7322,6 +7330,13 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"