1 | /**
|
---|
2 | * Copyright (c) 2014-present, Facebook, Inc.
|
---|
3 | *
|
---|
4 | * This source code is licensed under the MIT license found in the
|
---|
5 | * LICENSE file in the root directory of this source tree.
|
---|
6 | */
|
---|
7 |
|
---|
8 | ///<reference path='../../../resources/jest.d.ts'/>
|
---|
9 | ///<reference path='../../../dist/immutable.d.ts'/>
|
---|
10 | ///<reference path='../index.d.ts'/>
|
---|
11 |
|
---|
12 | jest.autoMockOff();
|
---|
13 |
|
---|
14 | import Immutable = require('immutable');
|
---|
15 | import Cursor = require('immutable/contrib/cursor');
|
---|
16 |
|
---|
17 | describe('Cursor', () => {
|
---|
18 |
|
---|
19 | beforeEach(function () {
|
---|
20 | this.addMatchers({
|
---|
21 | toValueEqual: function (expected) {
|
---|
22 | var actual = this.actual;
|
---|
23 | if (!Immutable.is(expected, this.actual)) {
|
---|
24 | this.message = 'Expected\n' + this.actual + '\nto equal\n' + expected;
|
---|
25 | return false;
|
---|
26 | }
|
---|
27 | return true;
|
---|
28 | }
|
---|
29 | });
|
---|
30 | });
|
---|
31 |
|
---|
32 | var json = { a: { b: { c: 1 } } };
|
---|
33 |
|
---|
34 | it('gets from its path', () => {
|
---|
35 | var data = Immutable.fromJS(json);
|
---|
36 | var cursor = Cursor.from(data);
|
---|
37 |
|
---|
38 | expect(cursor.deref()).toBe(data);
|
---|
39 |
|
---|
40 | var deepCursor = cursor.cursor(['a', 'b']);
|
---|
41 | expect(deepCursor.deref().toJS()).toEqual(json.a.b);
|
---|
42 | expect(deepCursor.deref()).toBe(data.getIn(['a', 'b']));
|
---|
43 | expect(deepCursor.get('c')).toBe(1);
|
---|
44 |
|
---|
45 | var leafCursor = deepCursor.cursor('c');
|
---|
46 | expect(leafCursor.deref()).toBe(1);
|
---|
47 |
|
---|
48 | var missCursor = leafCursor.cursor('d');
|
---|
49 | expect(missCursor.deref()).toBe(undefined);
|
---|
50 | });
|
---|
51 |
|
---|
52 | it('gets return new cursors', () => {
|
---|
53 | var data = Immutable.fromJS(json);
|
---|
54 | var cursor = Cursor.from(data);
|
---|
55 | var deepCursor = cursor.getIn(['a', 'b']);
|
---|
56 | expect(deepCursor.deref()).toBe(data.getIn(['a', 'b']));
|
---|
57 | });
|
---|
58 |
|
---|
59 | it('gets return new cursors using List', () => {
|
---|
60 | var data = Immutable.fromJS(json);
|
---|
61 | var cursor = Cursor.from(data);
|
---|
62 | var deepCursor = cursor.getIn(Immutable.fromJS(['a', 'b']));
|
---|
63 | expect(deepCursor.deref()).toBe(data.getIn(Immutable.fromJS(['a', 'b'])));
|
---|
64 | });
|
---|
65 |
|
---|
66 | it('cursor return new cursors of correct type', () => {
|
---|
67 | var data = Immutable.fromJS({ a: [1, 2, 3] });
|
---|
68 | var cursor = Cursor.from(data);
|
---|
69 | var deepCursor = <any>cursor.cursor('a');
|
---|
70 | expect(deepCursor.findIndex).toBeDefined();
|
---|
71 | });
|
---|
72 |
|
---|
73 | it('can be treated as a value', () => {
|
---|
74 | var data = Immutable.fromJS(json);
|
---|
75 | var cursor = Cursor.from(data, ['a', 'b']);
|
---|
76 | expect(cursor.toJS()).toEqual(json.a.b);
|
---|
77 | expect(cursor).toValueEqual(data.getIn(['a', 'b']));
|
---|
78 | expect(cursor.size).toBe(1);
|
---|
79 | expect(cursor.get('c')).toBe(1);
|
---|
80 | });
|
---|
81 |
|
---|
82 | it('can be value compared to a primitive', () => {
|
---|
83 | var data = Immutable.Map({ a: 'A' });
|
---|
84 | var aCursor = Cursor.from(data, 'a');
|
---|
85 | expect(aCursor.size).toBe(undefined);
|
---|
86 | expect(aCursor.deref()).toBe('A');
|
---|
87 | expect(Immutable.is(aCursor, 'A')).toBe(true);
|
---|
88 | });
|
---|
89 |
|
---|
90 | it('updates at its path', () => {
|
---|
91 | var onChange = jest.genMockFunction();
|
---|
92 |
|
---|
93 | var data = Immutable.fromJS(json);
|
---|
94 | var aCursor = Cursor.from(data, 'a', onChange);
|
---|
95 |
|
---|
96 | var deepCursor = aCursor.cursor(['b', 'c']);
|
---|
97 | expect(deepCursor.deref()).toBe(1);
|
---|
98 |
|
---|
99 | // cursor edits return new cursors:
|
---|
100 | var newDeepCursor = deepCursor.update(x => x + 1);
|
---|
101 | expect(newDeepCursor.deref()).toBe(2);
|
---|
102 | var call1 = onChange.mock.calls[0];
|
---|
103 | expect(call1[0]).toValueEqual(Immutable.fromJS({a:{b:{c:2}}}));
|
---|
104 | expect(call1[1]).toBe(data);
|
---|
105 | expect(call1[2]).toEqual(['a', 'b', 'c']);
|
---|
106 |
|
---|
107 | var newestDeepCursor = newDeepCursor.update(x => x + 1);
|
---|
108 | expect(newestDeepCursor.deref()).toBe(3);
|
---|
109 | var call2 = onChange.mock.calls[1];
|
---|
110 | expect(call2[0]).toValueEqual(Immutable.fromJS({a:{b:{c:3}}}));
|
---|
111 | expect(call2[1]).toValueEqual(Immutable.fromJS({a:{b:{c:2}}}));
|
---|
112 | expect(call2[2]).toEqual(['a', 'b', 'c']);
|
---|
113 |
|
---|
114 | // meanwhile, data is still immutable:
|
---|
115 | expect(data.toJS()).toEqual(json);
|
---|
116 |
|
---|
117 | // as is the original cursor.
|
---|
118 | expect(deepCursor.deref()).toBe(1);
|
---|
119 | var otherNewDeepCursor = deepCursor.update(x => x + 10);
|
---|
120 | expect(otherNewDeepCursor.deref()).toBe(11);
|
---|
121 | var call3 = onChange.mock.calls[2];
|
---|
122 | expect(call3[0]).toValueEqual(Immutable.fromJS({a:{b:{c:11}}}));
|
---|
123 | expect(call3[1]).toBe(data);
|
---|
124 | expect(call3[2]).toEqual(['a', 'b', 'c']);
|
---|
125 |
|
---|
126 | // and update has been called exactly thrice.
|
---|
127 | expect(onChange.mock.calls.length).toBe(3);
|
---|
128 | });
|
---|
129 |
|
---|
130 | it('updates with the return value of onChange', () => {
|
---|
131 | var onChange = jest.genMockFunction();
|
---|
132 |
|
---|
133 | var data = Immutable.fromJS(json);
|
---|
134 | var deepCursor = Cursor.from(data, ['a', 'b', 'c'], onChange);
|
---|
135 |
|
---|
136 | onChange.mockReturnValueOnce(undefined);
|
---|
137 | // onChange returning undefined has no effect
|
---|
138 | var newCursor = deepCursor.update(x => x + 1);
|
---|
139 | expect(newCursor.deref()).toBe(2);
|
---|
140 | var call1 = onChange.mock.calls[0];
|
---|
141 | expect(call1[0]).toValueEqual(Immutable.fromJS({a:{b:{c:2}}}));
|
---|
142 | expect(call1[1]).toBe(data);
|
---|
143 | expect(call1[2]).toEqual(['a', 'b', 'c']);
|
---|
144 |
|
---|
145 | onChange.mockReturnValueOnce(Immutable.fromJS({a:{b:{c:11}}}));
|
---|
146 | // onChange returning something else has an effect
|
---|
147 | newCursor = newCursor.update(x => 999);
|
---|
148 | expect(newCursor.deref()).toBe(11);
|
---|
149 | var call2 = onChange.mock.calls[1];
|
---|
150 | expect(call2[0]).toValueEqual(Immutable.fromJS({a:{b:{c:999}}}));
|
---|
151 | expect(call2[1]).toValueEqual(Immutable.fromJS({a:{b:{c:2}}}));
|
---|
152 | expect(call2[2]).toEqual(['a', 'b', 'c']);
|
---|
153 |
|
---|
154 | // and update has been called exactly twice
|
---|
155 | expect(onChange.mock.calls.length).toBe(2);
|
---|
156 | });
|
---|
157 |
|
---|
158 | it('has map API for update shorthand', () => {
|
---|
159 | var onChange = jest.genMockFunction();
|
---|
160 |
|
---|
161 | var data = Immutable.fromJS(json);
|
---|
162 | var aCursor = Cursor.from(data, 'a', onChange);
|
---|
163 | var bCursor = aCursor.cursor('b');
|
---|
164 | var cCursor = bCursor.cursor('c');
|
---|
165 |
|
---|
166 | expect(bCursor.set('c', 10).deref()).toValueEqual(
|
---|
167 | Immutable.fromJS({ c: 10 })
|
---|
168 | );
|
---|
169 |
|
---|
170 | var call1 = onChange.mock.calls[0];
|
---|
171 | expect(call1[0]).toValueEqual(Immutable.fromJS({a:{b:{c:10}}}));
|
---|
172 | expect(call1[1]).toBe(data);
|
---|
173 | expect(call1[2]).toEqual(['a', 'b', 'c']);
|
---|
174 | });
|
---|
175 |
|
---|
176 | it('creates maps as necessary', () => {
|
---|
177 | var data = Immutable.Map();
|
---|
178 | var cursor = Cursor.from(data, ['a', 'b', 'c']);
|
---|
179 | expect(cursor.deref()).toBe(undefined);
|
---|
180 | cursor = cursor.set('d', 3);
|
---|
181 | expect(cursor.deref()).toValueEqual(Immutable.Map({d: 3}));
|
---|
182 | });
|
---|
183 |
|
---|
184 | it('can set undefined', () => {
|
---|
185 | var data = Immutable.Map();
|
---|
186 | var cursor = Cursor.from(data, ['a', 'b', 'c']);
|
---|
187 | expect(cursor.deref()).toBe(undefined);
|
---|
188 | cursor = cursor.set('d', undefined);
|
---|
189 | expect(cursor.toJS()).toEqual({d: undefined});
|
---|
190 | });
|
---|
191 |
|
---|
192 | it('has the sequence API', () => {
|
---|
193 | var data = Immutable.Map({a: 1, b: 2, c: 3});
|
---|
194 | var cursor = Cursor.from(data);
|
---|
195 | expect(cursor.map((x: number) => x * x)).toValueEqual(Immutable.Map({a: 1, b: 4, c: 9}));
|
---|
196 | });
|
---|
197 |
|
---|
198 | it('can push values on a List', () => {
|
---|
199 | var onChange = jest.genMockFunction();
|
---|
200 | var data = Immutable.fromJS({a: {b: [0, 1, 2]}});
|
---|
201 | var cursor = Cursor.from(data, ['a', 'b'], onChange);
|
---|
202 |
|
---|
203 | expect(cursor.push(3,4)).toValueEqual(Immutable.List([0, 1, 2, 3, 4]));
|
---|
204 |
|
---|
205 | var call = onChange.mock.calls[0];
|
---|
206 | expect(call[0]).toValueEqual(Immutable.fromJS({a: {b: [0, 1, 2, 3, 4]}}));
|
---|
207 | expect(call[1]).toBe(data);
|
---|
208 | expect(call[2]).toEqual(['a', 'b']);
|
---|
209 | });
|
---|
210 |
|
---|
211 | it('can pop values of a List', () => {
|
---|
212 | var onChange = jest.genMockFunction();
|
---|
213 | var data = Immutable.fromJS({a: {b: [0, 1, 2]}});
|
---|
214 | var cursor = Cursor.from(data, ['a', 'b'], onChange);
|
---|
215 |
|
---|
216 | expect(cursor.pop()).toValueEqual(Immutable.List([0, 1]));
|
---|
217 |
|
---|
218 | var call = onChange.mock.calls[0];
|
---|
219 | expect(call[0]).toValueEqual(Immutable.fromJS({a: {b: [0, 1]}}));
|
---|
220 | expect(call[1]).toBe(data);
|
---|
221 | expect(call[2]).toEqual(['a', 'b']);
|
---|
222 | });
|
---|
223 |
|
---|
224 | it('can unshift values on a List', () => {
|
---|
225 | var onChange = jest.genMockFunction();
|
---|
226 | var data = Immutable.fromJS({a: {b: [0, 1, 2]}});
|
---|
227 | var cursor = Cursor.from(data, ['a', 'b'], onChange);
|
---|
228 |
|
---|
229 | expect(cursor.unshift(-2, -1)).toValueEqual(Immutable.List([-2, -1, 0, 1, 2]));
|
---|
230 |
|
---|
231 | var call = onChange.mock.calls[0];
|
---|
232 | expect(call[0]).toValueEqual(Immutable.fromJS({a: {b: [-2, -1, 0, 1, 2]}}));
|
---|
233 | expect(call[1]).toBe(data);
|
---|
234 | expect(call[2]).toEqual(['a', 'b']);
|
---|
235 | });
|
---|
236 |
|
---|
237 | it('can shift values of a List', () => {
|
---|
238 | var onChange = jest.genMockFunction();
|
---|
239 | var data = Immutable.fromJS({a: {b: [0, 1, 2]}});
|
---|
240 | var cursor = Cursor.from(data, ['a', 'b'], onChange);
|
---|
241 |
|
---|
242 | expect(cursor.shift()).toValueEqual(Immutable.List([1, 2]));
|
---|
243 |
|
---|
244 | var call = onChange.mock.calls[0];
|
---|
245 | expect(call[0]).toValueEqual(Immutable.fromJS({a: {b: [1, 2]}}));
|
---|
246 | expect(call[1]).toBe(data);
|
---|
247 | expect(call[2]).toEqual(['a', 'b']);
|
---|
248 | });
|
---|
249 |
|
---|
250 |
|
---|
251 | it('returns wrapped values for sequence API', () => {
|
---|
252 | var data = Immutable.fromJS({a: {v: 1}, b: {v: 2}, c: {v: 3}});
|
---|
253 | var onChange = jest.genMockFunction();
|
---|
254 | var cursor = Cursor.from(data, onChange);
|
---|
255 |
|
---|
256 | var found = cursor.find(map => map.get('v') === 2);
|
---|
257 | expect(typeof found.deref).toBe('function'); // is a cursor!
|
---|
258 | found = found.set('v', 20);
|
---|
259 |
|
---|
260 | var call = onChange.mock.calls[0];
|
---|
261 | expect(call[0]).toValueEqual(Immutable.fromJS({a: {v: 1}, b: {v: 20}, c: {v: 3}}));
|
---|
262 | expect(call[1]).toBe(data);
|
---|
263 | expect(call[2]).toEqual(['b', 'v']);
|
---|
264 | });
|
---|
265 |
|
---|
266 | it('returns wrapped values for iteration API', () => {
|
---|
267 | var jsData = [{val: 0}, {val: 1}, {val: 2}];
|
---|
268 | var data = Immutable.fromJS(jsData);
|
---|
269 | var cursor = Cursor.from(data);
|
---|
270 | cursor.forEach(function (c, i) {
|
---|
271 | expect(typeof c.deref).toBe('function'); // is a cursor!
|
---|
272 | expect(c.get('val')).toBe(i);
|
---|
273 | });
|
---|
274 | });
|
---|
275 |
|
---|
276 | it('can map over values to get subcursors', () => {
|
---|
277 | var data = Immutable.fromJS({a: {v: 1}, b: {v: 2}, c: {v: 3}});
|
---|
278 | var cursor = Cursor.from(data);
|
---|
279 |
|
---|
280 | var mapped = cursor.map(val => {
|
---|
281 | expect(typeof val.deref).toBe('function'); // mapped values are cursors.
|
---|
282 | return val;
|
---|
283 | }).toMap();
|
---|
284 | // Mapped is not a cursor, but it is a sequence of cursors.
|
---|
285 | expect(typeof (<any>mapped).deref).not.toBe('function');
|
---|
286 | expect(typeof (<any>mapped.get('a')).deref).toBe('function');
|
---|
287 |
|
---|
288 | // Same for indexed cursors
|
---|
289 | var data2 = Immutable.fromJS({x: [{v: 1}, {v: 2}, {v: 3}]});
|
---|
290 | var cursor2 = Cursor.from(data2);
|
---|
291 |
|
---|
292 | var mapped2 = cursor2.get('x').map(val => {
|
---|
293 | expect(typeof val.deref).toBe('function'); // mapped values are cursors.
|
---|
294 | return val;
|
---|
295 | }).toList();
|
---|
296 | // Mapped is not a cursor, but it is a sequence of cursors.
|
---|
297 | expect(typeof mapped2.deref).not.toBe('function');
|
---|
298 | expect(typeof mapped2.get(0).deref).toBe('function');
|
---|
299 | });
|
---|
300 |
|
---|
301 | it('can have mutations apply with a single callback', () => {
|
---|
302 | var onChange = jest.genMockFunction();
|
---|
303 | var data = Immutable.fromJS({'a': 1});
|
---|
304 |
|
---|
305 | var c1 = Cursor.from(data, onChange);
|
---|
306 | var c2 = c1.withMutations(m => m.set('b', 2).set('c', 3).set('d', 4));
|
---|
307 |
|
---|
308 | expect(c1.deref().toObject()).toEqual({'a': 1});
|
---|
309 | expect(c2.deref().toObject()).toEqual({'a': 1, 'b': 2, 'c': 3, 'd': 4});
|
---|
310 | expect(onChange.mock.calls.length).toBe(1);
|
---|
311 | });
|
---|
312 |
|
---|
313 | it('can use withMutations on an unfulfilled cursor', () => {
|
---|
314 | var onChange = jest.genMockFunction();
|
---|
315 | var data = Immutable.fromJS({});
|
---|
316 |
|
---|
317 | var c1 = Cursor.from(data, ['a', 'b', 'c'], onChange);
|
---|
318 | var c2 = c1.withMutations(m => m.set('x', 1).set('y', 2).set('z', 3));
|
---|
319 |
|
---|
320 | expect(c1.deref()).toEqual(undefined);
|
---|
321 | expect(c2.deref()).toValueEqual(Immutable.fromJS(
|
---|
322 | { x: 1, y: 2, z: 3 }
|
---|
323 | ));
|
---|
324 | expect(onChange.mock.calls.length).toBe(1);
|
---|
325 | });
|
---|
326 |
|
---|
327 | it('maintains indexed sequences', () => {
|
---|
328 | var data = Immutable.fromJS([]);
|
---|
329 | var c = Cursor.from(data);
|
---|
330 | expect(c.toJS()).toEqual([]);
|
---|
331 | });
|
---|
332 |
|
---|
333 | it('properly acts as an iterable', () => {
|
---|
334 | var data = Immutable.fromJS({key: {val: 1}});
|
---|
335 | var c = Cursor.from(data).values();
|
---|
336 | var c1 = c.next().value.get('val');
|
---|
337 | expect(c1).toBe(1);
|
---|
338 | });
|
---|
339 |
|
---|
340 | it('can update deeply', () => {
|
---|
341 | var onChange = jest.genMockFunction();
|
---|
342 | var data = Immutable.fromJS({a:{b:{c:1}}});
|
---|
343 | var c = Cursor.from(data, ['a'], onChange);
|
---|
344 | var c1 = c.updateIn(['b', 'c'], x => x * 10);
|
---|
345 | expect(c1.getIn(['b', 'c'])).toBe(10);
|
---|
346 |
|
---|
347 | var call = onChange.mock.calls[0];
|
---|
348 | expect(call[0]).toValueEqual(Immutable.fromJS({a:{b:{c:10}}}));
|
---|
349 | expect(call[1]).toBe(data);
|
---|
350 | expect(call[2]).toEqual(['a', 'b', 'c']);
|
---|
351 | });
|
---|
352 |
|
---|
353 | it('can set deeply', () => {
|
---|
354 | var onChange = jest.genMockFunction();
|
---|
355 | var data = Immutable.fromJS({a:{b:{c:1}}});
|
---|
356 | var c = Cursor.from(data, ['a'], onChange);
|
---|
357 | var c1 = c.setIn(['b', 'c'], 10);
|
---|
358 | expect(c1.getIn(['b', 'c'])).toBe(10);
|
---|
359 |
|
---|
360 | var call = onChange.mock.calls[0];
|
---|
361 | expect(call[0]).toValueEqual(Immutable.fromJS({a:{b:{c:10}}}));
|
---|
362 | expect(call[1]).toBe(data);
|
---|
363 | expect(call[2]).toEqual(['a', 'b', 'c']);
|
---|
364 | });
|
---|
365 |
|
---|
366 | it('can get Record value as a property', () => {
|
---|
367 | var User = Immutable.Record({ name: 'John' });
|
---|
368 | var users = Immutable.List.of(new User());
|
---|
369 | var data = Immutable.Map({'users': users});
|
---|
370 | var cursor = Cursor.from(data, ['users']);
|
---|
371 | expect(cursor.first().name).toBe('John');
|
---|
372 | });
|
---|
373 |
|
---|
374 | it('can set value of a cursor directly', () => {
|
---|
375 | var onChange = jest.genMockFunction();
|
---|
376 | var data = Immutable.fromJS({a:1});
|
---|
377 | var c = Cursor.from(data, ['a'], onChange);
|
---|
378 | var c1 = c.set(2);
|
---|
379 | expect(c1.deref()).toBe(2);
|
---|
380 |
|
---|
381 | var call = onChange.mock.calls[0];
|
---|
382 | expect(call[0]).toValueEqual(Immutable.fromJS({a:2}));
|
---|
383 | expect(call[1]).toBe(data);
|
---|
384 | expect(call[2]).toEqual(['a']);
|
---|
385 | });
|
---|
386 |
|
---|
387 | it('can set value of a cursor to undefined directly', () => {
|
---|
388 | var onChange = jest.genMockFunction();
|
---|
389 | var data = Immutable.fromJS({a:1});
|
---|
390 | var c = Cursor.from(data, ['a'], onChange);
|
---|
391 | var c1 = c.set(undefined);
|
---|
392 | expect(c1.deref()).toBe(undefined);
|
---|
393 |
|
---|
394 | var call = onChange.mock.calls[0];
|
---|
395 | expect(call[0]).toValueEqual(Immutable.fromJS({a:undefined}));
|
---|
396 | expect(call[1]).toBe(data);
|
---|
397 | expect(call[2]).toEqual(['a']);
|
---|
398 | });
|
---|
399 |
|
---|
400 | });
|
---|