diff --git a/shared/src/util/searchUtil.test.ts b/shared/src/util/searchUtil.test.ts index 4dde4e65..ab7699a8 100644 --- a/shared/src/util/searchUtil.test.ts +++ b/shared/src/util/searchUtil.test.ts @@ -627,4 +627,26 @@ describe('searchFilterToString', () => { const request = parsedSearchToRequest(query); expect(searchFilterToString(request)).toEqual('genre in ["comedy"]'); }); + + test('starts with renders as < not literal "starts with"', () => { + const filter = { + type: 'value', + fieldSpec: { + key: 'title', + name: 'title', + op: 'starts with', + type: 'string', + value: ['The'], + }, + } satisfies SearchFilter; + + expect(searchFilterToString(filter)).toEqual('title < "The"'); + }); + + test('round-trips starts with through parse and stringify', () => { + const input = 'title < "The"'; + const query = parseAndCheckExpression(input); + const request = parsedSearchToRequest(query); + expect(searchFilterToString(request)).toEqual(input); + }); }); diff --git a/shared/src/util/searchUtil.ts b/shared/src/util/searchUtil.ts index 074dc0fc..5aa856db 100644 --- a/shared/src/util/searchUtil.ts +++ b/shared/src/util/searchUtil.ts @@ -234,7 +234,16 @@ const SearchExpressionLexer = new Lexer({ defaultMode: 'normalMode', }); -const StringOps = ['=', '!=', '<', '<=', 'in', 'not in', 'contains', 'not contains'] as const; +const StringOps = [ + '=', + '!=', + '<', + '<=', + 'in', + 'not in', + 'contains', + 'not contains', +] as const; type StringOps = TupleToUnion; const NumericOps = ['=', '!=', '<', '<=', '>', '>=', 'between'] as const; type NumericOps = TupleToUnion; @@ -364,6 +373,7 @@ const indexOperatorToSyntax: Dictionary = { contains: '~', 'not contains': '!~', to: 'between', + 'starts with': '<', }; function normalizeReleaseDate(value: string) {