@e22m4u/js-repository 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {Service} from '@e22m4u/js-service';
2
- import {getValueByPath} from '../utils/index.js';
3
2
  import {InvalidArgumentError} from '../errors/index.js';
4
3
  import {OperatorClauseTool} from './operator-clause-tool.js';
4
+ import {getValueByPath, isDeepEqual, isPureObject} from '../utils/index.js';
5
5
 
6
6
  /**
7
7
  * Where clause tool.
@@ -75,8 +75,9 @@ export class WhereClauseTool extends Service {
75
75
  const andClause = whereClause[key];
76
76
  if (Array.isArray(andClause))
77
77
  return andClause.every(clause => this._createFilter(clause)(data));
78
- // OrClause (recursion)
79
- } else if (key === 'or' && key in whereClause) {
78
+ }
79
+ // OrClause (recursion)
80
+ else if (key === 'or' && key in whereClause) {
80
81
  const orClause = whereClause[key];
81
82
  if (Array.isArray(orClause))
82
83
  return orClause.some(clause => this._createFilter(clause)(data));
@@ -84,34 +85,6 @@ export class WhereClauseTool extends Service {
84
85
  // PropertiesClause (properties)
85
86
  const value = getValueByPath(data, key);
86
87
  const matcher = whereClause[key];
87
- // Property value is an array.
88
- if (Array.isArray(value)) {
89
- // {neq: ...}
90
- if (
91
- typeof matcher === 'object' &&
92
- matcher !== null &&
93
- 'neq' in matcher &&
94
- matcher.neq !== undefined
95
- ) {
96
- // The following condition is for the case where
97
- // we are querying with a neq filter, and when
98
- // the value is an empty array ([]).
99
- if (value.length === 0) return true;
100
- // The neq operator requires each element
101
- // of the array to be excluded.
102
- return value.every((el, index) => {
103
- const where = {};
104
- where[index] = matcher;
105
- return this._createFilter(where)({...value});
106
- });
107
- }
108
- // Requires one of an array elements to be match.
109
- return value.some((el, index) => {
110
- const where = {};
111
- where[index] = matcher;
112
- return this._createFilter(where)({...value});
113
- });
114
- }
115
88
  // Test property value.
116
89
  if (this._test(matcher, value)) return true;
117
90
  });
@@ -126,30 +99,55 @@ export class WhereClauseTool extends Service {
126
99
  * @returns {boolean}
127
100
  */
