mirror of
https://github.com/zadam/trilium.git
synced 2025-01-01 12:52:17 +08:00
Merge remote-tracking branch 'origin/stable'
# Conflicts: # package-lock.json # package.json
This commit is contained in:
commit
926e9e12c0
6 changed files with 93 additions and 46 deletions
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
|
@ -87,14 +87,16 @@ describe("Lexer expression", () => {
|
|||
.toEqual(["#label", "*=*", "text"]);
|
||||
});
|
||||
|
||||
it("simple label operator with in quotes and without", () => {
|
||||
it("simple label operator with in quotes", () => {
|
||||
expect(lex("#label*=*'text'").expressionTokens)
|
||||
.toEqual([
|
||||
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
||||
{token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8},
|
||||
{token: "text", inQuotes: true, startIndex: 10, endIndex: 13}
|
||||
]);
|
||||
});
|
||||
|
||||
it("simple label operator with param without quotes", () => {
|
||||
expect(lex("#label*=*text").expressionTokens)
|
||||
.toEqual([
|
||||
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
||||
|
@ -103,6 +105,16 @@ describe("Lexer expression", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("simple label operator with empty string param", () => {
|
||||
expect(lex("#label = ''").expressionTokens)
|
||||
.toEqual([
|
||||
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
||||
{token: "=", inQuotes: false, startIndex: 7, endIndex: 7},
|
||||
// weird case for empty strings which ends up with endIndex < startIndex :-(
|
||||
{token: "", inQuotes: true, startIndex: 10, endIndex: 9}
|
||||
]);
|
||||
});
|
||||
|
||||
it("note. prefix also separates fulltext from expression", () => {
|
||||
expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]);
|
||||
|
|
|
@ -19,6 +19,13 @@ function tokens(toks, cur = 0) {
|
|||
});
|
||||
}
|
||||
|
||||
function assertIsArchived(exp) {
|
||||
expect(exp.constructor.name).toEqual("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({
|
||||
|
@ -29,8 +36,9 @@ describe("Parser", () => {
|
|||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||
expect(rootExp.subExpressions[1].tokens).toEqual(["hello", "hi"]);
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||
expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||
expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
|
||||
});
|
||||
|
||||
it("fulltext parser with content", () => {
|
||||
|
@ -40,9 +48,12 @@ describe("Parser", () => {
|
|||
searchContext: new SearchContext({includeNoteContent: true})
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("OrExp");
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const subs = rootExp.subExpressions;
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||
|
||||
const subs = rootExp.subExpressions[1].subExpressions;
|
||||
|
||||
expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||
expect(subs[0].tokens).toEqual(["hello", "hi"]);
|
||||
|
@ -61,10 +72,12 @@ describe("Parser", () => {
|
|||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(rootExp.attributeType).toEqual("label");
|
||||
expect(rootExp.attributeName).toEqual("mylabel");
|
||||
expect(rootExp.comparator).toBeTruthy();
|
||||
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();
|
||||
});
|
||||
|
||||
it("simple attribute negation", () => {
|
||||
|
@ -74,10 +87,12 @@ describe("Parser", () => {
|
|||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("NotExp");
|
||||
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(rootExp.subExpression.attributeType).toEqual("label");
|
||||
expect(rootExp.subExpression.attributeName).toEqual("mylabel");
|
||||
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");
|
||||
|
||||
rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
|
@ -85,10 +100,12 @@ describe("Parser", () => {
|
|||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("NotExp");
|
||||
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(rootExp.subExpression.attributeType).toEqual("relation");
|
||||
expect(rootExp.subExpression.attributeName).toEqual("myrelation");
|
||||
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");
|
||||
});
|
||||
|
||||
it("simple label AND", () => {
|
||||
|
@ -99,7 +116,10 @@ describe("Parser", () => {
|
|||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
|
@ -116,7 +136,10 @@ describe("Parser", () => {
|
|||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
|
@ -132,8 +155,11 @@ describe("Parser", () => {
|
|||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("OrExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
||||
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;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
|
@ -155,8 +181,9 @@ describe("Parser", () => {
|
|||
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
|
||||
expect(firstSub.propertyName).toEqual('isArchived');
|
||||
|
||||
expect(secondSub.constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||
expect(secondSub.tokens).toEqual(["hello"]);
|
||||
expect(secondSub.constructor.name).toEqual("OrExp");
|
||||
expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||
expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
|
||||
|
||||
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(thirdSub.attributeName).toEqual("mylabel");
|
||||
|
@ -169,8 +196,11 @@ describe("Parser", () => {
|
|||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("OrExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
||||
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;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
|
@ -232,10 +262,12 @@ describe("Invalid expressions", () => {
|
|||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(rootExp.attributeType).toEqual("label");
|
||||
expect(rootExp.attributeName).toEqual("first");
|
||||
expect(rootExp.comparator).toBeTruthy();
|
||||
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();
|
||||
});
|
||||
|
||||
it("searching by relation without note property", () => {
|
||||
|
|
|
@ -562,8 +562,8 @@ describe("Search", () => {
|
|||
expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria");
|
||||
expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy");
|
||||
|
||||
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 0', searchContext);
|
||||
expect(searchResults.length).toEqual(0);
|
||||
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1', searchContext);
|
||||
expect(searchResults.length).toEqual(1);
|
||||
|
||||
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext);
|
||||
expect(searchResults.length).toEqual(4);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const {note} = require('./note_cache_mocking.js');
|
||||
const ValueExtractor = require('../../src/services/search/value_extractor.js');
|
||||
const noteCache = require('../../src/services/note_cache/note_cache.js');
|
||||
const SearchContext = require("../../src/services/search/search_context.js");
|
||||
|
||||
const dsc = new SearchContext();
|
||||
|
||||
describe("Value extractor", () => {
|
||||
beforeEach(() => {
|
||||
|
@ -10,7 +13,7 @@ describe("Value extractor", () => {
|
|||
it("simple title extraction", async () => {
|
||||
const europe = note("Europe").note;
|
||||
|
||||
const valueExtractor = new ValueExtractor(["note", "title"]);
|
||||
const valueExtractor = new ValueExtractor(dsc, ["note", "title"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(europe)).toEqual("Europe");
|
||||
|
@ -21,12 +24,12 @@ describe("Value extractor", () => {
|
|||
.label("Capital", "Vienna")
|
||||
.note;
|
||||
|
||||
let valueExtractor = new ValueExtractor(["note", "labels", "capital"]);
|
||||
let valueExtractor = new ValueExtractor(dsc, ["note", "labels", "capital"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(austria)).toEqual("Vienna");
|
||||
|
||||
valueExtractor = new ValueExtractor(["#capital"]);
|
||||
valueExtractor = new ValueExtractor(dsc, ["#capital"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(austria)).toEqual("Vienna");
|
||||
|
@ -38,12 +41,12 @@ describe("Value extractor", () => {
|
|||
.child(note("Austria")
|
||||
.child(vienna));
|
||||
|
||||
let valueExtractor = new ValueExtractor(["note", "children", "children", "title"]);
|
||||
let valueExtractor = new ValueExtractor(dsc, ["note", "children", "children", "title"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(europe.note)).toEqual("Vienna");
|
||||
|
||||
valueExtractor = new ValueExtractor(["note", "parents", "parents", "title"]);
|
||||
valueExtractor = new ValueExtractor(dsc, ["note", "parents", "parents", "title"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(vienna.note)).toEqual("Europe");
|
||||
|
@ -56,12 +59,12 @@ describe("Value extractor", () => {
|
|||
.relation('neighbor', czechRepublic.note)
|
||||
.relation('neighbor', slovakia.note);
|
||||
|
||||
let valueExtractor = new ValueExtractor(["note", "relations", "neighbor", "labels", "capital"]);
|
||||
let valueExtractor = new ValueExtractor(dsc, ["note", "relations", "neighbor", "labels", "capital"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
|
||||
|
||||
valueExtractor = new ValueExtractor(["~neighbor", "labels", "capital"]);
|
||||
valueExtractor = new ValueExtractor(dsc, ["~neighbor", "labels", "capital"]);
|
||||
|
||||
expect(valueExtractor.validate()).toBeFalsy();
|
||||
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
|
||||
|
@ -70,17 +73,17 @@ describe("Value extractor", () => {
|
|||
|
||||
describe("Invalid value extractor property path", () => {
|
||||
it('each path must start with "note" (or label/relation)',
|
||||
() => expect(new ValueExtractor(["neighbor"]).validate()).toBeTruthy());
|
||||
() => expect(new ValueExtractor(dsc, ["neighbor"]).validate()).toBeTruthy());
|
||||
|
||||
it("extra path element after terminal label",
|
||||
() => expect(new ValueExtractor(["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy());
|
||||
() => expect(new ValueExtractor(dsc, ["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy());
|
||||
|
||||
it("extra path element after terminal title",
|
||||
() => expect(new ValueExtractor(["note", "title", "isProtected"]).validate()).toBeTruthy());
|
||||
() => expect(new ValueExtractor(dsc, ["note", "title", "isProtected"]).validate()).toBeTruthy());
|
||||
|
||||
it("relation name and note property is missing",
|
||||
() => expect(new ValueExtractor(["note", "relations"]).validate()).toBeTruthy());
|
||||
() => expect(new ValueExtractor(dsc, ["note", "relations"]).validate()).toBeTruthy());
|
||||
|
||||
it("relation is specified but target note property is not specified",
|
||||
() => expect(new ValueExtractor(["note", "relations", "myrel"]).validate()).toBeTruthy());
|
||||
() => expect(new ValueExtractor(dsc, ["note", "relations", "myrel"]).validate()).toBeTruthy());
|
||||
});
|
||||
|
|
|
@ -21,8 +21,8 @@ function lex(str) {
|
|||
}
|
||||
}
|
||||
|
||||
function finishWord(endIndex) {
|
||||
if (currentWord === '') {
|
||||
function finishWord(endIndex, createAlsoForEmptyWords = false) {
|
||||
if (currentWord === '' && !createAlsoForEmptyWords) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ function lex(str) {
|
|||
}
|
||||
}
|
||||
else if (quotes === chr) {
|
||||
finishWord(i - 1);
|
||||
finishWord(i - 1, true);
|
||||
|
||||
quotes = false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue