UNPKG

validated-changeset

Version:
1,547 lines (1,246 loc) 87.3 kB
import { ValidationChangesetFactory as Changeset } from '../src'; import get from '../src/utils/get-deep'; import set from '../src/utils/set-deep'; import { array, object, string, number, date } from 'yup'; let userSchema = object({ name: string().required(), age: number().required().positive().integer(), email: string().email(), org: object({ usa: object({ minAge: number().moreThan(18) }) }), teams: array(string()), website: string().url().nullable(), createdOn: date().default(() => new Date()) }); let dummyModel: any = {}; const exampleArray: Array<any> = []; describe('Unit | Utility | validation changeset', () => { beforeEach(() => { dummyModel = {}; }); /** * #toString */ it('content can be an empty hash', () => { expect.assertions(1); const emptyObject = {}; const dummyChangeset = Changeset(emptyObject); expect(dummyChangeset.toString()).toEqual('changeset:[object Object]'); }); /** * #change */ it('#change returns the changes object', () => { const dummyChangeset = Changeset(dummyModel); const expectedResult = { name: 'a' }; dummyChangeset.set('name', 'a'); expect(dummyChangeset.change).toEqual(expectedResult); }); it('#change supports `undefined`', () => { const model = { name: 'a' }; const dummyChangeset = Changeset(model); const expectedResult = { name: undefined }; dummyChangeset.set('name', undefined); expect(dummyChangeset.change).toEqual(expectedResult); }); it('#change works with arrays', () => { const dummyChangeset = Changeset(dummyModel); const newArray = [...exampleArray, 'new']; const expectedResult = { teams: newArray }; dummyChangeset.set('teams', newArray); expect(dummyChangeset.change).toEqual(expectedResult); }); // /** // * #errors // */ // it('#errors returns the error object and keeps changes', () => { // const dummyChangeset = Changeset(dummyModel); // let expectedResult = [{ key: 'name', validation: 'too short', value: 'a' }]; // dummyChangeset.set('name', 'a'); // expect(dummyChangeset.errors).toEqual(expectedResult); // expect(dummyChangeset.get('errors')).toEqual(expectedResult); // }); // it('can get nested values in the errors object', () => { // const dummyChangeset = Changeset(dummyModel); // dummyChangeset.set('unknown', 'wat'); // dummyChangeset.set('org.usa.ny', ''); // dummyChangeset.set('name', ''); // let expectedErrors = [ // { key: 'org.usa.ny', validation: ['must be present', 'only letters work'], value: '' }, // { key: 'name', validation: 'too short', value: '' } // ]; // expect(dummyChangeset.get('errors')).toEqual(expectedErrors); // dummyChangeset.set('org.usa.ny', '1'); // expectedErrors = [ // { key: 'org.usa.ny', validation: ['only letters work'], value: '1' }, // { key: 'name', validation: 'too short', value: '' } // ]; // expect(dummyChangeset.get('errors')).toEqual(expectedErrors); // }); // /** // * #changes // */ /** * #data */ it('data reads the changeset CONTENT', () => { const dummyChangeset = Changeset(dummyModel); expect(dummyChangeset.data).toEqual(dummyModel); }); /** * #isValid */ it('does not validate with default options', () => { const dummyChangeset = Changeset(dummyModel); expect(dummyChangeset.get('isValid')).toBeTruthy(); }); // /** // * #isInvalid // */ /** * #isPristine */ it("isPristine returns true if changes are equal to content's values", () => { dummyModel.name = 'Bobby'; dummyModel.thing = 123; dummyModel.nothing = null; const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('name', 'Bobby'); dummyChangeset.set('nothing', null); expect(dummyChangeset.get('isPristine')).toBeTruthy(); }); it("isPristine returns false if changes are not equal to content's values", () => { dummyModel.name = 'Bobby'; const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('name', 'Bobby'); dummyChangeset.set('thing', 123); expect(dummyChangeset.get('isPristine')).toBeFalsy(); }); it('isPristine works with `null` values', () => { dummyModel.name = null; dummyModel.age = 15; const dummyChangeset = Changeset(dummyModel); expect(dummyChangeset.get('isPristine')).toBeTruthy(); dummyChangeset.set('name', 'Kenny'); expect(dummyChangeset.get('isPristine')).toBeFalsy(); dummyChangeset.set('name', null); expect(dummyChangeset.get('isPristine')).toBeTruthy(); }); /** * #isDirty */ it('#set dirties changeset', () => { const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('name', 'foo'); expect(dummyChangeset.isDirty).toBe(true); }); it('#isDirty after set', () => { const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('name', 'foo'); expect(dummyChangeset.isDirty).toBe(true); }); it('#isDirty reset after execute', () => { dummyModel.name = {}; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = { short: 'foo' }; expect(dummyChangeset.get('isDirty')).toBe(true); dummyChangeset.execute(); expect(dummyChangeset.get('isDirty')).toBe(false); }); it('#isDirty reset after rollback', () => { dummyModel.name = {}; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = { short: 'foo' }; expect(dummyChangeset.get('isDirty')).toBe(true); dummyChangeset.rollback(); expect(dummyChangeset.get('isDirty')).toBe(false); }); it('#isDirty is false when no set', () => { dummyModel['name'] = { nick: 'bar' }; const dummyChangeset = Changeset(dummyModel); dummyChangeset.name; expect(dummyChangeset.isDirty).toBe(false); }); it('#isDirty is false when no set with deep values', () => { dummyModel['details'] = { name: { nick: 'bar' } }; const dummyChangeset = Changeset(dummyModel); dummyChangeset.get('details.name'); expect(dummyChangeset.isDirty).toBe(false); expect(dummyChangeset.change).toEqual({}); }); it('#isDirty is true when set with deep values', () => { class Dog { value: any; constructor(value: any) { this.value = value; } } dummyModel['details'] = { name: {} }; const dummyChangeset = Changeset(dummyModel); dummyChangeset.get('details.name'); const dogKlass = new Dog({ nickname: 'bar' }); dummyChangeset['details'] = { name: dogKlass }; expect(dummyChangeset.isDirty).toBe(true); expect(dummyChangeset.change).toEqual({ details: { name: dogKlass } }); }); it('#set does not dirty changeset with same date', () => { dummyModel.createTime = new Date('2013-05-01'); const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('createTime', new Date('2013-05-01')); expect(dummyChangeset.isDirty).toBe(false); }); /** * #get */ it('#get proxies to content', () => { dummyModel.name = 'Jim Bob'; const dummyChangeset = Changeset(dummyModel); const result = dummyChangeset.name; expect(result).toBe('Jim Bob'); }); it('#get proxies to content prototype', () => { class Dog { name?: string; } Dog.prototype.name = 'Jim Bob'; const dummyChangeset = Changeset(new Dog()); const result = dummyChangeset.name; expect(result).toBe('Jim Bob'); }); it('#get returns the content when the proxied content is a class', () => { class Moment { date: unknown; constructor(date: Date) { this.date = date; } } const d = new Date('2015'); const momentInstance = new Moment(d); const c = Changeset({ startDate: momentInstance }); const newValue = c.get('startDate'); expect(newValue.date).toEqual(momentInstance.date); expect(newValue.content instanceof Moment).toBeTruthy(); expect(newValue.date).toBe(d); }); it('#get handles changes that are non primitives', () => { class Moment { _isUTC: any; date: unknown; constructor(date: Date) { this.date = date; this._isUTC = false; } } const d = new Date('2015'); const momentInstance = new Moment(d); momentInstance._isUTC = true; const c = Changeset({ startDate: momentInstance }); let newValue = c.get('startDate'); expect(newValue.date).toEqual(momentInstance.date); expect(newValue.content instanceof Moment).toBeTruthy(); expect(newValue.date).toBe(d); expect(newValue._isUTC).toEqual(true); const newD = new Date('2020'); const newMomentInstance = new Moment(newD); c.set('startDate', newMomentInstance); newValue = c.get('startDate'); newMomentInstance._isUTC = undefined; expect(newValue).toEqual(newMomentInstance); expect(newValue instanceof Moment).toBeTruthy(); expect(newValue.date).toBe(newD); expect(newValue._isUTC).toBeUndefined(); }); it('#get merges sibling keys from CONTENT with CHANGES', () => { class Moment { _isUTC: boolean; date: unknown; constructor(date: Date) { this.date = date; this._isUTC = false; } } const d = new Date('2015'); const momentInstance = new Moment(d); momentInstance._isUTC = true; const c = Changeset({ startDate: momentInstance }); let newValue = c.get('startDate'); expect(newValue.date).toEqual(momentInstance.date); expect(newValue.content instanceof Moment).toBeTruthy(); expect(newValue.date).toBe(d); expect(newValue._isUTC).toEqual(true); const newD = new Date('2020'); c.set('startDate.date', newD); newValue = c.get('startDate'); expect(newValue.date).toEqual(newD); expect(newValue.content instanceof Moment).toBeTruthy(); expect(newValue.date).toBe(newD); expect(newValue._isUTC).toBe(true); }); it('#get returns change if present', () => { dummyModel.name = 'Jim Bob'; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = 'Milton Waddams'; const result = dummyChangeset.name; expect(result).toBe('Milton Waddams'); }); it('#get returns change that is a blank value', () => { dummyModel.name = 'Jim Bob'; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = ''; const result = dummyChangeset.name; expect(result).toBe(''); }); it('#get returns change that is has undefined as value', () => { dummyModel.name = 'Jim Bob'; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = undefined; const result = dummyChangeset.name; expect(result).toBeUndefined(); }); it('#get nested objects can contain arrays', () => { expect.assertions(7); dummyModel.name = 'Bob'; dummyModel.contact = { emails: ['bob@email.com', 'the_bob@email.com'] }; expect(get(dummyModel, 'contact.emails')).toEqual(['bob@email.com', 'the_bob@email.com']); const dummyChangeset = Changeset(dummyModel); expect(dummyChangeset.get('name')).toBe('Bob'); expect(dummyChangeset.get('contact.emails')).toEqual(['bob@email.com', 'the_bob@email.com']); dummyChangeset.set('contact.emails', ['fred@email.com', 'the_fred@email.com']); expect(dummyChangeset.get('contact.emails')).toEqual(['fred@email.com', 'the_fred@email.com']); dummyChangeset.rollback(); expect(dummyChangeset.get('contact.emails')).toEqual(['bob@email.com', 'the_bob@email.com']); dummyChangeset.set('contact.emails', ['fred@email.com', 'the_fred@email.com']); expect(dummyChangeset.get('contact.emails')).toEqual(['fred@email.com', 'the_fred@email.com']); dummyChangeset.execute(); expect(dummyModel.contact.emails).toEqual(['fred@email.com', 'the_fred@email.com']); }); it('#getted Object proxies to underlying method', () => { class Dog { breed: string; constructor(b: string) { this.breed = b; } bark() { return `woof i'm a ${this.breed}`; } } const model: Record<string, any> = { foo: { bar: { dog: new Dog('shiba inu, wow') } } }; { const c = Changeset(model); const actual = c.get('foo.bar.dog'); const expectedResult = "woof i'm a shiba inu, wow"; expect(actual.bark()).toEqual(expectedResult); } { const c = Changeset(model); const actual = get(c, 'foo.bar.dog'); const expectedResult = get(model, 'foo.bar.dog'); expect(actual).toEqual(expectedResult); } { const c = Changeset(model); const actual = get(c, 'foo.bar.dog'); const expectedResult = get(model, 'foo.bar.dog'); expect(actual).toEqual(expectedResult); } }); it('#get proxies to underlying array properties', () => { dummyModel.users = ['user1', 'user2']; const dummyChangeset = Changeset(dummyModel); expect((dummyChangeset.users as Array<string>).length).toBe(2); }); it('#get works if content is undefined for nested key', () => { const model: Record<string, any> = {}; const c = Changeset(model); c.set('foo.bar.cat', { color: 'red' }); const cat = c.get('foo.bar.cat'); expect(cat.color).toEqual('red'); }); it('#get works with toString override', () => { dummyModel.toString = function () { return 'mine'; }; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = undefined; const result = dummyChangeset.toString(); expect(result).toEqual('changeset:mine'); }); it('#get prioritizes own methods/getters', () => { dummyModel.trigger = function (arg: any) { expect(arg).toEqual('mine'); }; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = undefined; dummyChangeset.trigger('mine'); }); /** * #set */ it('#set adds a change if valid', () => { const expectedChanges = { name: { current: 'foo', original: undefined } }; const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('name', 'foo'); const changes = dummyChangeset.changes; expect(dummyModel.name).toBeUndefined(); expect(dummyChangeset.get('name')).toEqual('foo'); expect(changes).toEqual(expectedChanges); expect(dummyChangeset.isDirty).toBe(true); expect(dummyChangeset.change).toEqual({ name: 'foo' }); }); it('#set adds a change with plain assignment without existing values', () => { dummyModel['name'] = { nick: 'bar' }; const dummyChangeset = Changeset(dummyModel); const proxy: any = dummyChangeset.name; proxy['nick'] = 'foo'; expect(dummyChangeset.get('name.nick')).toEqual('foo'); const expectedChanges = { 'name.nick': { current: 'foo', original: 'bar' } }; const changes = dummyChangeset.changes; expect(changes).toEqual(expectedChanges); }); it('#set adds a change with plain assignment', () => { dummyModel['name'] = 'bar'; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = 'foo'; const changes = dummyChangeset.changes; expect(dummyModel.name).toBe('bar'); expect(dummyChangeset.name).toEqual('foo'); const expectedChanges = { name: { current: 'foo', original: 'bar' } }; expect(changes).toEqual(expectedChanges); }); it('#set adds a date', () => { const d = new Date(); const expectedChanges = { dateOfBirth: { current: d, original: undefined } }; const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('dateOfBirth', d); const changes = dummyChangeset.changes; expect(dummyModel.dateOfBirth).toBeUndefined(); expect(dummyChangeset.get('dateOfBirth')).toEqual(d); expect(changes).toEqual(expectedChanges); }); it('#set adds a date if already set on model', () => { const model = { dateOfBirth: new Date() }; const dummyChangeset = Changeset(model); const d = new Date('March 25, 1990'); dummyChangeset.set('dateOfBirth', d); const changes = dummyChangeset.changes; expect(dummyModel.dateOfBirth).toBeUndefined(); expect(dummyChangeset.get('dateOfBirth')).toEqual(d); expect(dummyChangeset.dateOfBirth).toEqual(d); const expectedChanges = { dateOfBirth: { current: d, original: model.dateOfBirth } }; expect(changes).toEqual(expectedChanges); }); it('#set Ember.set works', () => { const expectedChanges = { name: { current: 'foo', original: undefined } }; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = 'foo'; expect(dummyModel.name).toBeUndefined(); expect(dummyChangeset.get('name')).toBe('foo'); const changes = dummyChangeset.changes; expect(changes).toEqual(expectedChanges); dummyChangeset.execute(); expect(dummyModel.name).toBe('foo'); expect(dummyChangeset.get('name')).toBe('foo'); }); it('#set works for nested', () => { const expectedChanges = { name: { current: { short: 'foo' }, original: {} } }; dummyModel.name = {}; dummyModel.org = {}; const dummyChangeset = Changeset(dummyModel); dummyChangeset['name'] = { short: 'foo' }; expect(dummyChangeset.get('name.short')).toBe('foo'); expect(dummyModel.name).toEqual({}); const changes = dummyChangeset.changes; expect(changes).toEqual(expectedChanges); expect(dummyChangeset.name).toEqual({ short: 'foo' }); expect(dummyChangeset.org).toEqual({}); dummyChangeset.execute(); expect(dummyModel.name.short).toBe('foo'); }); it('#set overrides', () => { const expectedChanges = { age: { current: '90', original: '10' } }; let dummyChangeset = Changeset({ age: '10' }); dummyChangeset.set('age', '80'); dummyChangeset.set('age', '10'); dummyChangeset.set('age', '90'); const changes = dummyChangeset.changes; expect(dummyModel.age).toBeUndefined(); expect(dummyChangeset.get('age')).toEqual('90'); expect(changes).toEqual(expectedChanges); expect(dummyChangeset.isDirty).toBe(true); expect(dummyChangeset.change).toEqual({ age: '90' }); }); test('#set Ember.set with Object actually does work TWICE for nested', () => { set(dummyModel, 'name', {}); let title1 = { id: 'Mr', description: 'Mister' }; let title2 = { id: 'Mrs', description: 'Missus' }; let dummyChangeset: any = Changeset(dummyModel); set(dummyChangeset, 'name.title', title1); expect(get(dummyModel, 'name.title.id')).toBeUndefined(); expect(dummyChangeset.name.title.id).toEqual('Mr'); expect(dummyChangeset.get('name.title.id')).toEqual('Mr'); let changes = get(dummyChangeset, 'changes'); let expected = { 'name.title': { current: title1, original: undefined } }; expect(changes).toEqual(expected); set(dummyChangeset, 'name.title', title2); expect(get(dummyModel, 'name.title.id')).toBeUndefined(); expect(dummyChangeset.name.title.id).toEqual('Mrs'); expect(dummyChangeset.get('name.title.id')).toEqual('Mrs'); changes = get(dummyChangeset, 'changes'); expected = { 'name.title': { current: title2, original: undefined } }; expect(changes).toEqual(expected); dummyChangeset.execute(); expect(dummyModel.name.title.id).toEqual('Mrs'); }); test('#set with Object should work TWICE for nested', () => { set(dummyModel, 'name', {}); let title1 = { id: 'Mr', description: 'Mister' }; let title2 = { id: 'Mrs', description: 'Missus' }; let dummyChangeset: any = Changeset(dummyModel); dummyChangeset.set('name.title', title1); expect(get(dummyModel, 'name.title.id')).toBeUndefined(); expect(dummyChangeset.name.title.id).toEqual('Mr'); expect(dummyChangeset.get('name.title.id')).toEqual('Mr'); let changes = dummyChangeset.changes; let expected = { 'name.title': { current: title1, original: undefined } }; expect(changes).toEqual(expected); dummyChangeset.set('name.title', title2); expect(get(dummyModel, 'name.title.id')).toBeUndefined(); expect(dummyChangeset.name.title.id).toEqual('Mrs'); expect(dummyChangeset.get('name.title.id')).toEqual('Mrs'); changes = dummyChangeset.changes; expected = { 'name.title': { current: title2, original: undefined } }; expect(changes).toEqual(expected); dummyChangeset.execute(); expect(dummyModel.name.title.id).toEqual('Mrs'); }); describe('arrays within nested objects', () => { describe('#set', () => { let initialData: { contact: { emails?: string[] } } = { contact: { emails: [] } }; beforeEach(() => { initialData = { contact: { emails: ['bob@email.com'] } }; }); it('works with boolean values', () => { let initialData = { contact: { emails: [{}, {}] } }; const changeset = Changeset(initialData); changeset.set('contact.emails.2', { nested: false }); expect(changeset.get('contact.emails.2')).toEqual({ nested: false }); changeset.set('contact.emails.2.nested', true); expect(changeset.get('contact.emails.2')).toEqual({ nested: true }); changeset.set('contact.emails.2.nested', false); expect(changeset.get('contact.emails.2')).toEqual({ nested: false }); }); it('nested objects cannot create arrays when we have no hints', () => { initialData.contact = {}; const changeset = Changeset(initialData); expect(changeset.get('contact.emails')).toEqual(undefined); changeset.set('contact.emails.0', 'fred@email.com'); expect(changeset.get('contact.emails.0')).toEqual('fred@email.com'); expect(changeset.get('contact.emails')).toEqual({ '0': 'fred@email.com' }); }); it('can be rolled back', () => { const changeset = Changeset(initialData); changeset.set('contact.emails.0', 'fred@email.com'); expect(changeset.get('contact.emails.0')).toEqual('fred@email.com'); const expected = { 'contact.emails.0': { current: 'fred@email.com', original: 'bob@email.com' } }; expect(changeset.changes).toEqual(expected); expect(changeset.get('contact.emails').unwrap()).toEqual(['fred@email.com']); changeset.rollback(); expect(changeset.get('contact.emails.0')).toEqual('bob@email.com'); expect(changeset.changes).toEqual({}); expect(changeset.get('contact.emails')).toEqual(['bob@email.com']); }); it('can add items to the array', () => { const changeset = Changeset(initialData); changeset.set('contact.emails.1', 'fred@email.com'); expect(changeset.get('contact.emails.1')).toEqual('fred@email.com'); expect(changeset.get('contact.emails').unwrap()).toEqual([ 'bob@email.com', 'fred@email.com' ]); let expected: any = { 'contact.emails.1': { current: 'fred@email.com', original: undefined } }; expect(changeset.changes).toEqual(expected); changeset.set('contact.emails.3', 'greg@email.com'); expect(changeset.get('contact.emails.3')).toEqual('greg@email.com'); expect(changeset.get('contact.emails').unwrap()).toEqual([ 'bob@email.com', 'fred@email.com', undefined, 'greg@email.com' ]); expected = { 'contact.emails.1': { current: 'fred@email.com', original: undefined }, 'contact.emails.3': { current: 'greg@email.com', original: undefined } }; expect(changeset.changes).toEqual(expected); expect(changeset.change).toEqual({ contact: { emails: { 1: 'fred@email.com', 3: 'greg@email.com' } } }); }); it('can remove items from the array', () => { const changeset = Changeset(initialData); changeset.set('contact.emails.1', 'fred@email.com'); expect(changeset.get('contact.emails.1')).toEqual('fred@email.com'); expect(changeset.get('contact.emails').unwrap()).toEqual([ 'bob@email.com', 'fred@email.com' ]); let expected: any = { 'contact.emails.1': { current: 'fred@email.com', original: undefined } }; expect(changeset.changes).toEqual(expected); changeset.set('contact.emails.0', null); expect(changeset.get('contact.emails.0')).toEqual(null); expect(changeset.get('contact.emails').unwrap()).toEqual([null, 'fred@email.com']); expected = { 'contact.emails.0': { current: null, original: 'bob@email.com' }, 'contact.emails.1': { current: 'fred@email.com', original: undefined } }; expect(changeset.changes).toEqual(expected); changeset.set('contact.emails.1', null); expected = { 'contact.emails.0': { current: null, original: 'bob@email.com' }, 'contact.emails.1': { current: null, original: undefined } }; expect(changeset.get('contact.emails').unwrap()).toEqual([null, null]); expect(changeset.changes).toEqual(expected); }); it('can add an item to an index in an array where that item was previously removed', () => { const deepObj = (email: string) => ({ emails: { primary: email } }); const bob = deepObj('bob@email.com'); const fred = deepObj('fred@email.com'); const sanHolo = deepObj('sanholo@email.com'); const changeset = Changeset({ contacts: [bob, fred] }); // "Delete" array element changeset.set('contacts.0', null); expect(changeset.isDirty).toBeTruthy(); expect(changeset.get('contacts.0')).toEqual(null); expect(changeset.get('contacts')).toEqual([null, fred]); let expected: any = { 'contacts.0': { current: null, original: bob } }; expect(changeset.changes).toEqual(expected); // Set array element to entirely new object changeset.set('contacts.0', sanHolo); expect(changeset.isDirty).toBeTruthy(); expect(changeset.get('contacts')).toEqual([sanHolo, fred]); expect(changeset.get('contacts.0.emails.primary')).toEqual('sanholo@email.com'); expected = { 'contacts.0': { current: sanHolo, original: sanHolo } }; // todo: is 'original' sanHolo concerning? expect(changeset.changes).toEqual(expected); // "Delete" array element again changeset.set('contacts.0', null); expect(changeset.isDirty).toBeTruthy(); expect(changeset.get('contacts.0')).toEqual(null); expect(changeset.get('contacts')).toEqual([null, fred]); expected = { 'contacts.0': { current: null, original: sanHolo } }; expect(changeset.changes).toEqual(expected); // Revert everything changeset.rollback(); expect(changeset.isDirty).toBeFalsy(); expect(changeset.changes).toEqual({}); expect(changeset.get('contacts')).toEqual([bob, fred]); }); xit(`negative values are not allowed`, () => { // This test is currently disabled because setDeep doesn't have a reference to the // original array and setDeep is where we'd throw on invalid key values const changeset = Changeset(initialData); expect(changeset.get('contact.emails')).toEqual(['bob@email.com']); expect(() => { changeset.set('contact.emails.-1', 'fred@email.com'); }).toThrow( 'Negative indices are not allowed as arrays do not serialize values at negative indices' ); }); }); }); // describe('arrays as values of top level objects', () => { // let initialData: { emails: Record<string, string>[] } = { emails: [] }; // beforeEach(() => { // initialData = { emails: [{ primary: 'bob@email.com' }] }; // }); // it('can modify properties on an entry', () => { // const changeset = Changeset(initialData); // changeset.set('emails.0.primary', 'fun@email.com'); // expect(changeset.get('emails.0.primary')).toEqual('fun@email.com'); // expect(changeset.get('emails')).toEqual([{ primary: 'fun@email.com' }]); // expect(changeset.changes).toEqual([{ key: 'emails.0.primary', value: 'fun@email.com' }]); // }); // it('can add properties to an entry', () => { // const changeset = Changeset(initialData); // changeset.set('emails.0.funEmail', 'fun@email.com'); // expect(changeset.get('emails.0.funEmail')).toEqual('fun@email.com'); // expect(changeset.changes).toEqual([{ key: 'emails.0.funEmail', value: 'fun@email.com' }]); // expect(changeset.get('emails')).toEqual([ // { primary: 'bob@email.com', funEmail: 'fun@email.com' } // ]); // }); // it('can add new properties to new entries', () => { // const changeset = Changeset(initialData); // changeset.set('emails.1.funEmail', 'fun@email.com'); // changeset.set('emails.1.primary', 'primary@email.com'); // expect(changeset.get('emails.1.funEmail')).toEqual('fun@email.com'); // expect(changeset.get('emails.1.primary')).toEqual('primary@email.com'); // expect(changeset.get('emails')).toEqual([ // { primary: 'bob@email.com' }, // { primary: 'primary@email.com', funEmail: 'fun@email.com' } // ]); // expect(changeset.changes).toEqual([ // { key: 'emails.1.funEmail', value: 'fun@email.com' }, // { key: 'emails.1.primary', value: 'primary@email.com' } // ]); // }); // it('can add a new object all at once, and edit it', () => { // const changeset = Changeset(initialData); // changeset.set('emails.1', { // funEmail: 'fun@email.com', // primary: 'primary@email.com' // }); // expect(changeset.get('emails.1.funEmail')).toEqual('fun@email.com'); // expect(changeset.get('emails.1.primary')).toEqual('primary@email.com'); // expect(changeset.get('emails')).toEqual([ // { primary: 'bob@email.com' }, // { primary: 'primary@email.com', funEmail: 'fun@email.com' } // ]); // expect(changeset.changes).toEqual([ // { // key: 'emails.1', // value: { funEmail: 'fun@email.com', primary: 'primary@email.com' } // } // ]); // changeset.set('emails.1.primary', 'primary2@email.com'); // expect(changeset.get('emails.1.primary')).toEqual('primary2@email.com'); // expect(changeset.changes).toEqual([ // { // key: 'emails.1', // value: { // primary: 'primary2@email.com', // funEmail: 'fun@email.com' // } // } // ]); // expect(changeset.get('emails')).toEqual([ // { primary: 'bob@email.com' }, // { primary: 'primary2@email.com', funEmail: 'fun@email.com' } // ]); // }); // it('can edit a new object that was added after deleting an array entry', () => { // const changeset = Changeset({ // emails: [ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com' // }, // { // fun: 'fun1@email.com', // primary: 'primary1@email.com' // } // ] // }); // changeset.set('emails.1', null); // expect(changeset.get('emails.0.fun')).toEqual('fun0@email.com'); // expect(changeset.get('emails.0.primary')).toEqual('primary0@email.com'); // expect(changeset.get('emails')).toEqual([ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com' // }, // null // ]); // expect(changeset.changes).toEqual([ // { // key: 'emails.1', // value: null // } // ]); // changeset.set('emails.1', { // fun: 'brandNew@email.com', // primary: 'brandNewPrimary@email.com' // }); // expect(changeset.get('emails')).toEqual([ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com' // }, // { // fun: 'brandNew@email.com', // primary: 'brandNewPrimary@email.com' // } // ]); // expect(changeset.changes).toEqual([ // { // key: 'emails.1', // value: { // fun: 'brandNew@email.com', // primary: 'brandNewPrimary@email.com' // } // } // ]); // }); // it('can edit an object with a key of value after another array entry has been deleted', () => { // const changeset = Changeset({ // emails: [ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com', // value: 'the value' // }, // { // fun: 'fun1@email.com', // primary: 'primary1@email.com', // value: 'some value' // } // ] // }); // changeset.set('emails.1', null); // expect(changeset.get('emails')).toEqual([ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com', // value: 'the value' // }, // null // ]); // expect(changeset.changes).toEqual([ // { // key: 'emails.1', // value: null // } // ]); // expect(changeset.get('emails.0.fun')).toEqual('fun0@email.com'); // expect(changeset.get('emails.0.primary')).toEqual('primary0@email.com'); // // does not need to be unwrapped // expect(changeset.get('emails.0.value')).toEqual('the value'); // }); // }); // describe('arrays of objects within nested objects', () => { // describe('#set', () => { // let initialData: { contact: { emails: Record<string, string>[] } } = { // contact: { emails: [] } // }; // beforeEach(() => { // initialData = { contact: { emails: [{ primary: 'bob@email.com' }] } }; // }); // it('can modify properties on an entry', () => { // const changeset = Changeset(initialData); // changeset.set('contact.emails.0.primary', 'fun@email.com'); // expect(changeset.get('contact.emails.0.primary')).toEqual('fun@email.com'); // expect(changeset.get('contact.emails').unwrap()).toEqual([{ primary: 'fun@email.com' }]); // expect(changeset.changes).toEqual([ // { key: 'contact.emails.0.primary', value: 'fun@email.com' } // ]); // }); // it('can add properties to an entry', () => { // const changeset = Changeset(initialData); // changeset.set('contact.emails.0.funEmail', 'fun@email.com'); // expect(changeset.get('contact.emails.0.funEmail')).toEqual('fun@email.com'); // expect(changeset.changes).toEqual([ // { key: 'contact.emails.0.funEmail', value: 'fun@email.com' } // ]); // expect(changeset.get('contact.emails').unwrap()).toEqual([ // { primary: 'bob@email.com', funEmail: 'fun@email.com' } // ]); // }); // it('can add new properties to new entries', () => { // const changeset = Changeset(initialData); // changeset.set('contact.emails.1.funEmail', 'fun@email.com'); // changeset.set('contact.emails.1.primary', 'primary@email.com'); // expect(changeset.get('contact.emails.1.funEmail')).toEqual('fun@email.com'); // expect(changeset.get('contact.emails.1.primary')).toEqual('primary@email.com'); // expect(changeset.get('contact.emails').unwrap()).toEqual([ // { primary: 'bob@email.com' }, // { primary: 'primary@email.com', funEmail: 'fun@email.com' } // ]); // expect(changeset.changes).toEqual([ // { key: 'contact.emails.1.funEmail', value: 'fun@email.com' }, // { key: 'contact.emails.1.primary', value: 'primary@email.com' } // ]); // }); // it('can add a new object all at once, and edit it', () => { // const changeset = Changeset(initialData); // changeset.set('contact.emails.1', { // funEmail: 'fun@email.com', // primary: 'primary@email.com' // }); // expect(changeset.get('contact.emails.1.funEmail')).toEqual('fun@email.com'); // expect(changeset.get('contact.emails.1.primary')).toEqual('primary@email.com'); // expect(changeset.get('contact.emails').unwrap()).toEqual([ // { primary: 'bob@email.com' }, // { primary: 'primary@email.com', funEmail: 'fun@email.com' } // ]); // expect(changeset.changes).toEqual([ // { // key: 'contact.emails.1', // value: { funEmail: 'fun@email.com', primary: 'primary@email.com' } // } // ]); // changeset.set('contact.emails.1.primary', 'primary2@email.com'); // expect(changeset.get('contact.emails.1.primary')).toEqual('primary2@email.com'); // expect(changeset.changes).toEqual([ // { // key: 'contact.emails.1', // value: { // primary: 'primary2@email.com', // funEmail: 'fun@email.com' // } // } // ]); // expect(changeset.get('contact.emails').unwrap()).toEqual([ // { primary: 'bob@email.com' }, // { primary: 'primary2@email.com', funEmail: 'fun@email.com' } // ]); // }); // it('can edit a new object that was added after deleting an array entry', () => { // const changeset = Changeset({ // contacts: { // emails: [ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com' // }, // { // fun: 'fun1@email.com', // primary: 'primary1@email.com' // } // ] // } // }); // changeset.set('contacts.emails.1', null); // expect(changeset.get('contacts.emails').unwrap()).toEqual([ // { // fun: 'fun0@email.com', // primary: 'primary0@email.com' // }, // null // ]); // }); // }); // }); // it('#set works for nested when the root key is "value"', () => { // dummyModel.value = {}; // dummyModel.org = {}; // const dummyChangeset = Changeset(dummyModel); // dummyChangeset.set('value.short', 'foo'); // expect(dummyChangeset.get('value.short')).toBe('foo'); // expect(dummyModel.value).toEqual({}); // const changes = dummyChangeset.changes; // const expectedChanges = [{ key: 'value.short', value: 'foo' }]; // expect(changes).toEqual(expectedChanges); // expect(dummyChangeset.value).toEqual({ short: 'foo' }); // expect(dummyChangeset.org).toEqual({}); // dummyChangeset.execute(); // expect(dummyModel.value.short).toBe('foo'); // }); it('nested objects can be replaced with different ones without changing the nested return values', () => { dummyModel['org'] = { usa: { ny: 'ny' } }; const dummyChangeset = Changeset(dummyModel); dummyChangeset.set('org', { usa: { ca: 'ca' } }); expect(dummyChangeset.get('org')).toEqual({ usa: { ca: 'ca', ny: undefined } }); expect(dummyChangeset.get('org.usa')).toEqual({ ca: 'ca', ny: undefined }); expect(dummyChangeset.get('org.usa.ca')).toBe('ca'); expect(dummyChangeset.get('org.usa.ny')).toBeUndefined(); }); // it('nested objects can be replaced with different ones as classes', () => { // class Country { // details: object; // constructor(details: object) { // this.details = details; // } // } // dummyModel['org'] = new Country({ usa: { ny: 'ny' } }); // const dummyChangeset = Changeset(dummyModel)); // dummyChangeset.set('org', new Country({ usa: { ca: 'ca' } })); // expect(dummyChangeset.get('org')).toEqual(new Country({ usa: { ca: 'ca', ny: undefined } })); // expect(dummyChangeset.get('org.details')).toEqual({ usa: { ca: 'ca', ny: undefined } }); // expect(dummyChangeset.get('org.details.usa')).toEqual({ ca: 'ca', ny: undefined }); // expect(dummyChangeset.get('org.details.usa.ca')).toBe('ca'); // expect(dummyChangeset.get('org.details.usa.ny')).toBeUndefined(); // }); // it('#set doesnt lose sibling keys', () => { // dummyModel['org'] = { // usa: { // mn: 'mn', // ny: 'ny', // nz: 'nz' // }, // landArea: 100 // }; // const c: Record<string, any> = Changeset(dummyModel); // c.set('org.usa.ny', 'NY'); // expect(dummyModel.org.usa.ny).toBe('ny'); // expect(c.org.usa.ny).toBe('NY'); // expect(c.get('org.usa.ny')).toBe('NY'); // expect(c.get('org.usa.mn')).toBe('mn'); // expect(c.get('org.usa.nz')).toBe('nz'); // expect(c.get('org.landArea')).toBe(100); // // set again // c.set('org.usa.ny', 'nye'); // expect(dummyModel.org.usa.ny).toBe('ny'); // expect(c.org.usa.ny).toBe('nye'); // expect(c.get('org.usa.ny')).toBe('nye'); // expect(c.get('org.usa.mn')).toBe('mn'); // expect(c.get('org.usa.nz')).toBe('nz'); // expect(c.get('org.landArea')).toBe(100); // }); // it('#set adds a change if the key is an object', () => { // dummyModel['org'] = { // usa: { // mn: 'mn', // ny: 'ny', // nz: 'nz' // }, // landArea: 100 // }; // const c: any = Changeset(dummyModel); // c.set('org.usa.ny', 'NY'); // expect(dummyModel.org.usa.ny).toBe('ny'); // expect(c.org.usa.ny).toBe('NY'); // expect(c.get('org.usa.ny')).toBe('NY'); // expect(c.get('org.usa.mn')).toBe('mn'); // expect(c.get('org.usa.nz')).toBe('nz'); // expect(c.get('org.landArea')).toBe(100); // const expectedChanges = [{ key: 'org.usa.ny', value: 'NY' }]; // const changes = c.changes; // expect(changes).toEqual(expectedChanges); // }); // it('#set use native setters with nested doesnt work', () => { // dummyModel['org'] = { // usa: { // ny: 'ny' // } // }; // const c = Changeset(dummyModel); // set(c, 'org.usa.ny', 'foo'); // expect(dummyModel.org.usa.ny).toBe('foo'); // expect(c.get('org.usa.ny')).toBe('foo'); // const changes = c.changes; // expect(changes).toEqual([]); // }); // it('#set use native setters at single level', () => { // dummyModel.org = 'ny'; // const c = Changeset(dummyModel); // c.org = 'foo'; // expect(dummyModel.org).toBe('ny'); // expect(c.org).toBe('foo'); // const changes = c.changes; // expect(changes).toEqual([{ key: 'org', value: 'foo' }]); // }); // it('#set adds a change if value is an object', () => { // class Moment { // date: unknown; // constructor(date: Date) { // this.date = date; // } // } // const c = Changeset(dummyModel); // const d = new Date(); // const momentInstance = new Moment(d); // c.set('startDate', momentInstance); // const expectedChanges = [{ key: 'startDate', value: momentInstance }]; // const changes = c.changes; // expect(changes).toEqual(expectedChanges); // let newValue = c.get('startDate'); // expect(newValue.date).toEqual(momentInstance.date); // expect(newValue instanceof Moment).toBeTruthy(); // expect(newValue.date).toEqual(d); // newValue = c.startDate; // expect(newValue.date).toEqual(momentInstance.date); // expect(newValue instanceof Moment).toBeTruthy(); // expect(newValue.date).toEqual(d); // }); it('#set supports `undefined`', () => { const model = { name: 'foo' }; const dummyChangeset = Changeset(model); dummyChangeset.set('name', undefined); expect(dummyChangeset.name).toBeUndefined(); expect(dummyChangeset.changes).toEqual({ name: { current: undefined, original: 'foo' } }); }); it('#set does not add a change if new value equals old value', () => { const model = { name: 'foo' }; const dummyChangeset = Changeset(model); dummyChangeset.set('name', 'foo'); expect(dummyChangeset.changes).toEqual({}); }); // it('#set does not add a change if new value equals old value and `skipValidate` is true', () => { // const model = { name: 'foo' }; // const dummyChangeset = Changeset(model, null, null, { skipValidate: true }); // expect(dummyChangeset.isValid).toEqual(true); // dummyChangeset.set('name', 'foo'); // expect(dummyChangeset.changes).toEqual([]); // expect(dummyChangeset.isValid).toEqual(true); // }); // it('#set removes a change if set back to original value', () => { // const model = { name: 'foo' }; // const dummyChangeset = Changeset(model); // dummyChangeset.set('name', 'bar'); // expect(dummyChangeset.changes).toEqual([{ key: 'name', value: 'bar' }]); // dummyChangeset.set('name', 'foo'); // expect(dummyChangeset.changes).toEqual([]); // }); it('#set removes a change if set back to original value in nested context', () => { const model = { name: { email: 'foo' } }; const dummyChangeset = Changeset(model); dummyChangeset.safeGet = get; dummyChangeset.set('name.email', 'bar'); expect(dummyChangeset.changes).toEqual({ 'name.email': { current: 'bar', original: 'foo' } }); dummyChangeset.set('name.email', 'foo'); expect(dummyChangeset.changes).toEqual({}); }); // it('#set does add a change if invalid', () => { // const expectedErrors = [ // { key: 'name', validation: 'too short', value: 'a' }, // { key: 'password', validation: ['foo', 'bar'], value: false } // ]; // const dummyChangeset = Changeset(dummyModel)); // dummyChangeset.set('name', 'a'); // dummyChangeset.set('password', false); // const changes = dummyChangeset.changes; // const errors = dummyChangeset.errors; // const isValid = dummyChangeset.isValid; // const isInvalid = dummyChangeset.isInvalid; // const expectedChanges = [ // { key: 'name', value: 'a' }, // { key: 'password', value: false } // ]; // expect(changes).toEqual(expectedChanges); // expect(errors).toEqual(expectedErrors); // expect(isValid).toEqual(false); // expect(isInvalid).toBeTruthy(); // }); // it('#set adds errors if undefined value', () => { // const dummyChangeset = Changeset(dummyModel)); // let expectedResult = [{ key: 'name', validation: 'too short', value: undefined }]; // dummyChangeset.set('name', undefined); // expect(dummyChangeset.errors).toEqual(expectedResult); // expect(dummyChangeset.get('errors')).toEqual(expectedResult); // }); // it('#set if trigger null value', () => { // const dummyChangeset = Changeset(dummyModel)); // let expectedResult = [{ key: 'name', validation: 'too short', value: null }]; // dummyChangeset.set('name', null); // expect(dummyChangeset.errors).toEqual(expectedResult); // expect(dummyChangeset.get('errors')).toEqual(expectedResult); // }); // it('#set if trigger empty string value', () => { // const dummyChangeset = Changeset(dummyModel)); // let expectedResult = [{ key: 'name', validation: 'too short', value: '' }]; // dummyChangeset.set('name', ''); // expect(dummyChangeset.errors).toEqual(expectedResult); // expect(dummyChangeset.get('errors')).toEqual(expectedResult); // }); it('#set should remove nested changes when setting roots', () => { dummyModel['org'] = { usa: { ny: 'ny', ca: 'ca' } }; const c = Changeset(dummyModel); c.set('org.usa.ny', 'foo'); c.set('org.usa.ca', 'bar'); c.set('org', 'no travelling for you'); const actual = c.changes; const expectedResult = { org: { current: 'no travelling for you', original: { usa: { ca: 'ca', ny: 'ny' } } } }; expect(actual).toEqual(expectedResult); }); // it('#set should handle bulk replace', () => { // dummyModel['org'] = { // usa: { // ny: 'ny', // ca: 'ca' // } // }; // const c = Changeset(dummyModel); // c.set('org', { // isCompliant: true, // usa: { // ca: 'il', // ny: 'wi' // } // }); // let actual = c.changes; // let expectedResult = [ // { // key: 'org', // value: { // isCompliant: true, // usa: { // ca: 'il', // ny: 'wi' // } // } // } // ]; // expect(actual).toEqual(expectedResult); // c.set('org.isCompliant', false); // actual = c.changes; // expectedResult = [ // { // key: 'org', // value: { // isCompliant: false, // usa: { // ca: 'il', // ny: 'wi' // } // } // } // ]; // expect(actual).toEqual(expectedResult); // }); // it('#set works after save', () => { // delete dummyModel.save; // dummyModel['org'] = { // usa: { // mn: 'mn', // ny: 'ny' // } // }; // const c = Changeset(dummyModel); // c.set('org.usa.ny', 'NY'); // c.set('org.usa.mn', 'MN'); // expect(c.get('org.usa.ny')).toBe('NY'); // expect(c.get('org.usa.mn')).toBe('MN'); // expect(dummyModel.org.usa.ny).toBe('ny'); // expect(dummyModel.org.usa.mn).toBe('mn'); // c.save(); // expect(c.get('org.usa.ny')).toBe('NY'); // expect(c.get('org.usa.mn')).toBe('MN'); // expect(dummyModel.org.usa.ny).toBe('NY'); // expect(dummyModel.org.usa.mn).toBe('MN'); // c.set('org.usa.ny', 'nil'); // expect(c.get('org.usa.ny')).toBe('nil'); // expect(c.get('or