75 lines
3.8 KiB
TypeScript
75 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { parse } from './parse';
|
|
import { compile } from './compile';
|
|
import type { FieldType } from './types';
|
|
|
|
const FIELDS: Record<string, FieldType> = {
|
|
genre: 'text', title: 'text', plot: 'fulltext', type: 'enum', minutes: 'number', release_date: 'date'
|
|
};
|
|
|
|
describe('parse', () => {
|
|
it('parses a single quoted text atom as is', () => {
|
|
expect(parse('genre:"Horror"', FIELDS)).toEqual({ match: 'all', children: [{ field: 'genre', operator: 'is', value: 'Horror' }] });
|
|
});
|
|
|
|
it('parses NOT as isNot / notMatches by field type', () => {
|
|
expect(parse('NOT genre:"Horror"', FIELDS)).toEqual({ match: 'all', children: [{ field: 'genre', operator: 'isNot', value: 'Horror' }] });
|
|
expect(parse('NOT plot:"car"', FIELDS)).toEqual({ match: 'all', children: [{ field: 'plot', operator: 'notMatches', value: 'car' }] });
|
|
});
|
|
|
|
it('parses a quoted fulltext atom as matches', () => {
|
|
expect(parse('plot:"car"', FIELDS)).toEqual({ match: 'all', children: [{ field: 'plot', operator: 'matches', value: 'car' }] });
|
|
});
|
|
|
|
it('parses wildcard forms', () => {
|
|
expect(parse('title:*night*', FIELDS)).toEqual({ match: 'all', children: [{ field: 'title', operator: 'contains', value: 'night' }] });
|
|
expect(parse('title:The*', FIELDS)).toEqual({ match: 'all', children: [{ field: 'title', operator: 'startsWith', value: 'The' }] });
|
|
});
|
|
|
|
it('parses number/date ranges disambiguated by field type', () => {
|
|
expect(parse('minutes:{30 TO *}', FIELDS)).toEqual({ match: 'all', children: [{ field: 'minutes', operator: 'gt', value: '30' }] });
|
|
expect(parse('release_date:{* TO 2010-01-01}', FIELDS)).toEqual({ match: 'all', children: [{ field: 'release_date', operator: 'before', value: '2010-01-01' }] });
|
|
expect(parse('minutes:[30 TO 90]', FIELDS)).toEqual({ match: 'all', children: [{ field: 'minutes', operator: 'between', value: '30', value2: '90' }] });
|
|
expect(parse('minutes:42', FIELDS)).toEqual({ match: 'all', children: [{ field: 'minutes', operator: 'eq', value: '42' }] });
|
|
});
|
|
|
|
it('parses a nested group', () => {
|
|
expect(parse('type:"movie" AND (genre:"Horror" OR genre:"Thriller")', FIELDS)).toEqual({
|
|
match: 'all',
|
|
children: [
|
|
{ field: 'type', operator: 'is', value: 'movie' },
|
|
{ match: 'any', children: [
|
|
{ field: 'genre', operator: 'is', value: 'Horror' },
|
|
{ field: 'genre', operator: 'is', value: 'Thriller' }
|
|
] }
|
|
]
|
|
});
|
|
});
|
|
|
|
it('returns null on out-of-subset input', () => {
|
|
expect(parse('genre:"a" AND genre:"b" OR genre:"c"', FIELDS)).toBeNull(); // mixed AND/OR at one level
|
|
expect(parse('unknown_field:"x"', FIELDS)).toBeNull(); // unknown field
|
|
expect(parse('genre:"a" AND (type:"movie" AND (genre:"b"))', FIELDS)).toBeNull(); // two levels of nesting
|
|
expect(parse('title:jo~2', FIELDS)).toBeNull(); // fuzzy — not in subset
|
|
});
|
|
});
|
|
|
|
describe('parse: special-char round-trips', () => {
|
|
it('round-trips quoted values with lucene specials (compile∘parse)', () => {
|
|
for (const v of ['a*b', 'x y', 'a\\', 'has"quote', '(paren)', 'a AND b', 'a:b']) {
|
|
const g = { match: 'all' as const, children: [{ field: 'genre', operator: 'is' as const, value: v }] };
|
|
expect(parse(compile(g), FIELDS)).toEqual(g);
|
|
}
|
|
});
|
|
it('round-trips text contains/startsWith values with specials', () => {
|
|
for (const v of ['a*b', 'x y', 'plain']) {
|
|
const c = { match: 'all' as const, children: [{ field: 'title', operator: 'contains' as const, value: v }] };
|
|
expect(parse(compile(c), FIELDS)).toEqual(c);
|
|
}
|
|
for (const v of ['star*', 'x y', 'ab']) {
|
|
const s = { match: 'all' as const, children: [{ field: 'title', operator: 'startsWith' as const, value: v }] };
|
|
expect(parse(compile(s), FIELDS)).toEqual(s);
|
|
}
|
|
});
|
|
});
|