jazz-tools 0.19.7 → 0.19.10

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.
Files changed (167) hide show
  1. package/.turbo/turbo-build.log +65 -59
  2. package/CHANGELOG.md +34 -3
  3. package/dist/{chunk-CUS6O5NE.js → chunk-FFEEPZEG.js} +454 -122
  4. package/dist/chunk-FFEEPZEG.js.map +1 -0
  5. package/dist/expo/polyfills.js +22 -0
  6. package/dist/expo/polyfills.js.map +1 -0
  7. package/dist/index.js +26 -6
  8. package/dist/index.js.map +1 -1
  9. package/dist/react/hooks.d.ts +1 -1
  10. package/dist/react/hooks.d.ts.map +1 -1
  11. package/dist/react/index.d.ts +1 -1
  12. package/dist/react/index.d.ts.map +1 -1
  13. package/dist/react/index.js +5 -1
  14. package/dist/react/index.js.map +1 -1
  15. package/dist/react-core/hooks.d.ts +59 -0
  16. package/dist/react-core/hooks.d.ts.map +1 -1
  17. package/dist/react-core/index.js +133 -34
  18. package/dist/react-core/index.js.map +1 -1
  19. package/dist/react-core/tests/testUtils.d.ts +1 -0
  20. package/dist/react-core/tests/testUtils.d.ts.map +1 -1
  21. package/dist/react-core/tests/useSuspenseAccount.test.d.ts +2 -0
  22. package/dist/react-core/tests/useSuspenseAccount.test.d.ts.map +1 -0
  23. package/dist/react-core/tests/useSuspenseCoState.test.d.ts +2 -0
  24. package/dist/react-core/tests/useSuspenseCoState.test.d.ts.map +1 -0
  25. package/dist/react-core/use.d.ts +3 -0
  26. package/dist/react-core/use.d.ts.map +1 -0
  27. package/dist/react-native/index.d.ts +1 -1
  28. package/dist/react-native/index.d.ts.map +1 -1
  29. package/dist/react-native/index.js +717 -9
  30. package/dist/react-native/index.js.map +1 -1
  31. package/dist/react-native/polyfills.js +22 -0
  32. package/dist/react-native/polyfills.js.map +1 -0
  33. package/dist/react-native-core/crypto/RNCrypto.d.ts +2 -0
  34. package/dist/react-native-core/crypto/RNCrypto.d.ts.map +1 -0
  35. package/dist/react-native-core/crypto/RNCrypto.js +3 -0
  36. package/dist/react-native-core/crypto/RNCrypto.js.map +1 -0
  37. package/dist/react-native-core/hooks.d.ts +1 -1
  38. package/dist/react-native-core/hooks.d.ts.map +1 -1
  39. package/dist/react-native-core/index.d.ts.map +1 -1
  40. package/dist/react-native-core/index.js +5 -1
  41. package/dist/react-native-core/index.js.map +1 -1
  42. package/dist/react-native-core/platform.d.ts +2 -1
  43. package/dist/react-native-core/platform.d.ts.map +1 -1
  44. package/dist/testing.js +1 -1
  45. package/dist/testing.js.map +1 -1
  46. package/dist/tools/coValues/account.d.ts +3 -3
  47. package/dist/tools/coValues/account.d.ts.map +1 -1
  48. package/dist/tools/coValues/coFeed.d.ts +3 -3
  49. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  50. package/dist/tools/coValues/coList.d.ts +4 -4
  51. package/dist/tools/coValues/coList.d.ts.map +1 -1
  52. package/dist/tools/coValues/coMap.d.ts +7 -7
  53. package/dist/tools/coValues/coMap.d.ts.map +1 -1
  54. package/dist/tools/coValues/coPlainText.d.ts +2 -2
  55. package/dist/tools/coValues/coPlainText.d.ts.map +1 -1
  56. package/dist/tools/coValues/coVector.d.ts +2 -2
  57. package/dist/tools/coValues/coVector.d.ts.map +1 -1
  58. package/dist/tools/coValues/deepLoading.d.ts +24 -0
  59. package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
  60. package/dist/tools/coValues/group.d.ts +2 -2
  61. package/dist/tools/coValues/group.d.ts.map +1 -1
  62. package/dist/tools/coValues/interfaces.d.ts +7 -7
  63. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  64. package/dist/tools/coValues/schemaUnion.d.ts +2 -2
  65. package/dist/tools/coValues/schemaUnion.d.ts.map +1 -1
  66. package/dist/tools/config.d.ts +3 -0
  67. package/dist/tools/config.d.ts.map +1 -0
  68. package/dist/tools/exports.d.ts +2 -0
  69. package/dist/tools/exports.d.ts.map +1 -1
  70. package/dist/tools/implementation/ContextManager.d.ts +3 -0
  71. package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
  72. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +2 -2
  73. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
  74. package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts +2 -2
  75. package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts.map +1 -1
  76. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +2 -2
  77. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
  78. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +4 -4
  79. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
  80. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +4 -4
  81. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
  82. package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts +4 -4
  83. package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts.map +1 -1
  84. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +2 -2
  85. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
  86. package/dist/tools/implementation/zodSchema/schemaTypes/GroupSchema.d.ts +2 -2
  87. package/dist/tools/implementation/zodSchema/schemaTypes/GroupSchema.d.ts.map +1 -1
  88. package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +2 -2
  89. package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -1
  90. package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +2 -2
  91. package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts.map +1 -1
  92. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  93. package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +8 -22
  94. package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
  95. package/dist/tools/subscribe/JazzError.d.ts.map +1 -1
  96. package/dist/tools/subscribe/SubscriptionCache.d.ts +51 -0
  97. package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -0
  98. package/dist/tools/subscribe/SubscriptionScope.d.ts +27 -2
  99. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  100. package/dist/tools/subscribe/errorReporting.d.ts +31 -0
  101. package/dist/tools/subscribe/errorReporting.d.ts.map +1 -0
  102. package/dist/tools/subscribe/utils.d.ts +9 -1
  103. package/dist/tools/subscribe/utils.d.ts.map +1 -1
  104. package/dist/tools/testing.d.ts +2 -2
  105. package/dist/tools/testing.d.ts.map +1 -1
  106. package/dist/tools/tests/SubscriptionCache.test.d.ts +2 -0
  107. package/dist/tools/tests/SubscriptionCache.test.d.ts.map +1 -0
  108. package/dist/tools/tests/errorReporting.test.d.ts +2 -0
  109. package/dist/tools/tests/errorReporting.test.d.ts.map +1 -0
  110. package/package.json +22 -7
  111. package/src/react/hooks.tsx +2 -0
  112. package/src/react/index.ts +1 -14
  113. package/src/react-core/hooks.ts +181 -16
  114. package/src/react-core/tests/createCoValueSubscriptionContext.test.tsx +18 -8
  115. package/src/react-core/tests/testUtils.tsx +67 -5
  116. package/src/react-core/tests/useCoState.test.ts +3 -7
  117. package/src/react-core/tests/useSubscriptionSelector.test.ts +3 -7
  118. package/src/react-core/tests/useSuspenseAccount.test.tsx +343 -0
  119. package/src/react-core/tests/useSuspenseCoState.test.tsx +1182 -0
  120. package/src/react-core/use.ts +46 -0
  121. package/src/react-native/index.ts +1 -1
  122. package/src/react-native-core/crypto/RNCrypto.ts +1 -0
  123. package/src/react-native-core/hooks.tsx +2 -0
  124. package/src/react-native-core/index.ts +2 -0
  125. package/src/react-native-core/platform.ts +2 -1
  126. package/src/react-native-core/polyfills/index.js +28 -0
  127. package/src/tools/coValues/account.ts +3 -4
  128. package/src/tools/coValues/coFeed.ts +3 -2
  129. package/src/tools/coValues/coList.ts +4 -4
  130. package/src/tools/coValues/coMap.ts +4 -4
  131. package/src/tools/coValues/coPlainText.ts +2 -2
  132. package/src/tools/coValues/coVector.ts +2 -2
  133. package/src/tools/coValues/deepLoading.ts +31 -0
  134. package/src/tools/coValues/group.ts +2 -2
  135. package/src/tools/coValues/interfaces.ts +21 -26
  136. package/src/tools/coValues/schemaUnion.ts +2 -2
  137. package/src/tools/config.ts +9 -0
  138. package/src/tools/exports.ts +4 -0
  139. package/src/tools/implementation/ContextManager.ts +13 -0
  140. package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +2 -2
  141. package/src/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.ts +2 -2
  142. package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +2 -2
  143. package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +4 -4
  144. package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +4 -4
  145. package/src/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.ts +4 -10
  146. package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +2 -2
  147. package/src/tools/implementation/zodSchema/schemaTypes/GroupSchema.ts +2 -2
  148. package/src/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.ts +2 -2
  149. package/src/tools/implementation/zodSchema/schemaTypes/RichTextSchema.ts +2 -2
  150. package/src/tools/subscribe/CoValueCoreSubscription.ts +71 -100
  151. package/src/tools/subscribe/JazzError.ts +9 -6
  152. package/src/tools/subscribe/SubscriptionCache.ts +272 -0
  153. package/src/tools/subscribe/SubscriptionScope.ts +218 -29
  154. package/src/tools/subscribe/errorReporting.ts +67 -0
  155. package/src/tools/subscribe/utils.ts +77 -0
  156. package/src/tools/testing.ts +0 -3
  157. package/src/tools/tests/CoValueCoreSubscription.test.ts +46 -12
  158. package/src/tools/tests/ContextManager.test.ts +85 -0
  159. package/src/tools/tests/SubscriptionCache.test.ts +237 -0
  160. package/src/tools/tests/coMap.test.ts +5 -7
  161. package/src/tools/tests/deepLoading.test.ts +47 -47
  162. package/src/tools/tests/errorReporting.test.ts +103 -0
  163. package/src/tools/tests/load.test.ts +21 -1
  164. package/src/tools/tests/request.test.ts +2 -1
  165. package/src/tools/tests/subscribe.test.ts +44 -0
  166. package/tsup.config.ts +17 -0
  167. package/dist/chunk-CUS6O5NE.js.map +0 -1
