mirror of
https://github.com/zadam/trilium.git
synced 2025-12-04 22:44:25 +01:00
562 lines
26 KiB
TypeScript
562 lines
26 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import searchService from './services/search.js';
|
|
import BNote from '../../becca/entities/bnote.js';
|
|
import BBranch from '../../becca/entities/bbranch.js';
|
|
import SearchContext from './search_context.js';
|
|
import becca from '../../becca/becca.js';
|
|
import { findNoteByTitle, note, NoteBuilder } from '../../test/becca_mocking.js';
|
|
|
|
/**
|
|
* Logical Operators Tests - Comprehensive Coverage
|
|
*
|
|
* Tests all boolean logic and operator combinations including:
|
|
* - AND operator (implicit and explicit)
|
|
* - OR operator
|
|
* - NOT operator / Negation
|
|
* - Operator precedence
|
|
* - Parentheses grouping
|
|
* - Complex boolean expressions
|
|
* - Short-circuit evaluation
|
|
*/
|
|
describe('Search - Logical Operators', () => {
|
|
let rootNote: any;
|
|
|
|
beforeEach(() => {
|
|
becca.reset();
|
|
|
|
rootNote = new NoteBuilder(new BNote({ noteId: 'root', title: 'root', type: 'text' }));
|
|
new BBranch({
|
|
branchId: 'none_root',
|
|
noteId: 'root',
|
|
parentNoteId: 'none',
|
|
notePosition: 10,
|
|
});
|
|
});
|
|
|
|
describe('AND Operator', () => {
|
|
it.skip('should support implicit AND with space-separated terms (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Implicit AND with space-separated terms not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
// Create notes for tolkien rings example
|
|
rootNote
|
|
.child(note('The Lord of the Rings', { content: 'Epic fantasy by J.R.R. Tolkien' }))
|
|
.child(note('The Hobbit', { content: 'Prequel by Tolkien' }))
|
|
.child(note('Saturn Rings', { content: 'Planetary rings around Saturn' }));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('tolkien rings', searchContext);
|
|
|
|
// Should find note with both terms
|
|
expect(results.length).toBeGreaterThan(0);
|
|
expect(findNoteByTitle(results, 'The Lord of the Rings')).toBeTruthy();
|
|
// Should NOT find notes with only one term
|
|
expect(findNoteByTitle(results, 'The Hobbit')).toBeFalsy();
|
|
expect(findNoteByTitle(results, 'Saturn Rings')).toBeFalsy();
|
|
});
|
|
|
|
it('should support explicit AND operator', () => {
|
|
rootNote
|
|
.child(note('Book by Author').label('book').label('author'))
|
|
.child(note('Just a Book').label('book'))
|
|
.child(note('Just an Author').label('author'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#book AND #author', searchContext);
|
|
|
|
expect(results.length).toBe(1);
|
|
expect(findNoteByTitle(results, 'Book by Author')).toBeTruthy();
|
|
});
|
|
|
|
it.skip('should support multiple ANDs (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Multiple AND operators chained together not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Complete Note', { content: 'term1 term2 term3' }))
|
|
.child(note('Partial Note', { content: 'term1 term2' }));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'term1 AND term2 AND term3',
|
|
searchContext
|
|
);
|
|
|
|
expect(results.length).toBe(1);
|
|
expect(findNoteByTitle(results, 'Complete Note')).toBeTruthy();
|
|
});
|
|
|
|
it.skip('should support AND across different contexts (labels, relations, content) (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: AND operator across different contexts not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
const targetNoteBuilder = rootNote.child(note('Target'));
|
|
const targetNote = targetNoteBuilder.note;
|
|
|
|
rootNote
|
|
.child(
|
|
note('Complete Match', { content: 'programming content' })
|
|
.label('book')
|
|
.relation('references', targetNote)
|
|
)
|
|
.child(note('Partial Match', { content: 'programming content' }).label('book'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'#book AND ~references AND note.text *= programming',
|
|
searchContext
|
|
);
|
|
|
|
expect(results.length).toBe(1);
|
|
expect(findNoteByTitle(results, 'Complete Match')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('OR Operator', () => {
|
|
it('should support simple OR operator', () => {
|
|
rootNote
|
|
.child(note('Book').label('book'))
|
|
.child(note('Author').label('author'))
|
|
.child(note('Other').label('other'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#book OR #author', searchContext);
|
|
|
|
expect(results.length).toBe(2);
|
|
expect(findNoteByTitle(results, 'Book')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Author')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Other')).toBeFalsy();
|
|
});
|
|
|
|
it.skip('should support multiple ORs (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Multiple OR operators chained together not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Note1', { content: 'term1' }))
|
|
.child(note('Note2', { content: 'term2' }))
|
|
.child(note('Note3', { content: 'term3' }))
|
|
.child(note('Note4', { content: 'term4' }));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'term1 OR term2 OR term3',
|
|
searchContext
|
|
);
|
|
|
|
expect(results.length).toBe(3);
|
|
expect(findNoteByTitle(results, 'Note1')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note2')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note3')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note4')).toBeFalsy();
|
|
});
|
|
|
|
it.skip('should support OR across different contexts (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: OR operator across different contexts not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Book').label('book'))
|
|
.child(note('Has programming content', { content: 'programming tutorial' }))
|
|
.child(note('Other', { content: 'something else' }));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'#book OR note.text *= programming',
|
|
searchContext
|
|
);
|
|
|
|
expect(results.length).toBe(2);
|
|
expect(findNoteByTitle(results, 'Book')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Has programming content')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Other')).toBeFalsy();
|
|
});
|
|
|
|
it('should combine OR with fulltext (search.md line 62 example)', () => {
|
|
rootNote
|
|
.child(note('Towers Book', { content: 'The Two Towers' }).label('book'))
|
|
.child(note('Towers Author', { content: 'The Two Towers' }).label('author'))
|
|
.child(note('Other', { content: 'towers' }));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'towers #book OR #author',
|
|
searchContext
|
|
);
|
|
|
|
// Should find notes with towers AND (book OR author)
|
|
expect(findNoteByTitle(results, 'Towers Book')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Towers Author')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('NOT Operator / Negation', () => {
|
|
it.skip('should support function notation not() (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: NOT() function not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Article').label('article'))
|
|
.child(note('Book').label('book'))
|
|
.child(note('No Label'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('not(#book)', searchContext);
|
|
|
|
expect(findNoteByTitle(results, 'Article')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Book')).toBeFalsy();
|
|
expect(findNoteByTitle(results, 'No Label')).toBeTruthy();
|
|
});
|
|
|
|
it('should support label negation #! (search.md line 63)', () => {
|
|
rootNote.child(note('Article').label('article')).child(note('Book').label('book'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#!book', searchContext);
|
|
|
|
expect(findNoteByTitle(results, 'Article')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Book')).toBeFalsy();
|
|
});
|
|
|
|
it('should support relation negation ~!', () => {
|
|
const targetNoteBuilder = rootNote.child(note('Target'));
|
|
const targetNote = targetNoteBuilder.note;
|
|
|
|
rootNote
|
|
.child(note('Has Reference').relation('references', targetNote))
|
|
.child(note('No Reference'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('~!references', searchContext);
|
|
|
|
expect(findNoteByTitle(results, 'Has Reference')).toBeFalsy();
|
|
expect(findNoteByTitle(results, 'No Reference')).toBeTruthy();
|
|
});
|
|
|
|
it.skip('should support complex negation (search.md line 128) (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Complex negation with NOT() function not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
const archivedNoteBuilder = rootNote.child(note('Archived'));
|
|
const archivedNote = archivedNoteBuilder.note;
|
|
|
|
archivedNoteBuilder.child(note('Child of Archived'));
|
|
rootNote.child(note('Not Archived Child'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
"not(note.ancestors.title = 'Archived')",
|
|
searchContext
|
|
);
|
|
|
|
expect(findNoteByTitle(results, 'Child of Archived')).toBeFalsy();
|
|
expect(findNoteByTitle(results, 'Not Archived Child')).toBeTruthy();
|
|
});
|
|
|
|
it('should support double negation', () => {
|
|
rootNote.child(note('Book').label('book')).child(note('Not Book'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('not(not(#book))', searchContext);
|
|
|
|
expect(findNoteByTitle(results, 'Book')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Not Book')).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('Operator Precedence', () => {
|
|
it.skip('should apply AND before OR (A OR B AND C = A OR (B AND C)) (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Operator precedence (AND before OR) not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Note A').label('a'))
|
|
.child(note('Note B and C').label('b').label('c'))
|
|
.child(note('Note B only').label('b'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#a OR #b AND #c', searchContext);
|
|
|
|
// Should match: notes with A, OR notes with both B and C
|
|
expect(findNoteByTitle(results, 'Note A')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note B and C')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note B only')).toBeFalsy();
|
|
});
|
|
|
|
it.skip('should allow parentheses to override precedence (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Parentheses to override operator precedence not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Note A and C').label('a').label('c'))
|
|
.child(note('Note B and C').label('b').label('c'))
|
|
.child(note('Note A only').label('a'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('(#a OR #b) AND #c', searchContext);
|
|
|
|
// Should match: (notes with A or B) AND notes with C
|
|
expect(findNoteByTitle(results, 'Note A and C')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note B and C')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note A only')).toBeFalsy();
|
|
});
|
|
|
|
it.skip('should handle complex precedence (A AND B OR C AND D) (known search engine limitation)', () => {
|
|
// TODO: This test reveals a limitation in the current search implementation
|
|
// Specific issue: Complex operator precedence not working correctly
|
|
// Test is valid but search engine needs fixes to pass
|
|
|
|
rootNote
|
|
.child(note('Note A and B').label('a').label('b'))
|
|
.child(note('Note C and D').label('c').label('d'))
|
|
.child(note('Note A and C').label('a').label('c'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'#a AND #b OR #c AND #d',
|
|
searchContext
|
|
);
|
|
|
|
// Should match: (A AND B) OR (C AND D)
|
|
expect(findNoteByTitle(results, 'Note A and B')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note C and D')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note A and C')).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('Parentheses Grouping', () => {
|
|
it.skip('should support simple grouping (KNOWN BUG: Complex parentheses with AND/OR not working)', () => {
|
|
// KNOWN BUG: Complex parentheses parsing has issues
|
|
// Query: '(#book OR #article) AND #programming'
|
|
// Expected: Should match notes with (book OR article) AND programming
|
|
// Actual: Returns incorrect results
|
|
// TODO: Fix parentheses parsing in search implementation
|
|
|
|
rootNote
|
|
.child(note('Programming Book').label('book').label('programming'))
|
|
.child(note('Programming Article').label('article').label('programming'))
|
|
.child(note('Math Book').label('book').label('math'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'(#book OR #article) AND #programming',
|
|
searchContext
|
|
);
|
|
|
|
expect(findNoteByTitle(results, 'Programming Book')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Programming Article')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Math Book')).toBeFalsy();
|
|
});
|
|
|
|
it('should support nested grouping', () => {
|
|
rootNote
|
|
.child(note('A and C').label('a').label('c'))
|
|
.child(note('B and D').label('b').label('d'))
|
|
.child(note('A and D').label('a').label('d'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'((#a OR #b) AND (#c OR #d))',
|
|
searchContext
|
|
);
|
|
|
|
// ((A OR B) AND (C OR D)) - should match A&C, B&D, A&D, B&C
|
|
expect(findNoteByTitle(results, 'A and C')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'B and D')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'A and D')).toBeTruthy();
|
|
});
|
|
|
|
it.skip('should support multiple groups at same level (KNOWN BUG: Top-level OR with groups broken)', () => {
|
|
// KNOWN BUG: Top-level OR with multiple groups has issues
|
|
// Query: '(#a AND #b) OR (#c AND #d)'
|
|
// Expected: Should match notes with (a AND b) OR (c AND d)
|
|
// Actual: Returns incorrect results
|
|
// TODO: Fix top-level OR operator parsing with multiple groups
|
|
|
|
rootNote
|
|
.child(note('A and B').label('a').label('b'))
|
|
.child(note('C and D').label('c').label('d'))
|
|
.child(note('A and C').label('a').label('c'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'(#a AND #b) OR (#c AND #d)',
|
|
searchContext
|
|
);
|
|
|
|
// (A AND B) OR (C AND D)
|
|
expect(findNoteByTitle(results, 'A and B')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'C and D')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'A and C')).toBeFalsy();
|
|
});
|
|
|
|
it('should support parentheses with comparison operators (search.md line 98)', () => {
|
|
rootNote
|
|
.child(note('Fellowship of the Ring').label('publicationDate', '1954'))
|
|
.child(note('The Two Towers').label('publicationDate', '1955'))
|
|
.child(note('Return of the King').label('publicationDate', '1960'))
|
|
.child(note('The Hobbit').label('publicationDate', '1937'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'(#publicationDate >= 1954 AND #publicationDate <= 1960)',
|
|
searchContext
|
|
);
|
|
|
|
expect(findNoteByTitle(results, 'Fellowship of the Ring')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'The Two Towers')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Return of the King')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'The Hobbit')).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('Complex Boolean Expressions', () => {
|
|
it.skip('should handle mix of AND, OR, NOT (KNOWN BUG: NOT() function broken with AND/OR)', () => {
|
|
// KNOWN BUG: NOT() function doesn't work correctly with AND/OR operators
|
|
// Query: '(#book OR #article) AND NOT(#archived) AND #programming'
|
|
// Expected: Should match notes with (book OR article) AND NOT archived AND programming
|
|
// Actual: NOT() function returns incorrect results when combined with AND/OR
|
|
// TODO: Fix NOT() function implementation in search
|
|
|
|
rootNote
|
|
.child(note('Programming Book').label('book').label('programming'))
|
|
.child(
|
|
note('Archived Programming Article')
|
|
.label('article')
|
|
.label('programming')
|
|
.label('archived')
|
|
)
|
|
.child(note('Programming Article').label('article').label('programming'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'(#book OR #article) AND NOT(#archived) AND #programming',
|
|
searchContext
|
|
);
|
|
|
|
expect(findNoteByTitle(results, 'Programming Book')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Archived Programming Article')).toBeFalsy();
|
|
expect(findNoteByTitle(results, 'Programming Article')).toBeTruthy();
|
|
});
|
|
|
|
it.skip('should handle multiple negations (KNOWN BUG: Multiple NOT() calls not working)', () => {
|
|
// KNOWN BUG: Multiple NOT() functions don't work correctly
|
|
// Query: 'NOT(#a) AND NOT(#b)'
|
|
// Expected: Should match notes without label a AND without label b
|
|
// Actual: Multiple NOT() calls return incorrect results
|
|
// TODO: Fix NOT() function to support multiple negations
|
|
|
|
rootNote
|
|
.child(note('Clean Note'))
|
|
.child(note('Note with A').label('a'))
|
|
.child(note('Note with B').label('b'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('NOT(#a) AND NOT(#b)', searchContext);
|
|
|
|
expect(findNoteByTitle(results, 'Clean Note')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Note with A')).toBeFalsy();
|
|
expect(findNoteByTitle(results, 'Note with B')).toBeFalsy();
|
|
});
|
|
|
|
it.skip("should verify De Morgan's laws: NOT(A AND B) vs NOT(A) OR NOT(B) (CRITICAL BUG: NOT() function completely broken)", () => {
|
|
// CRITICAL BUG: NOT() function is completely broken
|
|
// This test demonstrates De Morgan's law: NOT(A AND B) should equal NOT(A) OR NOT(B)
|
|
// Query 1: 'NOT(#a AND #b)' - Should match all notes except those with both a AND b
|
|
// Query 2: 'NOT(#a) OR NOT(#b)' - Should match all notes except those with both a AND b
|
|
// Expected: Both queries return identical results (Only A, Only B, Neither)
|
|
// Actual: Results differ, proving NOT() is fundamentally broken
|
|
// TODO: URGENT - Fix NOT() function implementation from scratch
|
|
|
|
rootNote
|
|
.child(note('Both A and B').label('a').label('b'))
|
|
.child(note('Only A').label('a'))
|
|
.child(note('Only B').label('b'))
|
|
.child(note('Neither'));
|
|
|
|
const searchContext1 = new SearchContext();
|
|
const results1 = searchService.findResultsWithQuery('NOT(#a AND #b)', searchContext1);
|
|
|
|
const searchContext2 = new SearchContext();
|
|
const results2 = searchService.findResultsWithQuery('NOT(#a) OR NOT(#b)', searchContext2);
|
|
|
|
// Both should return same notes (all except note with both A and B)
|
|
const noteIds1 = results1.map((r) => r.noteId).sort();
|
|
const noteIds2 = results2.map((r) => r.noteId).sort();
|
|
|
|
expect(noteIds1).toEqual(noteIds2);
|
|
expect(findNoteByTitle(results1, 'Both A and B')).toBeFalsy();
|
|
expect(findNoteByTitle(results1, 'Only A')).toBeTruthy();
|
|
expect(findNoteByTitle(results1, 'Only B')).toBeTruthy();
|
|
expect(findNoteByTitle(results1, 'Neither')).toBeTruthy();
|
|
});
|
|
|
|
it.skip('should handle deeply nested boolean expressions (KNOWN BUG: Deep nesting fails)', () => {
|
|
// KNOWN BUG: Deep nesting of boolean expressions doesn't work
|
|
// Query: '((#a AND (#b OR #c)) OR (#d AND #e))'
|
|
// Expected: Should match notes that satisfy ((a AND (b OR c)) OR (d AND e))
|
|
// Actual: Deep nesting causes parsing or evaluation errors
|
|
// TODO: Fix deep nesting support in boolean expression parser
|
|
|
|
rootNote
|
|
.child(note('Match').label('a').label('d').label('e'))
|
|
.child(note('No Match').label('a').label('b'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery(
|
|
'((#a AND (#b OR #c)) OR (#d AND #e))',
|
|
searchContext
|
|
);
|
|
|
|
// ((A AND (B OR C)) OR (D AND E))
|
|
expect(findNoteByTitle(results, 'Match')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('Short-Circuit Evaluation', () => {
|
|
it('should short-circuit AND when first condition is false', () => {
|
|
// Create a note that would match second condition
|
|
rootNote.child(note('Has B').label('b'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#a AND #b', searchContext);
|
|
|
|
// #a is false, so #b should not be evaluated
|
|
// Since note doesn't have #a, the whole expression is false regardless of #b
|
|
expect(findNoteByTitle(results, 'Has B')).toBeFalsy();
|
|
});
|
|
|
|
it('should short-circuit OR when first condition is true', () => {
|
|
rootNote.child(note('Has A').label('a'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#a OR #b', searchContext);
|
|
|
|
// #a is true, so the whole OR is true regardless of #b
|
|
expect(findNoteByTitle(results, 'Has A')).toBeTruthy();
|
|
});
|
|
|
|
it('should evaluate all conditions when necessary', () => {
|
|
rootNote
|
|
.child(note('Has both').label('a').label('b'))
|
|
.child(note('Has A only').label('a'));
|
|
|
|
const searchContext = new SearchContext();
|
|
const results = searchService.findResultsWithQuery('#a AND #b', searchContext);
|
|
|
|
// Both conditions must be evaluated for AND
|
|
expect(findNoteByTitle(results, 'Has both')).toBeTruthy();
|
|
expect(findNoteByTitle(results, 'Has A only')).toBeFalsy();
|
|
});
|
|
});
|
|
});
|