Skip to content

Commit f0630e1

Browse files
committed
feature: run query via livequery
1 parent 389de84 commit f0630e1

File tree

4 files changed

+396
-2
lines changed

4 files changed

+396
-2
lines changed

spec/ParseLiveQueryQuery.spec.js

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
'use strict';
2+
3+
describe('ParseLiveQuery query operation', function () {
4+
beforeEach(() => {
5+
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
6+
});
7+
8+
afterEach(async () => {
9+
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
10+
if (client) {
11+
await client.close();
12+
}
13+
});
14+
15+
it('can execute query on existing subscription and receive results', async () => {
16+
await reconfigureServer({
17+
liveQuery: {
18+
classNames: ['TestObject'],
19+
},
20+
startLiveQueryServer: true,
21+
verbose: false,
22+
silent: true,
23+
});
24+
25+
// Create test objects
26+
const obj1 = new TestObject();
27+
obj1.set('name', 'object1');
28+
await obj1.save();
29+
30+
const obj2 = new TestObject();
31+
obj2.set('name', 'object2');
32+
await obj2.save();
33+
34+
// Subscribe to query
35+
const query = new Parse.Query(TestObject);
36+
const subscription = await query.subscribe();
37+
38+
// Wait for subscription to be ready
39+
await new Promise(resolve => subscription.on('open', resolve));
40+
41+
// Set up result listener
42+
const resultPromise = new Promise((resolve, reject) => {
43+
const timeout = setTimeout(() => reject(new Error('Timeout waiting for result')), 5000);
44+
subscription.on('result', results => {
45+
clearTimeout(timeout);
46+
resolve(results);
47+
});
48+
});
49+
50+
// Get the LiveQuery client and send query message
51+
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
52+
const message = {
53+
op: 'query',
54+
requestId: subscription.id,
55+
};
56+
client.socket.send(JSON.stringify(message));
57+
58+
// Wait for and verify results
59+
const results = await resultPromise;
60+
expect(Array.isArray(results)).toBe(true);
61+
expect(results.length).toBe(2);
62+
expect(results.some(r => r.name === 'object1')).toBe(true);
63+
expect(results.some(r => r.name === 'object2')).toBe(true);
64+
65+
await subscription.unsubscribe();
66+
});
67+
68+
it('respects field filtering (keys) when executing query', async () => {
69+
await reconfigureServer({
70+
liveQuery: {
71+
classNames: ['TestObject'],
72+
},
73+
startLiveQueryServer: true,
74+
verbose: false,
75+
silent: true,
76+
});
77+
78+
// Create test object with multiple fields
79+
const obj = new TestObject();
80+
obj.set('name', 'test');
81+
obj.set('secret', 'confidential');
82+
obj.set('public', 'visible');
83+
await obj.save();
84+
85+
// Subscribe with field selection
86+
const query = new Parse.Query(TestObject);
87+
query.select('name', 'public'); // Only select these fields
88+
const subscription = await query.subscribe();
89+
90+
// Wait for subscription to be ready
91+
await new Promise(resolve => subscription.on('open', resolve));
92+
93+
// Set up result listener
94+
const resultPromise = new Promise((resolve, reject) => {
95+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
96+
subscription.on('result', results => {
97+
clearTimeout(timeout);
98+
resolve(results);
99+
});
100+
});
101+
102+
// Send query message
103+
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
104+
const message = {
105+
op: 'query',
106+
requestId: subscription.id,
107+
};
108+
client.socket.send(JSON.stringify(message));
109+
110+
// Wait for and verify results
111+
const results = await resultPromise;
112+
expect(results.length).toBe(1);
113+
const result = results[0];
114+
expect(result.name).toBe('test');
115+
expect(result.public).toBe('visible');
116+
expect(result.secret).toBeUndefined(); // Should be filtered out
117+
118+
await subscription.unsubscribe();
119+
});
120+
121+
it('runs beforeFind and afterFind triggers', async () => {
122+
let beforeFindCalled = false;
123+
let afterFindCalled = false;
124+
125+
Parse.Cloud.beforeFind('TestObject', () => {
126+
beforeFindCalled = true;
127+
});
128+
129+
Parse.Cloud.afterFind('TestObject', req => {
130+
afterFindCalled = true;
131+
return req.objects;
132+
});
133+
134+
await reconfigureServer({
135+
liveQuery: {
136+
classNames: ['TestObject'],
137+
},
138+
startLiveQueryServer: true,
139+
verbose: false,
140+
silent: true,
141+
});
142+
143+
// Create test object
144+
const obj = new TestObject();
145+
obj.set('name', 'test');
146+
await obj.save();
147+
148+
// Subscribe
149+
const query = new Parse.Query(TestObject);
150+
const subscription = await query.subscribe();
151+
152+
// Wait for subscription to be ready
153+
await new Promise(resolve => subscription.on('open', resolve));
154+
155+
// Set up result listener
156+
const resultPromise = new Promise((resolve, reject) => {
157+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
158+
subscription.on('result', results => {
159+
clearTimeout(timeout);
160+
resolve(results);
161+
});
162+
});
163+
164+
// Send query message
165+
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
166+
const message = {
167+
op: 'query',
168+
requestId: subscription.id,
169+
};
170+
client.socket.send(JSON.stringify(message));
171+
172+
// Wait for results
173+
await resultPromise;
174+
175+
// Verify triggers were called
176+
expect(beforeFindCalled).toBe(true);
177+
expect(afterFindCalled).toBe(true);
178+
179+
await subscription.unsubscribe();
180+
});
181+
182+
it('handles query with where constraints', async () => {
183+
await reconfigureServer({
184+
liveQuery: {
185+
classNames: ['TestObject'],
186+
},
187+
startLiveQueryServer: true,
188+
verbose: false,
189+
silent: true,
190+
});
191+
192+
// Create multiple test objects
193+
const obj1 = new TestObject();
194+
obj1.set('name', 'apple');
195+
await obj1.save();
196+
197+
const obj2 = new TestObject();
198+
obj2.set('name', 'banana');
199+
await obj2.save();
200+
201+
const obj3 = new TestObject();
202+
obj3.set('name', 'cherry');
203+
await obj3.save();
204+
205+
// Subscribe with where constraint
206+
const query = new Parse.Query(TestObject);
207+
query.equalTo('name', 'banana');
208+
const subscription = await query.subscribe();
209+
210+
// Wait for subscription to be ready
211+
await new Promise(resolve => subscription.on('open', resolve));
212+
213+
// Set up result listener
214+
const resultPromise = new Promise((resolve, reject) => {
215+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
216+
subscription.on('result', results => {
217+
clearTimeout(timeout);
218+
resolve(results);
219+
});
220+
});
221+
222+
// Send query message
223+
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
224+
const message = {
225+
op: 'query',
226+
requestId: subscription.id,
227+
};
228+
client.socket.send(JSON.stringify(message));
229+
230+
// Wait for and verify results - should only get banana
231+
const results = await resultPromise;
232+
expect(results.length).toBe(1);
233+
expect(results[0].name).toBe('banana');
234+
235+
await subscription.unsubscribe();
236+
});
237+
238+
it('handles errors gracefully', async () => {
239+
await reconfigureServer({
240+
liveQuery: {
241+
classNames: ['TestObject'],
242+
},
243+
startLiveQueryServer: true,
244+
verbose: false,
245+
silent: true,
246+
});
247+
248+
// Create an object
249+
const obj = new TestObject();
250+
obj.set('name', 'test');
251+
await obj.save();
252+
253+
// Subscribe
254+
const query = new Parse.Query(TestObject);
255+
const subscription = await query.subscribe();
256+
await new Promise(resolve => subscription.on('open', resolve));
257+
258+
// Set up listeners for both result and error
259+
let resultReceived = false;
260+
let errorReceived = false;
261+
262+
subscription.on('result', () => {
263+
resultReceived = true;
264+
});
265+
266+
subscription.on('error', () => {
267+
errorReceived = true;
268+
});
269+
270+
// Send query message
271+
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
272+
const message = {
273+
op: 'query',
274+
requestId: subscription.id,
275+
};
276+
client.socket.send(JSON.stringify(message));
277+
278+
// Wait a bit for the response
279+
await new Promise(resolve => setTimeout(resolve, 500));
280+
281+
// Should have received result (not error) since query is valid
282+
expect(resultReceived).toBe(true);
283+
expect(errorReceived).toBe(false);
284+
285+
await subscription.unsubscribe();
286+
});
287+
});

