Path: blob/main/client/test/ObjectObserver.arrays.test.ts
1028 views
/**1ISC License (ISC)23Copyright 2015 Yuri Guller ([email protected])4Modifications 2021 Data Liberation Foundation56Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,7provided that the above copyright notice and this permission notice appear in all copies.89THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE10INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.11IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES12OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,13NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.14*/15import ObjectObserver from '../lib/ObjectObserver';1617test('array push - primitives', () => {18const events = [];19let callBacks = 0;20const pa = ObjectObserver.create([1, 2, 3, 4], changes => {21events.push(...changes);22callBacks += 1;23});2425pa.push(5);26pa.push(6, 7);2728expect(events).toHaveLength(3);29expect(callBacks).toBe(2);30expect(events[0]).toEqual({31type: 'insert',32path: [4],33value: 5,34});35expect(events[1]).toEqual({36type: 'insert',37path: [5],38value: 6,39});40expect(events[2]).toEqual({41type: 'insert',42path: [6],43value: 7,44});45});4647test('array push - objects', () => {48const events = [];49const pa = ObjectObserver.create([], changes => events.push(...changes));5051pa.push({ text: 'initial' }, { text: 'secondary' });52expect(events).toHaveLength(2);53expect(events[0]).toEqual({54type: 'insert',55path: [0],56value: { text: 'initial' },57});58expect(events[1]).toEqual({59type: 'insert',60path: [1],61value: { text: 'secondary' },62});63pa[0].text = 'name';64expect(events).toHaveLength(3);65expect(events[2]).toEqual({66type: 'update',67path: [0, 'text'],68value: 'name',69});7071pa[1].text = 'more';72expect(events).toHaveLength(4);73expect(events[3]).toEqual({74type: 'update',75path: [1, 'text'],76value: 'more',77});78});7980test('array push - arrays', () => {81const events = [];82const pa = ObjectObserver.create([], changes => events.push(...changes));8384pa.push([], [{}]);85expect(events).toHaveLength(2);86expect(events[0]).toEqual({87type: 'insert',88path: [0],89value: [],90});91expect(events[1]).toEqual({92type: 'insert',93path: [1],94value: [{}],95});96pa[0].push('name');97expect(events).toHaveLength(3);9899expect(events[2]).toEqual({100type: 'insert',101path: [0, 0],102value: 'name',103});104pa[1][0].prop = 'more';105expect(events).toHaveLength(4);106expect(events[3]).toEqual({107type: 'insert',108path: [1, 0, 'prop'],109value: 'more',110});111});112113test('array pop - primitives', () => {114const events = [];115const pa = ObjectObserver.create(['some'], changes => events.push(...changes));116117const popped = pa.pop();118119expect(events).toHaveLength(1);120expect(events[0]).toEqual({121type: 'delete',122path: [0],123});124expect(popped).toBe('some');125});126127test('array pop - objects', () => {128const events = [];129const pa = ObjectObserver.create([{ test: 'text' }], changes => events.push(...changes));130const pad: any = pa[0];131132pa[0].test = 'test';133pad.test = 'more';134expect(events).toHaveLength(2);135136const popped: any = pa.pop();137expect(popped.test).toBe('more');138expect(events).toHaveLength(3);139140popped.new = 'value';141expect(events).toHaveLength(3);142143const eventsA = [];144const newPad = ObjectObserver.create(pad, changes => eventsA.push(...changes));145newPad.test = 'change';146expect(eventsA).toHaveLength(1);147});148149test('array unshift - primitives', () => {150const events = [];151let callbacks = 0;152const pa = ObjectObserver.create([], changes => {153events.push(...changes);154callbacks += 1;155});156157pa.unshift('a');158pa.unshift('b', 'c');159expect(events).toHaveLength(3);160expect(callbacks).toBe(2);161expect(events[0]).toEqual({162type: 'insert',163path: [0],164value: 'a',165});166expect(events[1]).toEqual({167type: 'insert',168path: [0],169value: 'b',170});171expect(events[2]).toEqual({172type: 'insert',173path: [1],174value: 'c',175});176});177178test('array unshift - objects', () => {179const events = [];180const pa = ObjectObserver.create([{ text: 'original' }], changes => {181events.push(...changes);182});183184pa.unshift({ text: 'initial' });185expect(events).toHaveLength(1);186expect(events[0]).toEqual({187type: 'insert',188path: [0],189value: { text: 'initial' },190});191events.splice(0);192193pa[0].text = 'name';194pa[1].text = 'other';195expect(events).toHaveLength(2);196expect(events[0]).toEqual({197type: 'update',198path: [0, 'text'],199value: 'name',200});201expect(events[1]).toEqual({202type: 'update',203path: [1, 'text'],204value: 'other',205});206});207208test('array unshift - arrays', () => {209const events = [];210const pa = ObjectObserver.create([{ text: 'original' }], changes => {211events.push(...changes);212});213214pa.unshift([{}] as any);215expect(events).toHaveLength(1);216expect(events[0]).toEqual({217type: 'insert',218path: [0],219value: [{}],220});221events.splice(0);222223pa[0][0].text = 'name';224pa[1].text = 'other';225expect(events).toHaveLength(2);226expect(events[0]).toEqual({227type: 'insert',228path: [0, 0, 'text'],229value: 'name',230});231expect(events[1]).toEqual({232type: 'update',233path: [1, 'text'],234value: 'other',235});236});237238test('array shift - primitives', () => {239const events = [];240const pa = ObjectObserver.create(['some'], changes => {241events.push(...changes);242});243244const shifted = pa.shift();245expect(events).toHaveLength(1);246expect(events[0]).toEqual({247type: 'delete',248path: [0],249});250expect(shifted).toBe('some');251});252253test('array shift - objects', () => {254const events = [];255const pa = ObjectObserver.create(256[{ text: 'a', inner: { test: 'more' } }, { text: 'b' }],257changes => events.push(...changes),258);259const pa0 = pa[0];260const pa0i: any = pa0.inner;261262pa[0].text = 'b';263pa0i.test = 'test';264expect(events).toHaveLength(2);265events.splice(0);266267const shifted = pa.shift();268expect(shifted.text).toBe('b');269expect(shifted.inner.test).toBe('test');270271expect(events).toHaveLength(1);272expect(events[0]).toEqual({273type: 'delete',274path: [0],275});276events.splice(0);277278pa[0].text = 'c';279expect(events).toHaveLength(1);280expect(events[0]).toEqual({281type: 'update',282path: [0, 'text'],283value: 'c',284});285events.splice(0);286287shifted.text = 'd';288expect(events).toHaveLength(0);289});290291test('array reverse - primitives (flat array)', () => {292const events = [];293const pa = ObjectObserver.create([1, 2, 3], changes => events.push(...changes));294295const reversed = pa.reverse();296297expect(reversed).toEqual(pa);298expect(events).toHaveLength(1);299expect(events[0]).toEqual({300type: 'reorder',301path: [],302value: [2, 1, 0],303});304});305306test('array reverse - primitives (nested array)', () => {307const events = [];308const pa = ObjectObserver.create({ a1: { a2: [1, 2, 3] } }, changes => events.push(...changes));309310const reversed = pa.a1.a2.reverse();311312expect(reversed).toEqual(pa.a1.a2);313expect(events).toHaveLength(1);314expect(events[0]).toEqual({315type: 'reorder',316path: ['a1', 'a2'],317value: [2, 1, 0],318});319});320321test('array reverse - objects', () => {322const events = [];323const pa = ObjectObserver.create([{ name: 'a' }, { name: 'b' }, { name: 'c' }], changes =>324events.push(...changes),325);326327pa[0].name = 'A';328const reversed = pa.reverse();329pa[0].name = 'C';330331expect(reversed).toEqual(pa);332expect(events).toHaveLength(3);333expect(events[0]).toEqual({334type: 'update',335path: [0, 'name'],336value: 'A',337});338expect(events[1]).toEqual({339type: 'reorder',340path: [],341value: [2, 1, 0],342});343expect(events[2]).toEqual({344type: 'update',345path: [0, 'name'],346value: 'C',347});348});349350test('array sort - primitives (flat array)', () => {351const events = [];352const pa = ObjectObserver.create([3, 2, 1], changes => events.push(...changes));353354let sorted = pa.sort();355356expect(sorted).toEqual(pa);357expect(events).toHaveLength(1);358expect(events[0]).toEqual({359type: 'reorder',360path: [],361value: [2, 1, 0],362});363expect(pa).toEqual([1, 2, 3]);364365sorted = pa.sort((a, b) => {366return a < b ? 1 : -1;367});368expect(sorted).toEqual(pa);369370expect(events).toHaveLength(2);371expect(events[0]).toEqual({372type: 'reorder',373path: [],374value: [2, 1, 0],375});376expect(pa).toEqual([3, 2, 1]);377});378379test('array sort - primitives (flat array with duplicates)', () => {380const events = [];381const pa = ObjectObserver.create([3, 2, 1, 2, 1], changes => events.push(...changes));382383const sorted = pa.sort();384385expect(sorted).toEqual(pa);386expect(events).toHaveLength(1);387expect(events[0]).toEqual({388type: 'reorder',389path: [],390value: [2, 4, 1, 3, 0],391});392expect(pa).toEqual([1, 1, 2, 2, 3]);393});394395test('array sort - objects', () => {396const events = [];397const pa = ObjectObserver.create([{ name: 'a' }, { name: 'b' }, { name: 'c' }], changes =>398events.push(...changes),399);400401pa[0].name = 'A';402const sorted = pa.sort((a, b) => {403return a.name < b.name ? 1 : -1;404});405pa[0].name = 'C';406407if (sorted !== pa) throw new Error('sort base functionality broken');408409expect(events).toHaveLength(3);410expect(events[0]).toEqual({411type: 'update',412path: [0, 'name'],413value: 'A',414});415expect(events[1]).toEqual({416type: 'reorder',417path: [],418value: [2, 1, 0],419});420expect(events[2]).toEqual({421type: 'update',422path: [0, 'name'],423value: 'C',424});425});426427test('array fill - primitives', () => {428const events = [];429const pa: any[] = ObjectObserver.create([1, 2, 3], changes => events.push(...changes));430431const filled = pa.fill('a');432if (filled !== pa) throw new Error('fill base functionality broken');433434expect(events).toHaveLength(3);435expect(events[0]).toEqual({436type: 'update',437path: [0],438value: 'a',439});440expect(events[1]).toEqual({441type: 'update',442path: [1],443value: 'a',444});445expect(events[2]).toEqual({446type: 'update',447path: [2],448value: 'a',449});450events.splice(0);451452pa.fill('b', 1, 3);453454expect(events).toHaveLength(2);455expect(events[0]).toEqual({456type: 'update',457path: [1],458value: 'b',459});460expect(events[1]).toEqual({461type: 'update',462path: [2],463value: 'b',464});465events.splice(0);466467pa.fill('c', -1, 3);468469expect(events).toHaveLength(1);470expect(events[0]).toEqual({471type: 'update',472path: [2],473value: 'c',474});475events.splice(0);476477// simulating insertion of a new item into array (fill does not extend an array, so we may do it only on internal items)478delete pa[1];479pa.fill('d', 1, 2);480481expect(events).toHaveLength(2);482expect(events[0]).toEqual({483type: 'delete',484path: [1],485});486expect(events[1]).toEqual({487type: 'update',488path: [1],489value: 'd',490});491});492493test('array fill - objects', () => {494const events = [];495const pa: any = ObjectObserver.create(496[{ some: 'text' }, { some: 'else' }, { some: 'more' }],497changes => events.push(...changes),498);499500const filled = pa.fill({ name: 'Niv' });501if (filled !== pa) throw new Error('fill base functionality broken');502503expect(events).toHaveLength(3);504expect(events[0]).toEqual({505type: 'update',506path: [0],507value: { name: 'Niv' },508});509expect(events[1]).toEqual({510type: 'update',511path: [1],512value: { name: 'Niv' },513});514expect(events[2]).toEqual({515type: 'update',516path: [2],517value: { name: 'Niv' },518});519events.length = 0;520521pa[1].name = 'David';522expect(events[0]).toEqual({523type: 'update',524path: [1, 'name'],525value: 'David',526});527expect(events).toHaveLength(1);528});529530test('array fill - arrays', () => {531const events = [];532const pa: any = ObjectObserver.create(533[{ some: 'text' }, { some: 'else' }, { some: 'more' }],534changes => events.push(...changes),535);536537const filled = pa.fill([{ name: 'Niv' }]);538expect(filled).toEqual(pa);539540expect(events).toHaveLength(3);541expect(events[0]).toEqual({542type: 'update',543path: [0],544value: [{ name: 'Niv' }],545});546expect(events[1]).toEqual({547type: 'update',548path: [1],549value: [{ name: 'Niv' }],550});551expect(events[2]).toEqual({552type: 'update',553path: [2],554value: [{ name: 'Niv' }],555});556events.length = 0;557558pa[1][0].name = 'David';559560expect(events).toHaveLength(1);561expect(events[0]).toEqual({562type: 'update',563path: [1, 0, 'name'],564value: 'David',565});566});567568test('array splice - primitives', () => {569const events = [];570let callbacks = 0;571const pa: any = ObjectObserver.create([1, 2, 3, 4, 5, 6], changes => {572events.push(...changes);573callbacks += 1;574});575576const spliced = pa.splice(2, 2, 'a');577expect(spliced).toEqual([3, 4]);578579expect(events).toHaveLength(2);580expect(callbacks).toBe(1);581expect(events[0]).toEqual({582type: 'update',583path: [2],584value: 'a',585});586expect(events[1]).toEqual({587type: 'delete',588path: [3],589});590events.splice(0);591callbacks = 0;592593// pa = [1,2,'a',5,6]594pa.splice(-3);595596expect(events).toHaveLength(3);597expect(callbacks).toBe(1);598expect(events[0]).toEqual({599type: 'delete',600path: [2],601});602expect(events[1]).toEqual({603type: 'delete',604path: [3],605});606expect(events[2]).toEqual({607type: 'delete',608path: [4],609});610expect(pa).toHaveLength(2);611events.length = 0;612callbacks = 0;613614// pa = [1,2]615pa.splice(0);616617expect(events).toHaveLength(2);618expect(callbacks).toBe(1);619expect(events[0]).toEqual({620type: 'delete',621path: [0],622});623expect(events[1]).toEqual({624type: 'delete',625path: [1],626});627});628629test('array splice - objects', () => {630const events = [];631const pa = ObjectObserver.create(632[{ text: 'a' }, { text: 'b' }, { text: 'c' }, { text: 'd' }],633changes => events.push(...changes),634);635636pa.splice(1, 2, { text: '1' });637638expect(events).toHaveLength(2);639expect(events[0]).toEqual({640type: 'update',641path: [1],642value: { text: '1' },643});644expect(events[1]).toEqual({645type: 'delete',646path: [2],647});648expect(pa).toHaveLength(3);649events.splice(0);650651pa[1].text = 'B';652pa[2].text = 'D';653654expect(events).toHaveLength(2);655expect(events[0]).toEqual({656type: 'update',657path: [1, 'text'],658value: 'B',659});660expect(events[1]).toEqual({661type: 'update',662path: [2, 'text'],663value: 'D',664});665events.splice(0);666667pa.splice(1, 1, { text: 'A' }, { text: 'B' });668669expect(events).toHaveLength(2);670expect(events[0]).toEqual({671type: 'update',672path: [1],673value: { text: 'A' },674});675expect(events[1]).toEqual({676type: 'insert',677path: [2],678value: { text: 'B' },679});680events.splice(0);681682pa[3].text = 'C';683684expect(events).toHaveLength(1);685expect(events[0]).toEqual({686type: 'update',687path: [3, 'text'],688value: 'C',689});690});691692describe('copyWithin', () => {693test('array copyWithin - primitives', () => {694const events = [];695let callbacks = 0;696const pa = ObjectObserver.create([1, 2, 3, 4, 5, 6], changes => {697events.push(...changes);698callbacks += 1;699});700701let copied = pa.copyWithin(2, 0, 3);702expect(pa).toEqual(copied);703expect(events).toHaveLength(3);704expect(callbacks).toBe(1);705expect(events[0]).toEqual({706type: 'update',707path: [2],708value: 1,709});710expect(events[1]).toEqual({711type: 'update',712path: [3],713value: 2,714});715expect(events[2]).toEqual({716type: 'update',717path: [4],718value: 3,719});720events.splice(0);721callbacks = 0;722723// pa = [1,2,1,2,3,6]724copied = pa.copyWithin(-3, 0);725expect(pa).toEqual(copied);726expect(events).toHaveLength(3);727expect(callbacks).toBe(1);728expect(events[0]).toEqual({729type: 'update',730path: [3],731value: 1,732});733expect(events[1]).toEqual({734type: 'update',735path: [4],736value: 2,737});738expect(events[2]).toEqual({739type: 'update',740path: [5],741value: 1,742});743events.splice(0);744callbacks = 0;745746// pa = [1,2,1,1,2,1]747copied = pa.copyWithin(1, -3, 9);748expect(pa).toEqual(copied);749expect(events).toHaveLength(2);750expect(callbacks).toBe(1);751expect(events[0]).toEqual({752type: 'update',753path: [1],754value: 1,755});756expect(events[1]).toEqual({757type: 'update',758path: [2],759value: 2,760});761762// update at index 4 should not be evented, since 1 === 1763events.splice(0);764callbacks = 0;765});766767test('array copyWithin - objects', () => {768const events = [];769const pa = ObjectObserver.create(770[{ text: 'a' }, { text: 'b' }, { text: 'c' }, { text: 'd' }],771changes => {772events.push(...changes);773},774);775776const copied = pa.copyWithin(1, 2, 3);777expect(pa).toEqual(copied);778expect(events).toHaveLength(1);779expect(events[0]).toEqual({780type: 'update',781path: [1],782value: { text: 'c' },783});784events.length = 0;785786pa[1].text = 'B';787pa[2].text = 'D';788expect(events).toHaveLength(2);789expect(events[0]).toEqual({790type: 'update',791path: [1, 'text'],792value: 'B',793});794expect(events[1]).toEqual({795type: 'update',796path: [2, 'text'],797value: 'D',798});799});800});801802803