2024-05-08 23:59:11 +02:00
|
|
|
// @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.
|
|
|
|
|
2024-07-18 21:35:17 +03:00
|
|
|
import Expression from "../../src/services/search/expressions/expression.js";
|
|
|
|
import SearchContext from "../../src/services/search/search_context.js";
|
|
|
|
import parse from "../../src/services/search/services/parse.js";
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
function tokens(toks: Array<string>, cur = 0): Array<any> {
|
|
|
|
return toks.map((arg) => {
|
|
|
|
if (Array.isArray(arg)) {
|
|
|
|
return tokens(arg, cur);
|
|
|
|
} else {
|
|
|
|
cur += arg.length;
|
|
|
|
|
|
|
|
return {
|
|
|
|
token: arg,
|
|
|
|
inQuotes: false,
|
|
|
|
startIndex: cur - arg.length,
|
2025-01-09 18:07:02 +02:00
|
|
|
endIndex: cur - 1
|
2024-05-08 23:59:11 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertIsArchived(exp: Expression) {
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(exp.constructor.name).toEqual("PropertyComparisonExp");
|
|
|
|
expect(exp.propertyName).toEqual("isArchived");
|
|
|
|
expect(exp.operator).toEqual("=");
|
|
|
|
expect(exp.comparedValue).toEqual("false");
|
2024-05-08 23:59:11 +02:00
|
|
|
}
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
describe("Parser", () => {
|
|
|
|
it("fulltext parser without content", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
2025-01-09 18:07:02 +02:00
|
|
|
fulltextTokens: tokens(["hello", "hi"]),
|
2024-05-08 23:59:11 +02:00
|
|
|
expressionTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
searchContext: new SearchContext({ excludeArchived: true })
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
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"]);
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("fulltext parser with content", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
2025-01-09 18:07:02 +02:00
|
|
|
fulltextTokens: tokens(["hello", "hi"]),
|
2024-05-08 23:59:11 +02:00
|
|
|
expressionTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
const subs = rootExp.subExpressions[2].subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(subs[0].constructor.name).toEqual("NoteFlatTextExp");
|
|
|
|
expect(subs[0].tokens).toEqual(["hello", "hi"]);
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(subs[1].constructor.name).toEqual("NoteContentFulltextExp");
|
|
|
|
expect(subs[1].tokens).toEqual(["hello", "hi"]);
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("simple label comparison", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#mylabel", "=", "text"]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(rootExp.subExpressions[2].attributeType).toEqual("label");
|
|
|
|
expect(rootExp.subExpressions[2].attributeName).toEqual("mylabel");
|
2024-05-08 23:59:11 +02:00
|
|
|
expect(rootExp.subExpressions[2].comparator).toBeTruthy();
|
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("simple attribute negation", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
let rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#!mylabel"]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
2025-01-09 18:07:02 +02:00
|
|
|
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");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["~!myrelation"]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
2025-01-09 18:07:02 +02:00
|
|
|
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");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("simple label AND", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
|
|
|
|
searchContext: new SearchContext(true)
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(firstSub.attributeName).toEqual("first");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(secondSub.attributeName).toEqual("second");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("simple label AND without explicit AND", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(firstSub.attributeName).toEqual("first");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(secondSub.attributeName).toEqual("second");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("simple label OR", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(firstSub.attributeName).toEqual("first");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(secondSub.attributeName).toEqual("second");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("fulltext and simple label", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
2025-01-09 18:07:02 +02:00
|
|
|
fulltextTokens: tokens(["hello"]),
|
|
|
|
expressionTokens: tokens(["#mylabel", "=", "text"]),
|
|
|
|
searchContext: new SearchContext({ excludeArchived: true })
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSub, secondSub, thirdSub, fourth] = rootExp.subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
|
|
|
|
expect(firstSub.propertyName).toEqual("isArchived");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(thirdSub.constructor.name).toEqual("OrExp");
|
|
|
|
expect(thirdSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
|
|
|
|
expect(thirdSub.subExpressions[0].tokens).toEqual(["hello"]);
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(fourth.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(fourth.attributeName).toEqual("mylabel");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("label sub-expression", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(firstSub.attributeName).toEqual("first");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSub.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSubSub, secondSubSub] = secondSub.subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSubSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(firstSubSub.attributeName).toEqual("second");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(secondSubSub.attributeName).toEqual("third");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("label sub-expression without explicit operator", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
|
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[2].subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSub.constructor.name).toEqual("AttributeExistsExp");
|
|
|
|
expect(firstSub.attributeName).toEqual("first");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSub.constructor.name).toEqual("OrExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
const [firstSubSub, secondSubSub] = secondSub.subExpressions;
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(firstSubSub.constructor.name).toEqual("AttributeExistsExp");
|
|
|
|
expect(firstSubSub.attributeName).toEqual("second");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(secondSubSub.constructor.name).toEqual("AttributeExistsExp");
|
|
|
|
expect(secondSubSub.attributeName).toEqual("third");
|
2024-05-08 23:59:11 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(thirdSub.constructor.name).toEqual("AttributeExistsExp");
|
|
|
|
expect(thirdSub.attributeName).toEqual("fourth");
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
describe("Invalid expressions", () => {
|
|
|
|
it("incomplete comparison", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const searchContext = new SearchContext();
|
|
|
|
|
|
|
|
parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "="]),
|
|
|
|
searchContext
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
expect(searchContext.error).toEqual('Misplaced or incomplete expression "="');
|
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("comparison between labels is impossible", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
let searchContext = new SearchContext();
|
2025-01-09 18:07:02 +02:00
|
|
|
searchContext.originalQuery = "#first = #second";
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "=", "#second"]),
|
|
|
|
searchContext
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`);
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
searchContext = new SearchContext();
|
2025-01-09 18:07:02 +02:00
|
|
|
searchContext.originalQuery = "#first = note.relations.second";
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]),
|
|
|
|
searchContext
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`);
|
2024-05-08 23:59:11 +02:00
|
|
|
|
|
|
|
const rootExp = parse({
|
|
|
|
fulltextTokens: [],
|
|
|
|
expressionTokens: [
|
2025-01-09 18:07:02 +02:00
|
|
|
{ token: "#first", inQuotes: false },
|
|
|
|
{ token: "=", inQuotes: false },
|
|
|
|
{ token: "#second", inQuotes: true }
|
2024-05-08 23:59:11 +02:00
|
|
|
],
|
2025-01-09 18:07:02 +02:00
|
|
|
searchContext: new SearchContext()
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
2024-05-08 23:59:11 +02:00
|
|
|
assertIsArchived(rootExp.subExpressions[0]);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp");
|
|
|
|
expect(rootExp.subExpressions[2].attributeType).toEqual("label");
|
|
|
|
expect(rootExp.subExpressions[2].attributeName).toEqual("first");
|
2024-05-08 23:59:11 +02:00
|
|
|
expect(rootExp.subExpressions[2].comparator).toBeTruthy();
|
|
|
|
});
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
it("searching by relation without note property", () => {
|
2024-05-08 23:59:11 +02:00
|
|
|
const searchContext = new SearchContext();
|
|
|
|
|
|
|
|
parse({
|
|
|
|
fulltextTokens: [],
|
2025-01-09 18:07:02 +02:00
|
|
|
expressionTokens: tokens(["~first", "=", "text", "-", "abc"]),
|
|
|
|
searchContext
|
2024-05-08 23:59:11 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""');
|
|
|
|
});
|
|
|
|
});
|