From a2e1fb35b864523497d3397542a0a93389afbf50 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 23 May 2020 20:52:55 +0200 Subject: [PATCH] tests for note properties --- spec/search.spec.js | 78 +++++++++++++++++++ src/services/note_cache/entities/note.js | 34 ++++++++ src/services/note_cache/note_cache_loader.js | 2 +- .../search/expressions/property_comparison.js | 37 ++++++++- src/services/search/parser.js | 4 +- 5 files changed, 151 insertions(+), 4 deletions(-) diff --git a/spec/search.spec.js b/spec/search.spec.js index 32108f8e3..46d368559 100644 --- a/spec/search.spec.js +++ b/spec/search.spec.js @@ -312,6 +312,84 @@ describe("Search", () => { expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy(); expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); }); + + it("test note properties", async () => { + const austria = note("Austria"); + + austria.relation('myself', austria.note); + austria.label('capital', 'Vienna'); + austria.label('population', '8859000'); + + rootNote + .child(note("Asia")) + .child(note("Europe") + .child(austria + .child(note("Vienna")) + .child(note("Sebastian Kurz")) + ) + ) + .child(note("Mozart") + .child(austria)); + + austria.note.type = 'text'; + austria.note.mime = 'text/html'; + austria.note.isProtected = false; + austria.note.dateCreated = '2020-05-14 12:11:42.001+0200'; + austria.note.dateModified = '2020-05-14 13:11:42.001+0200'; + austria.note.utcDateCreated = '2020-05-14 10:11:42.001Z'; + austria.note.utcDateModified = '2020-05-14 11:11:42.001Z'; + austria.note.contentLength = 1001; + + const parsingContext = new ParsingContext(); + + async function test(propertyName, value, expectedResultCount) { + const searchResults = await searchService.findNotesWithQuery(`# note.${propertyName} = ${value}`, parsingContext); + expect(searchResults.length).toEqual(expectedResultCount); + + if (expectedResultCount === 1) { + expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy(); + } + } + + await test("type", "text", 1); + await test("type", "code", 0); + + await test("mime", "text/html", 1); + await test("mime", "application/json", 0); + + await test("isProtected", "false", 7); + await test("isProtected", "true", 0); + + await test("dateCreated", "'2020-05-14 12:11:42.001+0200'", 1); + await test("dateCreated", "wrong", 0); + + await test("dateModified", "'2020-05-14 13:11:42.001+0200'", 1); + await test("dateModified", "wrong", 0); + + await test("utcDateCreated", "'2020-05-14 10:11:42.001Z'", 1); + await test("utcDateCreated", "wrong", 0); + + await test("utcDateModified", "'2020-05-14 11:11:42.001Z'", 1); + await test("utcDateModified", "wrong", 0); + + await test("contentLength", "1001", 1); + await test("contentLength", "10010", 0); + + await test("parentCount", "2", 1); + await test("parentCount", "3", 0); + + await test("childrenCount", "2", 1); + await test("childrenCount", "10", 0); + + await test("attributeCount", "3", 1); + await test("attributeCount", "4", 0); + + await test("labelCount", "2", 1); + await test("labelCount", "3", 0); + + await test("relationCount", "1", 1); + await test("relationCount", "2", 0); + }) }); /** @return {Note} */ diff --git a/src/services/note_cache/entities/note.js b/src/services/note_cache/entities/note.js index 7556ce250..b5a47c576 100644 --- a/src/services/note_cache/entities/note.js +++ b/src/services/note_cache/entities/note.js @@ -10,6 +10,20 @@ class Note { this.noteId = row.noteId; /** @param {string} */ this.title = row.title; + /** @param {string} */ + this.type = row.type; + /** @param {string} */ + this.mime = row.mime; + /** @param {number} */ + this.contentLength = row.contentLength; + /** @param {string} */ + this.dateCreated = row.dateCreated; + /** @param {string} */ + this.dateModified = row.dateModified; + /** @param {string} */ + this.utcDateCreated = row.utcDateCreated; + /** @param {string} */ + this.utcDateModified = row.utcDateModified; /** @param {boolean} */ this.isProtected = !!row.isProtected; /** @param {boolean} */ @@ -224,6 +238,26 @@ class Note { return arr.flat(); } + get parentCount() { + return this.parents.length; + } + + get childrenCount() { + return this.children.length; + } + + get labelCount() { + return this.attributes.filter(attr => attr.type === 'label').length; + } + + get relationCount() { + return this.attributes.filter(attr => attr.type === 'relation').length; + } + + get attributeCount() { + return this.attributes.length; + } + /** @return {Note[]} - returns only notes which are templated, does not include their subtrees * in effect returns notes which are influenced by note's non-inheritable attributes */ get templatedNotes() { diff --git a/src/services/note_cache/note_cache_loader.js b/src/services/note_cache/note_cache_loader.js index b3f4893b9..e2104d984 100644 --- a/src/services/note_cache/note_cache_loader.js +++ b/src/services/note_cache/note_cache_loader.js @@ -13,7 +13,7 @@ async function load() { noteCache.reset(); - (await sql.getRows(`SELECT noteId, title, isProtected FROM notes WHERE isDeleted = 0`, [])) + (await sql.getRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified, contentLength FROM notes WHERE isDeleted = 0`, [])) .map(row => new Note(noteCache, row)); (await sql.getRows(`SELECT branchId, noteId, parentNoteId, prefix FROM branches WHERE isDeleted = 0`, [])) diff --git a/src/services/search/expressions/property_comparison.js b/src/services/search/expressions/property_comparison.js index 730bf5597..3a52f02df 100644 --- a/src/services/search/expressions/property_comparison.js +++ b/src/services/search/expressions/property_comparison.js @@ -3,11 +3,37 @@ const Expression = require('./expression'); const NoteSet = require('../note_set'); +/** + * Search string is lower cased for case insensitive comparison. But when retrieving properties + * we need case sensitive form so we have this translation object. + */ +const PROP_MAPPING = { + "noteid": "noteId", + "title": "title", + "type": "type", + "mime": "mime", + "isprotected": "isProtected", + "datecreated": "dateCreated", + "datemodified": "dateModified", + "utcdatecreated": "utcDateCreated", + "utcdatemodified": "utcDateModified", + "contentlength": "contentLength", + "parentcount": "parentCount", + "childrencount": "childrenCount", + "attributecount": "attributeCount", + "labelcount": "labelCount", + "relationcount": "relationCount" +}; + class PropertyComparisonExp extends Expression { + static isProperty(name) { + return name in PROP_MAPPING; + } + constructor(propertyName, comparator) { super(); - this.propertyName = propertyName; + this.propertyName = PROP_MAPPING[propertyName]; this.comparator = comparator; } @@ -15,8 +41,15 @@ class PropertyComparisonExp extends Expression { const resNoteSet = new NoteSet(); for (const note of inputNoteSet.notes) { - const value = note[this.propertyName].toLowerCase(); + let value = note[this.propertyName]; + if (value !== undefined && value !== null && typeof value !== 'string') { + value = value.toString(); + } + + if (value) { + value = value.toLowerCase(); + } if (this.comparator(value)) { resNoteSet.add(note); } diff --git a/src/services/search/parser.js b/src/services/search/parser.js index db6c3b21c..552707c82 100644 --- a/src/services/search/parser.js +++ b/src/services/search/parser.js @@ -86,7 +86,7 @@ function getExpression(tokens, parsingContext) { return parseRelation(tokens[i]); } - if (tokens[i] === 'title') { + if (PropertyComparisonExp.isProperty(tokens[i])) { const propertyName = tokens[i]; const operator = tokens[i + 1]; const comparedValue = tokens[i + 2]; @@ -101,6 +101,8 @@ function getExpression(tokens, parsingContext) { return new PropertyComparisonExp(propertyName, comparator); } + + parsingContext.addError(`Unrecognized note property "${tokens[i]}"`); } function parseLabel(labelName) {