+
+
+
+
+
diff --git a/src/router.js b/src/router.js
index 4e7b1d64b..ca0657395 100644
--- a/src/router.js
+++ b/src/router.js
@@ -116,7 +116,7 @@ export default new Router({
},
},
{
- path: 'card/:cardId',
+ path: 'card/:cardId/:tabId?/:tabQuery?',
name: 'card',
components: {
sidebar: CardSidebar,
@@ -130,6 +130,8 @@ export default new Router({
sidebar: (route) => {
return {
id: parseInt(route.params.cardId, 10),
+ tabId: route.params.tabId,
+ tabQuery: route.params.tabQuery,
}
},
},
diff --git a/src/store/card.js b/src/store/card.js
index 4f4f4c987..c3c0c39ca 100644
--- a/src/store/card.js
+++ b/src/store/card.js
@@ -21,6 +21,7 @@
*/
import { CardApi } from './../services/CardApi'
+import moment from 'moment'
import Vue from 'vue'
const apiClient = new CardApi()
@@ -86,8 +87,90 @@ export default {
return true
}
- return card.title.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
- || card.description.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
+ let hasMatch = true
+ const matches = getters.getSearchQuery.match(/(?:[^\s"]+|"[^"]*")+/g)
+
+ const filterOutQuotes = (q) => {
+ if (q[0] === '"' && q[q.length - 1] === '"') {
+ return q.substr(1, -1)
+ }
+ return q
+ }
+ for (const match of matches) {
+ let [filter, query] = match.indexOf(':') !== -1 ? match.split(/:(.+)/) : [null, match]
+
+ if (filter === 'title') {
+ hasMatch = hasMatch && card.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
+ } else if (filter === 'description') {
+ hasMatch = hasMatch && card.description.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
+ } else if (filter === 'list') {
+ const stack = this.getters.stackById(card.stackId)
+ if (!stack) {
+ return false
+ }
+ hasMatch = hasMatch && stack.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
+ } else if (filter === 'tag') {
+ hasMatch = hasMatch && card.labels.findIndex((label) => label.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())) !== -1
+ } else if (filter === 'date') {
+ const datediffHour = ((new Date(card.duedate) - new Date()) / 3600 / 1000)
+ query = filterOutQuotes(query)
+ switch (query) {
+ case 'overdue':
+ hasMatch = hasMatch && (card.overdue === 3)
+ break
+ case 'today':
+ hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 24 && card.duedate !== null)
+ break
+ case 'week':
+ hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 7 * 24 && card.duedate !== null)
+ break
+ case 'month':
+ hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 30 * 24 && card.duedate !== null)
+ break
+ case 'none':
+ hasMatch = hasMatch && (card.duedate === null)
+ break
+ }
+
+ if (card.duedate === null || !hasMatch) {
+ return false
+ }
+ const comparator = query[0] + (query[1] === '=' ? '=' : '')
+ const isValidComparator = ['<', '<=', '>', '>='].indexOf(comparator) !== -1
+ const parsedCardDate = moment(card.duedate)
+ const parsedDate = moment(query.substr(isValidComparator ? comparator.length : 0))
+ switch (comparator) {
+ case '<':
+ hasMatch = hasMatch && parsedCardDate.isBefore(parsedDate)
+ break
+ case '<=':
+ hasMatch = hasMatch && parsedCardDate.isSameOrBefore(parsedDate)
+ break
+ case '>':
+ hasMatch = hasMatch && parsedCardDate.isAfter(parsedDate)
+ break
+ case '>=':
+ hasMatch = hasMatch && parsedCardDate.isSameOrAfter(parsedDate)
+ break
+ default:
+ hasMatch = hasMatch && parsedCardDate.isSame(parsedDate)
+ break
+ }
+
+ } else if (filter === 'assigned') {
+ hasMatch = hasMatch && card.assignedUsers.findIndex((assignment) => {
+ return assignment.participant.primaryKey.toLowerCase() === filterOutQuotes(query).toLowerCase()
+ || assignment.participant.displayname.toLowerCase() === filterOutQuotes(query).toLowerCase()
+ }) !== -1
+ } else {
+ hasMatch = hasMatch && (card.title.toLowerCase().includes(filterOutQuotes(match).toLowerCase())
+ || card.description.toLowerCase().includes(filterOutQuotes(match).toLowerCase()))
+ }
+ if (!hasMatch) {
+ return false
+ }
+ }
+ return true
})
.sort((a, b) => a.order - b.order || a.createdAt - b.createdAt)
},
@@ -210,7 +293,7 @@ export default {
}
const updatedCard = await apiClient[call](card)
- commit('deleteCard', updatedCard)
+ commit('updateCard', updatedCard)
},
async assignCardToUser({ commit }, { card, assignee }) {
const user = await apiClient.assignUser(card.id, assignee.userId, assignee.type)
@@ -236,5 +319,14 @@ export default {
const updatedCard = await apiClient.updateCard(card)
commit('updateCardProperty', { property: 'duedate', card: updatedCard })
},
+
+ addCardData({ commit }, cardData) {
+ const card = { ...cardData }
+ commit('addStack', card.relatedStack)
+ commit('addBoard', card.relatedBoard)
+ delete card.relatedStack
+ delete card.relatedBoard
+ commit('addCard', card)
+ },
},
}
diff --git a/src/store/main.js b/src/store/main.js
index 6cccd231d..c5562eaf3 100644
--- a/src/store/main.js
+++ b/src/store/main.js
@@ -91,6 +91,9 @@ export default new Vuex.Store({
boards: state => {
return state.boards
},
+ boardById: state => (id) => {
+ return state.boards.find((board) => board.id === id)
+ },
assignables: state => {
return [
...state.assignableUsers.map((user) => ({ ...user, type: 0 })),
diff --git a/tests/integration/config/behat.yml b/tests/integration/config/behat.yml
index 0c083eca0..8a019df34 100644
--- a/tests/integration/config/behat.yml
+++ b/tests/integration/config/behat.yml
@@ -5,10 +5,7 @@ default:
- '%paths.base%/../features/'
contexts:
- ServerContext:
- baseUrl: http://localhost:8080/index.php/ocs/
- admin:
- - admin
- - admin
- regular_user_password: 123456
- - BoardContext:
baseUrl: http://localhost:8080/
+ - RequestContext
+ - BoardContext
+ - SearchContext
diff --git a/tests/integration/features/bootstrap/BoardContext.php b/tests/integration/features/bootstrap/BoardContext.php
index 995ec1fd7..cd994151b 100644
--- a/tests/integration/features/bootstrap/BoardContext.php
+++ b/tests/integration/features/bootstrap/BoardContext.php
@@ -1,6 +1,7 @@
getEnvironment();
+
+ $this->serverContext = $environment->getContext('ServerContext');
+ }
+
/**
* @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/
*/
public function createsABoardNamedWithColor($title, $color) {
- $this->sendJSONrequest('POST', '/index.php/apps/deck/boards', [
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/boards', [
'title' => $title,
'color' => $color
]);
- $this->response->getBody()->seek(0);
- $this->board = json_decode((string)$this->response->getBody(), true);
+ $this->getResponse()->getBody()->seek(0);
+ $this->board = json_decode((string)$this->getResponse()->getBody(), true);
}
/**
* @When /^fetches the board named "([^"]*)"$/
*/
public function fetchesTheBoardNamed($boardName) {
- $this->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $this->board['id'], []);
- $this->response->getBody()->seek(0);
- $this->board = json_decode((string)$this->response->getBody(), true);
+ $this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $this->board['id'], []);
+ $this->getResponse()->getBody()->seek(0);
+ $this->board = json_decode((string)$this->getResponse()->getBody(), true);
}
/**
@@ -48,7 +59,7 @@ public function sharesTheBoardWithUser($user, TableNode $permissions = null) {
];
$tableRows = isset($permissions) ? $permissions->getRowsHash() : [];
$result = array_merge($defaults, $tableRows);
- $this->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [
'type' => 0,
'participant' => $user,
'permissionEdit' => $result['permissionEdit'] === '1',
@@ -68,7 +79,7 @@ public function sharesTheBoardWithGroup($group, TableNode $permissions = null) {
];
$tableRows = isset($permissions) ? $permissions->getRowsHash() : [];
$result = array_merge($defaults, $tableRows);
- $this->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [
'type' => 1,
'participant' => $group,
'permissionEdit' => $result['permissionEdit'] === '1',
@@ -82,38 +93,38 @@ public function sharesTheBoardWithGroup($group, TableNode $permissions = null) {
* @When /^fetching the board list$/
*/
public function fetchingTheBoardList() {
- $this->sendJSONrequest('GET', '/index.php/apps/deck/boards');
+ $this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/boards');
}
/**
* @When /^fetching the board with id "([^"]*)"$/
*/
public function fetchingTheBoardWithId($id) {
- $this->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $id);
+ $this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $id);
}
/**
* @Given /^create a stack named "([^"]*)"$/
*/
public function createAStackNamed($name) {
- $this->sendJSONrequest('POST', '/index.php/apps/deck/stacks', [
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/stacks', [
'title' => $name,
'boardId' => $this->board['id']
]);
- $this->response->getBody()->seek(0);
- $this->stack = json_decode((string)$this->response->getBody(), true);
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ $this->stack = json_decode((string)$this->getResponse()->getBody(), true);
}
/**
* @Given /^create a card named "([^"]*)"$/
*/
public function createACardNamed($name) {
- $this->sendJSONrequest('POST', '/index.php/apps/deck/cards', [
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/cards', [
'title' => $name,
'stackId' => $this->stack['id']
]);
- $this->response->getBody()->seek(0);
- $this->card = json_decode((string)$this->response->getBody(), true);
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ $this->card = json_decode((string)$this->getResponse()->getBody(), true);
}
/**
@@ -151,4 +162,70 @@ public function shareWithTheCard($file) {
]);
$this->serverContext->creatingShare($table);
}
+
+ /**
+ * @Given /^set the description to "([^"]*)"$/
+ */
+ public function setTheDescriptionTo($description) {
+ $this->requestContext->sendJSONrequest('PUT', '/index.php/apps/deck/cards/' . $this->card['id'], array_merge(
+ $this->card,
+ ['description' => $description]
+ ));
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ $this->card = json_decode((string)$this->getResponse()->getBody(), true);
+ }
+
+ /**
+ * @Given /^set the card attribute "([^"]*)" to "([^"]*)"$/
+ */
+ public function setCardAttribute($attribute, $value) {
+ $this->requestContext->sendJSONrequest('PUT', '/index.php/apps/deck/cards/' . $this->card['id'], array_merge(
+ $this->card,
+ [$attribute => $value]
+ ));
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ $this->card = json_decode((string)$this->getResponse()->getBody(), true);
+ }
+
+ /**
+ * @Given /^set the card duedate to "([^"]*)"$/
+ */
+ public function setTheCardDuedateTo($arg1) {
+ $date = new DateTime($arg1);
+ $this->setCardAttribute('duedate', $date->format(DateTimeInterface::ATOM));
+ }
+
+ /**
+ * @Given /^assign the card to the user "([^"]*)"$/
+ */
+ public function assignTheCardToTheUser($user) {
+ $this->assignToCard($user, 0);
+ }
+
+ /**
+ * @Given /^assign the card to the group "([^"]*)"$/
+ */
+ public function assignTheCardToTheGroup($user) {
+ $this->assignToCard($user, 1);
+ }
+
+ private function assignToCard($participant, $type) {
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/cards/' . $this->card['id'] .'/assign', [
+ 'userId' => $participant,
+ 'type' => $type
+ ]);
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ }
+
+ /**
+ * @Given /^assign the tag "([^"]*)" to the card$/
+ */
+ public function assignTheTagToTheCard($tag) {
+ $filteredLabels = array_filter($this->board['labels'], function ($label) use ($tag) {
+ return $label['title'] === $tag;
+ });
+ $label = array_shift($filteredLabels);
+ $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/cards/' . $this->card['id'] .'/label/' . $label['id']);
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ }
}
diff --git a/tests/integration/features/bootstrap/RequestContext.php b/tests/integration/features/bootstrap/RequestContext.php
new file mode 100644
index 000000000..f3aedc8d2
--- /dev/null
+++ b/tests/integration/features/bootstrap/RequestContext.php
@@ -0,0 +1,140 @@
+getEnvironment();
+
+ $this->serverContext = $environment->getContext('ServerContext');
+ }
+
+ private function getBaseUrl() {
+ }
+
+ /**
+ * @Then the response should have a status code :code
+ * @param string $code
+ * @throws InvalidArgumentException
+ */
+ public function theResponseShouldHaveStatusCode($code) {
+ $currentCode = $this->response->getStatusCode();
+ if ($currentCode !== (int)$code) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Expected %s as code got %s',
+ $code,
+ $currentCode
+ )
+ );
+ }
+ }
+
+ /**
+ * @Then /^the response Content-Type should be "([^"]*)"$/
+ * @param string $contentType
+ */
+ public function theResponseContentTypeShouldbe($contentType) {
+ Assert::assertEquals($contentType, $this->response->getHeader('Content-Type')[0]);
+ }
+
+ /**
+ * @Then the response should be a JSON array with the following mandatory values
+ * @param TableNode $table
+ * @throws InvalidArgumentException
+ */
+ public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table) {
+ $this->response->getBody()->seek(0);
+ $expectedValues = $table->getColumnsHash();
+ $realResponseArray = json_decode($this->response->getBody()->getContents(), true);
+ foreach ($expectedValues as $value) {
+ if ((string)$realResponseArray[$value['key']] !== (string)$value['value']) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Expected %s for key %s got %s',
+ (string)$value['value'],
+ $value['key'],
+ (string)$realResponseArray[$value['key']]
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * @Then the response should be a JSON array with a length of :length
+ * @param int $length
+ * @throws InvalidArgumentException
+ */
+ public function theResponseShouldBeAJsonArrayWithALengthOf($length) {
+ $this->response->getBody()->seek(0);
+ $realResponseArray = json_decode($this->response->getBody()->getContents(), true);
+ if ((int)count($realResponseArray) !== (int)$length) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Expected %d as length got %d',
+ $length,
+ count($realResponseArray)
+ )
+ );
+ }
+ }
+
+ public function sendJSONrequest($method, $url, $data = []) {
+ $client = new Client;
+ try {
+ $this->response = $client->request(
+ $method,
+ rtrim($this->serverContext->getBaseUrl(), '/') . '/' . ltrim($url, '/'),
+ [
+ 'cookies' => $this->serverContext->getCookieJar(),
+ 'json' => $data,
+ 'headers' => [
+ 'requesttoken' => $this->serverContext->getReqestToken(),
+ ]
+ ]
+ );
+ } catch (ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ public function sendOCSRequest($method, $url, $data = []) {
+ $client = new Client;
+ try {
+ $this->response = $client->request(
+ $method,
+ rtrim($this->serverContext->getBaseUrl(), '/') . '/ocs/v2.php/' . ltrim($url, '/'),
+ [
+ 'cookies' => $this->serverContext->getCookieJar(),
+ 'json' => $data,
+ 'headers' => [
+ 'requesttoken' => $this->serverContext->getReqestToken(),
+ 'OCS-APIREQUEST' => 'true',
+ 'Accept' => 'application/json'
+ ]
+ ]
+ );
+ } catch (ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ public function getResponse(): ResponseInterface {
+ return $this->response;
+ }
+}
diff --git a/tests/integration/features/bootstrap/RequestTrait.php b/tests/integration/features/bootstrap/RequestTrait.php
index c6fc1f978..dd8939f62 100644
--- a/tests/integration/features/bootstrap/RequestTrait.php
+++ b/tests/integration/features/bootstrap/RequestTrait.php
@@ -1,121 +1,46 @@
+ *
+ * @author Julius Härtl
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
-use Behat\Gherkin\Node\TableNode;
-use GuzzleHttp\Client;
-use GuzzleHttp\Exception\ClientException;
-use PHPUnit\Framework\Assert;
require_once __DIR__ . '/../../vendor/autoload.php';
-trait RequestTrait {
- private $baseUrl;
- private $adminUser;
- private $regularUser;
- private $cookieJar;
-
- private $response;
- public function __construct($baseUrl, $admin = 'admin', $regular_user_password = '123456') {
- $this->baseUrl = $baseUrl;
- $this->adminUser = $admin === 'admin' ? ['admin', 'admin'] : $admin;
- $this->regularUser = $regular_user_password;
- }
+trait RequestTrait {
- /** @var ServerContext */
- private $serverContext;
+ /** @var RequestContext */
+ protected $requestContext;
/** @BeforeScenario */
- public function gatherContexts(BeforeScenarioScope $scope) {
+ public function gatherRequestTraitContext(BeforeScenarioScope $scope) {
$environment = $scope->getEnvironment();
-
- $this->serverContext = $environment->getContext('ServerContext');
- }
-
- /**
- * @Then the response should have a status code :code
- * @param string $code
- * @throws InvalidArgumentException
- */
- public function theResponseShouldHaveStatusCode($code) {
- $currentCode = $this->response->getStatusCode();
- if ($currentCode !== (int)$code) {
- throw new InvalidArgumentException(
- sprintf(
- 'Expected %s as code got %s',
- $code,
- $currentCode
- )
- );
- }
- }
-
- /**
- * @Then /^the response Content-Type should be "([^"]*)"$/
- * @param string $contentType
- */
- public function theResponseContentTypeShouldbe($contentType) {
- Assert::assertEquals($contentType, $this->response->getHeader('Content-Type')[0]);
- }
-
- /**
- * @Then the response should be a JSON array with the following mandatory values
- * @param TableNode $table
- * @throws InvalidArgumentException
- */
- public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table) {
- $this->response->getBody()->seek(0);
- $expectedValues = $table->getColumnsHash();
- $realResponseArray = json_decode($this->response->getBody()->getContents(), true);
- foreach ($expectedValues as $value) {
- if ((string)$realResponseArray[$value['key']] !== (string)$value['value']) {
- throw new InvalidArgumentException(
- sprintf(
- 'Expected %s for key %s got %s',
- (string)$value['value'],
- $value['key'],
- (string)$realResponseArray[$value['key']]
- )
- );
- }
- }
- }
-
- /**
- * @Then the response should be a JSON array with a length of :length
- * @param int $length
- * @throws InvalidArgumentException
- */
- public function theResponseShouldBeAJsonArrayWithALengthOf($length) {
- $this->response->getBody()->seek(0);
- $realResponseArray = json_decode($this->response->getBody()->getContents(), true);
- if ((int)count($realResponseArray) !== (int)$length) {
- throw new InvalidArgumentException(
- sprintf(
- 'Expected %d as length got %d',
- $length,
- count($realResponseArray)
- )
- );
- }
+ $this->requestContext = $environment->getContext('RequestContext');
}
- private function sendJSONrequest($method, $url, $data = []) {
- $client = new Client;
- try {
- $this->response = $client->request(
- $method,
- $this->baseUrl . $url,
- [
- 'cookies' => $this->serverContext->getCookieJar(),
- 'json' => $data,
- 'headers' => [
- 'requesttoken' => $this->serverContext->getReqestToken()
- ]
- ]
- );
- } catch (ClientException $e) {
- $this->response = $e->getResponse();
- }
+ public function getResponse() {
+ return $this->requestContext->getResponse();
}
}
diff --git a/tests/integration/features/bootstrap/SearchContext.php b/tests/integration/features/bootstrap/SearchContext.php
new file mode 100644
index 000000000..c65ca6bc6
--- /dev/null
+++ b/tests/integration/features/bootstrap/SearchContext.php
@@ -0,0 +1,81 @@
+getEnvironment();
+
+ $this->boardContext = $environment->getContext('BoardContext');
+ }
+
+ /**
+ * @When /^searching for "([^"]*)"$/
+ * @param string $term
+ */
+ public function searchingFor(string $term) {
+ $this->requestContext->sendOCSRequest('GET', '/apps/deck/api/v1.0/search?term=' . urlencode($term), []);
+ $this->requestContext->getResponse()->getBody()->seek(0);
+ $data = (string)$this->getResponse()->getBody();
+ $this->searchResults = json_decode($data, true);
+ }
+
+ /**
+ * @When /^searching for '([^']*)'$/
+ * @param string $term
+ */
+ public function searchingForQuotes(string $term) {
+ $this->searchingFor($term);
+ }
+
+ /**
+ * @Then /^the board "([^"]*)" is found$/
+ */
+ public function theBoardIsFound($arg1) {
+ $ocsData = $this->searchResults['ocs']['data'];
+ $found = false;
+ foreach ($ocsData as $result) {
+ if ($result['title'] === $arg1) {
+ $found = true;
+ }
+ }
+ Assert::assertTrue($found, 'Board can be found');
+ }
+
+ private function cardIsFound($arg1) {
+ $ocsData = $this->searchResults['ocs']['data'];
+ $found = false;
+ foreach ($ocsData as $result) {
+ if ($result['title'] === $arg1) {
+ $found = true;
+ }
+ }
+ return $found;
+ }
+
+ /**
+ * @Then /^the card "([^"]*)" is found$/
+ */
+ public function theCardIsFound($arg1) {
+ Assert::assertTrue($this->cardIsFound($arg1), 'Card can be found');
+ }
+
+ /**
+ * @Then /^the card "([^"]*)" is not found$/
+ */
+ public function theCardIsNotFound($arg1) {
+ Assert::assertFalse($this->cardIsFound($arg1), 'Card can not be found');
+ }
+}
diff --git a/tests/integration/features/bootstrap/ServerContext.php b/tests/integration/features/bootstrap/ServerContext.php
index 56a172aa1..e4ed567f1 100644
--- a/tests/integration/features/bootstrap/ServerContext.php
+++ b/tests/integration/features/bootstrap/ServerContext.php
@@ -6,7 +6,14 @@
require_once __DIR__ . '/../../vendor/autoload.php';
class ServerContext implements Context {
- use WebDav;
+ use WebDav {
+ WebDav::__construct as private __tConstruct;
+ }
+
+ public function __construct($baseUrl) {
+ $this->rawBaseUrl = $baseUrl;
+ $this->__tConstruct($baseUrl . '/index.php/ocs/', ['admin', 'admin'], '123456');
+ }
/** @var string */
private $mappedUserId;
@@ -28,6 +35,10 @@ public function actingAsUser($user) {
$this->asAn($user);
}
+ public function getBaseUrl(): string {
+ return $this->rawBaseUrl;
+ }
+
public function getCookieJar(): CookieJar {
return $this->cookieJar;
}
diff --git a/tests/integration/features/decks.feature b/tests/integration/features/decks.feature
index f2354209c..bb141ef7d 100644
--- a/tests/integration/features/decks.feature
+++ b/tests/integration/features/decks.feature
@@ -4,23 +4,6 @@ Feature: decks
Given user "admin" exists
Given user "user0" exists
- Scenario: Request the main frontend page
- Given Logging in using web as "admin"
- When Sending a "GET" to "/index.php/apps/deck" without requesttoken
- Then the HTTP status code should be "200"
-
- Scenario: Fetch the board list
- Given Logging in using web as "admin"
- When fetching the board list
- Then the response should have a status code "200"
- And the response Content-Type should be "application/json; charset=utf-8"
-
- Scenario: Fetch board details of a nonexisting board
- Given Logging in using web as "admin"
- When fetching the board with id "99999999"
- Then the response should have a status code "403"
- And the response Content-Type should be "application/json; charset=utf-8"
-
Scenario: Create a new board
Given Logging in using web as "admin"
When creates a board named "MyBoard" with color "000000"
diff --git a/tests/integration/features/search.feature b/tests/integration/features/search.feature
new file mode 100644
index 000000000..179ffc277
--- /dev/null
+++ b/tests/integration/features/search.feature
@@ -0,0 +1,259 @@
+Feature: Searching for cards
+
+ Background:
+ Given user "admin" exists
+ Given user "user0" exists
+ Given Logging in using web as "admin"
+ When creates a board named "MyBoard" with color "000000"
+ When create a stack named "ToDo"
+ And create a card named "Example task 1"
+ And create a card named "Example task 2"
+ When create a stack named "In progress"
+ And create a card named "Progress task 1"
+ And create a card named "Progress task 2"
+ When create a stack named "Done"
+ And create a card named "Done task 1"
+ And set the description to "Done task description 1"
+ And create a card named "Done task 2"
+ And set the description to "Done task description 2"
+ And shares the board with user "user0"
+
+
+ Scenario: Search for a card with multiple terms
+ When searching for "Example task"
+ Then the card "Example task 1" is found
+ Then the card "Example task 2" is found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+
+ Scenario: Search for a card in a specific list
+ When searching for "task list:Done"
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is found
+
+ Scenario: Search for a card with one term
+ When searching for "task"
+ Then the card "Example task 1" is found
+ Then the card "Example task 2" is found
+ Then the card "Progress task 1" is found
+ Then the card "Progress task 2" is found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is found
+
+ Scenario: Search for a card with an differently cased term
+ When searching for "tAsk"
+ Then the card "Example task 1" is found
+ Then the card "Example task 2" is found
+ Then the card "Progress task 1" is found
+ Then the card "Progress task 2" is found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is found
+
+ Scenario: Search for a card title
+ When searching for 'title:"Done task 1"'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is not found
+
+ Scenario: Search for a card description
+ When searching for 'description:"Done task description"'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is found
+
+ Scenario: Search for a non-existing card description
+ When searching for 'description:"Example"'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+
+ Scenario: Search on shared boards
+ Given Logging in using web as "user0"
+ When searching for "task"
+ Then the card "Example task 1" is found
+ Then the card "Example task 2" is found
+ Then the card "Progress task 1" is found
+ Then the card "Progress task 2" is found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is found
+
+ Scenario: Search for a card due date
+ Given create a card named "Overdue task"
+ And set the card attribute "duedate" to "2020-12-12"
+ And create a card named "Future task"
+ And set the card attribute "duedate" to "3000-12-12"
+ And create a card named "Tomorrow task"
+ And set the card duedate to "tomorrow"
+ When searching for 'date:overdue'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+ Then the card "Overdue task" is found
+ Then the card "Future task" is not found
+
+ Scenario: Search for a card due date
+ And create a card named "Overdue task"
+ And set the card attribute "duedate" to "2020-12-12"
+ And create a card named "Future task"
+ And set the card attribute "duedate" to "3000-12-12"
+ And create a card named "Tomorrow task"
+ And set the card duedate to "+12 hours"
+ And create a card named "Next week task"
+ And set the card duedate to "+5 days"
+
+ When searching for 'date:today'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+ Then the card "Overdue task" is not found
+ Then the card "Future task" is not found
+ Then the card "Tomorrow task" is found
+ Then the card "Next week task" is not found
+
+ When searching for 'date:week'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+ Then the card "Overdue task" is not found
+ Then the card "Future task" is not found
+ Then the card "Tomorrow task" is found
+ Then the card "Next week task" is found
+
+ When searching for 'date:month'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+ Then the card "Overdue task" is not found
+ Then the card "Future task" is not found
+ Then the card "Tomorrow task" is found
+ Then the card "Next week task" is found
+
+ When searching for 'date:none'
+ Then the card "Example task 1" is found
+ Then the card "Example task 2" is found
+ Then the card "Progress task 1" is found
+ Then the card "Progress task 2" is found
+ Then the card "Done task 1" is found
+ Then the card "Done task 2" is found
+ Then the card "Overdue task" is not found
+ Then the card "Future task" is not found
+ Then the card "Tomorrow task" is not found
+ Then the card "Next week task" is not found
+
+ When searching for 'date:<"+7 days"'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+ Then the card "Overdue task" is found
+ Then the card "Future task" is not found
+ Then the card "Tomorrow task" is found
+ Then the card "Next week task" is found
+
+ When searching for 'date:>"+10 days"'
+ Then the card "Example task 1" is not found
+ Then the card "Example task 2" is not found
+ Then the card "Progress task 1" is not found
+ Then the card "Progress task 2" is not found
+ Then the card "Done task 1" is not found
+ Then the card "Done task 2" is not found
+ Then the card "Overdue task" is not found
+ Then the card "Future task" is found
+ Then the card "Tomorrow task" is not found
+ Then the card "Next week task" is not found
+
+ Scenario: Search for assigned user
+ Given user "user1" exists
+ And shares the board with user "user1"
+ Given create a card named "Assigned card to user1"
+ And assign the card to the user "user1"
+ When searching for 'assigned:user1'
+ Then the card "Example task 1" is not found
+ And the card "Assigned card to user1" is found
+
+ Scenario: Search for assigned user by displayname
+ Given user "ada" with displayname "Ada Lovelace" exists
+ And shares the board with user "ada"
+ Given create a card named "Assigned card to ada"
+ And assign the card to the user "ada"
+ When searching for 'assigned:"Ada Lovelace"'
+ Then the card "Example task 1" is not found
+ And the card "Assigned card to ada" is found
+
+ Scenario: Search for assigned users
+ Given user "user1" exists
+ And shares the board with user "user1"
+ Given create a card named "Assigned card to user0"
+ And assign the card to the user "user0"
+ Given create a card named "Assigned card to user01"
+ And assign the card to the user "user0"
+ And assign the card to the user "user1"
+ When searching for 'assigned:user0 assigned:user1'
+ Then the card "Example task 1" is not found
+ And the card "Assigned card to user0" is not found
+ And the card "Assigned card to user01" is found
+
+ Scenario: Search for assigned group
+ Given user "user1" exists
+ And shares the board with user "user1"
+ Given group "group1" exists
+ And shares the board with group "group1"
+ Given user "user1" belongs to group "group1"
+ Given create a card named "Assigned card to group1"
+ And assign the card to the group "group1"
+ When searching for 'assigned:user1'
+ Then the card "Example task 1" is not found
+ And the card "Assigned card to group1" is found
+
+ When searching for 'assigned:group1'
+ Then the card "Example task 1" is not found
+ And the card "Assigned card to group1" is found
+
+ Scenario: Search for assigned tag
+ Given create a card named "Labeled card"
+ # Default labels from boards are used for this test case
+ And assign the tag "Finished" to the card
+ When searching for 'tag:Finished'
+ Then the card "Example task 1" is not found
+ And the card "Labeled card" is found
+
+ Given create a card named "Multi labeled card"
+ And assign the tag "Finished" to the card
+ And assign the tag "To review" to the card
+ When searching for 'tag:Finished tag:Later'
+ Then the card "Example task 1" is not found
+ And the card "Multi labeled card" is not found
+
+ When searching for 'tag:Finished tag:"To review"'
+ Then the card "Example task 1" is not found
+ And the card "Labeled card" is not found
+ And the card "Multi labeled card" is found
diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml
index 0881083d7..7145e7996 100644
--- a/tests/psalm-baseline.xml
+++ b/tests/psalm-baseline.xml
@@ -10,11 +10,6 @@
(int)$subjectParams['comment']
-
-
- method_exists($shareManager, 'registerShareProvider')
-
- void
@@ -121,12 +116,16 @@
+ $entity->getId()$cardId
+
+ getUserIdGroups
+
@@ -271,7 +270,7 @@
$this->rootFolder$this->rootFolderIRootFolder
- Share\Exceptions\ShareNotFound
+ ShareNotFound
@@ -280,6 +279,13 @@
\OCA\Circles\Api\v1\Circles
+
+
+ $this->l10n
+ $this->urlGenerator
+ $this->userManager
+
+ BadRquestException
@@ -292,7 +298,7 @@
getSharesInFolder
-
+ GenericShareExceptionGenericShareExceptionShareNotFound
@@ -300,6 +306,7 @@
ShareNotFoundShareNotFoundShareNotFound
+ ShareNotFound
diff --git a/tests/unit/Command/UserExportTest.php b/tests/unit/Command/UserExportTest.php
index b232399f0..81fc49642 100644
--- a/tests/unit/Command/UserExportTest.php
+++ b/tests/unit/Command/UserExportTest.php
@@ -111,7 +111,7 @@ public function testExecute() {
->method('find')
->willReturn($cards[0]);
$this->assignedUserMapper->expects($this->exactly(count($boards) * count($stacks) * count($cards)))
- ->method('find')
+ ->method('findAll')
->willReturn([]);
$result = $this->invokePrivate($this->userExport, 'execute', [$input, $output]);
}
diff --git a/tests/unit/Db/BoardMapperTest.php b/tests/unit/Db/BoardMapperTest.php
index b34f92b93..2b6ff56ff 100644
--- a/tests/unit/Db/BoardMapperTest.php
+++ b/tests/unit/Db/BoardMapperTest.php
@@ -132,9 +132,15 @@ public function testFindAllByUser() {
public function testFindAll() {
$actual = $this->boardMapper->findAll();
- $this->assertEquals($this->boards[0]->getId(), $actual[0]->getId());
- $this->assertEquals($this->boards[1]->getId(), $actual[1]->getId());
- $this->assertEquals($this->boards[2]->getId(), $actual[2]->getId());
+ $this->assertEquals(1, count(array_filter($actual, function ($card) {
+ return $card->getId() === $this->boards[0]->getId();
+ })));
+ $this->assertEquals(1, count(array_filter($actual, function ($card) {
+ return $card->getId() === $this->boards[1]->getId();
+ })));
+ $this->assertEquals(1, count(array_filter($actual, function ($card) {
+ return $card->getId() === $this->boards[2]->getId();
+ })));
}
public function testFindAllToDelete() {
diff --git a/tests/unit/Notification/NotificationHelperTest.php b/tests/unit/Notification/NotificationHelperTest.php
index 80d7997e6..e5b8951d0 100644
--- a/tests/unit/Notification/NotificationHelperTest.php
+++ b/tests/unit/Notification/NotificationHelperTest.php
@@ -24,7 +24,7 @@
namespace OCA\Deck\Notification;
use OCA\Deck\Db\Acl;
-use OCA\Deck\Db\AssignedUsersMapper;
+use OCA\Deck\Db\AssignmentMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card;
@@ -59,7 +59,7 @@ class NotificationHelperTest extends \Test\TestCase {
protected $cardMapper;
/** @var BoardMapper|MockObject */
protected $boardMapper;
- /** @var AssignedUsersMapper|MockObject */
+ /** @var AssignmentMapper|MockObject */
protected $assignedUsersMapper;
/** @var PermissionService|MockObject */
protected $permissionService;
@@ -78,7 +78,7 @@ public function setUp(): void {
parent::setUp();
$this->cardMapper = $this->createMock(CardMapper::class);
$this->boardMapper = $this->createMock(BoardMapper::class);
- $this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
+ $this->assignedUsersMapper = $this->createMock(AssignmentMapper::class);
$this->permissionService = $this->createMock(PermissionService::class);
$this->config = $this->createMock(IConfig::class);
$this->notificationManager = $this->createMock(IManager::class);
diff --git a/tests/unit/Search/FilterStringParserTest.php b/tests/unit/Search/FilterStringParserTest.php
new file mode 100644
index 000000000..91c94163c
--- /dev/null
+++ b/tests/unit/Search/FilterStringParserTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * @author Julius Härtl
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+declare(strict_types=1);
+
+namespace OCA\Deck\Search;
+
+use OCA\Deck\Search\Query\DateQueryParameter;
+use OCA\Deck\Search\Query\SearchQuery;
+use OCA\Deck\Search\Query\StringQueryParameter;
+use OCP\IL10N;
+use PHPUnit\Framework\Assert;
+use PHPUnit\Framework\TestCase;
+
+class FilterStringParserTest extends TestCase {
+ private $l10n;
+ private $parser;
+
+ public function setUp(): void {
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->parser = new FilterStringParser($this->l10n);
+ }
+
+ public function testParseEmpty() {
+ $result = $this->parser->parse(null);
+ $expected = new SearchQuery();
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function testParseTextTokens() {
+ $result = $this->parser->parse('a b c');
+ $expected = new SearchQuery();
+ $expected->addTextToken('a');
+ $expected->addTextToken('b');
+ $expected->addTextToken('c');
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function testParseTextToken() {
+ $result = $this->parser->parse('abc');
+ $expected = new SearchQuery();
+ $expected->addTextToken('abc');
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function testParseTextTokenQuotes() {
+ $result = $this->parser->parse('a b c "a b c" tag:abc tag:"a b c" tag:\'d e f\'');
+ $expected = new SearchQuery();
+ $expected->addTextToken('a');
+ $expected->addTextToken('b');
+ $expected->addTextToken('c');
+ $expected->addTextToken('a b c');
+ $expected->addTag(new StringQueryParameter('tag', SearchQuery::COMPARATOR_EQUAL, 'abc'));
+ $expected->addTag(new StringQueryParameter('tag', SearchQuery::COMPARATOR_EQUAL, 'a b c'));
+ $expected->addTag(new StringQueryParameter('tag', SearchQuery::COMPARATOR_EQUAL, 'd e f'));
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function testParseTagComparatorNotSupported() {
+ $result = $this->parser->parse('tag:<"a tag"');
+ $expected = new SearchQuery();
+ $expected->addTag(new StringQueryParameter('tag', SearchQuery::COMPARATOR_EQUAL, '<"a tag"'));
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function testParseTextTokenQuotesSingle() {
+ $result = $this->parser->parse('a b c \'a b c\'');
+ $expected = new SearchQuery();
+ $expected->addTextToken('a');
+ $expected->addTextToken('b');
+ $expected->addTextToken('c');
+ $expected->addTextToken('a b c');
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function testParseTextTokenQuotesWrong() {
+ $result = $this->parser->parse('"a b" c"');
+ $expected = new SearchQuery();
+ $expected->addTextToken('a b');
+ $expected->addTextToken('c"');
+ Assert::assertEquals($expected, $result);
+ }
+
+ public function dataParseDate() {
+ return [
+ ['date:today', [new DateQueryParameter('date', SearchQuery::COMPARATOR_EQUAL, 'today')], []],
+ ['date:>today', [new DateQueryParameter('date', SearchQuery::COMPARATOR_MORE, 'today')], []],
+ ['date:>=today', [new DateQueryParameter('date', SearchQuery::COMPARATOR_MORE_EQUAL, 'today')], []],
+ ['date:today', [new DateQueryParameter('date', SearchQuery::COMPARATOR_LESS, '>today')], []],
+ ['date:=today', [new DateQueryParameter('date', SearchQuery::COMPARATOR_EQUAL, '=today')], []],
+ ['date:today todo', [new DateQueryParameter('date', SearchQuery::COMPARATOR_EQUAL, 'today')], ['todo']],
+ ['date:"last day of next month" todo', [new DateQueryParameter('date', SearchQuery::COMPARATOR_EQUAL, 'last day of next month')], ['todo']],
+ ['date:"last day of next month" "todo task" task', [new DateQueryParameter('date', SearchQuery::COMPARATOR_EQUAL, 'last day of next month')], ['todo task', 'task']],
+ ];
+ }
+ /**
+ * @dataProvider dataParseDate
+ */
+ public function testParseDate($query, $dates, array $tokens) {
+ $result = $this->parser->parse($query);
+ $expected = new SearchQuery();
+ foreach ($dates as $date) {
+ $expected->addDuedate($date);
+ }
+ foreach ($tokens as $token) {
+ $expected->addTextToken($token);
+ }
+ Assert::assertEquals($expected, $result);
+ }
+}
diff --git a/tests/unit/Service/BoardServiceTest.php b/tests/unit/Service/BoardServiceTest.php
index d0893ef46..7dba39b5d 100644
--- a/tests/unit/Service/BoardServiceTest.php
+++ b/tests/unit/Service/BoardServiceTest.php
@@ -382,7 +382,7 @@ public function testDeleteAcl() {
$assignment = new Assignment();
$assignment->setParticipant('admin');
$this->assignedUsersMapper->expects($this->once())
- ->method('findByUserId')
+ ->method('findByParticipant')
->with('admin')
->willReturn([$assignment]);
$this->assignedUsersMapper->expects($this->once())
diff --git a/tests/unit/Service/CardServiceTest.php b/tests/unit/Service/CardServiceTest.php
index cdcabcb4a..3e351ac30 100644
--- a/tests/unit/Service/CardServiceTest.php
+++ b/tests/unit/Service/CardServiceTest.php
@@ -25,9 +25,11 @@
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Db\AssignmentMapper;
+use OCA\Deck\Db\Board;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\ChangeHelper;
+use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
@@ -126,6 +128,17 @@ public function testFind() {
$this->userManager->expects($this->once())
->method('get')
->willReturn($user);
+ $this->commentsManager->expects($this->once())
+ ->method('getNumberOfCommentsForObject')
+ ->willReturn(0);
+ $boardMock = $this->createMock(Board::class);
+ $stackMock = $this->createMock(Stack::class);
+ $this->stackMapper->expects($this->any())
+ ->method('find')
+ ->willReturn($stackMock);
+ $this->boardService->expects($this->any())
+ ->method('find')
+ ->willReturn($boardMock);
$card = new Card();
$card->setId(1337);
$this->cardMapper->expects($this->any())
@@ -133,12 +146,14 @@ public function testFind() {
->with(123)
->willReturn($card);
$this->assignedUsersMapper->expects($this->any())
- ->method('find')
+ ->method('findAll')
->with(1337)
->willReturn(['user1', 'user2']);
$cardExpected = new Card();
$cardExpected->setId(1337);
$cardExpected->setAssignedUsers(['user1', 'user2']);
+ $cardExpected->setRelatedBoard($boardMock);
+ $cardExpected->setRelatedStack($stackMock);
$this->assertEquals($cardExpected, $this->cardService->find(123));
}