@automerge/automerge-repo-react-hooks 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-react-hooks",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Hooks to access an Automerge Repo from your react app.",
5
5
  "repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo-react-hooks",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -10,12 +10,12 @@
10
10
  "scripts": {
11
11
  "build": "tsc --noEmit && vite build",
12
12
  "test": "vitest run",
13
- "watch": "npm-watch",
13
+ "watch": "npm-watch build",
14
14
  "visualize": "VISUALIZE=true vite build"
15
15
  },
16
16
  "dependencies": {
17
17
  "@automerge/automerge": "^2.1.9",
18
- "@automerge/automerge-repo": "1.1.2",
18
+ "@automerge/automerge-repo": "1.1.4",
19
19
  "eventemitter3": "^5.0.1",
20
20
  "react": "^18.2.0",
21
21
  "react-dom": "^18.2.0",
@@ -42,5 +42,5 @@
42
42
  "publishConfig": {
43
43
  "access": "public"
44
44
  },
45
- "gitHead": "8bcba031ae952b1f3302cd41d1066cd2db5b677f"
45
+ "gitHead": "e819a7ed6302ce34f372b119ea3e8bab052e3af6"
46
46
  }
@@ -1,22 +1,21 @@
1
1
  import { AutomergeUrl, PeerId, Repo } from "@automerge/automerge-repo"
2
2
  import { DummyStorageAdapter } from "@automerge/automerge-repo/test/helpers/DummyStorageAdapter"
3
- import { renderHook, waitFor } from "@testing-library/react"
4
- import React, { useState } from "react"
5
- import { act } from "react-dom/test-utils"
6
- import { describe, expect, it } from "vitest"
3
+ import { render, waitFor } from "@testing-library/react"
4
+ import React from "react"
5
+ import { describe, expect, it, vi } from "vitest"
7
6
  import { useDocument } from "../src/useDocument"
8
7
  import { RepoContext } from "../src/useRepo"
9
8
 
10
9
  const SLOW_DOC_LOAD_TIME_MS = 10
11
10
 
12
11
  describe("useDocument", () => {
13
- const repo = new Repo({
14
- peerId: "bob" as PeerId,
15
- network: [],
16
- storage: new DummyStorageAdapter(),
17
- })
18
-
19
12
  function setup() {
13
+ const repo = new Repo({
14
+ peerId: "bob" as PeerId,
15
+ network: [],
16
+ storage: new DummyStorageAdapter(),
17
+ })
18
+
20
19
  const handleA = repo.create<ExampleDoc>()
21
20
  handleA.change(doc => (doc.foo = "A"))
22
21
 
@@ -36,121 +35,98 @@ describe("useDocument", () => {
36
35
  return result
37
36
  }
38
37
 
38
+ const wrapper = ({ children }) => {
39
+ return <RepoContext.Provider value={repo}>{children}</RepoContext.Provider>
40
+ }
41
+
39
42
  return {
40
43
  repo,
41
44
  handleA,
42
45
  handleB,
43
46
  handleSlow,
44
- wrapper: getRepoWrapper(repo),
47
+ wrapper,
45
48
  }
46
49
  }
47
50
 
51
+ const Component = ({ url, onDoc }: {
52
+ url: AutomergeUrl,
53
+ onDoc: (doc: ExampleDoc) => void,
54
+ }) => {
55
+ const [doc] = useDocument(url)
56
+ onDoc(doc)
57
+ return null
58
+ }
59
+
48
60
  it("should load a document", async () => {
49
61
  const { handleA, wrapper } = setup()
62
+ const onDoc = vi.fn()
50
63
 
51
- const { result } = renderHook(() => useDocument(handleA.url), { wrapper })
52
-
53
- await waitFor(() => {
54
- const [doc] = result.current
55
- expect(doc).toEqual({ foo: "A" })
56
- })
64
+ render(<Component url={handleA.url} onDoc={onDoc} />, {wrapper})
65
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))
57
66
  })
58
67
 
59
68
  it("should update if the url changes", async () => {
60
- const { wrapper, handleA, handleB } = setup()
69
+ const { handleA, handleB, wrapper } = setup()
70
+ const onDoc = vi.fn()
61
71
 
62
- const { result } = await act(() =>
63
- renderHook(
64
- () => {
65
- const [url, setUrl] = useState<AutomergeUrl>()
66
- const [doc] = useDocument(url)
67
- return { setUrl, doc }
68
- },
69
- { wrapper }
70
- )
71
- )
72
-
73
- await waitFor(() => expect(result.current).not.toBeNull())
72
+ const { rerender } = render(<Component url={undefined} onDoc={onDoc} />, {wrapper})
73
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
74
74
 
75
75
  // set url to doc A
76
- act(() => result.current.setUrl(handleA.url))
77
- await waitFor(() => expect(result.current.doc).toEqual({ foo: "A" }))
76
+ rerender(<Component url={handleA.url} onDoc={onDoc} />)
77
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))
78
78
 
79
79
  // set url to doc B
80
- act(() => result.current.setUrl(handleB.url))
81
- await waitFor(() => expect(result.current.doc).toEqual({ foo: "B" }))
80
+ rerender(<Component url={handleB.url} onDoc={onDoc} />)
81
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "B" }))
82
82
 
