query_parser_test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. suite('lunr.QueryParser', function () {
  2. var parse = function (q) {
  3. var query = new lunr.Query (['title', 'body']),
  4. parser = new lunr.QueryParser(q, query)
  5. parser.parse()
  6. return query.clauses
  7. }
  8. suite('#parse', function () {
  9. suite('single term', function () {
  10. setup(function () {
  11. this.clauses = parse('foo')
  12. })
  13. test('has 1 clause', function () {
  14. assert.lengthOf(this.clauses, 1)
  15. })
  16. suite('clauses', function () {
  17. setup(function () {
  18. this.clause = this.clauses[0]
  19. })
  20. test('term', function () {
  21. assert.equal('foo', this.clause.term)
  22. })
  23. test('fields', function () {
  24. assert.sameMembers(['title', 'body'], this.clause.fields)
  25. })
  26. test('presence', function () {
  27. assert.equal(lunr.Query.presence.OPTIONAL, this.clause.presence)
  28. })
  29. test('usePipeline', function () {
  30. assert.ok(this.clause.usePipeline)
  31. })
  32. })
  33. })
  34. suite('single term, uppercase', function () {
  35. setup(function () {
  36. this.clauses = parse('FOO')
  37. })
  38. test('has 1 clause', function () {
  39. assert.lengthOf(this.clauses, 1)
  40. })
  41. suite('clauses', function () {
  42. setup(function () {
  43. this.clause = this.clauses[0]
  44. })
  45. test('term', function () {
  46. assert.equal('foo', this.clause.term)
  47. })
  48. test('fields', function () {
  49. assert.sameMembers(['title', 'body'], this.clause.fields)
  50. })
  51. test('usePipeline', function () {
  52. assert.ok(this.clause.usePipeline)
  53. })
  54. })
  55. })
  56. suite('single term with wildcard', function () {
  57. setup(function () {
  58. this.clauses = parse('fo*')
  59. })
  60. test('has 1 clause', function () {
  61. assert.lengthOf(this.clauses, 1)
  62. })
  63. suite('clauses', function () {
  64. setup(function () {
  65. this.clause = this.clauses[0]
  66. })
  67. test('#term', function () {
  68. assert.equal('fo*', this.clause.term)
  69. })
  70. test('#usePipeline', function () {
  71. assert.ok(!this.clause.usePipeline)
  72. })
  73. })
  74. })
  75. suite('multiple terms', function () {
  76. setup(function () {
  77. this.clauses = parse('foo bar')
  78. })
  79. test('has 2 clause', function () {
  80. assert.lengthOf(this.clauses, 2)
  81. })
  82. suite('clauses', function () {
  83. test('#term', function () {
  84. assert.equal('foo', this.clauses[0].term)
  85. assert.equal('bar', this.clauses[1].term)
  86. })
  87. })
  88. })
  89. suite('multiple terms with presence', function () {
  90. setup(function () {
  91. this.clauses = parse('+foo +bar')
  92. })
  93. test('has 2 clause', function () {
  94. assert.lengthOf(this.clauses, 2)
  95. })
  96. })
  97. suite('edit distance followed by presence', function () {
  98. setup(function () {
  99. this.clauses = parse('foo~10 +bar')
  100. })
  101. test('has 2 clause', function () {
  102. assert.lengthOf(this.clauses, 2)
  103. })
  104. suite('clauses', function () {
  105. setup(function () {
  106. this.fooClause = this.clauses[0]
  107. this.barClause = this.clauses[1]
  108. })
  109. test('#term', function () {
  110. assert.equal('foo', this.fooClause.term)
  111. assert.equal('bar', this.barClause.term)
  112. })
  113. test('#presence', function () {
  114. assert.equal(lunr.Query.presence.OPTIONAL, this.fooClause.presence)
  115. assert.equal(lunr.Query.presence.REQUIRED, this.barClause.presence)
  116. })
  117. test('#editDistance', function () {
  118. assert.equal(10, this.fooClause.editDistance)
  119. // It feels dirty asserting that something is undefined
  120. // but there is no Optional so this is what we are reduced to
  121. assert.isUndefined(this.barClause.editDistance)
  122. })
  123. })
  124. })
  125. suite('boost followed by presence', function () {
  126. setup(function () {
  127. this.clauses = parse('foo^10 +bar')
  128. })
  129. test('has 2 clause', function () {
  130. assert.lengthOf(this.clauses, 2)
  131. })
  132. suite('clauses', function () {
  133. setup(function () {
  134. this.fooClause = this.clauses[0]
  135. this.barClause = this.clauses[1]
  136. })
  137. test('#term', function () {
  138. assert.equal('foo', this.fooClause.term)
  139. assert.equal('bar', this.barClause.term)
  140. })
  141. test('#presence', function () {
  142. assert.equal(lunr.Query.presence.OPTIONAL, this.fooClause.presence)
  143. assert.equal(lunr.Query.presence.REQUIRED, this.barClause.presence)
  144. })
  145. test('#boost', function () {
  146. assert.equal(10, this.fooClause.boost)
  147. assert.equal(1, this.barClause.boost)
  148. })
  149. })
  150. })
  151. suite('field without a term', function () {
  152. test('fails with lunr.QueryParseError', function () {
  153. assert.throws(function () { parse('title:') }, lunr.QueryParseError)
  154. })
  155. })
  156. suite('unknown field', function () {
  157. test('fails with lunr.QueryParseError', function () {
  158. assert.throws(function () { parse('unknown:foo') }, lunr.QueryParseError)
  159. })
  160. })
  161. suite('term with field', function () {
  162. setup(function () {
  163. this.clauses = parse('title:foo')
  164. })
  165. test('has 1 clause', function () {
  166. assert.lengthOf(this.clauses, 1)
  167. })
  168. test('clause contains only scoped field', function () {
  169. assert.sameMembers(this.clauses[0].fields, ['title'])
  170. })
  171. })
  172. suite('uppercase field with uppercase term', function () {
  173. setup(function () {
  174. // Using a different query to the rest of the tests
  175. // so that only this test has to worry about an upcase field name
  176. var query = new lunr.Query (['TITLE']),
  177. parser = new lunr.QueryParser("TITLE:FOO", query)
  178. parser.parse()
  179. this.clauses = query.clauses
  180. })
  181. test('has 1 clause', function () {
  182. assert.lengthOf(this.clauses, 1)
  183. })
  184. test('clause contains downcased term', function () {
  185. assert.equal(this.clauses[0].term, 'foo')
  186. })
  187. test('clause contains only scoped field', function () {
  188. assert.sameMembers(this.clauses[0].fields, ['TITLE'])
  189. })
  190. })
  191. suite('multiple terms scoped to different fields', function () {
  192. setup(function () {
  193. this.clauses = parse('title:foo body:bar')
  194. })
  195. test('has 2 clauses', function () {
  196. assert.lengthOf(this.clauses, 2)
  197. })
  198. test('fields', function () {
  199. assert.sameMembers(['title'], this.clauses[0].fields)
  200. assert.sameMembers(['body'], this.clauses[1].fields)
  201. })
  202. test('terms', function () {
  203. assert.equal('foo', this.clauses[0].term)
  204. assert.equal('bar', this.clauses[1].term)
  205. })
  206. })
  207. suite('single term with edit distance', function () {
  208. setup(function () {
  209. this.clauses = parse('foo~2')
  210. })
  211. test('has 1 clause', function () {
  212. assert.lengthOf(this.clauses, 1)
  213. })
  214. test('term', function () {
  215. assert.equal('foo', this.clauses[0].term)
  216. })
  217. test('editDistance', function () {
  218. assert.equal(2, this.clauses[0].editDistance)
  219. })
  220. test('fields', function () {
  221. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  222. })
  223. })
  224. suite('multiple terms with edit distance', function () {
  225. setup(function () {
  226. this.clauses = parse('foo~2 bar~3')
  227. })
  228. test('has 2 clauses', function () {
  229. assert.lengthOf(this.clauses, 2)
  230. })
  231. test('term', function () {
  232. assert.equal('foo', this.clauses[0].term)
  233. assert.equal('bar', this.clauses[1].term)
  234. })
  235. test('editDistance', function () {
  236. assert.equal(2, this.clauses[0].editDistance)
  237. assert.equal(3, this.clauses[1].editDistance)
  238. })
  239. test('fields', function () {
  240. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  241. assert.sameMembers(['title', 'body'], this.clauses[1].fields)
  242. })
  243. })
  244. suite('single term scoped to field with edit distance', function () {
  245. setup(function () {
  246. this.clauses = parse('title:foo~2')
  247. })
  248. test('has 1 clause', function () {
  249. assert.lengthOf(this.clauses, 1)
  250. })
  251. test('term', function () {
  252. assert.equal('foo', this.clauses[0].term)
  253. })
  254. test('editDistance', function () {
  255. assert.equal(2, this.clauses[0].editDistance)
  256. })
  257. test('fields', function () {
  258. assert.sameMembers(['title'], this.clauses[0].fields)
  259. })
  260. })
  261. suite('non-numeric edit distance', function () {
  262. test('throws lunr.QueryParseError', function () {
  263. assert.throws(function () { parse('foo~a') }, lunr.QueryParseError)
  264. })
  265. })
  266. suite('edit distance without a term', function () {
  267. test('throws lunr.QueryParseError', function () {
  268. assert.throws(function () { parse('~2') }, lunr.QueryParseError)
  269. })
  270. })
  271. suite('single term with boost', function () {
  272. setup(function () {
  273. this.clauses = parse('foo^2')
  274. })
  275. test('has 1 clause', function () {
  276. assert.lengthOf(this.clauses, 1)
  277. })
  278. test('term', function () {
  279. assert.equal('foo', this.clauses[0].term)
  280. })
  281. test('boost', function () {
  282. assert.equal(2, this.clauses[0].boost)
  283. })
  284. test('fields', function () {
  285. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  286. })
  287. })
  288. suite('non-numeric boost', function () {
  289. test('throws lunr.QueryParseError', function () {
  290. assert.throws(function () { parse('foo^a') }, lunr.QueryParseError)
  291. })
  292. })
  293. suite('boost without a term', function () {
  294. test('throws lunr.QueryParseError', function () {
  295. assert.throws(function () { parse('^2') }, lunr.QueryParseError)
  296. })
  297. })
  298. suite('multiple terms with boost', function () {
  299. setup(function () {
  300. this.clauses = parse('foo^2 bar^3')
  301. })
  302. test('has 2 clauses', function () {
  303. assert.lengthOf(this.clauses, 2)
  304. })
  305. test('term', function () {
  306. assert.equal('foo', this.clauses[0].term)
  307. assert.equal('bar', this.clauses[1].term)
  308. })
  309. test('boost', function () {
  310. assert.equal(2, this.clauses[0].boost)
  311. assert.equal(3, this.clauses[1].boost)
  312. })
  313. test('fields', function () {
  314. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  315. assert.sameMembers(['title', 'body'], this.clauses[1].fields)
  316. })
  317. })
  318. suite('term scoped by field with boost', function () {
  319. setup(function () {
  320. this.clauses = parse('title:foo^2')
  321. })
  322. test('has 1 clause', function () {
  323. assert.lengthOf(this.clauses, 1)
  324. })
  325. test('term', function () {
  326. assert.equal('foo', this.clauses[0].term)
  327. })
  328. test('boost', function () {
  329. assert.equal(2, this.clauses[0].boost)
  330. })
  331. test('fields', function () {
  332. assert.sameMembers(['title'], this.clauses[0].fields)
  333. })
  334. })
  335. suite('term with presence required', function () {
  336. setup(function () {
  337. this.clauses = parse('+foo')
  338. })
  339. test('has 1 clauses', function () {
  340. assert.lengthOf(this.clauses, 1)
  341. })
  342. test('term', function () {
  343. assert.equal('foo', this.clauses[0].term)
  344. })
  345. test('boost', function () {
  346. assert.equal(1, this.clauses[0].boost)
  347. })
  348. test('fields', function () {
  349. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  350. })
  351. test('presence', function () {
  352. assert.equal(lunr.Query.presence.REQUIRED, this.clauses[0].presence)
  353. })
  354. })
  355. suite('term with presence prohibited', function () {
  356. setup(function () {
  357. this.clauses = parse('-foo')
  358. })
  359. test('has 1 clauses', function () {
  360. assert.lengthOf(this.clauses, 1)
  361. })
  362. test('term', function () {
  363. assert.equal('foo', this.clauses[0].term)
  364. })
  365. test('boost', function () {
  366. assert.equal(1, this.clauses[0].boost)
  367. })
  368. test('fields', function () {
  369. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  370. })
  371. test('presence', function () {
  372. assert.equal(lunr.Query.presence.PROHIBITED, this.clauses[0].presence)
  373. })
  374. })
  375. suite('term scoped by field with presence required', function () {
  376. setup(function () {
  377. this.clauses = parse('+title:foo')
  378. })
  379. test('has 1 clauses', function () {
  380. assert.lengthOf(this.clauses, 1)
  381. })
  382. test('term', function () {
  383. assert.equal('foo', this.clauses[0].term)
  384. })
  385. test('boost', function () {
  386. assert.equal(1, this.clauses[0].boost)
  387. })
  388. test('fields', function () {
  389. assert.sameMembers(['title'], this.clauses[0].fields)
  390. })
  391. test('presence', function () {
  392. assert.equal(lunr.Query.presence.REQUIRED, this.clauses[0].presence)
  393. })
  394. })
  395. suite('term scoped by field with presence prohibited', function () {
  396. setup(function () {
  397. this.clauses = parse('-title:foo')
  398. })
  399. test('has 1 clauses', function () {
  400. assert.lengthOf(this.clauses, 1)
  401. })
  402. test('term', function () {
  403. assert.equal('foo', this.clauses[0].term)
  404. })
  405. test('boost', function () {
  406. assert.equal(1, this.clauses[0].boost)
  407. })
  408. test('fields', function () {
  409. assert.sameMembers(['title'], this.clauses[0].fields)
  410. })
  411. test('presence', function () {
  412. assert.equal(lunr.Query.presence.PROHIBITED, this.clauses[0].presence)
  413. })
  414. })
  415. })
  416. suite('term with boost and edit distance', function () {
  417. setup(function () {
  418. this.clauses = parse('foo^2~3')
  419. })
  420. test('has 1 clause', function () {
  421. assert.lengthOf(this.clauses, 1)
  422. })
  423. test('term', function () {
  424. assert.equal('foo', this.clauses[0].term)
  425. })
  426. test('editDistance', function () {
  427. assert.equal(3, this.clauses[0].editDistance)
  428. })
  429. test('boost', function () {
  430. assert.equal(2, this.clauses[0].boost)
  431. })
  432. test('fields', function () {
  433. assert.sameMembers(['title', 'body'], this.clauses[0].fields)
  434. })
  435. })
  436. })