mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-18 18:44:16 +00:00
219 lines
6.9 KiB
PHP
219 lines
6.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\unit\Support\Search\QueryParser;
|
|
|
|
use FireflyIII\Support\Search\QueryParser\FieldNode;
|
|
use FireflyIII\Support\Search\QueryParser\QueryParserInterface;
|
|
use FireflyIII\Support\Search\QueryParser\StringNode;
|
|
use FireflyIII\Support\Search\QueryParser\NodeGroup;
|
|
use FireflyIII\Support\Search\QueryParser\Node;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use Tests\integration\TestCase;
|
|
|
|
abstract class AbstractQueryParserInterfaceParseQueryTester extends TestCase
|
|
{
|
|
abstract protected function createParser(): QueryParserInterface;
|
|
|
|
/**
|
|
* @param string $query The query string to parse
|
|
* @param Node $expected The expected parse result
|
|
*/
|
|
#[DataProvider('queryDataProvider')]
|
|
public function testQueryParsing(string $query, Node $expected): void
|
|
{
|
|
$actual = $this->createParser()->parse($query);
|
|
|
|
$this->assertObjectEquals($expected, $actual);
|
|
|
|
}
|
|
|
|
public static function queryDataProvider(): iterable
|
|
{
|
|
yield 'empty query' => [
|
|
'',
|
|
new NodeGroup([]),
|
|
];
|
|
|
|
yield 'simple word' => [
|
|
'groceries',
|
|
new NodeGroup([new StringNode('groceries')]),
|
|
];
|
|
|
|
yield 'prohibited word' => [
|
|
'-groceries',
|
|
new NodeGroup([new StringNode('groceries', true)]),
|
|
];
|
|
|
|
yield 'prohibited field' => [
|
|
'-amount:100',
|
|
new NodeGroup([new FieldNode('amount', '100', true)]),
|
|
];
|
|
|
|
yield 'quoted word' => [
|
|
'"test phrase"',
|
|
new NodeGroup([new StringNode('test phrase')]),
|
|
];
|
|
|
|
yield 'prohibited quoted word' => [
|
|
'-"test phrase"',
|
|
new NodeGroup([new StringNode('test phrase', true)]),
|
|
];
|
|
|
|
yield 'multiple words' => [
|
|
'groceries shopping market',
|
|
new NodeGroup([
|
|
new StringNode('groceries'),
|
|
new StringNode('shopping'),
|
|
new StringNode('market'),
|
|
]),
|
|
];
|
|
|
|
yield 'field operator' => [
|
|
'amount:100',
|
|
new NodeGroup([new FieldNode('amount', '100')]),
|
|
];
|
|
|
|
yield 'quoted field value with single space' => [
|
|
'description:"test phrase"',
|
|
new NodeGroup([new FieldNode('description', 'test phrase')]),
|
|
];
|
|
|
|
yield 'multiple fields' => [
|
|
'amount:100 category:food',
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new FieldNode('category', 'food'),
|
|
]),
|
|
];
|
|
|
|
yield 'simple subquery' => [
|
|
'(amount:100 category:food)',
|
|
new NodeGroup([
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new FieldNode('category', 'food'),
|
|
]),
|
|
]),
|
|
];
|
|
|
|
yield 'prohibited subquery' => [
|
|
'-(amount:100 category:food)',
|
|
new NodeGroup([
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new FieldNode('category', 'food'),
|
|
], true),
|
|
]),
|
|
];
|
|
|
|
yield 'nested subquery' => [
|
|
'(amount:100 (description:"test" category:food))',
|
|
new NodeGroup([
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new NodeGroup([
|
|
new FieldNode('description', 'test'),
|
|
new FieldNode('category', 'food'),
|
|
]),
|
|
]),
|
|
]),
|
|
];
|
|
|
|
yield 'mixed words and operators' => [
|
|
'groceries amount:50 shopping',
|
|
new NodeGroup([
|
|
new StringNode('groceries'),
|
|
new FieldNode('amount', '50'),
|
|
new StringNode('shopping'),
|
|
]),
|
|
];
|
|
|
|
yield 'subquery after field value' => [
|
|
'amount:100 (description:"market" category:food)',
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new NodeGroup([
|
|
new FieldNode('description', 'market'),
|
|
new FieldNode('category', 'food'),
|
|
]),
|
|
]),
|
|
];
|
|
|
|
yield 'word followed by subquery' => [
|
|
'groceries (amount:100 description_contains:"test")',
|
|
new NodeGroup([
|
|
new StringNode('groceries'),
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new FieldNode('description_contains', 'test'),
|
|
]),
|
|
]),
|
|
];
|
|
|
|
yield 'nested subquery with prohibited field' => [
|
|
'(amount:100 (description_contains:"test payment" -has_attachments:true))',
|
|
new NodeGroup([
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new NodeGroup([
|
|
new FieldNode('description_contains', 'test payment'),
|
|
new FieldNode('has_attachments', 'true', true),
|
|
]),
|
|
]),
|
|
]),
|
|
];
|
|
|
|
yield 'complex nested subqueries' => [
|
|
'shopping (amount:50 market (-category:food word description:"test phrase" (has_notes:true)))',
|
|
new NodeGroup([
|
|
new StringNode('shopping'),
|
|
new NodeGroup([
|
|
new FieldNode('amount', '50'),
|
|
new StringNode('market'),
|
|
new NodeGroup([
|
|
new FieldNode('category', 'food', true),
|
|
new StringNode('word'),
|
|
new FieldNode('description', 'test phrase'),
|
|
new NodeGroup([
|
|
new FieldNode('has_notes', 'true'),
|
|
]),
|
|
]),
|
|
]),
|
|
]),
|
|
];
|
|
|
|
yield 'word with multiple spaces' => [
|
|
'"multiple spaces"',
|
|
new NodeGroup([new StringNode('multiple spaces')]),
|
|
];
|
|
|
|
yield 'field with multiple spaces in value' => [
|
|
'description:"multiple spaces here"',
|
|
new NodeGroup([new FieldNode('description', 'multiple spaces here')]),
|
|
];
|
|
|
|
yield 'unmatched right parenthesis in word' => [
|
|
'test)word',
|
|
new NodeGroup([new StringNode('test)word')]),
|
|
];
|
|
|
|
yield 'unmatched right parenthesis in field' => [
|
|
'description:test)phrase',
|
|
new NodeGroup([new FieldNode('description', 'test)phrase')]),
|
|
];
|
|
|
|
yield 'subquery followed by word' => [
|
|
'(amount:100 category:food) shopping',
|
|
new NodeGroup([
|
|
new NodeGroup([
|
|
new FieldNode('amount', '100'),
|
|
new FieldNode('category', 'food'),
|
|
]),
|
|
new StringNode('shopping'),
|
|
]),
|
|
];
|
|
}
|
|
}
|