Skip to content

Commit

Permalink
tests: finish moving away from renderHook
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahhh committed Mar 10, 2024
1 parent 060a6f2 commit 5977b9d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 97 deletions.
60 changes: 30 additions & 30 deletions packages/automerge-repo-react-hooks/test/useDocument.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,80 +48,80 @@ describe("useDocument", () => {
}
}

const Component = ({ url, onRender }: {
const Component = ({ url, onDoc }: {
url: AutomergeUrl,
onRender: (doc: ExampleDoc) => void,
onDoc: (doc: ExampleDoc) => void,
}) => {
const [doc] = useDocument(url)
onRender(doc)
onDoc(doc)
return null
}

it("should load a document", async () => {
const { handleA, wrapper } = setup()
const onRender = vi.fn()
const onDoc = vi.fn()

render(<Component url={handleA.url} onRender={onRender} />, {wrapper})
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith({ foo: "A" }))
render(<Component url={handleA.url} onDoc={onDoc} />, {wrapper})
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))
})

it("should update if the url changes", async () => {
const { handleA, handleB, wrapper } = setup()
const onRender = vi.fn()
const onDoc = vi.fn()

const { rerender } = render(<Component url={handleA.url} onRender={onRender} />, {wrapper})
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith(undefined))
const { rerender } = render(<Component url={undefined} onDoc={onDoc} />, {wrapper})
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))

// set url to doc A
rerender(<Component url={handleA.url} onRender={onRender} />)
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith({ foo: "A" }))
rerender(<Component url={handleA.url} onDoc={onDoc} />)
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))

// set url to doc B
rerender(<Component url={handleB.url} onRender={onRender} />)
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith({ foo: "B" }))
rerender(<Component url={handleB.url} onDoc={onDoc} />)
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "B" }))

// set url to undefined
rerender(<Component url={undefined} onRender={onRender} />)
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith(undefined))
rerender(<Component url={undefined} onDoc={onDoc} />)
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
})

it("sets the doc to undefined while the initial load is happening", async () => {
const { handleA, handleSlow, wrapper } = setup()
const onRender = vi.fn()
const onDoc = vi.fn()

const { rerender } = render(<Component url={handleA.url} onRender={onRender} />, {wrapper})
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith(undefined))
const { rerender } = render(<Component url={undefined} onDoc={onDoc} />, {wrapper})
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))

// start by setting url to doc A
rerender(<Component url={handleA.url} onRender={onRender} />)
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith({ foo: "A" }))
rerender(<Component url={handleA.url} onDoc={onDoc} />)
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))

// Now we set the URL to a handle that's slow to load.
// The doc should be undefined while the load is happening.
rerender(<Component url={handleSlow.url} onRender={onRender} />)
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith(undefined))
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith({ foo: "slow" }))
rerender(<Component url={handleSlow.url} onDoc={onDoc} />)
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "slow" }))
})

it("avoids showing stale data", async () => {
const { handleA, handleSlow, wrapper } = setup()
const onRender = vi.fn()
const onDoc = vi.fn()

const { rerender } = render(<Component url={handleA.url} onRender={onRender} />, {wrapper})
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith(undefined))
const { rerender } = render(<Component url={undefined} onDoc={onDoc} />, {wrapper})
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))

// Set the URL to a slow doc and then a fast doc.
// We should see the fast doc forever, even after
// the slow doc has had time to finish loading.
rerender(<Component url={handleSlow.url} onRender={onRender} />)
rerender(<Component url={handleA.url} onRender={onRender} />)
await waitFor(() => expect(onRender).toHaveBeenLastCalledWith({ foo: "A" }))
rerender(<Component url={handleSlow.url} onDoc={onDoc} />)
rerender(<Component url={handleA.url} onDoc={onDoc} />)
await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))

// wait for the slow doc to finish loading...
await pause(SLOW_DOC_LOAD_TIME_MS * 2)

// we didn't update the doc to the slow doc, so it should still be A
expect(onRender).not.toHaveBeenCalledWith({ foo: "slow" })
expect(onDoc).not.toHaveBeenCalledWith({ foo: "slow" })
})
})

Expand Down
30 changes: 15 additions & 15 deletions packages/automerge-repo-react-hooks/test/useDocuments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,31 @@ describe("useDocuments", () => {
return { repo, wrapper, documentIds }
}

const Component = ({ ids, onRender }: {
const Component = ({ ids, onDocs }: {
ids: DocumentId[],
onRender: (documents: Record<DocumentId, unknown>) => void,
onDocs: (documents: Record<DocumentId, unknown>) => void,
}) => {
const documents = useDocuments(ids)
onRender(documents)
onDocs(documents)
return null
}

it("returns a collection of documents, given a list of ids", async () => {
const { documentIds, wrapper } = setup()
const onRender = vi.fn()
const onDocs = vi.fn()

render(<Component ids={documentIds} onRender={onRender} />, { wrapper })
await waitFor(() => expect(onRender).toHaveBeenCalledWith(
render(<Component ids={documentIds} onDocs={onDocs} />, { wrapper })
await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]))
))
})

it("updates documents when they change", async () => {
const { repo, documentIds, wrapper } = setup()
const onRender = vi.fn()
const onDocs = vi.fn()

render(<Component ids={documentIds} onRender={onRender} />, { wrapper })
await waitFor(() => expect(onRender).toHaveBeenCalledWith(
render(<Component ids={documentIds} onDocs={onDocs} />, { wrapper })
await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]))
))

Expand All @@ -61,26 +61,26 @@ describe("useDocuments", () => {
handle.change(s => (s.foo *= 10))
})
})
await waitFor(() => expect(onRender).toHaveBeenCalledWith(
await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
Object.fromEntries(documentIds.map((id, i) => [id, { foo: i * 10 }]))
))
})

