UNPKG

mongo-parse

Version:
1,033 lines (864 loc) 37.8 kB
"use strict"; var Unit = require('deadunit') var parser = require("../mongoParse") var parse = parser.parse var DotNotationPointers = parser.DotNotationPointers Unit.test("mongo-parse", function(t) { //* this.test('query parts', function(t) { this.test('simple equality', function() { var parsedQuery = parse({a: 'av'}) this.eq(parsedQuery.parts.length, 1) var part0 = parsedQuery.parts[0] this.eq(part0.field , 'a') this.eq(part0.operator , '$eq') this.eq(part0.operand , 'av') this.eq(part0.implicitField, undefined) this.eq(part0.parts.length, 0) }) this.test('simple operator query', function() { var parsedQuery = parse({b: {$gt: 3}}) this.eq(parsedQuery.parts.length, 1) var part0 = parsedQuery.parts[0] this.eq(part0.field , 'b') this.eq(part0.operator , '$gt') this.eq(part0.operand , 3) this.eq(part0.parts.length, 0) }) this.test("basic and", function() { var parsedQuery = parse({c: 'cv', d: 'dv'}) this.eq(parsedQuery.parts.length, 2) var part = parsedQuery.parts[0] this.eq(part.field, 'c') this.eq(part.operator, '$eq') this.eq(part.operand, 'cv') this.eq(part.parts.length, 0) part = parsedQuery.parts[1] this.eq(part.field, 'd') this.eq(part.operator, '$eq') this.eq(part.operand, 'dv') this.eq(part.parts.length, 0) }) this.test("combination of operators", function() { var parsedQuery = parse({e: {$gt: 3, $lt: 4}}) this.eq(parsedQuery.parts.length, 2) var part = parsedQuery.parts[0] this.eq(part.field, 'e') this.eq(part.operator, '$gt') this.eq(part.operand, 3) this.eq(part.parts.length, 0) part = parsedQuery.parts[1] this.eq(part.field, 'e') this.eq(part.operator, '$lt') this.eq(part.operand, 4) this.eq(part.parts.length, 0) }) this.test("$not", function() { var parsedQuery = parse({x: {$not:{$gt: 3}}, y: {$not: 7}}) this.eq(parsedQuery.parts.length, 2) var part = parsedQuery.parts[0] this.eq(part.field, 'x') this.eq(part.operator, '$not') this.ok(deepEqual(part.operand, {$gt: 3}), part.operand) this.eq(part.parts.length, 1) var subPart = part.parts[0] this.eq(subPart.field, 'x') this.eq(subPart.operator, '$gt') this.eq(subPart.operand, 3) this.eq(subPart.parts.length, 0) part = parsedQuery.parts[1] this.eq(part.field, 'y') this.eq(part.operator, '$not') this.ok(part.operand, 7) this.eq(part.parts.length, 0) }) this.test("$text", function() { var parsedQuery = parse({$text: {$search: "whatever"}}) this.eq(parsedQuery.parts.length, 1) var part = parsedQuery.parts[0] this.eq(part.field, undefined) this.eq(part.operator, '$text') this.ok(deepEqual(part.operand, {$search: "whatever"}), part.operand) this.eq(part.parts.length, 0) }) this.test("bare function and string - equivalent of the $where operator", function(t) { var where1 = function() {return this.whatever === 'whatever'} var where1String = "this.whatever === 'whatever'" var where2 = function() {return obj.whatever === 'whatever'} var where2String = "obj.whatever === 'whatever'" ;[where1, where1String, where2, where2String].forEach(function(whereValue) { var parsedQuery = parse(whereValue) t.eq(parsedQuery.parts.length, 1) var part = parsedQuery.parts[0] t.eq(part.field, undefined) t.eq(part.operator, '$where') t.ok(part.operand instanceof Function, part.operand) t.ok(part.operand.call({whatever:"whatever"})) t.eq(part.parts.length, 0) }) }) this.test("complex field-indepedant operators", function() { var parsedQuery = parse({$or: [{a: 3}, {b:'four', c:5}]}) this.eq(parsedQuery.parts.length, 1) var part = parsedQuery.parts[0] this.eq(part.field, undefined) this.eq(part.operator, '$or') this.ok(deepEqual(part.operand, [{a: 3}, {b:'four', c:5}]), part.operand) this.eq(part.parts.length, 2) var subPart = part.parts[0] this.eq(subPart.field, undefined) this.eq(subPart.operator, '$and') this.ok(deepEqual(subPart.operand,[{a: 3}]), subPart.operand) this.eq(subPart.parts.length, 1) var subSubPart = subPart.parts[0] this.eq(subSubPart.field, 'a') this.eq(subSubPart.operator, '$eq') this.eq(subSubPart.operand, 3) this.eq(subSubPart.parts.length, 0) subPart = part.parts[1] this.eq(subPart.field, undefined) this.eq(subPart.operator, '$and') this.ok(deepEqual(subPart.operand,[{b:'four', c:5}]), subPart.operand) this.eq(subPart.parts.length, 2) subSubPart = subPart.parts[0] this.eq(subSubPart.field, 'b') this.eq(subSubPart.operator, '$eq') this.eq(subSubPart.operand, 'four') this.eq(subSubPart.parts.length, 0) subSubPart = subPart.parts[1] this.eq(subSubPart.field, 'c') this.eq(subSubPart.operator, '$eq') this.eq(subSubPart.operand, 5) this.eq(subSubPart.parts.length, 0) }) this.test('$elemMatch', function() { var parsedQuery = parse({a: {$elemMatch:{$lt:5, $gt:9}}, b:{$elemMatch:{x:1, y:2}}}) this.eq(parsedQuery.parts.length, 2) var part = parsedQuery.parts[0] this.eq(part.field, 'a') this.eq(part.operator, '$elemMatch') this.ok(deepEqual(part.operand, {$lt:5, $gt:9}), part.operand) this.eq(part.implicitField, true) this.eq(part.parts.length, 2) var subPart = part.parts[0] this.eq(subPart.field, undefined) this.eq(subPart.operator, '$lt') this.eq(subPart.operand, 5) this.eq(subPart.parts.length, 0) subPart = part.parts[1] this.eq(subPart.field, undefined) this.eq(subPart.operator, '$gt') this.eq(subPart.operand, 9) this.eq(subPart.parts.length, 0) part = parsedQuery.parts[1] this.eq(part.field, 'b') this.eq(part.operator, '$elemMatch') this.ok(deepEqual(part.operand, {x:1, y:2}), part.operand) this.eq(part.implicitField, false) this.eq(part.parts.length, 2) subPart = part.parts[0] this.eq(subPart.field, 'x') this.eq(subPart.operator, '$eq') this.eq(subPart.operand, 1) this.eq(subPart.parts.length, 0) subPart = part.parts[1] this.eq(subPart.field, 'y') this.eq(subPart.operator, '$eq') this.eq(subPart.operand, 2) this.eq(subPart.parts.length, 0) }) this.test("basic and + multiple operators", function() { var parsedQuery = parse({a:'moose', d: {$lt:3, $gt:4}}) this.eq(parsedQuery.parts.length, 3) var part = parsedQuery.parts[0] this.eq(part.field, 'a') this.eq(part.operator, '$eq') this.eq(part.operand, 'moose') this.eq(part.parts.length, 0) part = parsedQuery.parts[1] this.eq(part.field, 'd') this.eq(part.operator, '$lt') this.eq(part.operand, 3) this.eq(part.parts.length, 0) part = parsedQuery.parts[2] this.eq(part.field, 'd') this.eq(part.operator, '$gt') this.eq(part.operand, 4) this.eq(part.parts.length, 0) }) this.test('$comment', function() { var parsedQuery = parse({$comment: 'whatever'}) this.eq(parsedQuery.parts.length, 1) var part0 = parsedQuery.parts[0] this.eq(part0.field , undefined) this.eq(part0.operator , '$comment') this.eq(part0.operand , 'whatever') this.eq(part0.parts.length, 0) }) }) this.test("DotNotationPointers", function() { var object = {a:1, b:{c:2, d:{e:3}}} this.test("root property", function() { var pointerz = DotNotationPointers(object) this.eq(pointerz.length, 1) var one = pointerz[0] this.eq(one.val.a, 1) this.eq(one.propertyInfo.obj, object) this.eq(one.propertyInfo.last, undefined) }) this.test("shallow property", function() { var pointerz = DotNotationPointers(object, 'a') this.eq(pointerz.length, 1) var one = pointerz[0] this.eq(one.val, 1) this.eq(one.propertyInfo.obj, object) this.eq(one.propertyInfo.last, 'a') var pointerz2 = DotNotationPointers(object, 'b') this.eq(pointerz2.length, 1) var two = pointerz2[0] this.eq(two.propertyInfo.obj, object) this.eq(two.propertyInfo.last, 'b') this.eq(two.val.c, 2) }) this.test("deep property", function() { var pointerz = DotNotationPointers(object, 'b.c') this.eq(pointerz.length, 1) var one = pointerz[0] this.eq(one.val, 2) this.eq(one.propertyInfo.obj, object.b) this.eq(one.propertyInfo.last, 'c') }) this.test('simple array index access', function() { var theObject = {a:[{b:1}, {b:2}, {b:3}]} var pointerz = DotNotationPointers(theObject, 'a.1.b') this.eq(pointerz.length, 1) this.eq(pointerz[0].val, 2) }) this.test('simple property tree', function() { var theObject = {a:[{b:1}, {b:2}, {b:3}]} var pointerz = DotNotationPointers(theObject, 'a.b') this.eq(pointerz.length, 3) this.eq(pointerz[0].val, 1) this.eq(pointerz[1].val, 2) this.eq(pointerz[2].val, 3) }) this.test('deeper property tree', function() { var theObject = {a:[{b:[{c:1}, {c:2}]}, {b:[{c:3}]}, {b:{c:4}}]} var pointerz = DotNotationPointers(theObject, 'a.b.c') this.eq(pointerz.length, 4) this.eq(pointerz[0].val, 1) this.eq(pointerz[1].val, 2) this.eq(pointerz[2].val, 3) this.eq(pointerz[3].val, 4) }) this.test('creating properties', function() { var theObject = {}; var pointerz = DotNotationPointers(theObject, "a.b"); this.eq(pointerz.length, 1); pointerz[0].val = 1; this.eq(theObject.a.b, 1); }); this.test('removing an existing property', function() { var theObject = {}; var pointerz = DotNotationPointers(theObject, "a.b"); this.eq(pointerz.length, 1); pointerz[0].val = 1; this.eq(theObject.a.b, 1); pointerz[0].val = undefined; this.eq(theObject.a.hasOwnProperty("b"), false); }); this.test('removing a property that does not exist', function() { var theObject = {}; var pointerz = DotNotationPointers(theObject, "a.b"); this.eq(pointerz.length, 1); pointerz[0].val = 1; this.eq(theObject.a.b, 1); pointerz = DotNotationPointers(theObject, "a.c"); pointerz[0].val = undefined; this.eq(theObject.a.hasOwnProperty("c"), false); }); }) this.test("compressQuery", function(t) { var test = function(a,b) { var result = parser.compressQuery(a) t.log(JSON.stringify(a)) t.ok(deepEqual(result, b), result) } test({a:1}, {a:1}) test({a:{$eq:1}}, {a:1}) test({a:{$gt:3}, $and:[{a:{$lt:9}}]}, {a:{$gt:3,$lt:9}}) test({a:{$gt:3}, $or:[{a:{$lt:1}}, {a:{$gt:9}}]}, {a:{$gt:3}, $or:[{a:{$lt:1}}, {a:{$gt:9}}]}) test({$or:[{},{a:1}]}, {a:1}) //test({a:null}, {a:null}); test({a:{$eq:null}}, {a:null}); var x = {"$and":[{"$or":[{"parent":"58a017558db10ff76667bba7"},{"ancestry":"58a017558db10ff76667bba7"}],"done":true,"complexity":{"$gt":0},"archived":{"$ne":true}},{"type":{"$ne":"Bug"}}]} var y = {"$or":[{"parent":"58a017558db10ff76667bba7"},{"ancestry":"58a017558db10ff76667bba7"}],"done":true,"complexity":{"$gt":0},"archived":{"$ne":true}, "type":{"$ne":"Bug"}} test(x, y) }) this.test('mapValues', function(t) { //this.count(20*2 + 10) var parsedQuery = parse({ $or: [ {a: {$eq: null}}, {b: {$gt: 3}}, {$nor: [ {c:'four'}, {d:{$ne:5}}, {$and:[ {e:6} ]}, ]}, {f: [1,2,3]}, // $eq array value {f2: {$ne:[1,2,3]}} // $ne array value ], g: 'testing', h: {$geoIntersects: {$geometry: { type: "<GeoJSON object type>" , coordinates: [] }}}, i: {$not: 5}, array: {$nin: [1,2], $all: [3,4], $in: [5,6]}, noValue: {$mod: [3,0],$exists: false, $regex: /whatever/, $size: 3, $nearSphere: {}, $near: {}, $comment: "blah"}, special: {$elemMatch: {x: 5, y: {$lt:9}}}, special2: {$elemMatch: {$gt: 6}}, $text: {$search: "stringy"} }) var done = false var sequence = seq(function(field, value) { t.eq(field, 'a') t.eq(value, null) },function(field, value) { t.eq(field, 'b') t.eq(value, 3) },function(field, value) { t.eq(field, 'c') t.eq(value, 'four') },function(field, value) { t.eq(field, 'd') t.eq(value, 5) },function(field, value) { t.eq(field, 'e') t.eq(value, 6) },function(field, value) { t.eq(field, 'f') t.eq(value, 1) },function(field, value) { t.eq(field, 'f') t.eq(value, 2) },function(field, value) { t.eq(field, 'f') t.eq(value, 3) },function(field, value) { t.eq(field, 'f2') t.eq(value, 1) },function(field, value) { t.eq(field, 'f2') t.eq(value, 2) },function(field, value) { t.eq(field, 'f2') t.eq(value, 3) },function(field, value) { t.eq(field, 'g') t.eq(value, 'testing') }, // function(field, value) { // gonna ignore these for now // t.eq(field, 'h') // t.eq(value, "<GeoJSON object type>") // } function(field, value) { t.eq(field, 'i') t.eq(value, 5) },function(field, value) { t.eq(field, 'array') t.eq(value, 1) },function(field, value) { t.eq(field, 'array') t.eq(value, 2) },function(field, value) { t.eq(field, 'array') t.eq(value, 3) },function(field, value) { t.eq(field, 'array') t.eq(value, 4) },function(field, value) { t.eq(field, 'array') t.eq(value, 5) },function(field, value) { t.eq(field, 'array') t.eq(value, 6) },function(field, value) { t.eq(field, 'special.x') t.eq(value, 5) },function(field, value) { t.eq(field, 'special.y') t.eq(value, 9) },function(field, value) { t.eq(field, 'special2') t.eq(value, 6) },function(field, value) { t.eq(field, undefined) t.eq(value, 'stringy') done = true }) var newQuery = parsedQuery.mapValues(function(field, value) { sequence(field, value) return value+'_' }) t.ok(done) t.eq(Object.keys(newQuery).length, 9) t.ok(deepEqual(newQuery.$or, [ {a: 'null_'}, {b: {$gt: '3_'}}, {$nor: [ {c:'four_'}, {d:{$ne:'5_'}}, {e:'6_'}, ]}, {f: ['1_','2_','3_']}, {f2: {$ne:['1_','2_','3_']}}, ]), newQuery.$or) t.eq(newQuery.g, 'testing_') t.ok(deepEqual(newQuery.h, {$geoIntersects: {$geometry: { type: "<GeoJSON object type>" , coordinates: [] }}}), newQuery.h) t.ok(deepEqual(newQuery.i, {$not: '5_'}), newQuery.i) t.ok(deepEqual(newQuery.array, {$nin: ['1_','2_'], $all: ['3_','4_'], $in: ['5_','6_']}), newQuery.array) t.ok(deepEqual(newQuery.noValue, {$mod: [3,0],$exists: false, $regex: /whatever/, $size: 3, $nearSphere: {}, $near: {}, $comment: "blah"}), newQuery.noValue) t.ok(deepEqual(newQuery.special, {$elemMatch: {x: '5_', y: {$lt:'9_'}}}), newQuery.special) t.ok(deepEqual(newQuery.special2, {$elemMatch: {$gt: '6_'}}), newQuery) t.ok(deepEqual(newQuery.$text, {$search: "stringy_"}), newQuery.$text) }) this.test('map', function(t) { var result = parse({a:1}).map(function(key, value) { t.ok(deepEqual(value,{$eq:1}), value) t.eq(key,'a') return {b:2} }) this.eq(Object.keys(result).length, 1) this.eq(result.b, 2) var result = parse({a:{$not:1, $lt:4}}).map(function(key, value) { t.eq(key,'a') if(value.$not === 1) { return {a:{$gt:3}} } else if(value.$lt === 4) { return {a:{$ne:5}} } else throw ': (' }) this.eq(Object.keys(result).length, 1) t.ok(deepEqual(result, {a:{$gt:3,$ne:5}}), result) var result = parse({a:{$exists:false}}).map(function(key, value) { t.eq(key,'a') t.eq(value.$exists, false) }) this.eq(Object.keys(result).length, 1) t.ok(deepEqual(result, {a:{$exists:false}}), result) var result = parse({a:{$geoIntersects: {$geometry: { type: "<GeoJSON object type>", coordinates: [] }}}}).map(function(key, value) { t.eq(key,'a') t.ok(deepEqual(value, {$geoIntersects: {$geometry: { type: "<GeoJSON object type>", coordinates: [] }}}), value) return {a:4, b:5} }) this.eq(Object.keys(result).length, 2) t.ok(deepEqual(result, {a:4,b:5}), result) var result = parse({$or: [ {a:1}, {b:{$nin:[1,2,3]}} ]}).map(function(key, value) { if(key === 'a') { t.ok(deepEqual(value,{$eq:1}), value) return null } else if(key ==='b') { t.ok(deepEqual(value, {$nin:[1,2,3]}), value) return {c:value} } }) t.ok(deepEqual(result, {c:{$nin:[1,2,3]}}), result) var result = parse({$and: [ {a:{$all:[1,2,3]}}, {b:{$elemMatch: {x: 5, y: {$lt:9}}}}, {$text: {$search: "stringy"}} ]}).map(function(key, value) { if(key === 'a') { t.ok(deepEqual(value, {$all:[1,2,3]}), value) } else if(key === 'b') { t.ok(deepEqual(value, {$elemMatch: {x: 5, y: {$lt:9}}}), value) return {$text: {$search: "moo"}} } else if(key === undefined) { t.ok(deepEqual(value, {$text: {$search: "stringy"}}), value) return {x:2} } }) t.ok(deepEqual(result, {a:{$all:[1,2,3]}, $text: {$search: "moo"}, x:2}), result) var result = parse({a:5, b:5}).map(function(key, value) { if(key === 'b') { return {a:{$gt:3}} } }) t.ok(deepEqual(result, {a:5, $and:[{a:{$gt:3}}]}), result) var result = parse({a:{$ne:5}, b:5}).map(function(key, value) { if(key === 'b') { return {a:{$ne:3}} } }) t.ok(deepEqual(result, {a:{$ne:5}, $and:[{a:{$ne:3}}]}), result) var result = parse({a:5, d:9, $nor:[{b:9}, {c:10}]}).map(function(key, value) { if(key === 'a') { return {$nor:[{a:{$gt:4}}, {a:{$lt:6}}]} } else if(key === 'd') { return; } else { return null } }) t.ok(deepEqual(result, {$nor:[{a:{$gt:4}}, {a:{$lt:6}}], d:9}), result) var result = parse({$text:{$search:"moose"}}).map(function(key, value) { return; }) t.ok(deepEqual(result, {$text:{$search:"moose"}}), result) this.test("former bugs", function(t) { this.test("$or was being treated like an $and", function(t) { var x = {"$and":[{"$or":[{"parent":"58a017558db10ff76667bba7"},{"ancestry":"58a017558db10ff76667bba7"}],"done":true,"complexity":{"$gt":0},"archived":{"$ne":true}},{"type":{"$ne":"Bug"}}]} var y = {"$or":[{"parent":"58a017558db10ff76667bba7"},{"ancestry":"58a017558db10ff76667bba7"}],"done":true,"complexity":{"$gt":0},"archived":{"$ne":true}, "type":{"$ne":"Bug"}} var result = parse(x).map(function() {}) this.ok(deepEqual(result, y), result) }) }) }) this.test('matching', function() { // simple equality {a: 'a'} // simple operator queries {b: {$gt: '3'}} // basic and {c: 'c', d: 'd')} // dot notation: {'e.x': 'ab'} // array equality: {array: [1,2,3]} // object equality: {obj: {x:1,y:2}} // order matters // array contains: {array: 2} // dot notation with array index: {'array.1.x': 'ab'} // any obj embedded in array: {'array.x': 'ab'} // combination of operators {e: {$gt: 3, $lt: 4}} // function or string - equivalent of the $where operator // function using obj or this // operators: // field operators // single-value // $gt, $gte, $lt, $lte, $ne // special (multiple operands) // $geoIntersects, $geoWithin // array // $nin, $all, $in, // no-value // $mod, $exists, $regex, $size, $nearSphere, $near // special // $elemMatch, $not // field-independant operators // no-value // $and, $or, $nor, $where, $comment // has value // $text // projection-operators // $, $elemMatch, $meta, $slice this.test("simple equality", function() { var parsedQuery = parse({}) this.ok(parsedQuery.matches({a:'a', b:'b'})) this.ok(parsedQuery.matches({a:'b', b:'a'})) this.ok(parsedQuery.matches({})) var parsedQuery = parse({a:null}) this.ok(parsedQuery.matches({a:null})) this.ok(parsedQuery.matches({})) this.ok(!parsedQuery.matches({a:'b'})) var parsedQuery = parse({a:'a'}) this.ok(parsedQuery.matches({a:'a', b:'b'})) this.ok(!parsedQuery.matches({a:'b', b:'a'})) }) this.test("simple operator queries", function() { //{b: {$gt: '3'}} var parsedQuery = parse({a: {$gt: 3}}) this.ok(parsedQuery.matches({a:4})) this.ok(!parsedQuery.matches({a:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$lt: 3}}) this.ok(parsedQuery.matches({a:2})) this.ok(!parsedQuery.matches({a:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$gte: 3}}) this.ok(parsedQuery.matches({a:4})) this.ok(parsedQuery.matches({a:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$lte: 3}}) this.ok(parsedQuery.matches({a:2})) this.ok(parsedQuery.matches({a:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$ne: 3}}) this.ok(parsedQuery.matches({a:2})) this.ok(parsedQuery.matches({})) this.ok(!parsedQuery.matches({a:3})) var parsedQuery = parse({a: {$in: [1,2]}}) this.ok(parsedQuery.matches({a:2})) this.ok(!parsedQuery.matches({a:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$nin: [1,2]}}) this.ok(parsedQuery.matches({a:3})) this.ok(parsedQuery.matches({})) this.ok(!parsedQuery.matches({a:2})) var parsedQuery = parse({a: {$all: [1,2]}}) this.ok(parsedQuery.matches({a:[1,2,2,2,1]})) this.ok(!parsedQuery.matches({a:2})) this.ok(!parsedQuery.matches({a:[1,2,3]})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$mod: [4,3]}}) this.ok(parsedQuery.matches({a:3})) this.ok(parsedQuery.matches({a:7})) this.ok(!parsedQuery.matches({a:5})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$exists: true}}) this.ok(parsedQuery.matches({a:{x:1}})) // object value this.ok(parsedQuery.matches({a:false})) // primitive value this.ok(!parsedQuery.matches({b:{b:'a'}, c:'a'})) this.ok(!parsedQuery.matches({})) // more complex queries for $exists var parsedQuery = parse({'a.b': {$exists: true}}) this.ok(parsedQuery.matches({a:[{b:1},{x:'moose'}]})) // array element this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$regex: /x+/}}) this.ok(parsedQuery.matches({a:'x'})) this.ok(parsedQuery.matches({a:'xxx'})) this.ok(parsedQuery.matches({a:'xxxwaef'})) this.ok(!parsedQuery.matches({a:''})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$size: 2}}) this.ok(parsedQuery.matches({a:[1,2]})) this.ok(!parsedQuery.matches({a:[1]})) this.ok(!parsedQuery.matches({a:2})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$elemMatch: {x:5}}}) this.ok(parsedQuery.matches({a:[{x:5}, {y:4}]})) this.ok(!parsedQuery.matches({a:[{x:4}, {y:5}]})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: {$elemMatch: {$gt:5}}}) this.ok(parsedQuery.matches({a:[4,5,6]})) this.ok(!parsedQuery.matches({a:[3,4,5]})) var parsedQuery = parse({a: {$not: {$gt:5}}}) this.ok(parsedQuery.matches({a:4})) this.ok(parsedQuery.matches({})) this.ok(!parsedQuery.matches({a:6})) var parsedQuery = parse({a: {$not: 4}}) this.ok(parsedQuery.matches({a:5})) this.ok(parsedQuery.matches({})) this.ok(!parsedQuery.matches({a:4})) var parsedQuery = parse({$and: [{a:3, b:{$gt:4}}]}) this.ok(parsedQuery.matches({a:3,b:6})) this.ok(!parsedQuery.matches({a:3,b:3})) this.ok(!parsedQuery.matches({a:6,b:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({$or: [{a:3}, {b:{$gt:4}}]}) this.ok(parsedQuery.matches({a:3,b:6})) this.ok(parsedQuery.matches({a:3,b:3})) this.ok(!parsedQuery.matches({a:6,b:3})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({$nor: [{a:3}, {b:{$gt:4}}]}) this.ok(!parsedQuery.matches({a:3,b:6})) this.ok(!parsedQuery.matches({a:3,b:3})) this.ok(parsedQuery.matches({a:6,b:3})) this.ok(parsedQuery.matches({})) var parsedQuery = parse({a: {$where: function() {return this === 5}}}) this.ok(parsedQuery.matches({a:5})) this.ok(!parsedQuery.matches({a:4})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse(function() {return this.a === 5}) this.ok(parsedQuery.matches({a:5})) this.ok(!parsedQuery.matches({a:4})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse(function() {return obj.a === 5}) this.ok(parsedQuery.matches({a:5})) this.ok(!parsedQuery.matches({a:4})) this.ok(!parsedQuery.matches({})) var parsedQuery = parse({a: 5, $comment: "whatever"}) this.ok(parsedQuery.matches({a:5})) this.ok(!parsedQuery.matches({a:6})) }) this.test("basic and", function() { var parsedQuery = parse({a: 5, b:6}) this.ok(parsedQuery.matches({a:5, b:6, c:7})) this.ok(!parsedQuery.matches({a:5, c:6})) }) this.test("dot notation", function() { var parsedQuery = parse({'a.b': 5}) this.ok(parsedQuery.matches({a:{b:5}})) this.ok(!parsedQuery.matches({a:5})) this.ok(!parsedQuery.matches({a:{b:6}})) this.ok(!parsedQuery.matches({b:6})) var parsedQuery = parse({'a.0': 5}) this.ok(parsedQuery.matches({a:[5,6,7]})) this.ok(!parsedQuery.matches({a:[6,6,7]})) this.ok(!parsedQuery.matches({a:[6,5,7]})) this.ok(!parsedQuery.matches({a:5})) var parsedQuery = parse({'a.0.x': 5}) this.ok(parsedQuery.matches({a:[{x:5}]})) this.ok(!parsedQuery.matches({a:[6,6,7]})) this.ok(!parsedQuery.matches({a:[{x:6}, {x:5}]})) // matching any element's subdocument var parsedQuery = parse({'a.x': 5}) this.ok(parsedQuery.matches({a:[0, {x:5}]})) this.ok(!parsedQuery.matches({a:[5,6,7]})) this.ok(!parsedQuery.matches({a:[{x:6}, {x:7}]})) // more complex array subdocuments var parsedQuery = parse({'a.x.y.z': 5}) this.ok(parsedQuery.matches({a:[{x:[{y:[{z:5}]}]}]})) this.ok(!parsedQuery.matches({a:[{x:[{y:[{z:6}]}]}]})) }) this.test('array equality and element match', function() { var parsedQuery = parse({a: [1,2,3]}) this.ok(parsedQuery.matches({a:[1,2,3]})) this.ok(!parsedQuery.matches({a:[0,1,2,3]})) this.ok(!parsedQuery.matches({a:2})) // array contains var parsedQuery = parse({a: 5}) this.ok(parsedQuery.matches({a:[5,6,7]})) this.ok(parsedQuery.matches({a:[6,5,7]})) this.ok(!parsedQuery.matches({a:[6,6,7]})) var parsedQuery = parse({a: {$gt:5}}) this.ok(parsedQuery.matches({a:[5,6,7]})) this.ok(parsedQuery.matches({a:[6,7]})) this.ok(!parsedQuery.matches({a:[1,2,3]})) }) this.test("object equality", function() { var parsedQuery = parse({a: {x:1,y:2}}) this.ok(parsedQuery.matches({a:{x:1,y:2}})) this.ok(!parsedQuery.matches({a:{y:2,x:1}})) this.ok(!parsedQuery.matches({a:{x:1}})) this.ok(!parsedQuery.matches({x:1})) }) this.test("inclusive", function() { this.eq(parser.inclusive({a:1}), true) this.eq(parser.inclusive({_id:1}), true) // mongo apparently cares if _id is explicitly included, but not if its explicitly excluded this.eq(parser.inclusive({a:true}), true) this.eq(parser.inclusive({a:45}), true) this.eq(parser.inclusive({'a.$':1}), true) this.eq(parser.inclusive({_id:0, a:45}), true) this.eq(parser.inclusive({$meta:'textScore'}), true) this.eq(parser.inclusive({a:0}), false) this.eq(parser.inclusive({a:false}), false) this.eq(parser.inclusive({}), undefined) this.eq(parser.inclusive({_id:0}), undefined) this.eq(parser.inclusive({a:{$elemMatch:{x:1}}}), undefined) this.eq(parser.inclusive({a:{$slice:4}}), undefined) this.eq(parser.inclusive({a:{$slice:[2,3]}}), undefined) }) this.test("errors", function() { this.count(2) var parsedQuery = parse({}) try { parsedQuery.matches({$a:5}) } catch(e) { this.eq(e.message, "Field names can't start with $") } try { parsedQuery.matches({'a.b':5}) } catch(e) { this.eq(e.message, "Field names can't contain .") } }) }) this.test('$in array match', function() { var parsedQuery = parser.parse({ tags:{ $in:['a','b']} }); this.ok(parsedQuery.matches({ tags:['a', 'b', 'c'] })); this.ok(parsedQuery.matches({ tags:['a'] })); this.ok(parsedQuery.matches({ tags:'a' })); this.ok(!parsedQuery.matches({ tags:[] })); this.ok(!parsedQuery.matches({ tags:['d'] })); this.ok(!parsedQuery.matches({ tags:['d', 'e'] })); this.ok(!parsedQuery.matches({ tags:null })); this.ok(!parsedQuery.matches({ tags:'' })); }); this.test('$nin array match', function() { var parsedQuery = parser.parse({ tags:{ $nin:['a','b']} }); this.ok(!parsedQuery.matches({ tags:['a', 'b', 'c'] })); this.ok(!parsedQuery.matches({ tags:['a'] })); this.ok(!parsedQuery.matches({ tags:'a' })); this.ok(parsedQuery.matches({ tags:[] })); this.ok(parsedQuery.matches({ tags:['d'] })); this.ok(parsedQuery.matches({ tags:['d', 'e'] })); this.ok(parsedQuery.matches({ tags:null })); this.ok(parsedQuery.matches({ tags:'' })); }) this.test('searching', function() { var documents = [{x:5,y:{z:10}},{x:7,y:{z:10}},{x:6,y:{z:4}},{x:4,y:{z:6}}] var results = parser.search(documents, {x: {$gt:4}}, {x:1}) this.ok(deepEqual(results, [{x:5,y:{z:10}}, {x:6,y:{z:4}}, {x:7,y:{z:10}}])) results = parser.search(documents, {'y.z': 10}, {x:-1}) this.ok(deepEqual(results, [{x:7,y:{z:10}}, {x:5,y:{z:10}}])) results = parser.search(documents, {}, {'y.z':1, x:-1}) this.ok(deepEqual(results, [{x:6,y:{z:4}},{x:4,y:{z:6}},{x:7,y:{z:10}},{x:5,y:{z:10}}])) }) //*/ }).writeConsole(1000) // you define the functions up front, and when you call the return value, it passes the arguments you call it with to the functions in sequence // returns a function that, each time its called, calls the next function in the list with the passed argument // example: /* var sequenceX = testUtils.seq( function(x) { t.ok(x === 'a') }, function(x) { t.ok(x === 'b') }, function(x) { t.ok(x === 'c') }) var obj = {a:1,b:2,c:3} for(var x in obj) { sequenceX(x) } */ function seq(/*functions*/) { var n=-1 var fns = arguments return function() { n++ if(n>=fns.length) throw new Error("Unexpected call: "+n) // else fns[n].apply(this,arguments) } } // compares arrays and objects for value equality (all elements and members must match) function deepEqual(a,b) { if(a instanceof Array) { if(!(b instanceof Array)) return false if(a.length !== b.length) { return false } else { return a.reduce(function(previousValue, currentValue, index) { return previousValue && deepEqual(currentValue,b[index]) }, true) } } else if(a instanceof Object) { if(!(b instanceof Object)) return false var aKeys = Object.keys(a) var bKeys = Object.keys(b) if(aKeys.length !== bKeys.length) { return false } else { for(var n=0; n<aKeys.length; n++) { var key = aKeys[n] var aVal = a[key] var bVal = b[key] if(!deepEqual(aVal,bVal)) { return false } } // else return true } } else { return a===b } }