| 
							
							
							
						 |  |  | @@ -1,12 +1,274 @@ | 
		
	
		
			
				|  |  |  |  | // @ts-nocheck | 
		
	
		
			
				|  |  |  |  | // There are many issues with the types of the parser e.g. "parse" function returns "Expression" | 
		
	
		
			
				|  |  |  |  | // but we access properties like "subExpressions" which is not defined in the "Expression" class. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | import AndExp from "../../src/services/search/expressions/and.js"; | 
		
	
		
			
				|  |  |  |  | import AttributeExistsExp from "../../src/services/search/expressions/attribute_exists.js"; | 
		
	
		
			
				|  |  |  |  | import Expression from "../../src/services/search/expressions/expression.js"; | 
		
	
		
			
				|  |  |  |  | import LabelComparisonExp from "../../src/services/search/expressions/label_comparison.js"; | 
		
	
		
			
				|  |  |  |  | import NotExp from "../../src/services/search/expressions/not.js"; | 
		
	
		
			
				|  |  |  |  | import NoteContentFulltextExp from "../../src/services/search/expressions/note_content_fulltext.js"; | 
		
	
		
			
				|  |  |  |  | import NoteFlatTextExp from "../../src/services/search/expressions/note_flat_text.js"; | 
		
	
		
			
				|  |  |  |  | import OrExp from "../../src/services/search/expressions/or.js"; | 
		
	
		
			
				|  |  |  |  | import OrderByAndLimitExp from "../../src/services/search/expressions/order_by_and_limit.js"; | 
		
	
		
			
				|  |  |  |  | import PropertyComparisonExp from "../../src/services/search/expressions/property_comparison.js"; | 
		
	
		
			
				|  |  |  |  | import SearchContext from "../../src/services/search/search_context.js"; | 
		
	
		
			
				|  |  |  |  | import parse from "../../src/services/search/services/parse.js"; | 
		
	
		
			
				|  |  |  |  | import { default as parseInternal, type ParseOpts } from "../../src/services/search/services/parse.js"; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | function tokens(toks: Array<string>, cur = 0): Array<any> { | 
		
	
		
			
				|  |  |  |  | describe("Parser", () => { | 
		
	
		
			
				|  |  |  |  |     it("fulltext parser without content", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello", "hi"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expectExpression(rootExp.subExpressions[0], PropertyComparisonExp); | 
		
	
		
			
				|  |  |  |  |         const orExp = expectExpression(rootExp.subExpressions[2], OrExp); | 
		
	
		
			
				|  |  |  |  |         const flatTextExp = expectExpression(orExp.subExpressions[0], NoteFlatTextExp); | 
		
	
		
			
				|  |  |  |  |         expect(flatTextExp.tokens).toEqual(["hello", "hi"]); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("fulltext parser with content", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello", "hi"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const orExp = expectExpression(rootExp.subExpressions[2], OrExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const firstSub = expectExpression(orExp.subExpressions[0], NoteFlatTextExp); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.tokens).toEqual(["hello", "hi"]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const secondSub = expectExpression(orExp.subExpressions[1], NoteContentFulltextExp); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.tokens).toEqual(["hello", "hi"]); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label comparison", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#mylabel", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |         const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |         expect(labelComparisonExp.attributeType).toEqual("label"); | 
		
	
		
			
				|  |  |  |  |         expect(labelComparisonExp.attributeName).toEqual("mylabel"); | 
		
	
		
			
				|  |  |  |  |         expect(labelComparisonExp.comparator).toBeTruthy(); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple attribute negation", () => { | 
		
	
		
			
				|  |  |  |  |         let rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#!mylabel"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |         let notExp = expectExpression(rootExp.subExpressions[2], NotExp); | 
		
	
		
			
				|  |  |  |  |         let attributeExistsExp = expectExpression(notExp.subExpression, AttributeExistsExp); | 
		
	
		
			
				|  |  |  |  |         expect(attributeExistsExp.attributeType).toEqual("label"); | 
		
	
		
			
				|  |  |  |  |         expect(attributeExistsExp.attributeName).toEqual("mylabel"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["~!myrelation"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |         notExp = expectExpression(rootExp.subExpressions[2], NotExp); | 
		
	
		
			
				|  |  |  |  |         attributeExistsExp = expectExpression(notExp.subExpression, AttributeExistsExp); | 
		
	
		
			
				|  |  |  |  |         expect(attributeExistsExp.attributeType).toEqual("relation"); | 
		
	
		
			
				|  |  |  |  |         expect(attributeExistsExp.attributeName).toEqual("myrelation"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label AND", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const andExp = expectExpression(rootExp.subExpressions[2], AndExp); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = expectSubexpressions(andExp, LabelComparisonExp, LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label AND without explicit AND", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const andExp = expectExpression(rootExp.subExpressions[2], AndExp); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = expectSubexpressions(andExp, LabelComparisonExp, LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label OR", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const orExp = expectExpression(rootExp.subExpressions[2], OrExp); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = expectSubexpressions(orExp, LabelComparisonExp, LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("fulltext and simple label", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#mylabel", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const [firstSub, _, thirdSub, fourth] = expectSubexpressions(rootExp, PropertyComparisonExp, undefined, OrExp, LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.propertyName).toEqual("isArchived"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const noteFlatTextExp = expectExpression(thirdSub.subExpressions[0], NoteFlatTextExp); | 
		
	
		
			
				|  |  |  |  |         expect(noteFlatTextExp.tokens).toEqual(["hello"]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(fourth.attributeName).toEqual("mylabel"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("label sub-expression", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const orExp = expectExpression(rootExp.subExpressions[2], OrExp); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = expectSubexpressions(orExp, LabelComparisonExp, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const [firstSubSub, secondSubSub] = expectSubexpressions(secondSub, LabelComparisonExp, LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |         expect(firstSubSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSubSub.attributeName).toEqual("third"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("label sub-expression without explicit operator", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const andExp = expectExpression(rootExp.subExpressions[2], AndExp); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub, thirdSub] = expectSubexpressions(andExp, AttributeExistsExp, OrExp, AttributeExistsExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const [firstSubSub, secondSubSub] = expectSubexpressions(secondSub, AttributeExistsExp, AttributeExistsExp); | 
		
	
		
			
				|  |  |  |  |         expect(firstSubSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSubSub.attributeName).toEqual("third"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(thirdSub.attributeName).toEqual("fourth"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("parses limit without order by", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello", "hi"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext({ limit: 2 }) | 
		
	
		
			
				|  |  |  |  |         }, OrderByAndLimitExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.limit).toBe(2); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpression).toBeInstanceOf(AndExp); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | describe("Invalid expressions", () => { | 
		
	
		
			
				|  |  |  |  |     it("incomplete comparison", () => { | 
		
	
		
			
				|  |  |  |  |         const searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parseInternal({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "="]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual('Misplaced or incomplete expression "="'); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("comparison between labels is impossible", () => { | 
		
	
		
			
				|  |  |  |  |         let searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |         searchContext.originalQuery = "#first = #second"; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parseInternal({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "#second"]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |         searchContext.originalQuery = "#first = note.relations.second"; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parseInternal({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [ | 
		
	
		
			
				|  |  |  |  |                 { token: "#first", inQuotes: false }, | 
		
	
		
			
				|  |  |  |  |                 { token: "=", inQuotes: false }, | 
		
	
		
			
				|  |  |  |  |                 { token: "#second", inQuotes: true } | 
		
	
		
			
				|  |  |  |  |             ], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }, AndExp); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp); | 
		
	
		
			
				|  |  |  |  |         expect(labelComparisonExp.attributeType).toEqual("label"); | 
		
	
		
			
				|  |  |  |  |         expect(labelComparisonExp.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |         expect(labelComparisonExp.comparator).toBeTruthy(); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("searching by relation without note property", () => { | 
		
	
		
			
				|  |  |  |  |         const searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parseInternal({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["~first", "=", "text", "-", "abc"]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""'); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | type ClassType<T extends Expression> = new (...args: any[]) => T; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | function tokens(toks: (string | string[])[], cur = 0): Array<any> { | 
		
	
		
			
				|  |  |  |  |     return toks.map((arg) => { | 
		
	
		
			
				|  |  |  |  |         if (Array.isArray(arg)) { | 
		
	
		
			
				|  |  |  |  |             return tokens(arg, cur); | 
		
	
	
		
			
				
					
					|  |  |  | @@ -23,293 +285,71 @@ function tokens(toks: Array<string>, cur = 0): Array<any> { | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | function assertIsArchived(exp: Expression) { | 
		
	
		
			
				|  |  |  |  |     expect(exp.constructor.name).toEqual("PropertyComparisonExp"); | 
		
	
		
			
				|  |  |  |  | function assertIsArchived(_exp: Expression) { | 
		
	
		
			
				|  |  |  |  |     const exp = expectExpression(_exp, PropertyComparisonExp); | 
		
	
		
			
				|  |  |  |  |     expect(exp.propertyName).toEqual("isArchived"); | 
		
	
		
			
				|  |  |  |  |     expect(exp.operator).toEqual("="); | 
		
	
		
			
				|  |  |  |  |     expect(exp.comparedValue).toEqual("false"); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | describe("Parser", () => { | 
		
	
		
			
				|  |  |  |  |     it("fulltext parser without content", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello", "hi"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext({ excludeArchived: true }) | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  | /** | 
		
	
		
			
				|  |  |  |  |  * Parses the corresponding {@link Expression} from plain text, while also expecting the resulting expression to be of the given type. | 
		
	
		
			
				|  |  |  |  |  * | 
		
	
		
			
				|  |  |  |  |  * @param opts the options for parsing. | 
		
	
		
			
				|  |  |  |  |  * @param type the expected type of the expression. | 
		
	
		
			
				|  |  |  |  |  * @returns the expression typecasted to the expected type. | 
		
	
		
			
				|  |  |  |  |  */ | 
		
	
		
			
				|  |  |  |  | function parse<T extends Expression>(opts: ParseOpts, type: ClassType<T>) { | 
		
	
		
			
				|  |  |  |  |     return expectExpression(parseInternal(opts), type); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpressions[0].constructor.name).toEqual("NoteFlatTextExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpressions[0].tokens).toEqual(["hello", "hi"]); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | /** | 
		
	
		
			
				|  |  |  |  |  * Expects the given {@link Expression} to be of the given type. | 
		
	
		
			
				|  |  |  |  |  * | 
		
	
		
			
				|  |  |  |  |  * @param exp an instance of an {@link Expression}. | 
		
	
		
			
				|  |  |  |  |  * @param type a type class such as {@link AndExp}, {@link OrExp}, etc. | 
		
	
		
			
				|  |  |  |  |  * @returns the same expression typecasted to the expected type. | 
		
	
		
			
				|  |  |  |  |  */ | 
		
	
		
			
				|  |  |  |  | function expectExpression<T extends Expression>(exp: Expression, type: ClassType<T>) { | 
		
	
		
			
				|  |  |  |  |     expect(exp).toBeInstanceOf(type); | 
		
	
		
			
				|  |  |  |  |     return exp as T; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("fulltext parser with content", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello", "hi"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const subs = rootExp.subExpressions[2].subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(subs[0].constructor.name).toEqual("NoteFlatTextExp"); | 
		
	
		
			
				|  |  |  |  |         expect(subs[0].tokens).toEqual(["hello", "hi"]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(subs[1].constructor.name).toEqual("NoteContentFulltextExp"); | 
		
	
		
			
				|  |  |  |  |         expect(subs[1].tokens).toEqual(["hello", "hi"]); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label comparison", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#mylabel", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].attributeType).toEqual("label"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].attributeName).toEqual("mylabel"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].comparator).toBeTruthy(); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple attribute negation", () => { | 
		
	
		
			
				|  |  |  |  |         let rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#!mylabel"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("NotExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual("AttributeExistsExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual("label"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual("mylabel"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["~!myrelation"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("NotExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual("AttributeExistsExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual("relation"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual("myrelation"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label AND", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext(true) | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label AND without explicit AND", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("simple label OR", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("fulltext and simple label", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: tokens(["hello"]), | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#mylabel", "=", "text"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext({ excludeArchived: true }) | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub, thirdSub, fourth] = rootExp.subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.constructor.name).toEqual("PropertyComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.propertyName).toEqual("isArchived"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(thirdSub.constructor.name).toEqual("OrExp"); | 
		
	
		
			
				|  |  |  |  |         expect(thirdSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp"); | 
		
	
		
			
				|  |  |  |  |         expect(thirdSub.subExpressions[0].tokens).toEqual(["hello"]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(fourth.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(fourth.attributeName).toEqual("mylabel"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("label sub-expression", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSubSub, secondSubSub] = secondSub.subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSubSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSubSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSubSub.attributeName).toEqual("third"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("label sub-expression without explicit operator", () => { | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]), | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[2].subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.constructor.name).toEqual("AttributeExistsExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSub.attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSub.constructor.name).toEqual("OrExp"); | 
		
	
		
			
				|  |  |  |  |         const [firstSubSub, secondSubSub] = secondSub.subExpressions; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(firstSubSub.constructor.name).toEqual("AttributeExistsExp"); | 
		
	
		
			
				|  |  |  |  |         expect(firstSubSub.attributeName).toEqual("second"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(secondSubSub.constructor.name).toEqual("AttributeExistsExp"); | 
		
	
		
			
				|  |  |  |  |         expect(secondSubSub.attributeName).toEqual("third"); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(thirdSub.constructor.name).toEqual("AttributeExistsExp"); | 
		
	
		
			
				|  |  |  |  |         expect(thirdSub.attributeName).toEqual("fourth"); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | describe("Invalid expressions", () => { | 
		
	
		
			
				|  |  |  |  |     it("incomplete comparison", () => { | 
		
	
		
			
				|  |  |  |  |         const searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "="]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual('Misplaced or incomplete expression "="'); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("comparison between labels is impossible", () => { | 
		
	
		
			
				|  |  |  |  |         let searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |         searchContext.originalQuery = "#first = #second"; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "#second"]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |         searchContext.originalQuery = "#first = note.relations.second"; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         const rootExp = parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: [ | 
		
	
		
			
				|  |  |  |  |                 { token: "#first", inQuotes: false }, | 
		
	
		
			
				|  |  |  |  |                 { token: "=", inQuotes: false }, | 
		
	
		
			
				|  |  |  |  |                 { token: "#second", inQuotes: true } | 
		
	
		
			
				|  |  |  |  |             ], | 
		
	
		
			
				|  |  |  |  |             searchContext: new SearchContext() | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.constructor.name).toEqual("AndExp"); | 
		
	
		
			
				|  |  |  |  |         assertIsArchived(rootExp.subExpressions[0]); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].attributeType).toEqual("label"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].attributeName).toEqual("first"); | 
		
	
		
			
				|  |  |  |  |         expect(rootExp.subExpressions[2].comparator).toBeTruthy(); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     it("searching by relation without note property", () => { | 
		
	
		
			
				|  |  |  |  |         const searchContext = new SearchContext(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         parse({ | 
		
	
		
			
				|  |  |  |  |             fulltextTokens: [], | 
		
	
		
			
				|  |  |  |  |             expressionTokens: tokens(["~first", "=", "text", "-", "abc"]), | 
		
	
		
			
				|  |  |  |  |             searchContext | 
		
	
		
			
				|  |  |  |  |         }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""'); | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | }); | 
		
	
		
			
				|  |  |  |  | /** | 
		
	
		
			
				|  |  |  |  |  * For an {@link AndExp}, it goes through all its subexpressions (up to fourth) and checks their type and returns them as a typecasted array. | 
		
	
		
			
				|  |  |  |  |  * Each subexpression can have their own type. | 
		
	
		
			
				|  |  |  |  |  * | 
		
	
		
			
				|  |  |  |  |  * @param exp the expression containing one or more subexpressions. | 
		
	
		
			
				|  |  |  |  |  * @param firstType the type of the first subexpression. | 
		
	
		
			
				|  |  |  |  |  * @param secondType the type of the second subexpression. | 
		
	
		
			
				|  |  |  |  |  * @param thirdType the type of the third subexpression. | 
		
	
		
			
				|  |  |  |  |  * @param fourthType the type of the fourth subexpression. | 
		
	
		
			
				|  |  |  |  |  * @returns an array of all the subexpressions (in order) typecasted to their expected type. | 
		
	
		
			
				|  |  |  |  |  */ | 
		
	
		
			
				|  |  |  |  | function expectSubexpressions<FirstT extends Expression, | 
		
	
		
			
				|  |  |  |  |                               SecondT extends Expression, | 
		
	
		
			
				|  |  |  |  |                               ThirdT extends Expression, | 
		
	
		
			
				|  |  |  |  |                               FourthT extends Expression>( | 
		
	
		
			
				|  |  |  |  |                                 exp: AndExp, | 
		
	
		
			
				|  |  |  |  |                                 firstType: ClassType<FirstT>, | 
		
	
		
			
				|  |  |  |  |                                 secondType?: ClassType<SecondT>, | 
		
	
		
			
				|  |  |  |  |                                 thirdType?: ClassType<ThirdT>, | 
		
	
		
			
				|  |  |  |  |                                 fourthType?: ClassType<FourthT>): [ FirstT, SecondT, ThirdT, FourthT ] | 
		
	
		
			
				|  |  |  |  | { | 
		
	
		
			
				|  |  |  |  |     expectExpression(exp.subExpressions[0], firstType); | 
		
	
		
			
				|  |  |  |  |     if (secondType) { | 
		
	
		
			
				|  |  |  |  |         expectExpression(exp.subExpressions[1], secondType); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     if (thirdType) { | 
		
	
		
			
				|  |  |  |  |         expectExpression(exp.subExpressions[2], thirdType); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     if (fourthType) { | 
		
	
		
			
				|  |  |  |  |         expectExpression(exp.subExpressions[3], fourthType); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return [ | 
		
	
		
			
				|  |  |  |  |         exp.subExpressions[0] as FirstT, | 
		
	
		
			
				|  |  |  |  |         exp.subExpressions[1] as SecondT, | 
		
	
		
			
				|  |  |  |  |         exp.subExpressions[2] as ThirdT, | 
		
	
		
			
				|  |  |  |  |         exp.subExpressions[3] as FourthT | 
		
	
		
			
				|  |  |  |  |     ] | 
		
	
		
			
				|  |  |  |  | } | 
		
	
	
		
			
				
					
					| 
							
							
							
						 |  |  |   |