83
83
  // set url to undefined
84
- act(() => result.current.setUrl(undefined))
85
- await waitFor(() => expect(result.current.doc).toBeUndefined())
84
+ rerender(<Component url={undefined} onDoc={onDoc} />)
85
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
86
86
  })
87
87
 
88
88
  it("sets the doc to undefined while the initial load is happening", async () => {
89
- const { wrapper, handleA, handleSlow } = setup()
90
-
91
- const { result } = await act(() =>
92
- renderHook(
93
- () => {
94
- const [url, setUrl] = useState<AutomergeUrl>()
95
- const [doc] = useDocument(url)
96
- return { setUrl, doc }
97
- },
98
- { wrapper }
99
- )
100
- )
89
+ const { handleA, handleSlow, wrapper } = setup()
90
+ const onDoc = vi.fn()
101
91
 
102
- await waitFor(() => expect(result.current).not.toBeNull())
92
+ const { rerender } = render(<Component url={undefined} onDoc={onDoc} />, {wrapper})
93
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
103
94
 
104
95
  // start by setting url to doc A
105
- act(() => result.current.setUrl(handleA.url))
106
- await waitFor(() => expect(result.current.doc).toEqual({ foo: "A" }))
96
+ rerender(<Component url={handleA.url} onDoc={onDoc} />)
97
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))
107
98
 
108
99
  // Now we set the URL to a handle that's slow to load.
109
100
  // The doc should be undefined while the load is happening.
110
- act(() => result.current.setUrl(handleSlow.url))
111
- await waitFor(() => expect(result.current.doc).toBeUndefined())
112
- await waitFor(() => expect(result.current.doc).toEqual({ foo: "slow" }))
101
+ rerender(<Component url={handleSlow.url} onDoc={onDoc} />)
102
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
103
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "slow" }))
113
104
  })
114
105
 
115
106
  it("avoids showing stale data", async () => {
116
- const { wrapper, handleA, handleSlow } = setup()
117
- const { result } = await act(() =>
118
- renderHook(
119
- () => {
120
- const [url, setUrl] = useState<AutomergeUrl>()
121
- const [doc] = useDocument(url)
122
- return { setUrl, doc }
123
- },
124
- { wrapper }
125
- )
126
- )
127
-
128
- await waitFor(() => expect(result.current).not.toBeNull())
107
+ const { handleA, handleSlow, wrapper } = setup()
108
+ const onDoc = vi.fn()
109
+
110
+ const { rerender } = render(<Component url={undefined} onDoc={onDoc} />, {wrapper})
111
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith(undefined))
129
112
 
130
113
  // Set the URL to a slow doc and then a fast doc.
131
114
  // We should see the fast doc forever, even after
132
115
  // the slow doc has had time to finish loading.
133
- act(() => {
134
- result.current.setUrl(handleSlow.url)
135
- result.current.setUrl(handleA.url)
136
- })
137
- await waitFor(() => expect(result.current.doc).toEqual({ foo: "A" }))
116
+ rerender(<Component url={handleSlow.url} onDoc={onDoc} />)
117
+ rerender(<Component url={handleA.url} onDoc={onDoc} />)
118
+ await waitFor(() => expect(onDoc).toHaveBeenLastCalledWith({ foo: "A" }))
138
119
 
139
120
  // wait for the slow doc to finish loading...
140
121
  await pause(SLOW_DOC_LOAD_TIME_MS * 2)
141
122
 
142
123
  // we didn't update the doc to the slow doc, so it should still be A
143
- await waitFor(() => expect(result.current.doc).toEqual({ foo: "A" }))
124
+ expect(onDoc).not.toHaveBeenCalledWith({ foo: "slow" })
144
125
  })
