Skip to content

Commit fd09182

Browse files
author
Mohammad Hunan Chughtai
authored
Merge branch 'realm-react-guidance' into DOCSP-26985-uuid-object
2 parents 449cfdb + 124d3fc commit fd09182

30 files changed

+1209
-83
lines changed

examples/react-native/.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
root: true,
3-
extends: '@react-native-community',
3+
extends: ['@react-native-community', 'prettier'],
44
parser: '@typescript-eslint/parser',
55
plugins: ['@typescript-eslint'],
66
env: {
@@ -13,5 +13,6 @@ module.exports = {
1313
'no-new': 'off',
1414
'jsx-quotes': 0, // do not remove this line, this removes the requirement for double quotes in jsx/tsx. The single quotes in jsx help bluehawk replace testIDs in the generated snippets for the docs
1515
'react-hooks/exhaustive-deps': 0,
16+
'react/jsx-max-props-per-line': [0, {'maximum': 4, 'when': 'multiline'}],
1617
},
1718
};

examples/react-native/.prettierrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
22
arrowParens: 'avoid',
3+
singleAttributePerLine: false,
34
bracketSameLine: true,
45
bracketSpacing: false,
56
singleQuote: true,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Realm from 'realm';
2+
3+
// :snippet-start: js-homeowner-schema
4+
class HomeOwner extends Realm.Object {
5+
static schema = {
6+
name: 'HomeOwner',
7+
properties: {
8+
name: 'string',
9+
home: '{}',
10+
},
11+
};
12+
}
13+
// :snippet-end:
14+
export default HomeOwner;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Realm from 'realm';
2+
3+
// :snippet-start: js-book-schema
4+
class Book extends Realm.Object {
5+
static schema = {
6+
name: 'Book',
7+
properties: {
8+
name: { type: 'string', indexed: true },
9+
price: 'int?',
10+
},
11+
};
12+
}
13+
// :snippet-end:
14+
export default Book;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Realm from 'realm';
2+
3+
// :snippet-start: js-car-schema
4+
class Car extends Realm.Object {
5+
static schema = {
6+
name: 'Car',
7+
properties: {
8+
make: 'string',
9+
model: 'string',
10+
miles: {type: 'int', default: 0},
11+
},
12+
};
13+
}
14+
// :snippet-end:
15+
export default Car;
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import React, {useState} from 'react';
2+
import {Button, TextInput, View, Text} from 'react-native';
3+
import {render, fireEvent, waitFor, act} from '@testing-library/react-native';
4+
import Realm from 'realm';
5+
import {createRealmContext} from '@realm/react';
6+
import HomeOwner from '../../Models/HomeOwner';
7+
8+
const realmConfig = {
9+
schema: [HomeOwner],
10+
deleteRealmIfMigrationNeeded: true,
11+
};
12+
13+
const {RealmProvider, useRealm, useQuery} = createRealmContext(realmConfig);
14+
15+
let assertionRealm;
16+
17+
describe('Dictionary Tests', () => {
18+
beforeEach(async () => {
19+
// we will use this Realm for assertions to access Realm Objects outside of a Functional Component (like required by @realm/react)
20+
assertionRealm = await Realm.open(realmConfig);
21+
// delete every object in the realmConfig in the Realm to make test idempotent
22+
assertionRealm.write(() => {
23+
assertionRealm.delete(assertionRealm.objects(HomeOwner));
24+
25+
new HomeOwner(assertionRealm, {
26+
name: 'Martin Doe',
27+
home: {address: 'Summerhill St.', color: 'pink'},
28+
});
29+
new HomeOwner(assertionRealm, {
30+
name: 'Tony Henry',
31+
home: {address: '200 lake street', price: 123000},
32+
});
33+
new HomeOwner(assertionRealm, {
34+
name: 'Rob Johnson',
35+
home: {address: '1 washington street', color: 'red'},
36+
});
37+
new HomeOwner(assertionRealm, {
38+
name: 'Anna Smith',
39+
home: {address: '2 jefferson lane', yearRenovated: 1994, color: 'blue'},
40+
});
41+
});
42+
});
43+
it('should create an object with a dictionary value', async () => {
44+
// :snippet-start: create-object-with-dictionary-value
45+
// :replace-start: {
46+
// "terms": {
47+
// " testID='submitHomeOwnerBtn'": ""
48+
// }
49+
// }
50+
const CreateHomeOwner = () => {
51+
const [homeOwnerName, setHomeOwnerName] = useState('John Smith');
52+
const [address, setAddress] = useState('1 Home Street');
53+
const realm = useRealm();
54+
55+
const submitHomeOwner = () => {
56+
// Create a HomeOwner within a Write Transaction
57+
realm.write(() => {
58+
new HomeOwner(realm, {
59+
name: homeOwnerName,
60+
// For the dictionary field, 'home', set the value to a regular javascript object
61+
home: {
62+
address,
63+
},
64+
});
65+
});
66+
};
67+
return (
68+
<View>
69+
<TextInput value={homeOwnerName} onChangeText={text => setHomeOwnerName(text)} />
70+
<TextInput value={address} onChangeText={text => setAddress(text)} />
71+
<Button title='Submit Home Owner' testID='submitHomeOwnerBtn' onPress={submitHomeOwner} />
72+
</View>
73+
);
74+
};
75+
// :replace-end:
76+
// :snippet-end:
77+
const App = () => (
78+
<RealmProvider>
79+
<CreateHomeOwner />
80+
</RealmProvider>
81+
);
82+
const {findByTestId} = render(<App />);
83+
const submitHomeOwnerBtn = await waitFor(() => findByTestId('submitHomeOwnerBtn'), {
84+
timeout: 5000,
85+
});
86+
await act(async () => {
87+
fireEvent.press(submitHomeOwnerBtn);
88+
});
89+
// check if the new HomeOwner object has been created
90+
const homeOwner = assertionRealm.objects(HomeOwner).filtered("name == 'John Smith'")[0];
91+
expect(homeOwner.name).toBe('John Smith');
92+
expect(homeOwner.home.address).toBe('1 Home Street');
93+
});
94+
it('should query for objects with a dictionary property', async () => {
95+
// :snippet-start: query-objects-with-dictionary
96+
// :replace-start: {
97+
// "terms": {
98+
// " testID='homeItem'": "",
99+
// " testID='homeWithAPriceItem'": "",
100+
// " testID='summerHillHouseColor'": "",
101+
// " testID='redHouseAddress'": ""
102+
// }
103+
// }
104+
const HomeList = () => {
105+
// query for all HomeOwner objects
106+
const homeOwners = useQuery(HomeOwner);
107+
108+
// run the `.filtered()` method on all the returned homeOwners to
109+
// find all homeOwners that have a house with a listed price
110+
const listedPriceHomes = homeOwners.filtered('home.@keys = "price"');
111+
112+
// run the `.filtered()` method on all the returned homeOwners to
113+
// find the house with the address "Summerhill St."
114+
const summerHillHouse = homeOwners.filtered('home["address"] = "Summerhill St."')[0].home;
115+
116+
// run the `.filtered()` method on all the returned homeOwners to
117+
// find the first house that has any field with a value of 'red'
118+
const redHouse = homeOwners.filtered('home.@values = "red"')[0].home;
119+
return (
120+
<View>
121+
<Text>All homes:</Text>
122+
{homeOwners.map(homeOwner => (
123+
<View>
124+
<Text testID='homeItem'>{homeOwner.home.address}</Text>
125+
</View>
126+
))}
127+
128+
<Text>All homes with a price:</Text>
129+
{listedPriceHomes.map(homeOwner => (
130+
<View>
131+
<Text testID='homeWithAPriceItem'>{homeOwner.home.address}</Text>
132+
<Text>{homeOwner.home.price}</Text>
133+
</View>
134+
))}
135+
136+
<Text>Summer Hill House:</Text>
137+
<Text>{summerHillHouse.address}</Text>
138+
<Text testID='summerHillHouseColor'>{summerHillHouse.color}</Text>
139+
140+
<Text>Red House:</Text>
141+
<Text testID='redHouseAddress'>{redHouse.address}</Text>
142+
</View>
143+
);
144+
};
145+
// :replace-end:
146+
// :snippet-end:
147+
const App = () => (
148+
<RealmProvider>
149+
<HomeList />
150+
</RealmProvider>
151+
);
152+
const {getByTestId, getAllByTestId} = render(<App />);
153+
154+
const homeItem = await waitFor(() => getAllByTestId('homeItem'));
155+
// test that 4 home items are rendered, since there are 4 HomeOwner realm objects
156+
expect(homeItem.length).toBe(4);
157+
158+
const homeWithAPriceItem = await waitFor(() => getAllByTestId('homeWithAPriceItem'));
159+
160+
// test that there is only one home with a price that is rendered, and its address is '200 lake street'
161+
expect(homeWithAPriceItem.length).toBe(1);
162+
expect(homeWithAPriceItem[0].props.children).toBe('200 lake street');
163+
164+
const summerHillHouseColor = await waitFor(() => getByTestId('summerHillHouseColor'));
165+
// test that the summer hill house has rendered properly in the UI by checking its color
166+
expect(summerHillHouseColor.props.children).toBe('pink');
167+
168+
const redHouseAddress = await waitFor(() => getByTestId('redHouseAddress'));
169+
// test that the red house has rendered properly in the UI by checking its address
170+
expect(redHouseAddress.props.children).toBe('1 washington street');
171+
});
172+
it('should update a dictionary', async () => {
173+
// :snippet-start: update-a-dictionary
174+
// :replace-start: {
175+
// "terms": {
176+
// " testID='homeOwnerName'": "",
177+
// " testID='updateAddressBtn'": "",
178+
// "3 jefferson lane": ""
179+
// }
180+
// }
181+
const UpdateHome = ({homeOwnerName}) => {
182+
const [address, setAddress] = useState('3 jefferson lane');
183+
const realm = useRealm();
184+
const homeOwner = useQuery(HomeOwner).filtered(`name == '${homeOwnerName}'`)[0];
185+
186+
const updateAddress = () => {
187+
// Update the home object with the new address
188+
realm.write(() => {
189+
// use the `set()` method to update a field of a dictionary
190+
homeOwner.home.set({address});
191+
// alternatively, update a field of a dictionary through dot notation
192+
homeOwner.home.yearRenovated = 2004;
193+
});
194+
};
195+
196+
return (
197+
<View>
198+
<Text testID='homeOwnerName'>{homeOwner.name}</Text>
199+
<TextInput value={address} onChangeText={setAddress} placeholder='Enter new address' />
200+
<Button onPress={updateAddress} title='Update Address' testID='updateAddressBtn' />
201+
</View>
202+
);
203+
};
204+
// :replace-end:
205+
// :snippet-end:
206+
const App = () => (
207+
<RealmProvider>
208+
<UpdateHome homeOwnerName='Anna Smith' />
209+
</RealmProvider>
210+
);
211+
const {getByTestId} = render(<App />);
212+
const homeOwnerName = await waitFor(() => getByTestId('homeOwnerName'));
213+
// Test that the homeOwner object has been found, by checking that 'Anna Smith' has rendered properly
214+
expect(homeOwnerName.props.children).toBe('Anna Smith');
215+
216+
const updateAddressBtn = await waitFor(() => getByTestId('updateAddressBtn'));
217+
// Test that the home owner's home has been updated by checking its address and year renovated before and after the updateAddressBtn has been pressed
218+
const annaSmithHome = assertionRealm.objects(HomeOwner).filtered('name == "Anna Smith"')[0].home;
219+
expect(annaSmithHome.address).toBe('2 jefferson lane');
220+
expect(annaSmithHome.yearRenovated).toBe(1994);
221+
await act(async () => {
222+
fireEvent.press(updateAddressBtn);
223+
});
224+
expect(annaSmithHome.address).toBe('3 jefferson lane');
225+
expect(annaSmithHome.yearRenovated).toBe(2004);
226+
});
227+
it('should delete members of a dictionary', async () => {
228+
// :snippet-start: delete-members-of-a-dictionary
229+
// :replace-start: {
230+
// "terms": {
231+
// " testID='deleteExtraHomeInfoBtn'": ""
232+
// }
233+
// }
234+
const HomeInfo = ({homeOwnerName}) => {
235+
const realm = useRealm();
236+
const homeOwner = useQuery(HomeOwner).filtered(`name == '${homeOwnerName}'`)[0];
237+
238+
const deleteExtraHomeInfo = () => {
239+
realm.write(() => {
240+
// remove the 'yearRenovated' and 'color' field of the house
241+
homeOwner.home.remove(['yearRenovated', 'color']);
242+
});
243+
};
244+
245+
return (
246+
<View>
247+
<Text>{homeOwner.name}</Text>
248+
<Text>{homeOwner.home.address}</Text>
249+
<Button onPress={deleteExtraHomeInfo} title='Delete extra home info' testID='deleteExtraHomeInfoBtn' />
250+
</View>
251+
);
252+
};
253+
// :replace-end:
254+
// :snippet-end:
255+
256+
const App = () => (
257+
<RealmProvider>
258+
<HomeInfo homeOwnerName='Anna Smith' />
259+
</RealmProvider>
260+
);
261+
const {getByTestId} = render(<App />);
262+
263+
const deleteExtraHomeInfoBtn = await waitFor(() => getByTestId('deleteExtraHomeInfoBtn'));
264+
// Test that the home owner's home had her 'yearRenovated' & 'color' removed by checking its address and year renovated before and after the deleteExtraHomeInfoBtn has been pressed
265+
const annaSmithHome = assertionRealm.objects(HomeOwner).filtered('name == "Anna Smith"')[0].home;
266+
expect(annaSmithHome.yearRenovated).toBe(1994);
267+
expect(annaSmithHome.color).toBe('blue');
268+
await act(async () => {
269+
fireEvent.press(deleteExtraHomeInfoBtn);
270+
});
271+
expect(annaSmithHome.yearRenovated).toBeUndefined();
272+
expect(annaSmithHome.color).toBeUndefined();
273+
});
274+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Realm from 'realm';
2+
3+
// TODO: Replace `static schema` with TS-first models + realm-babel-plugin (https://www.npmjs.com/package/@realm/babel-plugin) approach once realm-babel-plugin version 0.1.2 releases with bug fixes
4+
// :snippet-start: ts-homeowner-schema
5+
interface Home extends Realm.Dictionary {
6+
address?: string;
7+
color?: string;
8+
price?: number;
9+
yearRenovated?: number;
10+
}
11+
12+
class HomeOwner extends Realm.Object<HomeOwner> {
13+
name!: string;
14+
home!: Home;
15+
16+
static schema = {
17+
name: 'HomeOwner',
18+
properties: {
19+
name: 'string',
20+
home: '{}',
21+
},
22+
};
23+
}
24+
// :snippet-end:
25+
export default HomeOwner;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Realm from 'realm';
2+
3+
// TODO: Replace `static schema` with TS-first models + realm-babel-plugin (https://www.npmjs.com/package/@realm/babel-plugin) approach once realm-babel-plugin version 0.1.2 releases with bug fixes
4+
// :snippet-start: ts-book-schema
5+
class Book extends Realm.Object<Book> {
6+
name!: string;
7+
price?: number;
8+
9+
static schema = {
10+
name: 'Book',
11+
properties: {
12+
name: { type: 'string', indexed: true },
13+
price: 'int?',
14+
},
15+
};
16+
}
17+
// :snippet-end:
18+
export default Book;

0 commit comments

Comments
 (0)