diff options
-rw-r--r-- | app/Controller/Projectinfo.php | 8 | ||||
-rw-r--r-- | app/Core/Lexer.php | 142 | ||||
-rw-r--r-- | app/Model/Color.php | 23 | ||||
-rw-r--r-- | app/Model/TaskFilter.php | 140 | ||||
-rw-r--r-- | app/ServiceProvider/ClassProvider.php | 1 | ||||
-rw-r--r-- | docs/index.markdown | 1 | ||||
-rw-r--r-- | docs/search.markdown | 106 | ||||
-rw-r--r-- | tests/units/LexerTest.php | 193 | ||||
-rw-r--r-- | tests/units/TaskFilterTest.php | 170 |
9 files changed, 781 insertions, 3 deletions
diff --git a/app/Controller/Projectinfo.php b/app/Controller/Projectinfo.php index a9498f43..c30c1652 100644 --- a/app/Controller/Projectinfo.php +++ b/app/Controller/Projectinfo.php @@ -46,9 +46,11 @@ class Projectinfo extends Base if ($search !== '') { - $paginator - ->setQuery($this->taskFinder->getSearchQuery($project['id'], $search)) - ->calculate(); + // $paginator + // ->setQuery($this->taskFinder->getSearchQuery($project['id'], $search)) + // ->calculate(); + + $paginator->setQuery($this->taskFilter->search($search)->filterByProject($project['id'])->getQuery())->calculate(); $nb_tasks = $paginator->getTotal(); } diff --git a/app/Core/Lexer.php b/app/Core/Lexer.php new file mode 100644 index 00000000..a81965c5 --- /dev/null +++ b/app/Core/Lexer.php @@ -0,0 +1,142 @@ +<?php + +namespace Core; + +/** + * Lexer + * + * @package core + * @author Frederic Guillot + */ +class Lexer +{ + /** + * Current position + * + * @access private + * @var integer + */ + private $offset = 0; + + /** + * Token map + * + * @access private + * @var array + */ + private $tokenMap = array( + "/^(assignee:)/" => 'T_ASSIGNEE', + "/^(color:)/" => 'T_COLOR', + "/^(due:)/" => 'T_DUE', + "/^(title:)/" => 'T_TITLE', + "/^(\s+)/" => 'T_WHITESPACE', + '/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE', + '/^(yesterday|tomorrow|today)/' => 'T_DATE', + '/^("(.*?)")/' => 'T_STRING', + "/^(\w+)/" => 'T_STRING', + ); + + /** + * Tokenize input string + * + * @access public + * @param string $input + * @return array + */ + public function tokenize($input) + { + $tokens = array(); + $this->offset = 0; + + while (isset($input[$this->offset])) { + + $result = $this->match(substr($input, $this->offset)); + + if ($result === false) { + return array(); + } + + $tokens[] = $result; + } + + return $tokens; + } + + /** + * Find a token that match and move the offset + * + * @access public + * @param string $string + * @return array|boolean + */ + public function match($string) + { + foreach ($this->tokenMap as $pattern => $name) { + if (preg_match($pattern, $string, $matches)) { + + $this->offset += strlen($matches[1]); + + return array( + 'match' => trim($matches[1], '"'), + 'token' => $name, + ); + } + } + + return false; + } + + /** + * Change the output of tokenizer to be easily parsed by the database filter + * + * Example: ['T_ASSIGNEE' => ['user1', 'user2'], 'T_TITLE' => 'task title'] + * + * @access public + * @param array $tokens + * @return array + */ + public function map(array $tokens) + { + $map = array( + 'T_TITLE' => '', + ); + + while (false !== ($token = current($tokens))) { + + switch ($token['token']) { + case 'T_ASSIGNEE': + case 'T_COLOR': + $next = next($tokens); + + if ($next !== false && $next['token'] === 'T_STRING') { + $map[$token['token']][] = $next['match']; + } + + break; + + case 'T_DUE': + $next = next($tokens); + + if ($next !== false && $next['token'] === 'T_DATE') { + $map[$token['token']] = $next['match']; + } + + break; + + default: + $map['T_TITLE'] .= $token['match']; + break; + } + + next($tokens); + } + + $map['T_TITLE'] = trim($map['T_TITLE']); + + if (empty($map['T_TITLE'])) { + unset($map['T_TITLE']); + } + + return $map; + } +} diff --git a/app/Model/Color.php b/app/Model/Color.php index 9a21f691..a35aff8f 100644 --- a/app/Model/Color.php +++ b/app/Model/Color.php @@ -100,6 +100,29 @@ class Color extends Base ); /** + * Find a color id from the name or the id + * + * @access public + * @param string $color + * @return string + */ + public function find($color) + { + $color = strtolower($color); + + foreach ($this->default_colors as $color_id => $params) { + if ($color_id === $color) { + return $color_id; + } + else if ($color === strtolower($params['name'])) { + return $color_id; + } + } + + return ''; + } + + /** * Get available colors * * @access public diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php index bd03a8bc..4f306b14 100644 --- a/app/Model/TaskFilter.php +++ b/app/Model/TaskFilter.php @@ -24,6 +24,42 @@ class TaskFilter extends Base public $query; /** + * Apply filters according to the search input + * + * @access public + * @param string $input + * @return TaskFilter + */ + public function search($input) + { + $tree = $this->lexer->map($this->lexer->tokenize($input)); + $this->query = $this->taskFinder->getExtendedQuery(); + + if (empty($tree)) { + $this->query->addCondition('1 = 0'); + } + + foreach ($tree as $filter => $value) { + switch ($filter) { + case 'T_ASSIGNEE': + $this->filterByAssignee($value); + break; + case 'T_COLOR': + $this->filterByColors($value); + break; + case 'T_DUE': + $this->filterByDueDate($value); + break; + case 'T_TITLE': + $this->filterByTitle($value); + break; + } + } + + return $this; + } + + /** * Create a new query * * @access public @@ -164,6 +200,35 @@ class TaskFilter extends Base } /** + * Filter by assignee names + * + * @access public + * @param array $values List of assignees + * @return TaskFilter + */ + public function filterByAssignee(array $values) + { + $this->query->beginOr(); + + foreach ($values as $assignee) { + + switch ($assignee) { + case 'me': + $this->query->eq('owner_id', $this->userSession->getId()); + break; + case 'nobody': + $this->query->eq('owner_id', 0); + break; + default: + $this->query->ilike(User::TABLE.'.username', '%'.$assignee.'%'); + $this->query->ilike(User::TABLE.'.name', '%'.$assignee.'%'); + } + } + + $this->query->closeOr(); + } + + /** * Filter by color * * @access public @@ -180,6 +245,26 @@ class TaskFilter extends Base } /** + * Filter by colors + * + * @access public + * @param array $colors + * @return TaskFilter + */ + public function filterByColors(array $colors) + { + $this->query->beginOr(); + + foreach ($colors as $color) { + $this->filterByColor($this->color->find($color)); + } + + $this->query->closeOr(); + + return $this; + } + + /** * Filter by column * * @access public @@ -228,6 +313,18 @@ class TaskFilter extends Base } /** + * Filter by due date + * + * @access public + * @param string $date ISO8601 date format + * @return TaskFilter + */ + public function filterByDueDate($date) + { + return $this->filterWithOperator('date_due', $date, true); + } + + /** * Filter by due date (range) * * @access public @@ -295,6 +392,17 @@ class TaskFilter extends Base } /** + * Get the PicoDb query + * + * @access public + * @return \PicoDb\Table + */ + public function getQuery() + { + return $this->query; + } + + /** * Format the results to the ajax autocompletion * * @access public @@ -465,4 +573,36 @@ class TaskFilter extends Base return $vEvent; } + + /** + * Filter with an operator + * + * @access public + * @param string $field + * @param string $value + * @param boolean $is_date + * @return TaskFilter + */ + private function filterWithOperator($field, $value, $is_date) + { + $operators = array( + '<=' => 'lte', + '>=' => 'gte', + '<' => 'lt', + '>' => 'gt', + ); + + foreach ($operators as $operator => $method) { + + if (strpos($value, $operator) === 0) { + $value = substr($value, strlen($operator)); + $this->query->$method($field, $is_date ? $this->dateParser->getTimestampFromIsoFormat($value) : $value); + return $this; + } + } + + $this->query->eq($field, $is_date ? $this->dateParser->getTimestampFromIsoFormat($value) : $value); + + return $this; + } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 4ecd357b..1fa0d0ef 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -67,6 +67,7 @@ class ClassProvider implements ServiceProviderInterface 'EmailClient', 'Helper', 'HttpClient', + 'Lexer', 'MemoryCache', 'Request', 'Session', diff --git a/docs/index.markdown b/docs/index.markdown index 6eb6e6ef..77c6db2a 100644 --- a/docs/index.markdown +++ b/docs/index.markdown @@ -71,6 +71,7 @@ Using Kanboard ### More +- [Search syntax](search.markdown) - [Command line interface](cli.markdown) - [Syntax guide](syntax-guide.markdown) - [Frequently asked questions](faq.markdown) diff --git a/docs/search.markdown b/docs/search.markdown new file mode 100644 index 00000000..c6d5b76e --- /dev/null +++ b/docs/search.markdown @@ -0,0 +1,106 @@ +Advanced Search Syntax +====================== + +Kanboard use a simple query language for advanced search. + +Example of query +---------------- + +This example will returns all tasks assigned to me with a due date for tomorrow and that have a title that contains "my title": + +``` +assigne:me due:tomorrow my title +``` + +Search by assignee +------------------ + +Attribute: **assignee** + +Query with the full name: + +``` +assignee:"Frederic Guillot" +``` + +Query with the username: + +``` +assignee:fguillot +``` + +Multiple assignee lookup: + +``` +assignee:user1 assignee:"John Doe" +``` + +Kanboard will search tasks assigned to the "user1" or "John Doe". + +Query for unassigned tasks: + +``` +assignee:nobody +``` + +Query for my assigned tasks + +``` +assignee:me +``` + +Search by color +--------------- + +Attribute: **color** + +Query to search by color id: + +``` +color:blue +``` + +Query to search by color name: + +``` +color:"Deep Orange" +``` + +Search by due date +------------------ + +Attribute: **due** + +Query to search tasks due today: + +``` +due:today +``` + +Query to search tasks due tomorrow: + +``` +due:tomorrow +``` + +Query to search tasks due yesterday: + +``` +due:yesterday +``` + +Query to search tasks due with the exact date: + +``` +due:2015-06-29 +``` + +The date must use the ISO8601 format: **YYYY-MM-DD**. + +Operators supported: + +- Greater than: **due:>2015-06-29** +- Lower than: **due:<2015-06-29** +- Greater than or equal: **due:>=2015-06-29** +- Lower than or equal: **due:<=2015-06-29** + diff --git a/tests/units/LexerTest.php b/tests/units/LexerTest.php new file mode 100644 index 00000000..4abe451b --- /dev/null +++ b/tests/units/LexerTest.php @@ -0,0 +1,193 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Core\Lexer; + +class LexerTest extends Base +{ + public function testAssigneeQuery() + { + $lexer = new Lexer; + + $this->assertEquals( + array(array('match' => 'assignee:', 'token' => 'T_ASSIGNEE'), array('match' => 'me', 'token' => 'T_STRING')), + $lexer->tokenize('assignee:me') + ); + + $this->assertEquals( + array(array('match' => 'assignee:', 'token' => 'T_ASSIGNEE'), array('match' => 'everybody', 'token' => 'T_STRING')), + $lexer->tokenize('assignee:everybody') + ); + + $this->assertEquals( + array(array('match' => 'assignee:', 'token' => 'T_ASSIGNEE'), array('match' => 'nobody', 'token' => 'T_STRING')), + $lexer->tokenize('assignee:nobody') + ); + + $this->assertEquals( + array('T_ASSIGNEE' => array('nobody')), + $lexer->map($lexer->tokenize('assignee:nobody')) + ); + + $this->assertEquals( + array('T_ASSIGNEE' => array('John Doe', 'me')), + $lexer->map($lexer->tokenize('assignee:"John Doe" assignee:me')) + ); + } + + public function testColorQuery() + { + $lexer = new Lexer; + + $this->assertEquals( + array(array('match' => 'color:', 'token' => 'T_COLOR'), array('match' => 'Blue', 'token' => 'T_STRING')), + $lexer->tokenize('color:Blue') + ); + + $this->assertEquals( + array(array('match' => 'color:', 'token' => 'T_COLOR'), array('match' => 'Dark Grey', 'token' => 'T_STRING')), + $lexer->tokenize('color:"Dark Grey"') + ); + + $this->assertEquals( + array('T_COLOR' => array('Blue')), + $lexer->map($lexer->tokenize('color:Blue')) + ); + + $this->assertEquals( + array('T_COLOR' => array('Dark Grey')), + $lexer->map($lexer->tokenize('color:"Dark Grey"')) + ); + + $this->assertEquals( + array(), + $lexer->map($lexer->tokenize('color: ')) + ); + } + + public function testDueDateQuery() + { + $lexer = new Lexer; + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '2015-05-01', 'token' => 'T_DATE')), + $lexer->tokenize('due:2015-05-01') + ); + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '<2015-05-01', 'token' => 'T_DATE')), + $lexer->tokenize('due:<2015-05-01') + ); + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '>2015-05-01', 'token' => 'T_DATE')), + $lexer->tokenize('due:>2015-05-01') + ); + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '<=2015-05-01', 'token' => 'T_DATE')), + $lexer->tokenize('due:<=2015-05-01') + ); + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => '>=2015-05-01', 'token' => 'T_DATE')), + $lexer->tokenize('due:>=2015-05-01') + ); + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => 'yesterday', 'token' => 'T_DATE')), + $lexer->tokenize('due:yesterday') + ); + + $this->assertEquals( + array(array('match' => 'due:', 'token' => 'T_DUE'), array('match' => 'tomorrow', 'token' => 'T_DATE')), + $lexer->tokenize('due:tomorrow') + ); + + $this->assertEquals( + array(), + $lexer->tokenize('due:#2015-05-01') + ); + + $this->assertEquals( + array(), + $lexer->tokenize('due:01-05-1024') + ); + + $this->assertEquals( + array('T_DUE' => '2015-05-01'), + $lexer->map($lexer->tokenize('due:2015-05-01')) + ); + + $this->assertEquals( + array('T_DUE' => '<2015-05-01'), + $lexer->map($lexer->tokenize('due:<2015-05-01')) + ); + + $this->assertEquals( + array('T_DUE' => 'today'), + $lexer->map($lexer->tokenize('due:today')) + ); + } + + public function testMultipleCriterias() + { + $lexer = new Lexer; + + $this->assertEquals( + array('T_COLOR' => array('Dark Grey'), 'T_ASSIGNEE' => array('Fred G'), 'T_TITLE' => 'my task title'), + $lexer->map($lexer->tokenize('color:"Dark Grey" assignee:"Fred G" my task title')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title', 'T_COLOR' => array('yellow')), + $lexer->map($lexer->tokenize('my title color:yellow')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title', 'T_DUE' => '2015-04-01'), + $lexer->map($lexer->tokenize('my title due:2015-04-01')) + ); + + $this->assertEquals( + array('T_TITLE' => 'awesome', 'T_DUE' => '<=2015-04-01'), + $lexer->map($lexer->tokenize('due:<=2015-04-01 awesome')) + ); + + $this->assertEquals( + array('T_TITLE' => 'awesome', 'T_DUE' => 'today'), + $lexer->map($lexer->tokenize('due:today awesome')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title', 'T_COLOR' => array('yellow'), 'T_DUE' => '2015-04-01'), + $lexer->map($lexer->tokenize('my title color:yellow due:2015-04-01')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title', 'T_COLOR' => array('yellow'), 'T_DUE' => '2015-04-01', 'T_ASSIGNEE' => array('John Doe')), + $lexer->map($lexer->tokenize('my title color:yellow due:2015-04-01 assignee:"John Doe"')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title'), + $lexer->map($lexer->tokenize('my title color:')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title'), + $lexer->map($lexer->tokenize('my title color:assignee:')) + ); + + $this->assertEquals( + array('T_TITLE' => 'my title'), + $lexer->map($lexer->tokenize('my title ')) + ); + + $this->assertEquals( + array(), + $lexer->map($lexer->tokenize('color:assignee:')) + ); + } +} diff --git a/tests/units/TaskFilterTest.php b/tests/units/TaskFilterTest.php index 1872d429..e509371c 100644 --- a/tests/units/TaskFilterTest.php +++ b/tests/units/TaskFilterTest.php @@ -2,10 +2,180 @@ require_once __DIR__.'/Base.php'; +use Model\Project; +use Model\User; use Model\TaskFilter; +use Model\TaskCreation; +use Model\DateParser; class TaskFilterTest extends Base { + public function testSearchWithEmptyResult() + { + $dp = new DateParser($this->container); + $p = new Project($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFilter($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is awesome', 'date_due' => $dp->getTimestampFromIsoFormat('-2 days')))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is amazing', 'date_due' => $dp->getTimestampFromIsoFormat('+1 day')))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'Bob at work', 'date_due' => $dp->getTimestampFromIsoFormat('-1 day')))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'youpi', 'date_due' => $dp->getTimestampFromIsoFormat(time())))); + + $this->assertEmpty($tf->search('search something')->findAll()); + } + + public function testSearchWithDueDate() + { + $dp = new DateParser($this->container); + $p = new Project($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFilter($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is awesome', 'date_due' => $dp->getTimestampFromIsoFormat('-2 days')))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is amazing', 'date_due' => $dp->getTimestampFromIsoFormat('+1 day')))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'Bob at work', 'date_due' => $dp->getTimestampFromIsoFormat('-1 day')))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'youpi', 'date_due' => $dp->getTimestampFromIsoFormat(time())))); + + $tf->search('due:>'.date('Y-m-d')); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + + $tf->search('due:>='.date('Y-m-d')); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(2, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + $this->assertEquals('youpi', $tasks[1]['title']); + + $tf->search('due:<'.date('Y-m-d')); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(2, $tasks); + $this->assertEquals('my task title is awesome', $tasks[0]['title']); + $this->assertEquals('Bob at work', $tasks[1]['title']); + + $tf->search('due:<='.date('Y-m-d')); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(3, $tasks); + $this->assertEquals('my task title is awesome', $tasks[0]['title']); + $this->assertEquals('Bob at work', $tasks[1]['title']); + $this->assertEquals('youpi', $tasks[2]['title']); + + $tf->search('due:tomorrow'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + + $tf->search('due:yesterday'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('Bob at work', $tasks[0]['title']); + + $tf->search('due:today'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('youpi', $tasks[0]['title']); + } + + public function testSearchWithColor() + { + $p = new Project($this->container); + $u = new User($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFilter($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $this->assertEquals(2, $u->create(array('username' => 'bob', 'name' => 'Bob Ryan'))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is awesome', 'color_id' => 'light_green'))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is amazing', 'color_id' => 'blue'))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'Bob at work'))); + + $tf->search('color:"Light Green"'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('my task title is awesome', $tasks[0]['title']); + + $tf->search('color:"Light Green" amazing'); + $tasks = $tf->findAll(); + $this->assertEmpty($tasks); + + $tf->search('color:"plop'); + $tasks = $tf->findAll(); + $this->assertEmpty($tasks); + + $tf->search('color:unknown'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(3, $tasks); + + $tf->search('color:blue amazing'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + + $tf->search('color:blue color:Yellow'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(2, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + $this->assertEquals('Bob at work', $tasks[1]['title']); + } + + public function testSearchWithAssignee() + { + $p = new Project($this->container); + $u = new User($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFilter($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $this->assertEquals(2, $u->create(array('username' => 'bob', 'name' => 'Bob Ryan'))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is awesome', 'owner_id' => 1))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'my task title is amazing', 'owner_id' => 0))); + $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'Bob at work', 'owner_id' => 2))); + + $tf->search('assignee:john'); + $tasks = $tf->findAll(); + $this->assertEmpty($tasks); + + $tf->search('assignee:admin my task title'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('my task title is awesome', $tasks[0]['title']); + + $tf->search('my task title'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(2, $tasks); + $this->assertEquals('my task title is awesome', $tasks[0]['title']); + $this->assertEquals('my task title is amazing', $tasks[1]['title']); + + $tf->search('my task title assignee:nobody'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(1, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + + $tf->search('assignee:"Bob ryan" assignee:nobody'); + $tasks = $tf->findAll(); + $this->assertNotEmpty($tasks); + $this->assertCount(2, $tasks); + $this->assertEquals('my task title is amazing', $tasks[0]['title']); + $this->assertEquals('Bob at work', $tasks[1]['title']); + } + public function testCopy() { $tf = new TaskFilter($this->container); |