UNPKG

nodebook-nbql

Version:

Filter query language for nodebook

468 lines (423 loc) 20.9 kB
/* globals describe, it */ var nbql = require('../lib/nbql'); describe('Lexer', function () { var lexicalError = /^Query Error: unrecognized text/; describe('Symbols', function () { it('can recognise -', function () { nbql.lex('-').should.eql([{token: 'NOT', matched: '-'}]); }); it('can recognise +', function () { nbql.lex('+').should.eql([{token: 'AND', matched: '+'}]); }); it('can recognise ,', function () { nbql.lex(',').should.eql([{token: 'OR', matched: ','}]); }); it('can recognise [', function () { nbql.lex('[').should.eql([{token: 'LBRACKET', matched: '['}]); }); it('can recognise ]', function () { nbql.lex(']').should.eql([{token: 'RBRACKET', matched: ']'}]); }); it('can recognise (', function () { nbql.lex('(').should.eql([{token: 'LPAREN', matched: '('}]); }); it('can recognise )', function () { nbql.lex(')').should.eql([{token: 'RPAREN', matched: ')'}]); }); it('can recognise >', function () { nbql.lex('>').should.eql([{token: 'GT', matched: '>'}]); }); it('can recognise <', function () { nbql.lex('<').should.eql([{token: 'LT', matched: '<'}]); }); it('can recognise >=', function () { nbql.lex('>=').should.eql([{token: 'GTE', matched: '>='}]); }); it('can recognise <=', function () { nbql.lex('<=').should.eql([{token: 'LTE', matched: '<='}]); }); it('cannot recognise :', function () { (function () {nbql.lex(':');}).should.throw(lexicalError); }); it('cannot recognise =', function () { (function () {nbql.lex('=');}).should.throw(lexicalError); }); it('cannot recognise "', function () { (function () {nbql.lex('"');}).should.throw(lexicalError); }); it('cannot recognise \'', function () { (function () {nbql.lex('\'');}).should.throw(lexicalError); }); }); describe('VALUES', function () { it('can recognise null', function () { nbql.lex('null').should.eql([{token: 'NULL', matched: 'null'}]); }); it('can recognise true', function () { nbql.lex('true').should.eql([{token: 'TRUE', matched: 'true'}]); }); it('can recognise false', function () { nbql.lex('false').should.eql([{token: 'FALSE', matched: 'false'}]); }); it('can recognise a LITERAL', function () { nbql.lex('six').should.eql([{token: 'LITERAL', matched: 'six'}]); }); it('can recognise a STRING', function () { nbql.lex('\'six\'').should.eql([{token: 'STRING', matched: '\'six\''}]); }); it('can recognise a NUMBER', function () { nbql.lex('6').should.eql([{token: 'NUMBER', matched: '6'}]); }); it('does not confuse values in LITERALs', function () { nbql.lex('strueth').should.eql([{token: 'LITERAL', matched: 'strueth'}]); nbql.lex('trueth').should.eql([{token: 'LITERAL', matched: 'trueth'}]); nbql.lex('true_thing').should.eql([{token: 'LITERAL', matched: 'true_thing'}]); // nbql.lex("true-thing").should.eql([{token: "LITERAL", matched: "true-thing"}]); }); it('does not confuse values in STRINGs', function () { nbql.lex('\'strueth\'').should.eql([{token: 'STRING', matched: '\'strueth\''}]); nbql.lex('\'trueth\'').should.eql([{token: 'STRING', matched: '\'trueth\''}]); nbql.lex('\'true_thing\'').should.eql([{token: 'STRING', matched: '\'true_thing\''}]); nbql.lex('\'true-thing\'').should.eql([{token: 'STRING', matched: '\'true-thing\''}]); }); }); describe('LITERAL values', function () { it('should match literals', function () { nbql.lex('myvalue').should.eql([ {token: 'LITERAL', matched: 'myvalue'} ]); nbql.lex('my value').should.eql([ {token: 'LITERAL', matched: 'my'}, {token: 'LITERAL', matched: 'value'} ]); nbql.lex('my-value').should.eql([ {token: 'LITERAL', matched: 'my-value'} ]); nbql.lex('my&value!').should.eql([ {token: 'LITERAL', matched: 'my&value!'} ]); nbql.lex('my&valu\\\'e!').should.eql([ {token: 'LITERAL', matched: 'my&valu\\\'e!'} ]); (function () {nbql.lex('my&valu\'e!');}).should.throw(lexicalError); }); it('should separate NOT at beginning of literal', function () { nbql.lex('-photo').should.eql([ {token: 'NOT', matched: '-'}, {token: 'LITERAL', matched: 'photo'} ]); nbql.lex('-photo-graph').should.eql([ {token: 'NOT', matched: '-'}, {token: 'LITERAL', matched: 'photo-graph'} ]); }); it('should NOT permit special chars inside a literal', function () { (function () { nbql.lex('t+st');}).should.throw(lexicalError); (function () { nbql.lex('t,st');}).should.throw(lexicalError); (function () { nbql.lex('t(st');}).should.throw(lexicalError); (function () { nbql.lex('t)st');}).should.throw(lexicalError); (function () { nbql.lex('t>st');}).should.throw(lexicalError); (function () { nbql.lex('t<st');}).should.throw(lexicalError); (function () { nbql.lex('t=st');}).should.throw(lexicalError); (function () { nbql.lex('t[st');}).should.throw(lexicalError); (function () { nbql.lex('t]st');}).should.throw(lexicalError); (function () { nbql.lex('t\'st');}).should.throw(lexicalError); (function () { nbql.lex('t"st');}).should.throw(lexicalError); }); it('should not match special chars at the start of a literal', function () { nbql.lex('+test').should.eql([ {token: 'AND', matched: '+'}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex(',test').should.eql([ {token: 'OR', matched: ','}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex('(test').should.eql([ {token: 'LPAREN', matched: '('}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex(')test').should.eql([ {token: 'RPAREN', matched: ')'}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex('>test').should.eql([ {token: 'GT', matched: '>'}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex('<test').should.eql([ {token: 'LT', matched: '<'}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex('[test').should.eql([ {token: 'LBRACKET', matched: '['}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex(']test').should.eql([ {token: 'RBRACKET', matched: ']'}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex('>=test').should.eql([ {token: 'GTE', matched: '>='}, {token: 'LITERAL', matched: 'test'} ]); nbql.lex('<=test').should.eql([ {token: 'LTE', matched: '<='}, {token: 'LITERAL', matched: 'test'} ]); (function () { nbql.lex('=test');}).should.throw(lexicalError); (function () { nbql.lex('"test');}).should.throw(lexicalError); (function () { nbql.lex('\'test');}).should.throw(lexicalError); }); it('should not match special chars at the end of a literal', function () { nbql.lex('test+').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'AND', matched: '+'} ]); nbql.lex('test,').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'OR', matched: ','} ]); nbql.lex('test(').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'LPAREN', matched: '('} ]); nbql.lex('test)').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'RPAREN', matched: ')'} ]); nbql.lex('test>').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'GT', matched: '>'} ]); nbql.lex('test<').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'LT', matched: '<'} ]); nbql.lex('test[').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'LBRACKET', matched: '['} ]); nbql.lex('test]').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'RBRACKET', matched: ']'} ]); nbql.lex('test>=').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'GTE', matched: '>='} ]); nbql.lex('test<=').should.eql([ {token: 'LITERAL', matched: 'test'}, {token: 'LTE', matched: '<='} ]); (function () { nbql.lex('test=');}).should.throw(lexicalError); (function () { nbql.lex('test"');}).should.throw(lexicalError); (function () { nbql.lex('test\'');}).should.throw(lexicalError); }); it('should permit escaped special chars inside a literal', function () { nbql.lex('t\\+st').should.eql([{token: 'LITERAL', matched: 't\\+st'}]); nbql.lex('t\\,st').should.eql([{token: 'LITERAL', matched: 't\\,st'}]); nbql.lex('t\\(st').should.eql([{token: 'LITERAL', matched: 't\\(st'}]); nbql.lex('t\\)st').should.eql([{token: 'LITERAL', matched: 't\\)st'}]); nbql.lex('t\\>st').should.eql([{token: 'LITERAL', matched: 't\\>st'}]); nbql.lex('t\\<st').should.eql([{token: 'LITERAL', matched: 't\\<st'}]); nbql.lex('t\\=st').should.eql([{token: 'LITERAL', matched: 't\\=st'}]); nbql.lex('t\\[st').should.eql([{token: 'LITERAL', matched: 't\\[st'}]); nbql.lex('t\\]st').should.eql([{token: 'LITERAL', matched: 't\\]st'}]); nbql.lex('t\\\'st').should.eql([{token: 'LITERAL', matched: 't\\\'st'}]); nbql.lex('t\\"st').should.eql([{token: 'LITERAL', matched: 't\\"st'}]); }); }); describe('LITERAL vs PROP', function () { it('should match colon in string as PROP before, literal after', function () { nbql.lex(':test').should.eql([ {token: 'LITERAL', matched: ':test'} ]); nbql.lex('te:st').should.eql([ {token: 'PROP', matched: 'te:'}, {token: 'LITERAL', matched: 'st'} ]); nbql.lex('test:').should.eql([ {token: 'PROP', matched: 'test:'} ]); }); it('should only match colon-at-end as PROP if PROP is valPROP', function () { nbql.lex('te!:st').should.eql([ {token: 'LITERAL', matched: 'te!:st'} ]); nbql.lex('post-count:6').should.eql([ {token: 'LITERAL', matched: 'post-count:6'} ]); nbql.lex('post_count:6').should.eql([ {token: 'PROP', matched: 'post_count:'}, {token: 'NUMBER', matched: '6'} ]); }); }); describe('STRING values', function () { it('can recognise simple STRING', function () { nbql.lex('\'magic\'').should.eql([{token: 'STRING', matched: '\'magic\''}]); nbql.lex('\'magic mystery\'').should.eql([{token: 'STRING', matched: '\'magic mystery\''}]); nbql.lex('\'magic 123\'').should.eql([{token: 'STRING', matched: '\'magic 123\''}]); }); it('can recognise multiple STRING values', function () { nbql.lex('\'magic\'\'mystery\'').should.eql([ {token: 'STRING', matched: '\'magic\''}, {token: 'STRING', matched: '\'mystery\''} ]); nbql.lex('\'magic\' \'mystery\'').should.eql([ {token: 'STRING', matched: '\'magic\''}, {token: 'STRING', matched: '\'mystery\''} ]); nbql.lex('\'magic\',\'mystery\'').should.eql([ {token: 'STRING', matched: '\'magic\''}, {token: 'OR', matched: ','}, {token: 'STRING', matched: '\'mystery\''} ]); nbql.lex('[\'magic\',\'mystery\']').should.eql([ {token: 'LBRACKET', matched: '['}, {token: 'STRING', matched: '\'magic\''}, {token: 'OR', matched: ','}, {token: 'STRING', matched: '\'mystery\''}, {token: 'RBRACKET', matched: ']'} ]); }); it('can recognise STRING with special characters', function () { nbql.lex('\'magic+\'').should.eql([{token: 'STRING', matched: '\'magic+\''}]); nbql.lex('\'magic,\'').should.eql([{token: 'STRING', matched: '\'magic,\''}]); nbql.lex('\'magic-\'').should.eql([{token: 'STRING', matched: '\'magic-\''}]); nbql.lex('\'magic>\'').should.eql([{token: 'STRING', matched: '\'magic>\''}]); nbql.lex('\'magic<\'').should.eql([{token: 'STRING', matched: '\'magic<\''}]); }); it('should permit special chars inside a STRING, not including quotes', function () { nbql.lex('\'t+st\'').should.eql([{token: 'STRING', matched: '\'t+st\''}]); nbql.lex('\'t,st\'').should.eql([{token: 'STRING', matched: '\'t,st\''}]); nbql.lex('\'t(st\'').should.eql([{token: 'STRING', matched: '\'t(st\''}]); nbql.lex('\'t)st\'').should.eql([{token: 'STRING', matched: '\'t)st\''}]); nbql.lex('\'t>st\'').should.eql([{token: 'STRING', matched: '\'t>st\''}]); nbql.lex('\'t<st\'').should.eql([{token: 'STRING', matched: '\'t<st\''}]); nbql.lex('\'t=st\'').should.eql([{token: 'STRING', matched: '\'t=st\''}]); nbql.lex('\'t[st\'').should.eql([{token: 'STRING', matched: '\'t[st\''}]); nbql.lex('\'t]st\'').should.eql([{token: 'STRING', matched: '\'t]st\''}]); }); it('should NOT permit quotes inside a STRING', function () { (function () { nbql.lex('\'t\'st\'');}).should.throw(lexicalError); (function () { nbql.lex('\'t"st\'');}).should.throw(lexicalError); }); it('should permit escaped quotes inside a String', function () { nbql.lex('\'t\\\'st\'').should.eql([{token: 'STRING', matched: '\'t\\\'st\''}]); nbql.lex('\'t\\"st\'').should.eql([{token: 'STRING', matched: '\'t\\"st\''}]); }); }); describe('single & double QUOTE marks', function () { it('CANNOT match an UNescaped double quote in a LITERAL', function () { (function () {nbql.lex('thing"amabob');}).should.throw(lexicalError); }); it('CANNOT match an UNescaped single quote in a LITERAL', function () { (function () {nbql.lex('thing\'amabob');}).should.throw(lexicalError); }); it('CANNOT match an UNescaped double quote in a STRING', function () { (function () {nbql.lex('\'thing"amabob\'');}).should.throw(lexicalError); }); it('CANNOT match an UNescaped single quote in a STRING', function () { (function () {nbql.lex('\'thing\'amabob\'');}).should.throw(lexicalError); }); it('CAN match an escaped double quote in a LITERAL', function () { nbql.lex('thing\\"amabob').should.eql([{token: 'LITERAL', matched: 'thing\\"amabob'}]); }); it('CAN match an escaped single quote in a LITERAL', function () { nbql.lex('thing\\\'amabob').should.eql([{token: 'LITERAL', matched: 'thing\\\'amabob'}]); }); it('CAN match an escaped double quote in a STRING', function () { nbql.lex('\'thing\\"amabob\'').should.eql([{token: 'STRING', matched: '\'thing\\"amabob\''}]); }); it('CAN match an escaped single quote in a STRING', function () { nbql.lex('\'thing\\\'amabob\'').should.eql([{token: 'STRING', matched: '\'thing\\\'amabob\''}]); }); }); describe('Filter expressions', function () { it('should separate NOT at beginning of literal', function () { nbql.lex('tag:-photo').should.eql([ {token: 'PROP', matched: 'tag:'}, {token: 'NOT', matched: '-'}, {token: 'LITERAL', matched: 'photo'} ]); nbql.lex('tag:-photo-graph').should.eql([ {token: 'PROP', matched: 'tag:'}, {token: 'NOT', matched: '-'}, {token: 'LITERAL', matched: 'photo-graph'} ]); nbql.lex('tags:[-getting-started]').should.eql([ {token: 'PROP', matched: 'tags:'}, {token: 'LBRACKET', matched: '['}, {token: 'NOT', matched: '-'}, {token: 'LITERAL', matched: 'getting-started'}, {token: 'RBRACKET', matched: ']'} ]); }); it('should permit NOT inside a literal', function () { nbql.lex('tags:getting-started').should.eql([ {token: 'PROP', matched: 'tags:'}, {token: 'LITERAL', matched: 'getting-started'} ]); nbql.lex('tags:[getting-started]').should.eql([ {token: 'PROP', matched: 'tags:'}, {token: 'LBRACKET', matched: '['}, {token: 'LITERAL', matched: 'getting-started'}, {token: 'RBRACKET', matched: ']'} ]); nbql.lex('tags:-[getting-started]').should.eql([ {token: 'PROP', matched: 'tags:'}, {token: 'NOT', matched: '-'}, {token: 'LBRACKET', matched: '['}, {token: 'LITERAL', matched: 'getting-started'}, {token: 'RBRACKET', matched: ']'} ]); nbql.lex('id:-1+tags:[getting-started]').should.eql([ {token: 'PROP', matched: 'id:'}, {token: 'NOT', matched: '-'}, {token: 'NUMBER', matched: '1'}, {token: 'AND', matched: '+'}, {token: 'PROP', matched: 'tags:'}, {token: 'LBRACKET', matched: '['}, {token: 'LITERAL', matched: 'getting-started'}, {token: 'RBRACKET', matched: ']'} ]); }); }); describe('complex examples', function () { it('many expressions', function () { nbql.lex('tag:photo+featured:true,tag.count:>5').should.eql([ {token: 'PROP', matched: 'tag:'}, {token: 'LITERAL', matched: 'photo'}, {token: 'AND', matched: '+'}, {token: 'PROP', matched: 'featured:'}, {token: 'TRUE', matched: 'true'}, {token: 'OR', matched: ','}, {token: 'PROP', matched: 'tag.count:'}, {token: 'GT', matched: '>'}, {token: 'NUMBER', matched: '5'} ]); // nbql.lex("tag:photo+image:-null,tag.count:>5").should.eql(); }); it('grouped expressions', function () { // nbql.lex("author:-joe+(tag:photo,image:-null,featured:true)").should.eql(); }); it('in expressions', function () { nbql.lex('author:-joe+tag:[photo,video]').should.eql([ {token: 'PROP', matched: 'author:'}, {token: 'NOT', matched: '-'}, {token: 'LITERAL', matched: 'joe'}, {token: 'AND', matched: '+'}, {token: 'PROP', matched: 'tag:'}, {token: 'LBRACKET', matched: '['}, {token: 'LITERAL', matched: 'photo'}, {token: 'OR', matched: ','}, {token: 'LITERAL', matched: 'video'}, {token: 'RBRACKET', matched: ']'} ]); // nbql.lex("author:-joe+tag:-[photo,video]").should.eql(); // nbql.lex("author:-joe+tag:[photo,video]+post.count:>5+post.count:<100").should.eql(); }); }); });