trilium/spec/search/parser.spec.js

285 lines
11 KiB
JavaScript
Raw Normal View History

const SearchContext = require("../../src/services/search/search_context.js");
2020-07-21 06:01:07 +08:00
const parse = require('../../src/services/search/services/parse.js');
2020-05-20 06:03:33 +08:00
function tokens(toks, cur = 0) {
return toks.map(arg => {
2020-07-20 05:19:45 +08:00
if (Array.isArray(arg)) {
return tokens(arg, cur);
2020-07-20 05:19:45 +08:00
}
else {
cur += arg.length;
2020-07-20 05:19:45 +08:00
return {
token: arg,
inQuotes: false,
startIndex: cur - arg.length,
endIndex: cur - 1
2020-07-20 05:19:45 +08:00
};
}
});
}
2021-04-05 04:44:22 +08:00
function assertIsArchived(exp) {
expect(exp.constructor.name).toEqual("PropertyComparisonExp");
expect(exp.propertyName).toEqual("isArchived");
expect(exp.operator).toEqual("=");
expect(exp.comparedValue).toEqual("false");
}
2020-05-20 06:03:33 +08:00
describe("Parser", () => {
it("fulltext parser without content", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
fulltextTokens: tokens(["hello", "hi"]),
2020-05-21 17:18:15 +08:00
expressionTokens: [],
2020-09-24 03:05:26 +08:00
searchContext: new SearchContext({includeNoteContent: false, excludeArchived: true})
2020-05-21 17:18:15 +08:00
});
2020-05-20 06:03:33 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
2020-09-24 03:05:26 +08:00
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
2021-04-05 04:44:22 +08:00
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
2021-04-17 05:00:08 +08:00
expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("BeccaFlatTextExp");
2021-04-05 04:44:22 +08:00
expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
2020-05-21 05:20:39 +08:00
});
it("fulltext parser with content", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
fulltextTokens: tokens(["hello", "hi"]),
2020-05-21 17:18:15 +08:00
expressionTokens: [],
searchContext: new SearchContext({includeNoteContent: true})
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
2021-04-05 04:44:22 +08:00
const subs = rootExp.subExpressions[1].subExpressions;
2020-07-19 21:25:24 +08:00
2021-04-17 05:00:08 +08:00
expect(subs[0].constructor.name).toEqual("BeccaFlatTextExp");
2020-07-19 21:25:24 +08:00
expect(subs[0].tokens).toEqual(["hello", "hi"]);
2020-05-21 05:20:39 +08:00
2020-07-19 21:25:24 +08:00
expect(subs[1].constructor.name).toEqual("NoteContentProtectedFulltextExp");
expect(subs[1].tokens).toEqual(["hello", "hi"]);
2020-05-21 05:20:39 +08:00
2020-07-19 21:25:24 +08:00
expect(subs[2].constructor.name).toEqual("NoteContentUnprotectedFulltextExp");
expect(subs[2].tokens).toEqual(["hello", "hi"]);
2020-05-21 05:20:39 +08:00
});
it("simple label comparison", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
2020-05-21 17:18:15 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#mylabel", "=", "text"]),
searchContext: new SearchContext()
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel");
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
2020-05-21 05:20:39 +08:00
});
2020-07-19 21:25:24 +08:00
it("simple attribute negation", () => {
2020-07-21 06:01:07 +08:00
let rootExp = parse({
2020-07-19 21:25:24 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#!mylabel"]),
searchContext: new SearchContext()
2020-07-19 21:25:24 +08:00
});
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("label");
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("mylabel");
2020-07-19 21:25:24 +08:00
2020-07-21 06:01:07 +08:00
rootExp = parse({
2020-07-19 21:25:24 +08:00
fulltextTokens: [],
expressionTokens: tokens(["~!myrelation"]),
searchContext: new SearchContext()
2020-07-19 21:25:24 +08:00
});
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("relation");
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("myrelation");
2020-07-19 21:25:24 +08:00
});
2020-05-21 05:20:39 +08:00
it("simple label AND", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
2020-05-21 17:18:15 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
searchContext: new SearchContext(true)
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
2021-04-05 04:44:22 +08:00
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
2020-05-21 05:20:39 +08:00
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(firstSub.attributeName).toEqual("first");
expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(secondSub.attributeName).toEqual("second");
});
it("simple label AND without explicit AND", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
2020-05-21 17:18:15 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
searchContext: new SearchContext()
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
2021-04-05 04:44:22 +08:00
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
2020-05-21 05:20:39 +08:00
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(firstSub.attributeName).toEqual("first");
expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(secondSub.attributeName).toEqual("second");
});
it("simple label OR", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
2020-05-21 17:18:15 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
searchContext: new SearchContext()
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
2020-05-21 05:20:39 +08:00
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(firstSub.attributeName).toEqual("first");
expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(secondSub.attributeName).toEqual("second");
});
it("fulltext and simple label", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
fulltextTokens: tokens(["hello"]),
expressionTokens: tokens(["#mylabel", "=", "text"]),
2020-09-24 03:05:26 +08:00
searchContext: new SearchContext({excludeArchived: true})
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
2020-09-24 03:05:26 +08:00
const [firstSub, secondSub, thirdSub] = rootExp.subExpressions;
2020-05-21 05:20:39 +08:00
2020-09-24 03:05:26 +08:00
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
expect(firstSub.propertyName).toEqual('isArchived');
2020-05-21 05:20:39 +08:00
2021-04-05 04:44:22 +08:00
expect(secondSub.constructor.name).toEqual("OrExp");
2021-04-17 05:00:08 +08:00
expect(secondSub.subExpressions[0].constructor.name).toEqual("BeccaFlatTextExp");
2021-04-05 04:44:22 +08:00
expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
2020-09-24 03:05:26 +08:00
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
expect(thirdSub.attributeName).toEqual("mylabel");
2020-05-21 05:20:39 +08:00
});
it("label sub-expression", () => {
2020-07-21 06:01:07 +08:00
const rootExp = parse({
2020-05-21 17:18:15 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
searchContext: new SearchContext()
2020-05-21 17:18:15 +08:00
});
2020-05-21 05:20:39 +08:00
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
2020-05-21 05:20:39 +08:00
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(firstSub.attributeName).toEqual("first");
expect(secondSub.constructor.name).toEqual("AndExp");
const [firstSubSub, secondSubSub] = secondSub.subExpressions;
expect(firstSubSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(firstSubSub.attributeName).toEqual("second");
expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp");
2020-05-21 05:20:39 +08:00
expect(secondSubSub.attributeName).toEqual("third");
2020-05-20 06:03:33 +08:00
});
});
2020-07-20 05:19:45 +08:00
2020-07-21 06:01:07 +08:00
describe("Invalid expressions", () => {
2020-07-20 05:19:45 +08:00
it("incomplete comparison", () => {
const searchContext = new SearchContext();
2020-07-20 05:19:45 +08:00
2020-07-21 06:01:07 +08:00
parse({
2020-07-20 05:19:45 +08:00
fulltextTokens: [],
expressionTokens: tokens(["#first", "="]),
searchContext
2020-07-20 05:19:45 +08:00
});
expect(searchContext.error).toEqual('Misplaced or incomplete expression "="')
2020-07-20 05:19:45 +08:00
});
it("comparison between labels is impossible", () => {
let searchContext = new SearchContext();
searchContext.originalQuery = "#first = #second";
2020-07-21 06:01:07 +08:00
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";
2020-07-21 06:01:07 +08:00
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.`);
2020-07-21 06:01:07 +08:00
const rootExp = parse({
fulltextTokens: [],
expressionTokens: [
{ token: "#first", inQuotes: false },
{ token: "=", inQuotes: false },
{ token: "#second", inQuotes: true },
],
searchContext: new SearchContext()
});
2021-04-05 04:44:22 +08:00
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
expect(rootExp.subExpressions[1].attributeName).toEqual("first");
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
});
2020-09-24 03:05:26 +08:00
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 ""')
2020-09-24 03:05:26 +08:00
});
2020-07-20 05:19:45 +08:00
});