src/LiveQuery/Client.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Client {
2222
pushUpdate: Function;
2323
pushDelete: Function;
2424
pushLeave: Function;
25+
pushResult: Function;
2526

2627
constructor(
2728
id: number,
@@ -45,6 +46,7 @@ class Client {
4546
this.pushUpdate = this._pushEvent('update');
4647
this.pushDelete = this._pushEvent('delete');
4748
this.pushLeave = this._pushEvent('leave');
49+
this.pushResult = this._pushQueryResult.bind(this);
4850
}
4951

5052
static pushResponse(parseWebSocket: any, message: Message): void {
@@ -126,6 +128,27 @@ class Client {
126128
}
127129
return limitedParseObject;
128130
}
131+
132+
_pushQueryResult(subscriptionId: number, results: any[]): void {
133+
const response: Message = {
134+
op: 'result',
135+
clientId: this.id,
136+
installationId: this.installationId,
137+
requestId: subscriptionId,
138+
};
139+
140+
if (results && Array.isArray(results)) {
141+
let keys;
142+
if (this.subscriptionInfos.has(subscriptionId)) {
143+
keys = this.subscriptionInfos.get(subscriptionId).keys;
144+
}
145+
response['results'] = results.map(obj => this._toJSONWithFields(obj, keys));
146+
} else {
147+
response['results'] = [];
148+
}
149+
150+
Client.pushResponse(this.parseWebSocket, JSON.stringify(response));
151+
}
129152
}
130153

131154
export { Client };

0 commit comments

Comments
 (0)