145
126
  })
146
127
 
147
128
  const pause = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
148
129
 
149
- const getRepoWrapper =
150
- (repo: Repo) =>
151
- ({ children }) =>
152
- <RepoContext.Provider value={repo}>{children}</RepoContext.Provider>
153
-
154
130
  interface ExampleDoc {
155
131
  foo: string
156
132
  }
@@ -1,8 +1,8 @@
1
1
  import { DocumentId, PeerId, Repo } from "@automerge/automerge-repo"
2
2
  import { DummyStorageAdapter } from "@automerge/automerge-repo/test/helpers/DummyStorageAdapter"
3
- import { act, renderHook, waitFor } from "@testing-library/react"
3
+ import { act, render, waitFor } from "@testing-library/react"
4
4
  import React from "react"
5
- import { describe, expect, it } from "vitest"
5
+ import { describe, expect, it, vi } from "vitest"
6
6
  import { useDocuments } from "../src/useDocuments"
7
7
  import { RepoContext } from "../src/useRepo"
8
8
 
@@ -14,7 +14,9 @@ describe("useDocuments", () => {
14
14
  storage: new DummyStorageAdapter(),
15
15
  })
16
16
 
17
- const wrapper = getRepoWrapper(repo)
17
+ const wrapper = ({ children }) => {
18
+ return <RepoContext.Provider value={repo}>{children}</RepoContext.Provider>
19
+ }
18
20
 
19
21
  const documentIds = range(10).map(i => {
20
22
  const handle = repo.create({ foo: i })
@@ -24,88 +26,68 @@ describe("useDocuments", () => {
24
26
  return { repo, wrapper, documentIds }
25
27
  }
26
28
 
29
+ const Component = ({ ids, onDocs }: {
30
+ ids: DocumentId[],
31
+ onDocs: (documents: Record<DocumentId, unknown>) => void,
32
+ }) => {
33
+ const documents = useDocuments(ids)
34
+ onDocs(documents)
35
+ return null
36
+ }
37
+
27
38
  it("returns a collection of documents, given a list of ids", async () => {
28
39
  const { documentIds, wrapper } = setup()
29
- const { result } = renderHook(
30
- () => {
31
- const documents = useDocuments(documentIds)
32
- return { documents }
33
- },
34
- { wrapper }
35
- )
40
+ const onDocs = vi.fn()
36
41
 
37
- await waitFor(() => {
38
- const { documents } = result.current
39
- documentIds.forEach((id, i) => expect(documents[id]).toEqual({ foo: i }))
40
- })
42
+ render(<Component ids={documentIds} onDocs={onDocs} />, { wrapper })
43
+ await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
44
+ Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]))
45
+ ))
41
46
  })
42
47
 
43
48
  it("updates documents when they change", async () => {
44
49
  const { repo, documentIds, wrapper } = setup()
45
-
46
- const { result } = renderHook(
47
- () => {
48
- const documents = useDocuments(documentIds)
49
- return { documents }
50
- },
51
- { wrapper }
52
- )
53
-
54
- await waitFor(() => {
55
- const { documents } = result.current
56
- documentIds.forEach((id, i) => expect(documents[id]).toEqual({ foo: i }))
57
- })
58
-
59
- // multiply the value of foo in each document by 10
60
- documentIds.forEach(id => {
61
- const handle = repo.find(id)
62
- handle.change(s => (s.foo *= 10))
63
- })
64
-
65
- await waitFor(() => {
66
- const { documents } = result.current
67
- documentIds.forEach((id, i) =>
68
- expect(documents[id]).toEqual({ foo: i * 10 })
69
- )
50
+ const onDocs = vi.fn()
51
+
52
+ render(<Component ids={documentIds} onDocs={onDocs} />, { wrapper })
53
+ await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
54
+ Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]))
55
+ ))
56
+
57
+ act(() => {
58
+ // multiply the value of foo in each document by 10
59
+ documentIds.forEach(id => {
60
+ const handle = repo.find(id)
61
+ handle.change(s => (s.foo *= 10))
62
+ })
70
63
  })
