/*jslint indent: 2, maxlen: 120, nomen: true, vars: true */ /*global define, exports, require, module, complex_queries, window, test, ok, equal, deepEqual, sinon */ // define([module_name], [dependencies], module); (function (dependencies, module) { "use strict"; if (typeof define === 'function' && define.amd) { return define(dependencies, module); } if (typeof exports === 'object') { return module(require('complex_queries')); } module(complex_queries); }(['complex_queries', 'qunit'], function (complex_queries) { "use strict"; module('Custom Key Queries'); test('Simple Key with read_from', function () { /*jslint unparam: true*/ var doc_list, docList = function () { return [ {'identifier': 'a'}, {'identifier': 'A'}, {'identifier': 'b'} ]; }, keys = { title: { read_from: 'identifier' }, case_insensitive_identifier: { read_from: 'identifier', equal_match: function (object_value, value, wildcard_character) { return (object_value.toLowerCase() === value.toLowerCase()); } } }; /*jslint unparam: false*/ doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.title, value: 'a' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': 'a'} ], 'It should be possible to query with an alias key'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.case_insensitive_identifier, value: 'A' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': 'a'}, {'identifier': 'A'} ], 'It should be possible to query with a case-insensitive alias key'); }); var dateCast = function (obj) { if (Object.prototype.toString.call(obj) === '[object Date]') { // no need to clone return obj; } return new Date(obj); }; test('Simple Key with date casting', function () { var doc_list, docList = function () { return [ {'identifier': 'a', 'date': '2013-01-01'}, {'identifier': 'b', 'date': '2013-02-01'}, {'identifier': 'bb', 'date': '2013-02-02'}, {'identifier': 'bbb', 'date': '2013-02-03'}, {'identifier': 'c', 'date': '2013-03-03'}, {'identifier': 'd', 'date': '2013-04-04'} ]; }; var sameDay = function (a, b) { return ( (a.getFullYear() === b.getFullYear()) && (a.getMonth() === b.getMonth()) && (a.getDate() === b.getDate()) ); }; var sameMonth = function (a, b) { return ( (a.getFullYear() === b.getFullYear()) && (a.getMonth() === b.getMonth()) ); }; var sameYear = function (a, b) { return (a.getFullYear() === b.getFullYear()); }; var keys = { day: { read_from: 'date', cast_to: dateCast, equal_match: sameDay }, month: { read_from: 'date', cast_to: dateCast, equal_match: sameMonth }, year: { read_from: 'date', cast_to: dateCast, equal_match: sameYear } }; doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.day, value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': 'bb', 'date': '2013-02-02'} ], 'It should be possible to compare dates with sameDay'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.month, value: '2013-02-10' }).exec(doc_list); deepEqual(doc_list, [ { 'date': '2013-02-01', 'identifier': 'b' }, { 'date': '2013-02-02', 'identifier': 'bb' }, { 'date': '2013-02-03', 'identifier': 'bbb' } ], 'It should be possible to compare dates with sameMonth'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.year, value: '2013-02-10' }).exec(doc_list); deepEqual(doc_list.length, 6, 'It should be possible to compare dates with sameYear'); }); test('Simple Key with date casting and <=> operators', function () { var doc_list, docList = function () { return [ {'identifier': '1', 'date': '2013-01-01'}, {'identifier': '2', 'date': '2013-02-02'}, {'identifier': '3', 'date': '2013-03-03'} ]; }, keys = { mydate: { read_from: 'date', cast_to: dateCast } }; doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '=', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '2', 'date': '2013-02-02'} ], 'It should be possible to search for dates with operator ='); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '!=', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'date': '2013-01-01'}, {'identifier': '3', 'date': '2013-03-03'} ], 'It should be possible to search for dates with operator !='); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '<=', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'date': '2013-01-01'}, {'identifier': '2', 'date': '2013-02-02'} ], 'It should be possible to search for dates with operator <='); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '<', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'date': '2013-01-01'} ], 'It should be possible to search for dates with operator <'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '>', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '3', 'date': '2013-03-03'} ], 'It should be possible to search for dates with operator >'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '>=', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '2', 'date': '2013-02-02'}, {'identifier': '3', 'date': '2013-03-03'} ], 'It should be possible to search for dates with operator >='); }); test('Simple Key with both equal_match and operator attributes', function () { var doc_list, docList = function () { return [ {'identifier': '1', 'date': '2013-01-01'}, {'identifier': '2', 'date': '2013-02-02'}, {'identifier': '3', 'date': '2013-03-03'} ]; }, keys = { mydate: { read_from: 'date', cast_to: dateCast, equal_match: function alwaysTrue() { return true; } } }; doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'date': '2013-01-01'}, {'identifier': '2', 'date': '2013-02-02'}, {'identifier': '3', 'date': '2013-03-03'} ], 'It should be possible to use a catch-all filter'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '=', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'date': '2013-01-01'}, {'identifier': '2', 'date': '2013-02-02'}, {'identifier': '3', 'date': '2013-03-03'} ], "The catch-all filter overrides the default '=' operator"); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.mydate, operator: '>=', value: '2013-02-02' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '2', 'date': '2013-02-02'}, {'identifier': '3', 'date': '2013-03-03'} ], 'An explicit operator should override the catch-all filter'); }); var intType = function (value) { if (typeof value === 'string') { return parseInt(value, 10); } return value; }; test('Test overriding operators and compound query', function () { var doc_list, docList = function () { return [ {'identifier': '10', 'number': '10'}, {'identifier': '19', 'number': '19'}, {'identifier': '100', 'number': '100'} ]; }; doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: { read_from: 'number', cast_to: intType }, operator: '>', value: '19' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '100', 'number': '100'} ], 'Numbers are correctly compared (>) after casting'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: { read_from: 'number', cast_to: intType }, operator: '<', value: '19' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '10', 'number': '10'} ], 'Numbers are correctly compared (<) after casting'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'complex', operator: 'OR', query_list: [{ type: 'simple', key: { read_from: 'number', cast_to: intType }, operator: '<', value: '19' }, { type: 'simple', key: { read_from: 'number', cast_to: intType }, operator: '=', value: '19' }] }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '10', 'number': '10'}, {'identifier': '19', 'number': '19'} ], 'Custom keys should also work within compound queries'); }); var translationEqualityMatcher = function (data) { return function (object_value, value) { value = data[value]; return (object_value === value); }; }; test('Simple Key with translation lookup', function () { var doc_list, docList = function () { return [ {'identifier': '1', 'state': 'open'}, {'identifier': '2', 'state': 'closed'} ]; }, equalState = translationEqualityMatcher({'ouvert': 'open'}), keys = { translated_state: { read_from: 'state', equal_match: equalState } }; doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.translated_state, value: 'ouvert' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'state': 'open'} ], 'It should be possible to look for a translated string with a custom match function'); doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.translated_state, operator: '=', value: 'ouvert' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': '1', 'state': 'open'} ], 'It should be possible to look for a translated string with operator ='); // XXX not implemented yet // doc_list = docList(); // complex_queries.QueryFactory.create({ // type: 'simple', // key: keys.translated_state, // operator: '!=', // value: 'ouvert' // }).exec(doc_list); // deepEqual(doc_list, [ // {'identifier': '2', 'state': 'closed'} // ], 'It should be possible to look for a translated string with operator !='); }); // This method is provided as an example. // A more robust solution to manage diacritics is recommended for production // environments, with unicode normalization, like (untested): // https://github.com/walling/unorm/ var accentFold = function (s) { var map = [ [new RegExp('[àáâãäå]', 'gi'), 'a'], [new RegExp('æ', 'gi'), 'ae'], [new RegExp('ç', 'gi'), 'c'], [new RegExp('[èéêë]', 'gi'), 'e'], [new RegExp('[ìíîï]', 'gi'), 'i'], [new RegExp('ñ', 'gi'), 'n'], [new RegExp('[òóôõö]', 'gi'), 'o'], [new RegExp('œ', 'gi'), 'oe'], [new RegExp('[ùúûü]', 'gi'), 'u'], [new RegExp('[ýÿ]', 'gi'), 'y'] ]; map.forEach(function (o) { var rep = function (match) { if (match.toUpperCase() === match) { return o[1].toUpperCase(); } return o[1]; }; s = s.replace(o[0], rep); }); return s; }; test('Accent folding', function () { equal(accentFold('àéîöùç'), 'aeiouc'); equal(accentFold('ÀÉÎÖÙÇ'), 'AEIOUC'); equal(accentFold('àéî öùç'), 'aei ouc'); }); test('Query with accent folding and wildcard', function () { /*jslint unparam: true*/ var doc_list, docList = function () { return [ {'identifier': 'àéîöùç'}, {'identifier': 'âèî ôùc'}, {'identifier': 'ÀÉÎÖÙÇ'}, {'identifier': 'b'} ]; }, keys = { identifier: { read_from: 'identifier', cast_to: accentFold } }; /*jslint unparam: false*/ doc_list = docList(); complex_queries.QueryFactory.create({ type: 'simple', key: keys.identifier, value: 'aei%' }).exec(doc_list); deepEqual(doc_list, [ {'identifier': 'àéîöùç'}, {'identifier': 'âèî ôùc'} ], 'It should be possible to query regardless of accents'); }); }));