mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-29 03:43:16 +08:00
257 lines
9 KiB
JavaScript
257 lines
9 KiB
JavaScript
/* eslint quote-props: 0 */
|
|
import ModelQuery from '../../src/flux/models/query';
|
|
import Attributes from '../../src/flux/attributes';
|
|
import Message from '../../src/flux/models/message';
|
|
import Thread from '../../src/flux/models/thread';
|
|
import Account from '../../src/flux/models/account';
|
|
|
|
describe('ModelQuery', function ModelQuerySpecs() {
|
|
beforeEach(() => {
|
|
this.db = {};
|
|
});
|
|
|
|
describe('where', () => {
|
|
beforeEach(() => {
|
|
this.q = new ModelQuery(Thread, this.db);
|
|
this.m1 = Thread.attributes.id.equal(4);
|
|
this.m2 = Thread.attributes.categories.contains('category-id');
|
|
});
|
|
|
|
it('should accept an array of Matcher objects', () => {
|
|
this.q.where([this.m1, this.m2]);
|
|
expect(this.q._matchers.length).toBe(2);
|
|
expect(this.q._matchers[0]).toBe(this.m1);
|
|
expect(this.q._matchers[1]).toBe(this.m2);
|
|
});
|
|
|
|
it('should accept a single Matcher object', () => {
|
|
this.q.where(this.m1);
|
|
expect(this.q._matchers.length).toBe(1);
|
|
expect(this.q._matchers[0]).toBe(this.m1);
|
|
});
|
|
|
|
it('should append to any existing where clauses', () => {
|
|
this.q.where(this.m1);
|
|
this.q.where(this.m2);
|
|
expect(this.q._matchers.length).toBe(2);
|
|
expect(this.q._matchers[0]).toBe(this.m1);
|
|
expect(this.q._matchers[1]).toBe(this.m2);
|
|
});
|
|
|
|
it('should accept a shorthand format', () => {
|
|
this.q.where({ id: 4, lastMessageReceivedTimestamp: 1234 });
|
|
expect(this.q._matchers.length).toBe(2);
|
|
expect(this.q._matchers[0].attr.modelKey).toBe('id');
|
|
expect(this.q._matchers[0].comparator).toBe('=');
|
|
expect(this.q._matchers[0].val).toBe(4);
|
|
});
|
|
|
|
it('should return the query so it can be chained', () => {
|
|
expect(this.q.where({ id: 4 })).toBe(this.q);
|
|
});
|
|
|
|
it('should immediately raise an exception if an un-queryable attribute is specified', () =>
|
|
expect(() => {
|
|
this.q.where({ snippet: 'My Snippet' });
|
|
}).toThrow());
|
|
|
|
it('should immediately raise an exception if a non-existent attribute is specified', () =>
|
|
expect(() => {
|
|
this.q.where({ looksLikeADuck: 'of course' });
|
|
}).toThrow());
|
|
});
|
|
|
|
describe('order', () => {
|
|
beforeEach(() => {
|
|
this.q = new ModelQuery(Thread, this.db);
|
|
this.o1 = Thread.attributes.lastMessageReceivedTimestamp.descending();
|
|
this.o2 = Thread.attributes.subject.descending();
|
|
});
|
|
|
|
it('should accept an array of SortOrders', () => {
|
|
this.q.order([this.o1, this.o2]);
|
|
expect(this.q._orders.length).toBe(2);
|
|
});
|
|
|
|
it('should accept a single SortOrder object', () => {
|
|
this.q.order(this.o2);
|
|
expect(this.q._orders.length).toBe(1);
|
|
});
|
|
|
|
it('should extend any existing ordering', () => {
|
|
this.q.order(this.o1);
|
|
this.q.order(this.o2);
|
|
expect(this.q._orders.length).toBe(2);
|
|
expect(this.q._orders[0]).toBe(this.o1);
|
|
expect(this.q._orders[1]).toBe(this.o2);
|
|
});
|
|
|
|
it('should return the query so it can be chained', () => {
|
|
expect(this.q.order(this.o2)).toBe(this.q);
|
|
});
|
|
});
|
|
|
|
describe('include', () => {
|
|
beforeEach(() => {
|
|
this.q = new ModelQuery(Message, this.db);
|
|
});
|
|
|
|
it('should throw an exception if the attribute is not a joined data attribute', () =>
|
|
expect(() => {
|
|
this.q.include(Message.attributes.unread);
|
|
}).toThrow());
|
|
|
|
it('should add the provided property to the list of joined properties', () => {
|
|
expect(this.q._includeJoinedData).toEqual([]);
|
|
this.q.include(Message.attributes.body);
|
|
expect(this.q._includeJoinedData).toEqual([Message.attributes.body]);
|
|
});
|
|
});
|
|
|
|
describe('includeAll', () => {
|
|
beforeEach(() => {
|
|
this.q = new ModelQuery(Message, this.db);
|
|
});
|
|
|
|
it('should add all the JoinedData attributes of the class', () => {
|
|
expect(this.q._includeJoinedData).toEqual([]);
|
|
this.q.includeAll();
|
|
expect(this.q._includeJoinedData).toEqual([Message.attributes.body]);
|
|
});
|
|
});
|
|
|
|
describe('response formatting', () =>
|
|
it('should always return a Number for counts', () => {
|
|
const q = new ModelQuery(Message, this.db);
|
|
q.where({ accountId: 'abcd' }).count();
|
|
|
|
const raw = [{ count: '12' }];
|
|
expect(q.formatResult(q.inflateResult(raw))).toBe(12);
|
|
}));
|
|
|
|
describe('sql', () => {
|
|
beforeEach(() => {
|
|
this.runScenario = (klass, scenario) => {
|
|
const q = new ModelQuery(klass, this.db);
|
|
Attributes.Matcher.muid = 1;
|
|
scenario.builder(q);
|
|
expect(
|
|
q
|
|
.sql()
|
|
.replace(/ /g, '')
|
|
.trim()
|
|
).toBe(scenario.sql.replace(/ /g, '').trim());
|
|
};
|
|
});
|
|
|
|
it('should finalize the query so no further changes can be made', () => {
|
|
const q = new ModelQuery(Account, this.db);
|
|
spyOn(q, 'finalize');
|
|
q.sql();
|
|
expect(q.finalize).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should correctly generate queries with multiple where clauses', () => {
|
|
this.runScenario(Account, {
|
|
builder: q => q.where({ emailAddress: 'ben@nylas.com' }).where({ id: 2 }),
|
|
sql:
|
|
'SELECT `Account`.`data` FROM `Account` ' +
|
|
"WHERE `Account`.`emailAddress` = 'ben@nylas.com' AND `Account`.`id` = 2",
|
|
});
|
|
});
|
|
|
|
it('should correctly escape single quotes with more double single quotes (LIKE)', () => {
|
|
this.runScenario(Account, {
|
|
builder: q => q.where(Account.attributes.emailAddress.like("you're")),
|
|
sql:
|
|
"SELECT `Account`.`data` FROM `Account` WHERE `Account`.`emailAddress` like '%you''re%'",
|
|
});
|
|
});
|
|
|
|
it('should correctly escape single quotes with more double single quotes (equal)', () => {
|
|
this.runScenario(Account, {
|
|
builder: q => q.where(Account.attributes.emailAddress.equal("you're")),
|
|
sql: "SELECT `Account`.`data` FROM `Account` WHERE `Account`.`emailAddress` = 'you''re'",
|
|
});
|
|
});
|
|
|
|
it('should correctly generate COUNT queries', () => {
|
|
this.runScenario(Thread, {
|
|
builder: q => q.where({ accountId: 'abcd' }).count(),
|
|
sql: 'SELECT COUNT(*) as count FROM `Thread` ' + "WHERE `Thread`.`accountId` = 'abcd' ",
|
|
});
|
|
});
|
|
|
|
it('should correctly generate LIMIT 1 queries for single items', () => {
|
|
this.runScenario(Thread, {
|
|
builder: q => q.where({ accountId: 'abcd' }).one(),
|
|
sql:
|
|
'SELECT `Thread`.`data` FROM `Thread` ' +
|
|
"WHERE `Thread`.`accountId` = 'abcd' " +
|
|
'ORDER BY `Thread`.`lastMessageReceivedTimestamp` DESC LIMIT 1',
|
|
});
|
|
});
|
|
|
|
it('should correctly generate `contains` queries using JOINS', () => {
|
|
this.runScenario(Thread, {
|
|
builder: q =>
|
|
q.where(Thread.attributes.categories.contains('category-id')).where({ id: '1234' }),
|
|
sql:
|
|
'SELECT `Thread`.`data` FROM `Thread` ' +
|
|
'INNER JOIN `ThreadCategory` AS `M1` ON `M1`.`id` = `Thread`.`id` ' +
|
|
"WHERE `M1`.`value` = 'category-id' AND `Thread`.`id` = '1234' " +
|
|
'ORDER BY `Thread`.`lastMessageReceivedTimestamp` DESC',
|
|
});
|
|
|
|
this.runScenario(Thread, {
|
|
builder: q =>
|
|
q.where([
|
|
Thread.attributes.categories.contains('l-1'),
|
|
Thread.attributes.categories.contains('l-2'),
|
|
]),
|
|
sql:
|
|
'SELECT `Thread`.`data` FROM `Thread` ' +
|
|
'INNER JOIN `ThreadCategory` AS `M1` ON `M1`.`id` = `Thread`.`id` ' +
|
|
'INNER JOIN `ThreadCategory` AS `M2` ON `M2`.`id` = `Thread`.`id` ' +
|
|
"WHERE `M1`.`value` = 'l-1' AND `M2`.`value` = 'l-2' " +
|
|
'ORDER BY `Thread`.`lastMessageReceivedTimestamp` DESC',
|
|
});
|
|
});
|
|
|
|
it("should correctly generate queries with the class's naturalSortOrder when one is available and no other orders are provided", () => {
|
|
this.runScenario(Thread, {
|
|
builder: q => q.where({ accountId: 'abcd' }),
|
|
sql:
|
|
'SELECT `Thread`.`data` FROM `Thread` ' +
|
|
"WHERE `Thread`.`accountId` = 'abcd' " +
|
|
'ORDER BY `Thread`.`lastMessageReceivedTimestamp` DESC',
|
|
});
|
|
|
|
this.runScenario(Thread, {
|
|
builder: q =>
|
|
q
|
|
.where({ accountId: 'abcd' })
|
|
.order(Thread.attributes.lastMessageReceivedTimestamp.ascending()),
|
|
sql:
|
|
'SELECT `Thread`.`data` FROM `Thread` ' +
|
|
"WHERE `Thread`.`accountId` = 'abcd' " +
|
|
'ORDER BY `Thread`.`lastMessageReceivedTimestamp` ASC',
|
|
});
|
|
|
|
this.runScenario(Account, {
|
|
builder: q => q.where({ id: 'abcd' }),
|
|
sql: 'SELECT `Account`.`data` FROM `Account` ' + "WHERE `Account`.`id` = 'abcd' ",
|
|
});
|
|
});
|
|
|
|
it('should correctly generate queries requesting joined data attributes', () => {
|
|
this.runScenario(Message, {
|
|
builder: q => q.where({ id: '1234' }).include(Message.attributes.body),
|
|
sql:
|
|
"SELECT `Message`.`data`, IFNULL(`MessageBody`.`value`, '!NULLVALUE!') AS `body` " +
|
|
'FROM `Message` LEFT OUTER JOIN `MessageBody` ON `MessageBody`.`id` = `Message`.`id` ' +
|
|
"WHERE `Message`.`id` = '1234' ORDER BY `Message`.`date` ASC",
|
|
});
|
|
});
|
|
});
|
|
});
|