./test/search.js annotated source
Back to index1import {strict as assert} from 'node:assert';
2import {search} from '../dist/search.js';
3
4const item = name => ({name});
5
Most of the tests operate on this pre-set list of items to search
7const ITEMS = [
8 item('Linus Lee'),
9 item('@thesephist'),
10 item('@geohot'),
11 item('linuslee'),
12 item('linus is a person'),
13 item('@dlwlrma'),
14];
15
16describe('basic search', () => {
17 it('search empty array', () => {
18 assert.deepEqual(search([], 'query', x => x.name), []);
19 });
20
21 it('search with empty query', () => {
22 assert.deepEqual(search(ITEMS, '', x => x.name), ITEMS);
23 });
24
25 it('search with 1 letter returns correct result', () => {
26 assert.deepEqual(search(ITEMS, 'l', x => x.name), [
27 item('Linus Lee'),
28 item('linuslee'),
29 item('linus is a person'),
30 ]);
31 });
32
33 it('search does not match from middle of words', () => {
34 assert.deepEqual(search(ITEMS, 'w', x => x.name), []);
35 });
36
37 it('multi-word search returns correct result', () => {
38 assert.deepEqual(search(ITEMS, 'linus lee', x => x.name), [
39 item('Linus Lee'),
40 ]);
41 });
42
43 it('searching words out of order returns correct result', () => {
44 assert.deepEqual(search(ITEMS, 'lee linus', x => x.name), [
45 item('Linus Lee'),
46 ]);
47 });
48
49 it('search works even if the last query word is incomplete', () => {
50 assert.deepEqual(search(ITEMS, 'linus le', x => x.name), [
51 item('Linus Lee'),
52 ]);
53 });
54
55 it('search query may contain newlines, tabs, and multiple consecutive spaces', () => {
56 assert.deepEqual(search(ITEMS, ' linus\t is\nperson\t', x => x.name), [
57 item('linus is a person'),
58 ]);
59 });
60
61 it('correctly implements TF-IDF ranking', () => {
In this example, "mango" has much higher IDF (is a higher-signal word) in the corpus than "apple", which appears in nearly every document. Therefore, documents that mention "mango" more times (relative to the length of the document) should rank higher.
66 assert.deepEqual(
67 search([
68 // matches
69 item('mango mango mango apple'),
70 item('mango apple mango apple'),
71 item('apple mango apple mango apple mango apple mango'),
72 item('apple apple apple apple apple apple apple apple mango'),
73 // rejects
74 item('apple apple apple'),
75 item('mango mango mango'),
76 item('applemango'),
77 item('mangoapple'),
78 item('apple 1'),
79 item('apple 2'),
80 item('apple 3'),
81 item('apple 4'),
82 item('apple 5'),
83 item('apple 6'),
84 item('apple 7'),
85 item('apple 8'),
86 item('apple 9'),
87 ], 'apple mango', x => x.name),
88 [
89 item('mango mango mango apple'),
90 item('mango apple mango apple'),
91 item('apple mango apple mango apple mango apple mango'),
92 item('apple apple apple apple apple apple apple apple mango'),
93 ]
94 );
95 });
96});
97
98describe('custom search-by predicates', () => {
99 it('default predicate is provided as x => x', () => {
100 assert.deepEqual(
101 search([
102 'university',
103 'uni of california',
104 'university of california',
105 ], 'uni of cali'),
106 [
107 'uni of california',
108 ]
109 );
110 });
111
112 it('accepts and uses a custom predicate', () => {
113 assert.deepEqual(search(ITEMS, 'sunil ee', x => x.name.split('').reverse().join('')), [
114 item('Linus Lee'),
115 ]);
116 });
117});
118
119describe('search modes', () => {
120 it('in mode: word, search does not match if any words are incomplete', () => {
121 assert.deepEqual(search(ITEMS, 'linu lee', x => x.name, {mode: 'word'}), []);
122 });
123
124 it('in mode: prefix, every query word may be incomplete', () => {
125 assert.deepEqual(search(ITEMS, 'linu le', x => x.name, {mode: 'prefix'}), [
126 item('Linus Lee'),
127 ]);
128 });
129
130 it('in mode: autocomplete, only the last query word may be incomplete', () => {
131 assert.deepEqual(search(ITEMS, 'linus le', x => x.name, {mode: 'autocomplete'}), [
132 item('Linus Lee'),
133 ]);
134 assert.deepEqual(search(ITEMS, 'linu le', x => x.name, {mode: 'autocomplete'}), []);
135 });
136});
137
138describe('case sensitivity', () => {
139 it('caseSensitive: true omits non-matching results', () => {
140 assert.deepEqual(search(ITEMS, 'l', x => x.name, {caseSensitive: true}), [
141 item('linuslee'),
142 item('linus is a person'),
143 ]);
144 });
145
146 it('caseSensitive: false includes case-insensitive results', () => {
147 assert.deepEqual(search(ITEMS, 'l', x => x.name, {caseSensitive: false}), [
148 item('Linus Lee'),
149 item('linuslee'),
150 item('linus is a person'),
151 ]);
152 });
153});
154
155