it(`removes documents when they're removed from the list of ids`, async () => {
const { documentIds, wrapper } = setup()
const onRender = vi.fn()
const onDocs = vi.fn()

render(<Component ids={documentIds} onRender={onRender} />, { wrapper })
await waitFor(() => expect(onRender).toHaveBeenCalledWith(
const { rerender } = render(<Component ids={documentIds} onDocs={onDocs} />, { wrapper })
await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]))
))

// remove the first document
render(<Component ids={documentIds.slice(1)} onRender={onRender} />, { wrapper })
rerender(<Component ids={documentIds.slice(1)} onDocs={onDocs} />)
// 👆 Note that this only works because documentIds.slice(1) is a different
// object from documentIds. If we modified documentIds directly, the hook
// wouldn't re-run.
await waitFor(() => expect(onRender).toHaveBeenCalledWith(
await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]).slice(1))
))
})
Expand Down
75 changes: 29 additions & 46 deletions packages/automerge-repo-react-hooks/test/useHandle.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { PeerId, Repo, AutomergeUrl } from "@automerge/automerge-repo"
import { AutomergeUrl, DocHandle, PeerId, Repo } from "@automerge/automerge-repo"
import { DummyStorageAdapter } from "@automerge/automerge-repo/test/helpers/DummyStorageAdapter"
import { describe, expect, it } from "vitest"
import { RepoContext } from "../src/useRepo"
import { render, waitFor } from "@testing-library/react"
import React from "react"
import { describe, expect, it, vi } from "vitest"
import { useHandle } from "../src/useHandle"
import { act, renderHook, waitFor } from "@testing-library/react"
import React, { useState } from "react"
import assert from "assert"
import { RepoContext } from "../src/useRepo"

interface ExampleDoc {
foo: string
Expand Down Expand Up @@ -39,64 +38,48 @@ describe("useHandle", () => {
}
}

const Component = ({ url, onHandle }: {
url: AutomergeUrl,
onHandle: (handle: DocHandle<unknown> | undefined) => void,
}) => {
const handle = useHandle(url)
onHandle(handle)
return null
}

it("loads a handle", async () => {
const { handleA, wrapper } = setup()
const onHandle = vi.fn()

const { result } = await act(() =>
renderHook(
() => {
const handle = useHandle(handleA.url)
return { handle }
},
{ wrapper }
)
)

assert.deepStrictEqual(result.current.handle, handleA)
render(<Component url={handleA.url} onHandle={onHandle} />, {wrapper})
await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA))
})

it("returns undefined when no url given", async () => {
const { wrapper } = setup()
const onHandle = vi.fn()

const { result } = await act(() =>
renderHook(
() => {
const handle = useHandle()
return { handle }
},
{ wrapper }
)
)

await waitFor(() => expect(result.current.handle).toBeUndefined())
render(<Component url={undefined} onHandle={onHandle} />, {wrapper})
await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined))
})

it("updates the handle when the url changes", async () => {
const { wrapper, handleA, handleB } = setup()
const onHandle = vi.fn()

const { result } = await act(() =>
renderHook(
() => {
const [url, setUrl] = useState<AutomergeUrl>()
const handle = useHandle(url)
return { setUrl, handle }
},
{ wrapper }
)
)

await waitFor(() => expect(result.current).not.toBeNull())
const { rerender } = render(<Component url={undefined} onHandle={onHandle} />, {wrapper})
await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined))

// set url to doc A
act(() => result.current.setUrl(handleA.url))
await waitFor(() => expect(result.current.handle).toMatchObject(handleA))
rerender(<Component url={handleA.url} onHandle={onHandle} />)
await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA))

// set url to doc B
act(() => result.current.setUrl(handleB.url))
await waitFor(() => expect(result.current.handle).toMatchObject(handleB))
rerender(<Component url={handleB.url} onHandle={onHandle} />)
await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleB))

// set url to undefined
act(() => result.current.setUrl(undefined))
await waitFor(() => expect(result.current.handle).toBeUndefined())
rerender(<Component url={undefined} onHandle={onHandle} />)
await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined))
})
})
21 changes: 15 additions & 6 deletions packages/automerge-repo-react-hooks/test/useRepo.test.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { renderHook } from "@testing-library/react"
import { describe, expect, test, vi } from "vitest"
import { RepoContext, useRepo } from "../src/useRepo.js"
import { Repo } from "@automerge/automerge-repo"
import { render } from "@testing-library/react"
import React from "react"
import { describe, expect, test, vi } from "vitest"
import { RepoContext, useRepo } from "../src/useRepo.js"

describe("useRepo", () => {
const Component = ({ onRepo }: {
onRepo: (repo: Repo) => void,
}) => {
const repo = useRepo()
onRepo(repo)
return null
}

test("should error when context unavailable", () => {
const repo = new Repo({ network: [] })
// Prevent console spam by swallowing console.error "uncaught error" message
const spy = vi.spyOn(console, "error")
spy.mockImplementation(() => {})
expect(() => renderHook(() => useRepo())).toThrow(
expect(() => render(<Component onRepo={() => {}}/>)).toThrow(
/Repo was not found on RepoContext/
)
spy.mockRestore()
Expand All @@ -21,7 +29,8 @@ describe("useRepo", () => {
const wrapper = ({ children }) => (
<RepoContext.Provider value={repo} children={children} />
)
const { result } = renderHook(() => useRepo(), { wrapper })
expect(result.current).toBe(repo)
const onRepo = vi.fn()
render(<Component onRepo={onRepo} />, { wrapper })
expect(onRepo).toHaveBeenLastCalledWith(repo)
})
})

0 comments on commit 5977b9d

Please sign in to comment.