64
+ await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
65
+ Object.fromEntries(documentIds.map((id, i) => [id, { foo: i * 10 }]))
66
+ ))
71
67
  })
72
68
 
73
69
  it(`removes documents when they're removed from the list of ids`, async () => {
74
- const { repo, documentIds, wrapper } = setup()
75
- const { result } = renderHook(
76
- () => {
77
- const [ids, setIds] = React.useState(documentIds)
78
- const documents = useDocuments(ids)
79
- return { documents, setIds }
80
- },
81
- { wrapper }
82
- )
83
- const [firstId, ...restIds] = documentIds
70
+ const { documentIds, wrapper } = setup()
71
+ const onDocs = vi.fn()
84
72
 
85
- await waitFor(() => {
86
- const { documents } = result.current
87
- expect(documents[firstId]).toEqual({ foo: 0 })
88
- })
73
+ const { rerender } = render(<Component ids={documentIds} onDocs={onDocs} />, { wrapper })
74
+ await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
75
+ Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]))
76
+ ))
89
77
 
90
78
  // remove the first document
91
- act(() => result.current.setIds(restIds))
92
- // 👆 Note that this only works because restIds is a different object from documentIds.
93
- // If we modified documentIds directly, the hook wouldn't re-run.
94
-
95
- await waitFor(() => {
96
- const { documents } = result.current
97
- expect(documents[firstId]).toBeUndefined()
98
- })
79
+ rerender(<Component ids={documentIds.slice(1)} onDocs={onDocs} />)
80
+ // 👆 Note that this only works because documentIds.slice(1) is a different
81
+ // object from documentIds. If we modified documentIds directly, the hook
82
+ // wouldn't re-run.
83
+ await waitFor(() => expect(onDocs).toHaveBeenCalledWith(
84
+ Object.fromEntries(documentIds.map((id, i) => [id, { foo: i }]).slice(1))
85
+ ))
99
86
  })
100
87
  })
101
88
 
102
89
  const range = (n: number) => [...Array(n).keys()]
103
90
 
104
- const getRepoWrapper =
105
- (repo: Repo) =>
106
- ({ children }) =>
107
- <RepoContext.Provider value={repo}>{children}</RepoContext.Provider>
108
-
109
91
  interface ExampleDoc {
110
92
  foo: number
111
93
  }
@@ -1,11 +1,10 @@
1
- import { PeerId, Repo, AutomergeUrl } from "@automerge/automerge-repo"
1
+ import { AutomergeUrl, DocHandle, PeerId, Repo } from "@automerge/automerge-repo"
2
2
  import { DummyStorageAdapter } from "@automerge/automerge-repo/test/helpers/DummyStorageAdapter"
3
- import { describe, expect, it } from "vitest"
4
- import { RepoContext } from "../src/useRepo"
3
+ import { render, waitFor } from "@testing-library/react"
4
+ import React from "react"
5
+ import { describe, expect, it, vi } from "vitest"
5
6
  import { useHandle } from "../src/useHandle"
6
- import { act, renderHook, waitFor } from "@testing-library/react"
7
- import React, { useState } from "react"
8
- import assert from "assert"
7
+ import { RepoContext } from "../src/useRepo"
9
8
 
10
9
  interface ExampleDoc {
11
10
  foo: string
@@ -39,64 +38,48 @@ describe("useHandle", () => {
39
38
  }
40
39
  }
41
40
 
41
+ const Component = ({ url, onHandle }: {
42
+ url: AutomergeUrl,
43
+ onHandle: (handle: DocHandle<unknown> | undefined) => void,
44
+ }) => {
45
+ const handle = useHandle(url)
46
+ onHandle(handle)
47
+ return null
48
+ }
49
+
42
50
  it("loads a handle", async () => {
43
51
  const { handleA, wrapper } = setup()
52
+ const onHandle = vi.fn()
44
53
 
45
- const { result } = await act(() =>
46
- renderHook(
47
- () => {
48
- const handle = useHandle(handleA.url)
49
- return { handle }
50
- },
51
- { wrapper }
52
- )
53
- )
54
-
55
- assert.deepStrictEqual(result.current.handle, handleA)
54
+ render(<Component url={handleA.url} onHandle={onHandle} />, {wrapper})
55
+ await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA))
56
56
  })