@@ -164,15 +164,31 @@ describe("createCoValueSubscriptionContext", () => {
164
164
  });
165
165
 
166
166
  it("the provider shows a loading fallback when loading the coValue", async () => {
167
+ await setupJazzTestSync({
168
+ asyncPeers: true,
169
+ });
170
+
167
171
  const TestMap = co.map({
168
172
  value: z.string(),
169
173
  });
170
174
 
175
+ const map = TestMap.create({
176
+ value: "123",
177
+ });
178
+
179
+ await createJazzTestAccount({
180
+ isCurrentActiveAccount: true,
181
+ });
182
+
171
183
  const { Provider } = createCoValueSubscriptionContext(TestMap);
172
184
 
173
185
  const { container } = render(
174
186
  <JazzTestProvider>
175
- <Provider id="co_test123" loadingFallback={<div>Loading...</div>}>
187
+ <Provider
188
+ id={map.$jazz.id}
189
+ loadingFallback={<div>Loading...</div>}
190
+ unavailableFallback={<div>Unavailable</div>}
191
+ >
176
192
  <div>Children should not render</div>
177
193
  </Provider>
178
194
  </JazzTestProvider>,
@@ -203,13 +219,7 @@ describe("createCoValueSubscriptionContext", () => {
203
219
  </JazzTestProvider>,
204
220
  );
205
221
 
206
- // Initially shows loading fallback
207
- expect(container.textContent).toContain("Loading...");
208
-
209
- // Should show unavailable fallback after CoValue load timeout
210
- await waitFor(() => {
211
- expect(container.textContent).toContain("Unavailable");
212
- });
222
+ expect(container.textContent).toContain("Unavailable");
213
223
 
214
224
  // Children should never be rendered
215
225
  expect(container.textContent).not.toContain("Children should not render");
@@ -4,9 +4,12 @@ import {
4
4
  render,
5
5
  renderHook,
6
6
  } from "@testing-library/react";
7
- import { Account, AnonymousJazzAgent, AuthSecretStorage } from "jazz-tools";
7
+ import { Account, AnonymousJazzAgent } from "jazz-tools";
8
8
  import React from "react";
9
9
  import { JazzTestProvider } from "../testing.js";
10
+ import { getSqliteStorageAsync, SQLiteDatabaseDriverAsync } from "cojson";
11
+ import Database, { type Database as DatabaseT } from "libsql";
12
+ import { onTestFinished } from "vitest";
10
13
 
11
14
  type JazzExtendedOptions = {
12
15
  account?: Account | { guest: AnonymousJazzAgent };
@@ -23,12 +26,16 @@ const customRender = (
23
26
  account={options.account}
24
27
  isAuthenticated={options.isAuthenticated}
25
28
  >
26
- {children}
29
+ {options.wrapper ? (
30
+ <options.wrapper>{children}</options.wrapper>
31
+ ) : (
32
+ children
33
+ )}
27
34
  </JazzTestProvider>
28
35
  );
29
36
  };
30
37
 
31
- return render(ui, { wrapper: AllTheProviders, ...options });
38
+ return render(ui, { ...options, wrapper: AllTheProviders });
32
39
  };
33
40
 
34
41
  const customRenderHook = <TProps, TResult>(
@@ -41,12 +48,16 @@ const customRenderHook = <TProps, TResult>(
41
48
  account={options.account}
42
49
  isAuthenticated={options.isAuthenticated}
43
50
  >
44
- {children}
51
+ {options.wrapper ? (
52
+ <options.wrapper>{children}</options.wrapper>
53
+ ) : (
54
+ children
55
+ )}
45
56
  </JazzTestProvider>
46
57
  );
47
58
  };
48
59
 
49
- return renderHook(callback, { wrapper: AllTheProviders, ...options });
60
+ return renderHook(callback, { ...options, wrapper: AllTheProviders });
50
61
  };
51
62
 
52
63
  // re-export everything
@@ -55,3 +66,54 @@ export * from "@testing-library/react";
55
66
  // override render method
56
67
  export { customRender as render };
57
68
  export { customRenderHook as renderHook };
69
+
70
+ class LibSQLSqliteAsyncDriver implements SQLiteDatabaseDriverAsync {
71
+ private readonly db: DatabaseT;
72
+
73
+ constructor(filename: string) {
74
+ this.db = new Database(filename, {});
75
+ }
76
+
77
+ async initialize() {
78
+ await this.db.pragma("journal_mode = WAL");
79
+ }
80
+
81
+ async run(sql: string, params: unknown[]) {
82
+ this.db.prepare(sql).run(params);
83
+ }
84
+
85
+ async query<T>(sql: string, params: unknown[]): Promise<T[]> {
86
+ return this.db.prepare(sql).all(params) as T[];
87
+ }
88
+
89
+ async get<T>(sql: string, params: unknown[]): Promise<T | undefined> {
90
+ return this.db.prepare(sql).get(params) as T | undefined;
91
+ }
92
+
93
+ async transaction(callback: () => unknown) {
94
+ await this.run("BEGIN TRANSACTION", []);
95
+
96
+ try {
97
+ await callback();
98
+ await this.run("COMMIT", []);
99
+ } catch (error) {
100
+ await this.run("ROLLBACK", []);
101
+ }
102
+ }
103
+
104
+ async closeDb() {
105
+ this.db.close();
106
+ }
107
+ }
108
+
109
+ export async function createAsyncStorage() {
110
+ const storage = await getSqliteStorageAsync(
111
+ new LibSQLSqliteAsyncDriver(":memory:"),
112
+ );
113
+
114
+ onTestFinished(() => {
115
+ storage.close();
116
+ });
117
+
118
+ return storage;
119
+ }
@@ -64,13 +64,9 @@ describe("useCoState", () => {
64
64
  account,
65
65
  });
66
66
 
67
- expect(result.current.$jazz.loadingState).toBe(CoValueLoadingState.LOADING);
68
-
69
- await waitFor(() => {
70
- expect(result.current.$jazz.loadingState).toBe(
71
- CoValueLoadingState.UNAVAILABLE,
72
- );
73
- });
67
+ expect(result.current.$jazz.loadingState).toBe(
68
+ CoValueLoadingState.UNAVAILABLE,
69
+ );
74
70
  });