128
101
  _test(example, value) {
129
- // Test null.
102
+ // прямое сравнение
103
+ if (example === value) {
104
+ return true;
105
+ }
106
+ // условием является null
130
107
  if (example === null) {
131
108
  return value === null;
132
109
  }
133
- // Test undefined.
110
+ // условием является undefined
134
111
  if (example === undefined) {
135
112
  return value === undefined;
136
113
  }
137
- // Test RegExp.
138
- // noinspection ALL
114
+ // условием является регулярное выражение
139
115
  if (example instanceof RegExp) {
140
- if (typeof value === 'string') return !!value.match(example);
116
+ if (typeof value === 'string') {
117
+ return example.test(value);
118
+ }
119
+ // если значением является массив,
120
+ // то проверяется каждый элемент
121
+ if (Array.isArray(value)) {
122
+ return value.some(el => typeof el === 'string' && example.test(el));
123
+ }
141
124
  return false;
142
125
  }
143
- // Operator clause.
144
- if (typeof example === 'object') {
126
+ // условием является простой объект
127
+ if (isPureObject(example)) {
145
128
  const operatorsTest = this.getService(OperatorClauseTool).testAll(
146
129
  example,
147
130
  value,
148
131
  );
149
- if (operatorsTest !== undefined) return operatorsTest;
132
+ if (operatorsTest !== undefined) {
133
+ // особая логика для neq с массивами
134
+ // {hobbies: {neq: 'yoga'}}
135
+ // должно вернуть true для
136
+ // ['bicycle', 'meditation']
137
+ if ('neq' in example && Array.isArray(value)) {
138
+ return !value.some(el => isDeepEqual(el, example.neq));
139
+ }
140
+ return operatorsTest;
141
+ }
142
+ }
143
+ // значением является массив
144
+ if (Array.isArray(value)) {
145
+ // если один из элементов массива соответствует
146
+ // поиску, то возвращается true
147
+ const isElementMatched = value.some(el => isDeepEqual(el, example));
148
+ if (isElementMatched) return true;
150
149
  }
151
- // Not strict equality.
152
- return example == value;
150
+ return isDeepEqual(example, value);
153
151
  }
154
152
 
155
153
  /**
@@ -13,6 +13,7 @@ const OBJECTS = [
13
13
  hobbies: ['bicycle', 'yoga'],
14
14
  nickname: 'Spear',
15
15
  birthdate: '2002-04-14',
16
+ address: {city: 'New York', street: '5th Avenue'},
16
17
  },
17
18
  {
18
19
  id: 2,
@@ -22,6 +23,7 @@ const OBJECTS = [
22
23
  hobbies: ['yoga', 'meditation'],
23
24
  nickname: 'Flower',
24
25
  birthdate: '2002-01-12',
26
+ address: {city: 'London', street: 'Baker Street'},
25
27
  },
26
28
  {
27
29
  id: 3,
@@ -31,6 +33,7 @@ const OBJECTS = [
31
33
  hobbies: [],
32
34
  nickname: null,
33
35
  birthdate: '2002-03-01',
36
+ address: {city: 'Paris', street: 'Champs-Élysées'},
34
37
  },
35
38
  {
36
39
  id: 4,
@@ -39,6 +42,18 @@ const OBJECTS = [
39
42
  age: 32,
40
43
  hobbies: ['bicycle'],
41
44
  birthdate: '1991-06-24',
45
+ // нет nickname
46
+ address: {city: 'New York', street: 'Wall Street'},
47
+ },
48
+ {
49
+ id: 5,
50
+ name: 'Peter',
51
+ surname: 'Jones',
52
+ age: 45,
53
+ hobbies: ['fishing'],
54
+ birthdate: '1978-11-05',
55
+ // нет nickname
56
+ address: {city: 'New York', street: '5th Avenue'},
42
57
  },
43
58
  ];
44
59
 
@@ -154,32 +169,36 @@ describe('WhereClauseTool', function () {
154
169
 
155
170
  it('uses the "neq" operator to match non-equality', function () {
156
171
  const result = S.filter(OBJECTS, {name: {neq: 'John'}});
157
- expect(result).to.have.length(3);
172
+ expect(result).to.have.length(4);
158
173
  expect(result[0]).to.be.eql(OBJECTS[1]);
159
174
  expect(result[1]).to.be.eql(OBJECTS[2]);
160
175
  expect(result[2]).to.be.eql(OBJECTS[3]);
176
+ expect(result[3]).to.be.eql(OBJECTS[4]);
161
177
  });
162
178
 
163
179
  it('uses the "neq" operator to match an empty array', function () {
164
180
  const result = S.filter(OBJECTS, {hobbies: {neq: 'bicycle'}});
165
- expect(result).to.have.length(2);
181
+ expect(result).to.have.length(3);
166
182
  expect(result[0]).to.be.eql(OBJECTS[1]);
167
183
  expect(result[1]).to.be.eql(OBJECTS[2]);
184
+ expect(result[2]).to.be.eql(OBJECTS[4]);
168
185
  });
169
186
 
170
187
  it('uses the "gt" operator to compare values', function () {
171
188
  const result = S.filter(OBJECTS, {id: {gt: 2}});
172
- expect(result).to.have.length(2);
189
+ expect(result).to.have.length(3);
173
190
  expect(result[0]).to.be.eql(OBJECTS[2]);
174
191
  expect(result[1]).to.be.eql(OBJECTS[3]);
192
+ expect(result[2]).to.be.eql(OBJECTS[4]);
175
193
  });
176
194
 
177
195
  it('uses the "gte" operator to compare values', function () {
178
196
  const result = S.filter(OBJECTS, {id: {gte: 2}});
179
- expect(result).to.have.length(3);
197
+ expect(result).to.have.length(4);
180
198
  expect(result[0]).to.be.eql(OBJECTS[1]);
181
199
  expect(result[1]).to.be.eql(OBJECTS[2]);
182
200
  expect(result[2]).to.be.eql(OBJECTS[3]);
201
+ expect(result[3]).to.be.eql(OBJECTS[4]);
183
202
  });
184
203
 
185
204
  it('uses the "lt" operator to compare values', function () {
@@ -206,9 +225,10 @@ describe('WhereClauseTool', function () {
206
225
 
207
226
  it('uses the "nin" operator to compare values', function () {
208
227
  const result = S.filter(OBJECTS, {id: {nin: [2, 3]}});
209
- expect(result).to.have.length(2);
228
+ expect(result).to.have.length(3);
210
229
  expect(result[0]).to.be.eql(OBJECTS[0]);
211
230
  expect(result[1]).to.be.eql(OBJECTS[3]);
231
+ expect(result[2]).to.be.eql(OBJECTS[4]);
212
232
  });
213
233
 
214
234
  it('uses the "between" operator to compare values', function () {
@@ -228,36 +248,39 @@ describe('WhereClauseTool', function () {
228
248
 
229
249
  it('uses the "exists" operator to check non-existence', function () {
230
250
  const result = S.filter(OBJECTS, {nickname: {exists: false}});
231
- expect(result).to.have.length(1);
251
+ expect(result).to.have.length(2);
232
252
  expect(result[0]).to.be.eql(OBJECTS[3]);
253
+ expect(result[1]).to.be.eql(OBJECTS[4]);
233
254
  });
234
255
 
235
256
  it('uses the "like" operator to match by a substring', function () {
236
- const result = S.filter(OBJECTS, {name: {like: 'liv'}});
257
+ const result = S.filter(OBJECTS, {name: {like: '%liv%'}});
237
258
  expect(result).to.have.length(1);
238
259
  expect(result[0]).to.be.eql(OBJECTS[3]);
239
260
  });
240
261
 
241
262
  it('uses the "nlike" operator to exclude by a substring', function () {
242
- const result = S.filter(OBJECTS, {name: {nlike: 'liv'}});
243
- expect(result).to.have.length(3);
263
+ const result = S.filter(OBJECTS, {name: {nlike: '%liv%'}});
264
+ expect(result).to.have.length(4);
244
265
  expect(result[0]).to.be.eql(OBJECTS[0]);
245
266
  expect(result[1]).to.be.eql(OBJECTS[1]);
246
267
  expect(result[2]).to.be.eql(OBJECTS[2]);
268
+ expect(result[3]).to.be.eql(OBJECTS[4]);
247
269
  });
248
270
 
249
271
  it('uses the "ilike" operator to case-insensitively matching by a substring', function () {
250
- const result = S.filter(OBJECTS, {name: {ilike: 'LIV'}});
272
+ const result = S.filter(OBJECTS, {name: {ilike: '%LIV%'}});
251
273
  expect(result).to.have.length(1);
252
274
  expect(result[0]).to.be.eql(OBJECTS[3]);
253
275
  });
254
276
 
255
277
  it('uses the "nilike" operator to exclude case-insensitively by a substring', function () {
256
- const result = S.filter(OBJECTS, {name: {nilike: 'LIV'}});
257
- expect(result).to.have.length(3);
278
+ const result = S.filter(OBJECTS, {name: {nilike: '%LIV%'}});
279
+ expect(result).to.have.length(4);
258
280
  expect(result[0]).to.be.eql(OBJECTS[0]);
259
281
  expect(result[1]).to.be.eql(OBJECTS[1]);
260
282
  expect(result[2]).to.be.eql(OBJECTS[2]);
283
+ expect(result[3]).to.be.eql(OBJECTS[4]);
261
284
  });
262
285
 
263
286
  it('uses the "regexp" operator to compare values', function () {
@@ -274,8 +297,95 @@ describe('WhereClauseTool', function () {
274
297
 
275
298
  it('does not use undefined to match a null value', function () {
276
299
  const result = S.filter(OBJECTS, {nickname: undefined});
277
- expect(result).to.have.length(1);
300
+ expect(result).to.have.length(2);
278
301
  expect(result[0]).to.be.eql(OBJECTS[3]);
302
+ expect(result[1]).to.be.eql(OBJECTS[4]);
303
+ });
304
+
305
+ describe('advanced matching', function () {
306
+ it('combines multiple operators for one field using "and"', function () {
307
+ const result = S.filter(OBJECTS, {
308
+ and: [{age: {gt: 20}}, {age: {lt: 30}}],
309
+ });
310
+ expect(result).to.have.length(3);
311
+ expect(result.map(o => o.id)).to.eql([1, 2, 3]);
312
+ });
313
+
314
+ it('combines multiple operators for one field implicitly', function () {
315
+ const result = S.filter(OBJECTS, {age: {gt: 20, lt: 30}});
316
+ expect(result).to.have.length(3);
317
+ expect(result.map(o => o.id)).to.eql([1, 2, 3]);
318
+ });
319
+
320
+ it('uses dot notation to query nested objects', function () {
321
+ const result = S.filter(OBJECTS, {'address.city': 'New York'});
322
+ expect(result).to.have.length(3);
323
+ expect(result.map(o => o.id)).to.eql([1, 4, 5]);
324
+ });
325
+
326
+ it('uses dot notation combined with operators', function () {
327
+ const result = S.filter(OBJECTS, {
328
+ 'address.street': {like: '%Avenue%'},
329
+ });
330
+ expect(result).to.have.length(2);
331
+ expect(result.map(o => o.id)).to.eql([1, 5]);
332
+ });
333
+
334
+ it('matches an object by exact deep equality', function () {
335
+ const result = S.filter(OBJECTS, {
336
+ address: {city: 'New York', street: '5th Avenue'},
337
+ });
338
+ expect(result).to.have.length(2);
339
+ expect(result.map(o => o.id)).to.eql([1, 5]);
340
+ });
341
+
342
+ it('does not match an object if it has extra properties', function () {
343
+ const result = S.filter(OBJECTS, {
344
+ address: {city: 'New York'},
345
+ });
346
+ expect(result).to.have.length(0);
347
+ });
348
+
349
+ it('does match an object if property order is different', function () {
350
+ const result = S.filter(OBJECTS, {
351
+ address: {street: '5th Avenue', city: 'New York'},
352
+ });
353
+ expect(result).to.have.length(2);
354
+ expect(result[0].id).to.equal(1);
355
+ expect(result[1].id).to.equal(5);
356
+ });
357
+
358
+ it('matches an array by exact deep equality', function () {
359
+ const result = S.filter(OBJECTS, {
360
+ hobbies: ['bicycle', 'yoga'],
361
+ });
362
+ expect(result).to.have.length(1);
363
+ expect(result[0].id).to.equal(1);
364
+ });
365
+
366
+ it('does not match an array if order is different', function () {
367
+ const result = S.filter(OBJECTS, {
368
+ hobbies: ['yoga', 'bicycle'],
369
+ });
370
+ expect(result).to.have.length(0);
371
+ });
372
+
373
+ it('does not match an array if it contains extra items', function () {
374
+ const result = S.filter(OBJECTS, {
375
+ hobbies: ['bicycle'],
376
+ });
377
+ // Найдет только объект с id: 4, так как у него hobbies: ['bicycle']
378
+ expect(result).to.have.length(1);
379
+ expect(result[0].id).to.equal(4);
380
+ });
381
+
382
+ it('correctly combines multiple operators with dot notation in an "and" clause', function () {
383
+ const result = S.filter(OBJECTS, {
384
+ and: [{'address.city': 'New York'}, {age: {gt: 30}}],
385
+ });
386
+ expect(result).to.have.length(2);
387
+ expect(result.map(o => o.id)).to.eql([4, 5]);
388
+ });
279
389
  });
280
390
  });
281
391
 
@@ -6,6 +6,7 @@ export * from './singularize.js';
6
6
  export * from './is-deep-equal.js';
7
7
  export * from './get-ctor-name.js';
8
8
  export * from './is-pure-object.js';
9
+ export * from './like-to-regexp.js';
9
10
  export * from './string-to-regexp.js';
10
11
  export * from './get-value-by-path.js';
11
12
  export * from './transform-promise.js';
@@ -6,6 +6,7 @@ export * from './singularize.js';
6
6
  export * from './is-deep-equal.js';
7
7
  export * from './get-ctor-name.js';
8
8
  export * from './is-pure-object.js';
9
+ export * from './like-to-regexp.js';
9
10
  export * from './string-to-regexp.js';
10
11
  export * from './get-value-by-path.js';
11
12
  export * from './transform-promise.js';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Преобразует SQL LIKE-шаблон в объект RegExp.
3
+ *
4
+ * Экранирует специальные символы регулярных выражений,
5
+ * чтобы они обрабатывались как обычные символы, и преобразует
6
+ * SQL wildcards (% и _) в их эквиваленты в регулярных выражениях.
7
+ *
8
+ * @param pattern
9
+ * @param isCaseInsensitive
10
+ */
11
+ export function likeToRegexp(
12
+ pattern: string,
13
+ isCaseInsensitive?: boolean,
14
+ ): RegExp;
@@ -0,0 +1,57 @@
1
+ import {InvalidArgumentError} from '../errors/index.js';
2
+
3
+ /**
4
+ * Преобразует SQL LIKE-шаблон в объект RegExp.
5
+ *
6
+ * Экранирует специальные символы регулярных выражений,
7
+ * чтобы они обрабатывались как обычные символы, и преобразует
8
+ * SQL wildcards (% и _) в их эквиваленты в регулярных выражениях.
9
+ *
10
+ * @param {string} pattern
11
+ * @param {boolean} isCaseInsensitive
12
+ * @returns {RegExp}
13
+ */
14
+ export function likeToRegexp(pattern, isCaseInsensitive = false) {
15
+ if (typeof pattern !== 'string') {
16
+ throw new InvalidArgumentError(
17
+ 'The first argument of `likeToRegexp` ' +
18
+ 'should be a String, but %v was given.',
19
+ pattern,
20
+ );
21
+ }
22
+ // символы, которые имеют специальное значение
23
+ // в RegExp и должны быть экранированы
24
+ const regexSpecials = '-[]{}()*+?.\\^$|';
25
+ let regexString = '';
26
+ let isEscaping = false;
27
+ // экранирование
28
+ for (const char of pattern) {
29
+ if (isEscaping) {
30
+ // предыдущий символ был '\', значит текущий символ - литерал
31
+ regexString += regexSpecials.includes(char) ? `\\${char}` : char;
32
+ isEscaping = false;
33
+ } else if (char === '\\') {
34
+ // символ экранирования, следующий символ будет литералом
35
+ isEscaping = true;
36
+ } else if (char === '%') {
37
+ // SQL wildcard: любое количество любых символов
38
+ regexString += '.*';
39
+ } else if (char === '_') {
40
+ // SQL wildcard: ровно один любой символ
41
+ regexString += '.';
42
+ } else if (regexSpecials.includes(char)) {
43
+ // экранирование других специальных символов RegExp
44
+ regexString += `\\${char}`;
45
+ } else {
46
+ // обычный символ
47
+ regexString += char;
48
+ }
49
+ }
50
+ // если строка заканчивается на экранирующий символ,
51
+ // считаем его литералом.
52
+ if (isEscaping) {
53
+ regexString += '\\\\';
54
+ }
55
+ const flags = isCaseInsensitive ? 'i' : '';
56
+ return new RegExp(`^${regexString}$`, flags);
57
+ }
@@ -0,0 +1,143 @@
1
+ import {expect} from 'chai';
2
+ import {likeToRegexp} from './like-to-regexp.js';
3
+
4
+ describe('likeToRegexp', function () {
5
+ it('throws an error if the pattern is not a string', function () {
6
+ const error = v =>
7
+ 'The first argument of `likeToRegexp` ' +
8
+ `should be a String, but ${v} was given.`;
9
+ expect(() => likeToRegexp(123)).to.throw(error('123'));
10
+ expect(() => likeToRegexp(null)).to.throw(error('null'));
11
+ expect(() => likeToRegexp({})).to.throw(error('Object'));
12
+ });
13
+
14
+ describe('basic wildcards', function () {
15
+ it('should handle "%" as zero or more characters', function () {
16
+ const re = likeToRegexp('he%o');
17
+ expect(re.test('hello')).to.be.true;
18
+ expect(re.test('heo')).to.be.true;
19
+ expect(re.test('hexxxxo')).to.be.true;
20
+ expect(re.test('ahello')).to.be.false;
21
+ expect(re.test('hellob')).to.be.false;
22
+ });
23
+
24
+ it('should handle "_" as exactly one character', function () {
25
+ const re = likeToRegexp('he_lo');
26
+ expect(re.test('hello')).to.be.true;
27
+ expect(re.test('healo')).to.be.true;
28
+ expect(re.test('he_lo')).to.be.true;
29
+ expect(re.test('helo')).to.be.false;
30
+ expect(re.test('helllo')).to.be.false;
31
+ });
32
+
33
+ it('should handle multiple wildcards', function () {
34
+ const re = likeToRegexp('%_world%');
35
+ expect(re.test('hello_world_today')).to.be.true;
36
+ expect(re.test('a_world')).to.be.true;
37
+ expect(re.test('no_match')).to.be.false;
38
+ });
39
+ });
40
+
41
+ describe('case sensitivity', function () {
42
+ it('should be case-sensitive by default', function () {
43
+ const re = likeToRegexp('Hello%');
44
+ expect(re.test('Hello World')).to.be.true;
45
+ expect(re.test('hello World')).to.be.false;
46
+ });
47
+
48
+ it('should be case-insensitive when specified', function () {
49
+ const re = likeToRegexp('Hello%', true);
50
+ expect(re.test('Hello World')).to.be.true;
51
+ expect(re.test('hello World')).to.be.true;
52
+ expect(re.test('HELLO WORLD')).to.be.true;
53
+ });
54
+ });
55
+
56
+ describe('escaping', function () {
57
+ it('should handle escaped "%" as a literal character', function () {
58
+ const re = likeToRegexp('100\\%');
59
+ expect(re.test('100%')).to.be.true;
60
+ expect(re.test('100_')).to.be.false;
61
+ });
62
+
63
+ it('should handle escaped "_" as a literal character', function () {
64
+ const re = likeToRegexp('file\\_name');
65
+ expect(re.test('file_name')).to.be.true;
66
+ expect(re.test('filename')).to.be.false;
67
+ });
68
+
69
+ it('should handle escaped backslash as a literal backslash', function () {
70
+ const re = likeToRegexp('path\\\\to\\\\file');
71
+ expect(re.test('path\\to\\file')).to.be.true;
72
+ expect(re.test('pathtofile')).to.be.false;
73
+ });
74
+
75
+ it('should handle a trailing backslash as a literal backslash', function () {
76
+ const re = likeToRegexp('path\\');
77
+ expect(re.test('path\\')).to.be.true;
78
+ expect(re.test('path')).to.be.false;
79
+ });
80
+
81
+ it('should handle mixed escaping and wildcards', function () {
82
+ const re = likeToRegexp('%file\\_%.docx');
83
+ expect(re.test('my_awesome_file_v1.docx')).to.be.true;
84
+ expect(re.test('my_awesome_file-v1.docx')).to.be.false;
85
+ });
86
+ });
87
+
88
+ describe('escaping RegExp special character', function () {
89
+ it('should escape dots "." as literal dots', function () {
90
+ const re = likeToRegexp('v1.0');
91
+ expect(re.test('v1.0')).to.be.true;
92
+ expect(re.test('v1_0')).to.be.false;
93
+ });
94
+
95
+ it('should escape parentheses "()" as literals', function () {
96
+ const re = likeToRegexp('file(1)');
97
+ expect(re.test('file(1)')).to.be.true;
98
+ expect(re.test('file1')).to.be.false;
99
+ });
100
+
101
+ it('should escape plus signs "+"', function () {
102
+ const re = likeToRegexp('C++');
103
+ expect(re.test('C++')).to.be.true;
104
+ expect(re.test('C+')).to.be.false;
105
+ });
106
+
107
+ it('should escape question marks "?"', function () {
108
+ const re = likeToRegexp('Are you sure?');
109
+ expect(re.test('Are you sure?')).to.be.true;
110
+ expect(re.test('Are you sure')).to.be.false;
111
+ });
112
+
113
+ it('should escape curly braces "{}"', function () {
114
+ const re = likeToRegexp('{id}');
115
+ expect(re.test('{id}')).to.be.true;
116
+ expect(re.test('id')).to.be.false;
117
+ });
118
+
119
+ it('should handle a complex string with multiple special characters', function () {
120
+ const pattern = 'docs\\(v1.0\\)/%.txt+';
121
+ const re = likeToRegexp(pattern);
122
+ expect(re.test('docs(v1.0)/document_v2.txt+')).to.be.true;
123
+ expect(re.test('docs(v1.0)/document_v2.txt')).to.be.false;
124
+ });
125
+ });
126
+
127
+ describe('full pattern matching', function () {
128
+ it('should match the entire string, not just a part of it', function () {
129
+ const re = likeToRegexp('world');
130
+ expect(re.test('hello world')).to.be.false;
131
+ expect(re.test('world today')).to.be.false;
132
+ expect(re.test('world')).to.be.true;
133
+ });
134
+
135
+ it('should match the entire string when using wildcards at boundaries', function () {
136
+ const re = likeToRegexp('%world%');
137
+ expect(re.test('hello world')).to.be.true;
138
+ expect(re.test('world today')).to.be.true;
139
+ expect(re.test('hello world today')).to.be.true;
140
+ expect(re.test('world')).to.be.true;
141
+ });
142
+ });
143
+ });
@@ -9,14 +9,5 @@ export function stringToRegexp(pattern, flags = undefined) {
9
9
  if (pattern instanceof RegExp) {
10
10
  return new RegExp(pattern, flags);
11
11
  }
12
- let regex = '';
13
- for (let i = 0, n = pattern.length; i < n; i++) {
14
- const char = pattern.charAt(i);
15
- if (char === '%') {
16
- regex += '.*';
17
- } else {
18
- regex += char;
19
- }
20
- }
21
- return new RegExp(regex, flags);
12
+ return new RegExp(pattern, flags);
22
13
  }
@@ -2,34 +2,37 @@ import {expect} from 'chai';
2
2
  import {stringToRegexp} from './string-to-regexp.js';
3
3
 
4
4
  describe('stringToRegexp', function () {
5
- it('returns RegExp from a given string', function () {
5
+ it('should return RegExp from a given string', function () {
6
6
  expect(stringToRegexp('value').test('value')).to.be.true;
7
7
  expect(stringToRegexp('val.+').test('value')).to.be.true;
8
- expect(stringToRegexp('%alu%').test('value')).to.be.true;
9
8
  expect(stringToRegexp('val*').test('value')).to.be.true;
10
9
  });
11
10
 
12
- it('uses case-sensitive mode by default', function () {
11
+ it('should uses case-sensitive mode by default', function () {
13
12
  expect(stringToRegexp('value').test('VALUE')).to.be.false;
14
13
  expect(stringToRegexp('val.+').test('VALUE')).to.be.false;
15
14
  expect(stringToRegexp('%alu%').test('VALUE')).to.be.false;
16
15
  expect(stringToRegexp('val*').test('VALUE')).to.be.false;
17
16
  });
18
17
 
19
- it('uses given flags in a new RegExp', function () {
18
+ it('should uses given flags in a new RegExp', function () {
20
19
  expect(stringToRegexp('value', 'i').test('VALUE')).to.be.true;
21
20
  expect(stringToRegexp('val.+', 'i').test('VALUE')).to.be.true;
22
- expect(stringToRegexp('%alu%', 'i').test('VALUE')).to.be.true;
23
21
  expect(stringToRegexp('val*', 'i').test('VALUE')).to.be.true;
24
22
  });
25
23
 
26
- it('returns RegExp from a given RegExp', function () {
24
+ it('should return RegExp from a given RegExp', function () {
27
25
  const regExp = new RegExp('value');
28
26
  expect(stringToRegexp(regExp).test('value')).to.be.true;
29
27
  });
30
28
 
31
- it('overrides flags of a given RegExp', function () {
29
+ it('should overrides flags of a given RegExp', function () {
32
30
  const regExp = new RegExp('value');
33
31
  expect(stringToRegexp(regExp, 'i').test('VALUE')).to.be.true;
34
32
  });
33
+
34
+ it('should not replace "%" and "_" symbols as SQL-like wildcard', function () {
35
+ const res = stringToRegexp('%alu_');
36
+ expect(res).to.be.eql(/%alu_/);
37
+ });
35
38
  });