57
57
 
58
58
  it("returns undefined when no url given", async () => {
59
59
  const { wrapper } = setup()
60
+ const onHandle = vi.fn()
60
61
 
61
- const { result } = await act(() =>
62
- renderHook(
63
- () => {
64
- const handle = useHandle()
65
- return { handle }
66
- },
67
- { wrapper }
68
- )
69
- )
70
-
71
- await waitFor(() => expect(result.current.handle).toBeUndefined())
62
+ render(<Component url={undefined} onHandle={onHandle} />, {wrapper})
63
+ await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined))
72
64
  })
73
65
 
74
66
  it("updates the handle when the url changes", async () => {
75
67
  const { wrapper, handleA, handleB } = setup()
68
+ const onHandle = vi.fn()
76
69
 
77
- const { result } = await act(() =>
78
- renderHook(
79
- () => {
80
- const [url, setUrl] = useState<AutomergeUrl>()
81
- const handle = useHandle(url)
82
- return { setUrl, handle }
83
- },
84
- { wrapper }
85
- )
86
- )
87
-
88
- await waitFor(() => expect(result.current).not.toBeNull())
70
+ const { rerender } = render(<Component url={undefined} onHandle={onHandle} />, {wrapper})
71
+ await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined))
89
72
 
90
73
  // set url to doc A
91
- act(() => result.current.setUrl(handleA.url))
92
- await waitFor(() => expect(result.current.handle).toMatchObject(handleA))
74
+ rerender(<Component url={handleA.url} onHandle={onHandle} />)
75
+ await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleA))
93
76
 
94
77
  // set url to doc B
95
- act(() => result.current.setUrl(handleB.url))
96
- await waitFor(() => expect(result.current.handle).toMatchObject(handleB))
78
+ rerender(<Component url={handleB.url} onHandle={onHandle} />)
79
+ await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(handleB))
97
80
 
98
81
  // set url to undefined
99
- act(() => result.current.setUrl(undefined))
100
- await waitFor(() => expect(result.current.handle).toBeUndefined())
82
+ rerender(<Component url={undefined} onHandle={onHandle} />)
83
+ await waitFor(() => expect(onHandle).toHaveBeenLastCalledWith(undefined))
101
84
  })
102
85
  })
@@ -1,16 +1,24 @@
1
- import { renderHook } from "@testing-library/react"
2
- import { describe, expect, test, vi } from "vitest"
3
- import { RepoContext, useRepo } from "../src/useRepo.js"
4
1
  import { Repo } from "@automerge/automerge-repo"
2
+ import { render } from "@testing-library/react"
5
3
  import React from "react"
4
+ import { describe, expect, test, vi } from "vitest"
5
+ import { RepoContext, useRepo } from "../src/useRepo.js"
6
6
 
7
7
  describe("useRepo", () => {
8
+ const Component = ({ onRepo }: {
9
+ onRepo: (repo: Repo) => void,
10
+ }) => {
11
+ const repo = useRepo()
12
+ onRepo(repo)
13
+ return null
14
+ }
15
+
8
16
  test("should error when context unavailable", () => {
9
17
  const repo = new Repo({ network: [] })
10
18
  // Prevent console spam by swallowing console.error "uncaught error" message
11
19
  const spy = vi.spyOn(console, "error")
12
20
  spy.mockImplementation(() => {})
13
- expect(() => renderHook(() => useRepo())).toThrow(
21
+ expect(() => render(<Component onRepo={() => {}}/>)).toThrow(
14
22
  /Repo was not found on RepoContext/
15
23
  )
16
24
  spy.mockRestore()
@@ -21,7 +29,8 @@ describe("useRepo", () => {
21
29
  const wrapper = ({ children }) => (
22
30
  <RepoContext.Provider value={repo} children={children} />
23
31
  )
24
- const { result } = renderHook(() => useRepo(), { wrapper })
25
- expect(result.current).toBe(repo)
32
+ const onRepo = vi.fn()
33
+ render(<Component onRepo={onRepo} />, { wrapper })
34
+ expect(onRepo).toHaveBeenLastCalledWith(repo)
26
35
  })
27
36
  })