@pomwik/afterdb-react 0.0.0-stage → 0.20.1
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/CHANGES-FROM-UPSTREAM.md +57 -0
- package/LICENSE +177 -0
- package/NOTICE +9 -0
- package/README.md +10 -2
- package/dist/RealmContext.d.ts +153 -0
- package/dist/RealmContext.d.ts.map +1 -0
- package/dist/RealmProvider.d.ts +95 -0
- package/dist/RealmProvider.d.ts.map +1 -0
- package/dist/cachedCollection.d.ts +58 -0
- package/dist/cachedCollection.d.ts.map +1 -0
- package/dist/cachedObject.d.ts +48 -0
- package/dist/cachedObject.d.ts.map +1 -0
- package/dist/helpers.d.ts +22 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/index.cjs +2818 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2809 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.types.tsbuildinfo +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useObject.d.ts +30 -0
- package/dist/useObject.d.ts.map +1 -0
- package/dist/useQuery.d.ts +31 -0
- package/dist/useQuery.d.ts.map +1 -0
- package/dist/useRealm.d.ts +12 -0
- package/dist/useRealm.d.ts.map +1 -0
- package/package.json +117 -4
- package/src/RealmContext.ts +196 -0
- package/src/RealmProvider.tsx +251 -0
- package/src/__tests__/RealmProvider.test.tsx +583 -0
- package/src/__tests__/createRealmTestContext.ts +68 -0
- package/src/__tests__/helpers.ts +35 -0
- package/src/__tests__/mocks.ts +154 -0
- package/src/__tests__/profileHook.tsx +76 -0
- package/src/__tests__/setup.ts +22 -0
- package/src/__tests__/tsconfig.json +14 -0
- package/src/__tests__/useObjectHook.test.tsx +158 -0
- package/src/__tests__/useObjectRender.test.tsx +630 -0
- package/src/__tests__/useQueryHook.test.tsx +209 -0
- package/src/__tests__/useQueryRender.test.tsx +463 -0
- package/src/cachedCollection.ts +210 -0
- package/src/cachedObject.ts +164 -0
- package/src/helpers.ts +60 -0
- package/src/index.tsx +130 -0
- package/src/runtime.d.ts +31 -0
- package/src/types.ts +92 -0
- package/src/useObject.tsx +227 -0
- package/src/useQuery.tsx +205 -0
- package/src/useRealm.tsx +41 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2021 Realm Inc.
|
|
4
|
+
//
|
|
5
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
// you may not use this file except in compliance with the License.
|
|
7
|
+
// You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
// See the License for the specific language governing permissions and
|
|
15
|
+
// limitations under the License.
|
|
16
|
+
//
|
|
17
|
+
////////////////////////////////////////////////////////////////////////////
|
|
18
|
+
|
|
19
|
+
import React, { useRef, useState } from "react";
|
|
20
|
+
import Realm from "realm";
|
|
21
|
+
import { Button, Text, View } from "react-native";
|
|
22
|
+
import { act, fireEvent, render, renderHook, waitFor } from "@testing-library/react-native";
|
|
23
|
+
|
|
24
|
+
import { RealmProvider, createRealmContext } from "..";
|
|
25
|
+
import {
|
|
26
|
+
RealmProviderFallback,
|
|
27
|
+
RealmProviderFromRealm,
|
|
28
|
+
areConfigurationsIdentical,
|
|
29
|
+
mergeRealmConfiguration,
|
|
30
|
+
} from "../RealmProvider";
|
|
31
|
+
import { randomRealmPath } from "./helpers";
|
|
32
|
+
import { RealmContext } from "../RealmContext";
|
|
33
|
+
import { MockedProgressRealmPromiseWithDelay, mockRealmOpen } from "./mocks";
|
|
34
|
+
|
|
35
|
+
const dogSchema: Realm.ObjectSchema = {
|
|
36
|
+
name: "dog",
|
|
37
|
+
primaryKey: "_id",
|
|
38
|
+
properties: {
|
|
39
|
+
_id: "int",
|
|
40
|
+
name: "string",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const catSchema: Realm.ObjectSchema = {
|
|
45
|
+
name: "cat",
|
|
46
|
+
primaryKey: "_id",
|
|
47
|
+
properties: {
|
|
48
|
+
_id: "int",
|
|
49
|
+
name: "string",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const withConfigRealmContext = createRealmContext({
|
|
54
|
+
schema: [dogSchema],
|
|
55
|
+
inMemory: true,
|
|
56
|
+
path: randomRealmPath(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("RealmProvider", () => {
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
Realm.clearTestState();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("with a Realm Configuration", () => {
|
|
65
|
+
const { RealmProvider, useRealm } = withConfigRealmContext;
|
|
66
|
+
|
|
67
|
+
it("returns the configured realm with useRealm", async () => {
|
|
68
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => <RealmProvider>{children}</RealmProvider>;
|
|
69
|
+
const { result } = renderHook(() => useRealm(), { wrapper });
|
|
70
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
71
|
+
const realm = result.current;
|
|
72
|
+
expect(realm).toBeInstanceOf(Realm);
|
|
73
|
+
expect(realm.schema[0].name).toBe("dog");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("closes realm on unmount by default", async () => {
|
|
77
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => <RealmProvider>{children}</RealmProvider>;
|
|
78
|
+
const { result, unmount } = renderHook(() => useRealm(), { wrapper });
|
|
79
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
80
|
+
const realm = result.current;
|
|
81
|
+
unmount();
|
|
82
|
+
expect(realm.isClosed).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("does not close realm on unmount if closeOnUnmount is false", async () => {
|
|
86
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
87
|
+
<RealmProvider closeOnUnmount={false}>{children}</RealmProvider>
|
|
88
|
+
);
|
|
89
|
+
const { result, unmount } = renderHook(() => useRealm(), { wrapper });
|
|
90
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
91
|
+
const realm = result.current;
|
|
92
|
+
unmount();
|
|
93
|
+
expect(realm.isClosed).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("will override the configuration provided in createRealmContext", async () => {
|
|
97
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
98
|
+
<RealmProvider schema={[catSchema]}>{children}</RealmProvider>
|
|
99
|
+
);
|
|
100
|
+
const { result } = renderHook(() => useRealm(), { wrapper });
|
|
101
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
102
|
+
const realm = result.current;
|
|
103
|
+
expect(realm).toBeInstanceOf(Realm);
|
|
104
|
+
expect(realm.schema[0].name).toBe("cat");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("can be provided in multiple parts of an application", async () => {
|
|
108
|
+
const RealmComponent = () => {
|
|
109
|
+
const realm = useRealm();
|
|
110
|
+
return (
|
|
111
|
+
<Button
|
|
112
|
+
testID="action"
|
|
113
|
+
title="toggle"
|
|
114
|
+
onPress={() =>
|
|
115
|
+
realm.write(() => {
|
|
116
|
+
realm.create("dog", { _id: new Date().getTime(), name: "Rex" });
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
const App = () => {
|
|
123
|
+
const [toggleComponent, setToggleComponent] = useState(true);
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<View testID="firstRealmProvider">
|
|
127
|
+
<RealmProvider>
|
|
128
|
+
<RealmComponent />
|
|
129
|
+
</RealmProvider>
|
|
130
|
+
</View>
|
|
131
|
+
{toggleComponent && (
|
|
132
|
+
<View testID="secondRealmProvider">
|
|
133
|
+
<RealmProvider>
|
|
134
|
+
<View />
|
|
135
|
+
</RealmProvider>
|
|
136
|
+
</View>
|
|
137
|
+
)}
|
|
138
|
+
<Button testID="toggle" title="toggle" onPress={() => setToggleComponent(!toggleComponent)} />
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
const { getByTestId } = render(<App />);
|
|
143
|
+
const secondRealmProvider = getByTestId("secondRealmProvider");
|
|
144
|
+
const toggleComponent = getByTestId("toggle");
|
|
145
|
+
const actionComponent = await waitFor(() => getByTestId("action"));
|
|
146
|
+
|
|
147
|
+
expect(secondRealmProvider).not.toBeEmptyElement();
|
|
148
|
+
|
|
149
|
+
await act(async () => {
|
|
150
|
+
fireEvent.press(toggleComponent);
|
|
151
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
152
|
+
});
|
|
153
|
+
expect(() => getByTestId("secondRealmProvider")).toThrow(
|
|
154
|
+
"Unable to find an element with testID: secondRealmProvider",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// This is actually a bug that we need to fix on a deeper level
|
|
158
|
+
await act(async () => {
|
|
159
|
+
expect(() => fireEvent.press(actionComponent)).toThrow("Cannot access realm that has been closed.");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("handles state changes to its configuration", async () => {
|
|
164
|
+
const RealmComponent = () => {
|
|
165
|
+
const realm = useRealm();
|
|
166
|
+
return <Text testID="schemaName">{realm.schema[0].name}</Text>;
|
|
167
|
+
};
|
|
168
|
+
const App = () => {
|
|
169
|
+
const [schema, setSchema] = useState(dogSchema);
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
<View testID="firstRealmProvider">
|
|
173
|
+
<RealmProvider schema={[schema]}>
|
|
174
|
+
<RealmComponent />
|
|
175
|
+
</RealmProvider>
|
|
176
|
+
</View>
|
|
177
|
+
<Button testID="changeSchema" title="change schema" onPress={() => setSchema(catSchema)} />
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
const { getByTestId } = render(<App />);
|
|
182
|
+
const schemaNameContainer = await waitFor(() => getByTestId("schemaName"));
|
|
183
|
+
const changeSchemaButton = getByTestId("changeSchema");
|
|
184
|
+
|
|
185
|
+
expect(schemaNameContainer).toHaveTextContent("dog");
|
|
186
|
+
|
|
187
|
+
await act(async () => {
|
|
188
|
+
fireEvent.press(changeSchemaButton);
|
|
189
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Changing the realm provider configuration will cause a comlete new remount
|
|
193
|
+
// of the child component. Therefore it must be retreived again
|
|
194
|
+
const newSchemaNameContainer = getByTestId("schemaName");
|
|
195
|
+
|
|
196
|
+
expect(newSchemaNameContainer).toHaveTextContent("cat");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("can access realm through realmRef as a forwarded ref", async () => {
|
|
200
|
+
const RealmComponent = () => {
|
|
201
|
+
const realm = useRealm();
|
|
202
|
+
return <Text testID="schemaName">{realm.schema[0].name}</Text>;
|
|
203
|
+
};
|
|
204
|
+
const App = () => {
|
|
205
|
+
const realmRef = useRef<Realm | null>(null);
|
|
206
|
+
const [path, setPath] = useState("");
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
<View testID="firstRealmProvider">
|
|
210
|
+
<RealmProvider realmRef={realmRef} schema={[dogSchema]} path="testPath.realm">
|
|
211
|
+
<RealmComponent />
|
|
212
|
+
</RealmProvider>
|
|
213
|
+
</View>
|
|
214
|
+
<Button
|
|
215
|
+
testID="toggleRefPath"
|
|
216
|
+
title="toggle ref path"
|
|
217
|
+
onPress={() => setPath(realmRef?.current?.path ?? "")}
|
|
218
|
+
/>
|
|
219
|
+
{realmRef.current && <Text testID="realmRefPath">{path}</Text>}
|
|
220
|
+
</>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
const { getByTestId, queryByTestId } = render(<App />);
|
|
224
|
+
await waitFor(() => getByTestId("schemaName"));
|
|
225
|
+
const toggleRefPath = getByTestId("toggleRefPath");
|
|
226
|
+
|
|
227
|
+
// Wait a tick for the RealmProvider to set the reference and then call a function that uses the ref
|
|
228
|
+
await act(async () => {
|
|
229
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
230
|
+
fireEvent.press(toggleRefPath);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const realmRefPathText = await waitFor(() => queryByTestId("realmRefPath"));
|
|
234
|
+
|
|
235
|
+
expect(realmRefPathText).toHaveTextContent("testPath.realm", { exact: false });
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("with an existing Realm instance", () => {
|
|
240
|
+
let existingRealmInstance: Realm;
|
|
241
|
+
let realmContextWithRealmInstance: RealmContext<RealmProviderFromRealm>;
|
|
242
|
+
|
|
243
|
+
beforeEach(() => {
|
|
244
|
+
existingRealmInstance = new Realm({
|
|
245
|
+
schema: [dogSchema],
|
|
246
|
+
inMemory: true,
|
|
247
|
+
path: randomRealmPath(),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
realmContextWithRealmInstance = createRealmContext(existingRealmInstance);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("returns the given realm with useRealm", async () => {
|
|
254
|
+
const { RealmProvider, useRealm } = realmContextWithRealmInstance;
|
|
255
|
+
|
|
256
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => <RealmProvider>{children}</RealmProvider>;
|
|
257
|
+
const { result } = renderHook(() => useRealm(), { wrapper });
|
|
258
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
259
|
+
const realm = result.current;
|
|
260
|
+
|
|
261
|
+
expect(realm).toStrictEqual(existingRealmInstance);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("does not need a RealmProvider to be wrapped", async () => {
|
|
265
|
+
const { useRealm } = realmContextWithRealmInstance;
|
|
266
|
+
|
|
267
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
|
268
|
+
const { result } = renderHook(() => useRealm(), { wrapper });
|
|
269
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
270
|
+
const realm = result.current;
|
|
271
|
+
|
|
272
|
+
expect(realm).toStrictEqual(existingRealmInstance);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("does not close realm on unmount by default", async () => {
|
|
276
|
+
const { RealmProvider, useRealm } = realmContextWithRealmInstance;
|
|
277
|
+
|
|
278
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => <RealmProvider>{children}</RealmProvider>;
|
|
279
|
+
const { result, unmount } = renderHook(() => useRealm(), { wrapper });
|
|
280
|
+
await waitFor(() => expect(result.current).not.toBe(null));
|
|
281
|
+
const realm = result.current;
|
|
282
|
+
unmount();
|
|
283
|
+
expect(realm.isClosed).toBe(false);
|
|
284
|
+
expect(existingRealmInstance.isClosed).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("with an initially empty context", () => {
|
|
289
|
+
const emptyRealmContext = createRealmContext();
|
|
290
|
+
|
|
291
|
+
it("should use Realm instance if realm prop is passed", () => {
|
|
292
|
+
const existingRealm = new Realm({
|
|
293
|
+
schema: [dogSchema],
|
|
294
|
+
inMemory: true,
|
|
295
|
+
path: randomRealmPath(),
|
|
296
|
+
});
|
|
297
|
+
const { RealmProvider, useRealm } = emptyRealmContext;
|
|
298
|
+
|
|
299
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
300
|
+
<RealmProvider realm={existingRealm}>{children}</RealmProvider>
|
|
301
|
+
);
|
|
302
|
+
const { result, unmount } = renderHook(() => useRealm(), { wrapper });
|
|
303
|
+
|
|
304
|
+
expect(result.current.isClosed).toBe(false);
|
|
305
|
+
expect(result.current).toStrictEqual(existingRealm);
|
|
306
|
+
|
|
307
|
+
unmount();
|
|
308
|
+
// Closing a realm should not be managed by the provider if an existing instance was given
|
|
309
|
+
expect(result.current.isClosed).toBe(false);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should use Realm configuration if any config props are passed", () => {
|
|
313
|
+
const { RealmProvider, useRealm } = emptyRealmContext;
|
|
314
|
+
|
|
315
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
316
|
+
<RealmProvider schema={[dogSchema]}>{children}</RealmProvider>
|
|
317
|
+
);
|
|
318
|
+
const { result, unmount } = renderHook(() => useRealm(), { wrapper });
|
|
319
|
+
|
|
320
|
+
expect(result.current.schema.length).toEqual(1);
|
|
321
|
+
expect(result.current.schema[0].name).toEqual(dogSchema.name);
|
|
322
|
+
|
|
323
|
+
expect(result.current.isClosed).toBe(false);
|
|
324
|
+
unmount();
|
|
325
|
+
// Closing a realm should be managed by the provider by default if an existing instance was given
|
|
326
|
+
expect(result.current.isClosed).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should use an empty Realm configuration by default if no props are passed", () => {
|
|
330
|
+
const { RealmProvider, useRealm } = emptyRealmContext;
|
|
331
|
+
|
|
332
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => <RealmProvider>{children}</RealmProvider>;
|
|
333
|
+
const { result, unmount } = renderHook(() => useRealm(), { wrapper });
|
|
334
|
+
|
|
335
|
+
expect(result.current.schema.length).toEqual(0);
|
|
336
|
+
|
|
337
|
+
expect(result.current.isClosed).toBe(false);
|
|
338
|
+
unmount();
|
|
339
|
+
// Closing a realm should be managed by the provider by default if an existing instance was given
|
|
340
|
+
expect(result.current.isClosed).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("throws an error when both realm and configuration props are provided", () => {
|
|
344
|
+
expect(() =>
|
|
345
|
+
render(
|
|
346
|
+
// @ts-expect-error The realm and configuration props should be mutually exclusive
|
|
347
|
+
<RealmProvider realm={new Realm()} schema={[]}>
|
|
348
|
+
...
|
|
349
|
+
</RealmProvider>,
|
|
350
|
+
),
|
|
351
|
+
).toThrow("Cannot use configuration props when using an existing Realm instance.");
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe("with multiple providers", () => {
|
|
356
|
+
const createRealmObjectCreator =
|
|
357
|
+
(realmContext: RealmContext<unknown>) =>
|
|
358
|
+
({ testID }: { testID: string }) => {
|
|
359
|
+
const { useRealm } = realmContext;
|
|
360
|
+
const realm = useRealm();
|
|
361
|
+
return (
|
|
362
|
+
<Button
|
|
363
|
+
testID={testID}
|
|
364
|
+
title="toggle"
|
|
365
|
+
onPress={() =>
|
|
366
|
+
realm.write(() => {
|
|
367
|
+
realm.create("dog", { _id: new Date().getTime(), name: "Rex" });
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
/>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const WithConfig = withConfigRealmContext;
|
|
375
|
+
|
|
376
|
+
it("can have multiple providers with config and with realm", async () => {
|
|
377
|
+
const existingRealmInstance = new Realm({
|
|
378
|
+
schema: [dogSchema],
|
|
379
|
+
inMemory: true,
|
|
380
|
+
path: randomRealmPath(),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const WithRealmInstance = createRealmContext(existingRealmInstance);
|
|
384
|
+
const WithConfigObjectCreator = createRealmObjectCreator(WithConfig);
|
|
385
|
+
const WithConfigProviderComponent = ({ children }: { children?: React.ReactNode }) => (
|
|
386
|
+
<View testID="firstRealmProvider">
|
|
387
|
+
<WithConfig.RealmProvider>
|
|
388
|
+
{children}
|
|
389
|
+
<WithConfigObjectCreator testID="with-config-action" />
|
|
390
|
+
</WithConfig.RealmProvider>
|
|
391
|
+
</View>
|
|
392
|
+
);
|
|
393
|
+
const { result: withConfigResult } = renderHook(() => WithConfig.useRealm(), {
|
|
394
|
+
wrapper: WithConfigProviderComponent,
|
|
395
|
+
});
|
|
396
|
+
const withConfigRealm = withConfigResult.current;
|
|
397
|
+
|
|
398
|
+
const WithRealmObjectCreator = createRealmObjectCreator(WithRealmInstance);
|
|
399
|
+
const WithRealmInstanceProviderComponent = ({ children }: { children?: React.ReactNode }) => (
|
|
400
|
+
<View testID="secondRealmProvider">
|
|
401
|
+
<WithRealmInstance.RealmProvider>
|
|
402
|
+
{children}
|
|
403
|
+
<WithRealmObjectCreator testID="with-realm-action" />
|
|
404
|
+
</WithRealmInstance.RealmProvider>
|
|
405
|
+
</View>
|
|
406
|
+
);
|
|
407
|
+
const { result: withRealmInstanceResult } = renderHook(() => WithRealmInstance.useRealm(), {
|
|
408
|
+
wrapper: WithRealmInstanceProviderComponent,
|
|
409
|
+
});
|
|
410
|
+
const withRealmInstanceRealm = withRealmInstanceResult.current;
|
|
411
|
+
|
|
412
|
+
expect(withRealmInstanceRealm.path).not.toEqual(withConfigRealm.path);
|
|
413
|
+
|
|
414
|
+
expect(withRealmInstanceRealm.objects(dogSchema.name).length).toEqual(0);
|
|
415
|
+
expect(existingRealmInstance.objects(dogSchema.name).length).toEqual(0);
|
|
416
|
+
expect(withConfigRealm.objects(dogSchema.name).length).toEqual(0);
|
|
417
|
+
|
|
418
|
+
const pressButton = async (testId: string) => {
|
|
419
|
+
const toggleComponent = getByTestId(testId);
|
|
420
|
+
await act(async () => {
|
|
421
|
+
fireEvent.press(toggleComponent);
|
|
422
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const App = () => {
|
|
427
|
+
const [toggleComponent, setToggleComponent] = useState(true);
|
|
428
|
+
return (
|
|
429
|
+
<>
|
|
430
|
+
<WithConfigProviderComponent />
|
|
431
|
+
{toggleComponent && <WithRealmInstanceProviderComponent />}
|
|
432
|
+
<Button testID="toggle" title="toggle" onPress={() => setToggleComponent(!toggleComponent)} />
|
|
433
|
+
</>
|
|
434
|
+
);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const { getByTestId } = render(<App />);
|
|
438
|
+
|
|
439
|
+
const secondRealmProvider = getByTestId("secondRealmProvider");
|
|
440
|
+
|
|
441
|
+
expect(secondRealmProvider).not.toBeEmptyElement();
|
|
442
|
+
|
|
443
|
+
await act(async () => {
|
|
444
|
+
// Create a new Realm object using the existing Realm instance provider.
|
|
445
|
+
await pressButton("with-realm-action");
|
|
446
|
+
expect(existingRealmInstance.objects(dogSchema.name).length).toEqual(1);
|
|
447
|
+
expect(withRealmInstanceRealm.objects(dogSchema.name).length).toEqual(1);
|
|
448
|
+
expect(withRealmInstanceRealm.objects(dogSchema.name)[0]).toStrictEqual(
|
|
449
|
+
existingRealmInstance.objects(dogSchema.name)[0],
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
expect(withConfigRealm.objects(dogSchema.name).length).toEqual(0);
|
|
453
|
+
|
|
454
|
+
// Create a new Realm object using the Realm config provider.
|
|
455
|
+
await pressButton("with-config-action");
|
|
456
|
+
expect(withConfigRealm.objects(dogSchema.name).length).toEqual(1);
|
|
457
|
+
expect(existingRealmInstance.objects(dogSchema.name).length).toEqual(1);
|
|
458
|
+
expect(withRealmInstanceRealm.objects(dogSchema.name).length).toEqual(1);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("can have nested generalized providers with config and with realm", async () => {
|
|
463
|
+
const { RealmProvider, useRealm } = createRealmContext();
|
|
464
|
+
|
|
465
|
+
const customRealm = new Realm({ schema: [dogSchema], inMemory: true, path: randomRealmPath() });
|
|
466
|
+
|
|
467
|
+
const InstanceFirstWrapper = ({ children }: React.PropsWithChildren) => {
|
|
468
|
+
return (
|
|
469
|
+
<RealmProvider schema={[catSchema]} inMemory={true}>
|
|
470
|
+
<RealmProvider realm={customRealm}>{children}</RealmProvider>
|
|
471
|
+
</RealmProvider>
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const { result: instanceResult } = renderHook(() => useRealm(), { wrapper: InstanceFirstWrapper });
|
|
476
|
+
|
|
477
|
+
expect(instanceResult.current).toStrictEqual(customRealm);
|
|
478
|
+
|
|
479
|
+
const ConfigFirstWrapper = ({ children }: React.PropsWithChildren) => {
|
|
480
|
+
return (
|
|
481
|
+
<>
|
|
482
|
+
<RealmProvider realm={customRealm}>
|
|
483
|
+
<RealmProvider schema={[catSchema]} inMemory={true}>
|
|
484
|
+
{children}
|
|
485
|
+
</RealmProvider>
|
|
486
|
+
</RealmProvider>
|
|
487
|
+
</>
|
|
488
|
+
);
|
|
489
|
+
};
|
|
490
|
+
const { result: configFirstResult } = renderHook(() => useRealm(), { wrapper: ConfigFirstWrapper });
|
|
491
|
+
|
|
492
|
+
expect(configFirstResult.current).not.toStrictEqual(customRealm);
|
|
493
|
+
expect(configFirstResult.current.schema.length).toEqual(1);
|
|
494
|
+
expect(configFirstResult.current.schema[0].name).toEqual(catSchema.name);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe("mergeRealmConfiguration", () => {
|
|
499
|
+
it("merges two realm configurations", () => {
|
|
500
|
+
const configA: Realm.Configuration = { schema: [catSchema], deleteRealmIfMigrationNeeded: true };
|
|
501
|
+
const configB: Realm.Configuration = { path: "some.realm" };
|
|
502
|
+
|
|
503
|
+
const expectedResult = {
|
|
504
|
+
schema: [catSchema],
|
|
505
|
+
deleteRealmIfMigrationNeeded: true,
|
|
506
|
+
path: "some.realm",
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const result = mergeRealmConfiguration(configA, configB);
|
|
510
|
+
|
|
511
|
+
expect(result).toMatchObject(expectedResult);
|
|
512
|
+
});
|
|
513
|
+
it("merge updates to realm configuration", () => {
|
|
514
|
+
let configA: Realm.Configuration = { schema: [catSchema], deleteRealmIfMigrationNeeded: true };
|
|
515
|
+
let configB: Realm.Configuration = { schema: [dogSchema], deleteRealmIfMigrationNeeded: undefined };
|
|
516
|
+
|
|
517
|
+
let expectedResult: Realm.Configuration = {
|
|
518
|
+
schema: [dogSchema],
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
expect(mergeRealmConfiguration(configA, configB)).toMatchObject(expectedResult);
|
|
522
|
+
configA = { schema: [catSchema], deleteRealmIfMigrationNeeded: true };
|
|
523
|
+
configB = { schema: [catSchema, dogSchema], deleteRealmIfMigrationNeeded: false };
|
|
524
|
+
|
|
525
|
+
expectedResult = {
|
|
526
|
+
schema: [catSchema, dogSchema],
|
|
527
|
+
deleteRealmIfMigrationNeeded: false,
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
expect(mergeRealmConfiguration(configA, configB)).toMatchObject(expectedResult);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
describe("areConfigurationsIdentical", () => {
|
|
535
|
+
it("returns false if changes detected", () => {
|
|
536
|
+
let configA: Realm.Configuration = {};
|
|
537
|
+
let configB: Realm.Configuration = { path: "other.realm" };
|
|
538
|
+
|
|
539
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeFalsy();
|
|
540
|
+
|
|
541
|
+
configA = {
|
|
542
|
+
path: "a.realm",
|
|
543
|
+
schema: [dogSchema, catSchema],
|
|
544
|
+
};
|
|
545
|
+
configB = {
|
|
546
|
+
path: "b.realm",
|
|
547
|
+
schema: [dogSchema, catSchema],
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeFalsy();
|
|
551
|
+
configA = { schema: [catSchema], deleteRealmIfMigrationNeeded: true };
|
|
552
|
+
configB = {
|
|
553
|
+
schema: [dogSchema, catSchema],
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeFalsy();
|
|
557
|
+
|
|
558
|
+
configA = { schema: [catSchema], deleteRealmIfMigrationNeeded: true, onMigration: () => undefined };
|
|
559
|
+
configB = { schema: [catSchema], deleteRealmIfMigrationNeeded: true, onMigration: () => undefined };
|
|
560
|
+
|
|
561
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeFalsy();
|
|
562
|
+
|
|
563
|
+
configA = { schema: [dogSchema, catSchema], deleteRealmIfMigrationNeeded: true };
|
|
564
|
+
configB = { schema: [catSchema, dogSchema], deleteRealmIfMigrationNeeded: true };
|
|
565
|
+
|
|
566
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeFalsy();
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it("returns true there are no changes ", () => {
|
|
570
|
+
let configA: Realm.Configuration = { schema: [catSchema], deleteRealmIfMigrationNeeded: true };
|
|
571
|
+
let configB: Realm.Configuration = { schema: [catSchema], deleteRealmIfMigrationNeeded: true };
|
|
572
|
+
|
|
573
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeTruthy();
|
|
574
|
+
|
|
575
|
+
const onMigration = () => undefined;
|
|
576
|
+
|
|
577
|
+
configA = { schema: [catSchema], deleteRealmIfMigrationNeeded: true, onMigration };
|
|
578
|
+
configB = { schema: [catSchema], deleteRealmIfMigrationNeeded: true, onMigration };
|
|
579
|
+
|
|
580
|
+
expect(areConfigurationsIdentical(configA, configB)).toBeTruthy();
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2024 Realm Inc.
|
|
4
|
+
//
|
|
5
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
// you may not use this file except in compliance with the License.
|
|
7
|
+
// You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
// See the License for the specific language governing permissions and
|
|
15
|
+
// limitations under the License.
|
|
16
|
+
//
|
|
17
|
+
////////////////////////////////////////////////////////////////////////////
|
|
18
|
+
|
|
19
|
+
import assert from "node:assert";
|
|
20
|
+
import Realm, { Configuration } from "realm";
|
|
21
|
+
import { act } from "@testing-library/react-native";
|
|
22
|
+
|
|
23
|
+
import { randomRealmPath } from "./helpers";
|
|
24
|
+
|
|
25
|
+
export type RealmTestContext = {
|
|
26
|
+
realm: Realm;
|
|
27
|
+
useRealm: () => Realm;
|
|
28
|
+
write(cb: () => void): void;
|
|
29
|
+
openRealm(config?: Configuration): Realm;
|
|
30
|
+
cleanup(): void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Opens a test realm at a randomized and temporary path.
|
|
35
|
+
* @returns The `realm` and a `write` function, which will wrap `realm.write` with an `act` and prepand a second `realm.write` to force notifications to trigger synchronously.
|
|
36
|
+
*/
|
|
37
|
+
export function createRealmTestContext(rootConfig: Configuration = {}): RealmTestContext {
|
|
38
|
+
let realm: Realm | null = null;
|
|
39
|
+
const context = {
|
|
40
|
+
get realm(): Realm {
|
|
41
|
+
assert(realm, "Open the Realm first");
|
|
42
|
+
return realm;
|
|
43
|
+
},
|
|
44
|
+
useRealm() {
|
|
45
|
+
return context.realm;
|
|
46
|
+
},
|
|
47
|
+
openRealm(config: Configuration = {}) {
|
|
48
|
+
if (realm) {
|
|
49
|
+
// Close any realm, previously opened
|
|
50
|
+
realm.close();
|
|
51
|
+
}
|
|
52
|
+
realm = new Realm({ ...rootConfig, ...config, path: randomRealmPath() });
|
|
53
|
+
return realm;
|
|
54
|
+
},
|
|
55
|
+
write(callback: () => void) {
|
|
56
|
+
act(() => {
|
|
57
|
+
context.realm.write(callback);
|
|
58
|
+
// Starting another write transaction will force notifications to fire synchronously
|
|
59
|
+
context.realm.beginTransaction();
|
|
60
|
+
context.realm.cancelTransaction();
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
cleanup() {
|
|
64
|
+
Realm.clearTestState();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2023 Realm Inc.
|
|
4
|
+
//
|
|
5
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
// you may not use this file except in compliance with the License.
|
|
7
|
+
// You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
// See the License for the specific language governing permissions and
|
|
15
|
+
// limitations under the License.
|
|
16
|
+
//
|
|
17
|
+
////////////////////////////////////////////////////////////////////////////
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import os from "node:os";
|
|
22
|
+
|
|
23
|
+
export function randomRealmPath() {
|
|
24
|
+
const tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), "realm-react-tests-"));
|
|
25
|
+
return path.join(tempDirPath, "test.realm");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Adapted from integration-tests
|
|
30
|
+
* @param ms For how long should the promise be pending?
|
|
31
|
+
* @returns A promise that returns after `ms` milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
export function sleep(ms = 1000): Promise<void> {
|
|
34
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
}
|