Mailspring/app/spec/models/model-spec.es6
2017-09-26 11:33:08 -07:00

266 lines
8.6 KiB
JavaScript

/* eslint quote-props: 0 */
import Model from '../../src/flux/models/model';
import Attributes from '../../src/flux/attributes';
describe('Model', function modelSpecs() {
describe('constructor', () => {
it('should accept a hash of attributes and assign them to the new Model', () => {
const attrs = {
id: 'A',
accountId: 'B',
};
const m = new Model(attrs);
expect(m.id).toBe(attrs.id);
expect(m.accountId).toBe(attrs.accountId);
});
it('assigns id', () => {
const attrs = {
id: 'A',
};
const m = new Model(attrs);
expect(m.id).toBe(attrs.id);
});
});
describe('clone', () =>
it('should return a deep copy of the object (not reusing array members, etc.)', () => {
class SubSubmodel extends Model {
static attributes = Object.assign({}, Model.attributes, {
value: Attributes.Number({
modelKey: 'value',
jsonKey: 'value',
}),
});
}
class Submodel extends Model {
static attributes = Object.assign({}, Model.attributes, {
testNumber: Attributes.Number({
modelKey: 'testNumber',
jsonKey: 'test_number',
}),
testArray: Attributes.Collection({
itemClass: SubSubmodel,
modelKey: 'testArray',
jsonKey: 'test_array',
}),
});
}
const old = new Submodel({
testNumber: 4,
testArray: [new SubSubmodel({ value: 2 }), new SubSubmodel({ value: 6 })],
});
const clone = old.clone();
// Check entire trees are equivalent
expect(old.toJSON()).toEqual(clone.toJSON());
// Check object identity has changed
expect(old.constructor.name).toEqual(clone.constructor.name);
expect(old.testArray).not.toBe(clone.testArray);
// Check classes
expect(old.testArray[0]).not.toBe(clone.testArray[0]);
expect(old.testArray[0].constructor.name).toEqual(clone.testArray[0].constructor.name);
}));
describe('fromJSON', () => {
beforeEach(() => {
class SubmodelItem extends Model {}
class Submodel extends Model {
static attributes = Object.assign({}, Model.attributes, {
testNumber: Attributes.Number({
modelKey: 'testNumber',
jsonKey: 'test_number',
}),
testBoolean: Attributes.Boolean({
modelKey: 'testBoolean',
jsonKey: 'test_boolean',
}),
testCollection: Attributes.Collection({
modelKey: 'testCollection',
jsonKey: 'test_collection',
itemClass: SubmodelItem,
}),
testJoinedData: Attributes.JoinedData({
modelKey: 'testJoinedData',
jsonKey: 'test_joined_data',
}),
});
}
this.json = {
id: '1234',
aid: 'bla',
test_number: 4,
test_boolean: true,
daysOld: 4,
};
this.m = new Submodel();
});
it('should assign attribute values by calling through to attribute fromJSON functions', () => {
spyOn(Model.attributes.accountId, 'fromJSON').andCallFake(() => 'inflated value!');
this.m.fromJSON(this.json);
expect(Model.attributes.accountId.fromJSON.callCount).toBe(1);
expect(this.m.accountId).toBe('inflated value!');
});
it('should not touch attributes that are missing in the json', () => {
this.m.fromJSON(this.json);
expect(this.m.object).toBe(undefined);
this.m.object = 'abc';
this.m.fromJSON(this.json);
expect(this.m.object).toBe('abc');
});
it('should not do anything with extra JSON keys', () => {
this.m.fromJSON(this.json);
expect(this.m.daysOld).toBe(undefined);
});
it('should maintain empty string as empty strings', () => {
expect(this.m.accountId).toBe(undefined);
this.m.fromJSON({ aid: '' });
expect(this.m.accountId).toBe('');
});
describe('Attributes.Number', () =>
it('should read number attributes and coerce them to numeric values', () => {
this.m.fromJSON({ test_number: 4 });
expect(this.m.testNumber).toBe(4);
this.m.fromJSON({ test_number: '4' });
expect(this.m.testNumber).toBe(4);
this.m.fromJSON({ test_number: 'lolz' });
expect(this.m.testNumber).toBe(null);
this.m.fromJSON({ test_number: 0 });
expect(this.m.testNumber).toBe(0);
}));
describe('Attributes.JoinedData', () =>
it('should read joined data attributes and coerce them to string values', () => {
this.m.fromJSON({ test_joined_data: null });
expect(this.m.testJoinedData).toBe(null);
this.m.fromJSON({ test_joined_data: '' });
expect(this.m.testJoinedData).toBe('');
this.m.fromJSON({ test_joined_data: 'lolz' });
expect(this.m.testJoinedData).toBe('lolz');
}));
describe('Attributes.Collection', () => {
it('should parse and inflate items', () => {
this.m.fromJSON({ test_collection: [{ id: '123' }] });
expect(this.m.testCollection.length).toBe(1);
expect(this.m.testCollection[0].id).toBe('123');
expect(this.m.testCollection[0].constructor.name).toBe('SubmodelItem');
});
it('should be fine with malformed arrays', () => {
this.m.testCollection = null;
this.m.fromJSON({ test_collection: [] });
expect(this.m.testCollection.length).toBe(0);
this.m.testCollection = null;
this.m.fromJSON({ test_collection: null });
expect(this.m.testCollection.length).toBe(0);
});
});
describe('Attributes.Boolean', () =>
it('should read `true` or true or nonzero and coerce everything else to false', () => {
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: true });
expect(this.m.testBoolean).toBe(true);
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: 'true' });
expect(this.m.testBoolean).toBe(true);
// this case is important - there are several columns that Mailspring treats
// as booleans that are actually integers on the mailsync side for book keeping,.
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: 4 });
expect(this.m.testBoolean).toBe(true);
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: '4' });
expect(this.m.testBoolean).toBe(true);
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: false });
expect(this.m.testBoolean).toBe(false);
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: 0 });
expect(this.m.testBoolean).toBe(false);
this.m.testBoolean = undefined;
this.m.fromJSON({ test_boolean: null });
expect(this.m.testBoolean).toBe(false);
}));
});
describe('toJSON', () => {
beforeEach(() => {
this.model = new Model({
id: '1234',
accountId: 'ACD',
});
});
it('should return a JSON object and call attribute toJSON functions to map values', () => {
spyOn(Model.attributes.accountId, 'toJSON').andCallFake(() => 'inflated value!');
const json = this.model.toJSON();
expect(json instanceof Object).toBe(true);
expect(json.id).toBe('1234');
expect(json.aid).toBe('inflated value!');
});
it('should surface any exception one of the attribute toJSON functions raises', () => {
spyOn(Model.attributes.accountId, 'toJSON').andCallFake(() => {
throw new Error("Can't convert value into JSON format");
});
expect(() => {
this.model.toJSON();
}).toThrow();
});
});
describe('matches', () => {
beforeEach(() => {
this.model = new Model({
id: '1234',
accountId: 'ACD',
});
this.truthyMatcher = {
evaluate() {
return true;
},
};
this.falsyMatcher = {
evaluate() {
return false;
},
};
});
it('should run the matchers and return true iff all matchers pass', () => {
expect(this.model.matches([this.truthyMatcher, this.truthyMatcher])).toBe(true);
expect(this.model.matches([this.truthyMatcher, this.falsyMatcher])).toBe(false);
expect(this.model.matches([this.falsyMatcher, this.truthyMatcher])).toBe(false);
});
it('should pass itself as an argument to the matchers', () => {
spyOn(this.truthyMatcher, 'evaluate').andCallFake(param => {
expect(param).toBe(this.model);
});
this.model.matches([this.truthyMatcher]);
});
});
});