validated-changeset
Version:
Changesets for your local state
1,547 lines (1,246 loc) • 87.3 kB
text/typescript
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