75
71
 
76
72
  it("should update the value when the coValue changes", async () => {
@@ -89,13 +89,9 @@ describe("useSubscriptionSelector", () => {
89
89
  return useSubscriptionSelector(subscription);
90
90
  });
91
91
 
92
- expect(result.current.$jazz.loadingState).toBe(CoValueLoadingState.LOADING);
93
-
94
- await waitFor(() => {
95
- expect(result.current.$jazz.loadingState).toBe(
96
- CoValueLoadingState.UNAVAILABLE,
97
- );
98
- });
92
+ expect(result.current.$jazz.loadingState).toBe(
93
+ CoValueLoadingState.UNAVAILABLE,
94
+ );
99
95
  });
100
96
 
101
97
  it("should return coAccount", async () => {
@@ -0,0 +1,343 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { cojsonInternals } from "cojson";
4
+ import { Account, Group, Loaded, co, z } from "jazz-tools";
5
+ import { assertLoaded } from "jazz-tools/testing";
6
+ import { beforeEach, describe, expect, expectTypeOf, it } from "vitest";
7
+ import React, { Suspense } from "react";
8
+ import { useSuspenseAccount, useLogOut } from "../hooks.js";
9
+ import {
10
+ createJazzTestAccount,
11
+ createJazzTestGuest,
12
+ setActiveAccount,
13
+ setupJazzTestSync,
14
+ } from "../testing.js";
15
+ import { act, render, renderHook, waitFor } from "./testUtils.js";
16
+ import { ErrorBoundary } from "react-error-boundary";
17
+
18
+ // Silence unhandled rejection errors coming from Suspense
19
+ process.on("unhandledRejection", () => {});
20
+
21
+ beforeEach(async () => {
22
+ await setupJazzTestSync({
23
+ asyncPeers: true,
24
+ });
25
+
26
+ await createJazzTestAccount({
27
+ isCurrentActiveAccount: true,
28
+ });
29
+ });
30
+
31
+ cojsonInternals.setCoValueLoadingRetryDelay(10);
32
+
33
+ describe("useSuspenseAccount", () => {
34
+ it("should return loaded account without suspending when data is available", async () => {
35
+ const AccountRoot = co.map({
36
+ projects: co.list(
37
+ co.map({
38
+ name: z.string(),
39
+ description: z.string(),
40
+ }),
41
+ ),
42
+ });
43
+
44
+ const MyAppAccount = co
45
+ .account({
46
+ profile: co.profile({
47
+ name: z.string(),
48
+ }),
49
+ root: AccountRoot,
50
+ })
51
+ .withMigration((account, creationProps) => {
52
+ if (!account.$jazz.refs.profile) {
53
+ account.$jazz.set("profile", {
54
+ name: creationProps?.name || "John Doe",
55
+ });
56
+ }
57
+ if (!account.$jazz.refs.root) {
58
+ account.$jazz.set("root", {
59
+ projects: [],
60
+ });
61
+ }
62
+ });
63
+
64
+ const account = await createJazzTestAccount({
65
+ AccountSchema: MyAppAccount,
66
+ isCurrentActiveAccount: true,
67
+ creationProps: {
68
+ name: "John Doe",
69
+ },
70
+ });
71
+
72
+ let suspenseTriggered = false;
73
+
74
+ const SuspenseFallback = () => {
75
+ suspenseTriggered = true;
76
+ return <div>Loading...</div>;
77
+ };
78
+
79
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
80
+ <Suspense fallback={<SuspenseFallback />}>{children}</Suspense>
81
+ );
82
+
83
+ const { result } = renderHook(
84
+ () =>
85
+ useSuspenseAccount(MyAppAccount, {
86
+ resolve: {
87
+ profile: true,
88
+ root: {
89
+ projects: true,
90
+ },
91
+ },
92
+ }),
93
+ {
94
+ account,
95
+ wrapper,
96
+ },
97
+ );
98
+
99
+ // Wait for any async operations to complete
100
+ await waitFor(() => {
101
+ expect(result.current).toBeDefined();
102
+ });
103
+
104
+ // Verify Suspense was not triggered since data was immediately available
105
+ expect(suspenseTriggered).toBe(false);
106
+
107
+ // Verify the hook returns loaded data
108
+ assertLoaded(result.current);
109
+ expect(result.current.profile.name).toBe("John Doe");
110
+ expect(result.current.root.projects).toEqual([]);
111
+ });
112
+
113
+ it("should have Loaded<A> return type", async () => {
114
+ const AccountRoot = co.map({
115
+ value: z.string(),
116
+ });
117
+
118
+ const MyAppAccount = co
119
+ .account({
120
+ profile: co.profile({
121
+ name: z.string(),
122
+ }),
123
+ root: AccountRoot,
124
+ })
125
+ .withMigration((account, creationProps) => {
126
+ if (!account.$jazz.refs.profile) {
127
+ account.$jazz.set("profile", {
128
+ name: creationProps?.name || "Test User",
129
+ });
130
+ }
131
+ if (!account.$jazz.refs.root) {
132
+ account.$jazz.set("root", {
133
+ value: "test",
134
+ });
135
+ }
136
+ });
137
+
138
+ const account = await createJazzTestAccount({
139
+ AccountSchema: MyAppAccount,
140
+ isCurrentActiveAccount: true,
141
+ });
142
+
143
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
144
+ <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
145
+ );
146
+
147
+ const { result } = renderHook(() => useSuspenseAccount(MyAppAccount), {
148
+ account,
149
+ wrapper,
150
+ });
151
+
152
+ await waitFor(() => {
153
+ expect(result.current).toBeDefined();
154
+ });
155
+
156
+ // Verify the return type is Loaded<typeof MyAppAccount>
157
+ expectTypeOf(result.current).toEqualTypeOf<Loaded<typeof MyAppAccount>>();
158
+ });
159
+
160
+ it("should suspend when account data is not immediately available", async () => {
161
+ const Project = co.map({
162
+ name: z.string(),
163
+ description: z.string(),
164
+ });
165
+
166
+ const AccountRoot = co.map({
167
+ projects: co.list(Project),
168
+ });
169
+
170
+ const root = AccountRoot.create(
171
+ {
172
+ projects: [
173
+ {
174
+ name: "My Project",
175
+ description: "A test project",
176
+ },
177
+ ],
178
+ },
179
+ Group.create().makePublic(),
180
+ );
181
+
182
+ const MyAppAccount = co.account({
183
+ profile: co.profile({
184
+ name: z.string(),
185
+ }),
186
+ root: AccountRoot,
187
+ });
188
+
189
+ const account = await createJazzTestAccount({
190
+ AccountSchema: MyAppAccount,
191
+ isCurrentActiveAccount: true,
192
+ creationProps: {
193
+ name: "John Doe",
194
+ },
195
+ });
196
+
197
+ account.$jazz.set("root", root);
198
+
199
+ let suspenseTriggered = false;
200
+
201
+ const SuspenseFallback = () => {
202
+ suspenseTriggered = true;
203
+ return <div>Loading...</div>;
204
+ };
205
+
206
+ const TestComponent = () => {
207
+ const account = useSuspenseAccount(MyAppAccount, {
208
+ resolve: {
209
+ root: {
210
+ projects: {
211
+ $each: true,
212
+ },
213
+ },
214
+ },
215
+ });
216
+ return <div>{account.root.projects[0]?.name || "No project"}</div>;
217
+ };
218
+
219
+ const { container } = await act(async () => {
220
+ return render(
221
+ <Suspense fallback={<SuspenseFallback />}>
222
+ <TestComponent />
223
+ </Suspense>,
224
+ {
225
+ account,
226
+ },
227
+ );
228
+ });
229
+
230
+ expect(suspenseTriggered).toBe(true);
231
+
232
+ // Wait for data to load - the subscription should update and resolve
233
+ await waitFor(() => {
234
+ expect(container.textContent).toContain("My Project");
235
+ expect(container.textContent).not.toContain("Loading...");
236
+ });
237
+ });
238
+
239
+ it("should throw error for anonymous agent", async () => {
240
+ const MyAppAccount = co.account({
241
+ profile: co.profile({
242
+ name: z.string(),
243
+ }),
244
+ root: co.map({
245
+ value: z.string(),
246
+ }),
247
+ });
248
+
249
+ const guestAccount = await createJazzTestGuest();
250
+
251
+ const TestComponent = () => {
252
+ useSuspenseAccount(MyAppAccount);
253
+ return <div>Should not render</div>;
254
+ };
255
+
256
+ const { container } = await act(async () => {
257
+ return render(
258
+ <ErrorBoundary fallback={<div>Error!</div>}>
259
+ <Suspense fallback={<div>Loading...</div>}>
260
+ <TestComponent />
261
+ </Suspense>
262
+ </ErrorBoundary>,
263
+ {
264
+ account: guestAccount,
265
+ },
266
+ );
267
+ });
268
+
269
+ // Verify error is displayed in error boundary
270
+ await waitFor(
271
+ () => {
272
+ expect(container.textContent).toContain("Error!");
273
+ },
274
+ { timeout: 1000 },
275
+ );
276
+ });
277
+
278
+ it("should handle account logout", async () => {
279
+ const MyAppAccount = co
280
+ .account({
281
+ profile: co.profile({
282
+ name: z.string(),
283
+ }),
284
+ root: co.map({
285
+ value: z.string(),
286
+ }),
287
+ })
288
+ .withMigration((account, creationProps) => {
289
+ if (!account.$jazz.refs.profile) {
290
+ account.$jazz.set("profile", {
291
+ name: creationProps?.name || "John Doe",
292
+ });
293
+ }
294
+ if (!account.$jazz.refs.root) {
295
+ account.$jazz.set("root", {
296
+ value: "test",
297
+ });
298
+ }
299
+ });
300
+
301
+ const account = await createJazzTestAccount({
302
+ AccountSchema: MyAppAccount,
303
+ isCurrentActiveAccount: true,
304
+ creationProps: {
305
+ name: "John Doe",
306
+ },
307
+ });
308
+
309
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
310
+ <Suspense fallback={<div>Loading...</div>}>
311
+ <ErrorBoundary fallback={<div>Error!</div>}>{children}</ErrorBoundary>
312
+ </Suspense>
313
+ );
314
+
315
+ const { result } = renderHook(
316
+ () => {
317
+ const account = useSuspenseAccount(MyAppAccount);
318
+ const logOut = useLogOut();
319
+ return { account, logOut };
320
+ },
321
+ {
322
+ account,
323
+ wrapper,
324
+ },
325
+ );
326
+
327
+ // Wait for account to load
328
+ await waitFor(() => {
329
+ expect(result.current.account).toBeDefined();
330
+ });
331
+
332
+ // Verify initial account data
333
+ assertLoaded(result.current.account);
334
+ const initialAccountId = result.current.account.$jazz.id;
335
+
336
+ // Logout should cause an error since useSuspenseAccount requires authentication
337
+ await act(async () => {
338
+ result.current.logOut();
339
+ });
340
+
341
+ expect(result.current.account.$jazz.id).not.toBe(initialAccountId);
342
+ });
343
+ });