Merge pull request #6452 from nextcloud/feat/1813-clonecopy-a-card

feat(cards): add card cloning ability
This commit is contained in:
Julius Knorr
2024-12-19 16:26:13 +01:00
committed by GitHub
11 changed files with 263 additions and 101 deletions

View File

@@ -137,6 +137,8 @@ return [
['name' => 'comments_api#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'PUT'], ['name' => 'comments_api#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'PUT'],
['name' => 'comments_api#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'], ['name' => 'comments_api#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'],
['name' => 'card#clone', 'url' => '/api/v{apiVersion}/cards/{cardId}/clone', 'verb' => 'POST'],
['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'], ['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'],
['name' => 'search#search', 'url' => '/api/v{apiVersion}/search', 'verb' => 'GET'], ['name' => 'search#search', 'url' => '/api/v{apiVersion}/search', 'verb' => 'GET'],

View File

@@ -25,9 +25,9 @@ const useModal = (useModal) => {
}) })
} }
describe('Card', function() { describe('Card', function () {
let boardId let boardId
before(function() { before(function () {
cy.createUser(user) cy.createUser(user)
cy.login(user) cy.login(user)
cy.createExampleBoard({ cy.createExampleBoard({
@@ -38,11 +38,11 @@ describe('Card', function() {
}) })
}) })
beforeEach(function() { beforeEach(function () {
cy.login(user) cy.login(user)
}) })
it('Can add a card', function() { it('Can add a card', function () {
cy.visit(`/apps/deck/#/board/${boardId}`) cy.visit(`/apps/deck/#/board/${boardId}`)
const newCardTitle = 'Write some cypress tests' const newCardTitle = 'Write some cypress tests'
@@ -63,7 +63,7 @@ describe('Card', function() {
}) })
}) })
it('Create card from overview', function() { it('Create card from overview', function () {
cy.visit(`/apps/deck/#/`) cy.visit(`/apps/deck/#/`)
const newCardTitle = 'Test create from overview' const newCardTitle = 'Test create from overview'
cy.intercept({ method: 'POST', url: '**/apps/deck/cards' }).as('save') cy.intercept({ method: 'POST', url: '**/apps/deck/cards' }).as('save')
@@ -71,6 +71,10 @@ describe('Card', function() {
cy.get('.button-vue[aria-label*="Add card"]') cy.get('.button-vue[aria-label*="Add card"]')
.first().click() .first().click()
// Somehow this avoids the electron crash
cy.wait(2000)
cy.get('.modal-mask.card-selector .card-title').should('be.visible').click().type(newCardTitle) cy.get('.modal-mask.card-selector .card-title').should('be.visible').click().type(newCardTitle)
cy.get('.modal-mask.card-selector .multiselect-board').should('be.visible').click() cy.get('.modal-mask.card-selector .multiselect-board').should('be.visible').click()
cy.get('.vs__dropdown-menu [data-cy="board-select-title"]:contains("' + boardData.title + '")').should('be.visible').click() cy.get('.vs__dropdown-menu [data-cy="board-select-title"]:contains("' + boardData.title + '")').should('be.visible').click()
@@ -91,14 +95,14 @@ describe('Card', function() {
}) })
describe('Modal', () => { describe('Modal', () => {
beforeEach(function() { beforeEach(function () {
cy.login(user) cy.login(user)
useModal(true).then(() => { useModal(true).then(() => {
cy.visit(`/apps/deck/#/board/${boardId}`) cy.visit(`/apps/deck/#/board/${boardId}`)
}) })
}) })
it('Can show card details modal', function() { it('Can show card details modal', function () {
cy.getNavigationEntry(boardData.title) cy.getNavigationEntry(boardData.title)
.first().click({ force: true }) .first().click({ force: true })
@@ -124,7 +128,7 @@ describe('Card', function() {
cy.get('.attachment-list .basename').contains('welcome.txt') cy.get('.attachment-list .basename').contains('welcome.txt')
}) })
it.only('Shows the modal with the editor', () => { it('Shows the modal with the editor', () => {
cy.get('.card:contains("Hello world")').should('be.visible').click() cy.get('.card:contains("Hello world")').should('be.visible').click()
cy.intercept({ method: 'PUT', url: '**/apps/deck/cards/*' }).as('save') cy.intercept({ method: 'PUT', url: '**/apps/deck/cards/*' }).as('save')
cy.get('.modal__card').should('be.visible') cy.get('.modal__card').should('be.visible')
@@ -161,9 +165,9 @@ describe('Card', function() {
cy.get('.reference-picker-modal--content .reference-picker .multiselect-list').should('be.visible').contains(boardData.stacks[0].title) cy.get('.reference-picker-modal--content .reference-picker .multiselect-list').should('be.visible').contains(boardData.stacks[0].title)
cy.get('.reference-picker-modal--content .reference-picker button.button-vue--vue-primary').should('be.visible').click() cy.get('.reference-picker-modal--content .reference-picker button.button-vue--vue-primary').should('be.visible').click()
cy.wait('@save', { timeout: 7000 }) cy.wait('@save', { timeout: 7000 })
cy.get('.modal__card .ProseMirror').contains('/index.php/apps/deck/card/').should('be.visible') cy.get('.modal__card .ProseMirror').contains('/index.php/apps/deck/card/').should('have.length', 1)
cy.visit(`/apps/deck/#/board/${boardId}`) cy.visit(`/apps/deck/board/${boardId}`)
cy.reload() cy.reload()
cy.get('.board .stack').eq(0).within(() => { cy.get('.board .stack').eq(0).within(() => {
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible') cy.get(`.card:contains("${newCardTitle}")`).should('be.visible')
@@ -172,7 +176,7 @@ describe('Card', function() {
}) })
describe('Sidebar', () => { describe('Sidebar', () => {
beforeEach(function() { beforeEach(function () {
cy.login(user) cy.login(user)
useModal(false).then(() => { useModal(false).then(() => {
cy.visit(`/apps/deck/#/board/${boardId}`) cy.visit(`/apps/deck/#/board/${boardId}`)
@@ -185,7 +189,7 @@ describe('Card', function() {
.find('.ProseMirror h1').contains('Hello world writing more text').should('be.visible') .find('.ProseMirror h1').contains('Hello world writing more text').should('be.visible')
}) })
it('Set a due date', function() { it('Set a due date', function () {
const newCardTitle = 'Card with a due date' const newCardTitle = 'Card with a due date'
cy.get('.button-vue[aria-label*="Add card"]') cy.get('.button-vue[aria-label*="Add card"]')
@@ -223,7 +227,7 @@ describe('Card', function() {
cy.get(`.card:contains("${newCardTitle}")`).find('[data-due-state]').should('not.exist') cy.get(`.card:contains("${newCardTitle}")`).find('[data-due-state]').should('not.exist')
}) })
it('Add a label', function() { it('Add a label', function () {
const newCardTitle = 'Card with labels' const newCardTitle = 'Card with labels'
cy.get('.button-vue[aria-label*="Add card"]') cy.get('.button-vue[aria-label*="Add card"]')
@@ -252,7 +256,7 @@ describe('Card', function() {
}) })
describe('Card actions', () => { describe('Card actions', () => {
beforeEach(function() { beforeEach(function () {
cy.login(user) cy.login(user)
useModal(false).then(() => { useModal(false).then(() => {
cy.visit(`/apps/deck/#/board/${boardId}`) cy.visit(`/apps/deck/#/board/${boardId}`)
@@ -298,5 +302,18 @@ describe('Card', function() {
}) })
}) })
}) })
it('clone card', () => {
cy.intercept({ method: 'POST', url: '**/apps/deck/**/cards/*/clone' }).as('clone')
cy.get('.card:contains("Hello world")').should('be.visible').click()
cy.get('#app-sidebar-vue')
.find('.ProseMirror h1').contains('Hello world').should('be.visible')
cy.get('.app-sidebar-header .action-item__menutoggle').click()
cy.get('.v-popper__popper button:contains("Move/copy card")').click()
cy.get('.modal-container button:contains("Copy card")').click()
cy.wait('@clone', { timeout: 7000 })
cy.get('.card:contains("Hello world")').should('have.length', 2)
})
}) })
}) })

View File

@@ -90,6 +90,15 @@ class CardController extends Controller {
public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) { public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) {
return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt); return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt);
} }
/**
* @NoAdminRequired
* @param $cardId
* @param $targetStackId
* @return \OCP\AppFramework\Db\Entity
*/
public function clone(int $cardId, ?int $targetStackId = null) {
return $this->cardService->cloneCard($cardId, $targetStackId);
}
/** /**
* @NoAdminRequired * @NoAdminRequired

View File

@@ -37,69 +37,33 @@ use OCP\IUserManager;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
class CardService { class CardService {
private CardMapper $cardMapper;
private StackMapper $stackMapper; private string $currentUser;
private BoardMapper $boardMapper;
private LabelMapper $labelMapper;
private LabelService $labelService;
private PermissionService $permissionService;
private BoardService $boardService;
private NotificationHelper $notificationHelper;
private AssignmentMapper $assignedUsersMapper;
private AttachmentService $attachmentService;
private ?string $currentUser;
private ActivityManager $activityManager;
private ICommentsManager $commentsManager;
private ChangeHelper $changeHelper;
private IEventDispatcher $eventDispatcher;
private IUserManager $userManager;
private IURLGenerator $urlGenerator;
private LoggerInterface $logger;
private IRequest $request;
private CardServiceValidator $cardServiceValidator;
public function __construct( public function __construct(
CardMapper $cardMapper, private CardMapper $cardMapper,
StackMapper $stackMapper, private StackMapper $stackMapper,
BoardMapper $boardMapper, private BoardMapper $boardMapper,
LabelMapper $labelMapper, private LabelMapper $labelMapper,
LabelService $labelService, private LabelService $labelService,
PermissionService $permissionService, private PermissionService $permissionService,
BoardService $boardService, private BoardService $boardService,
NotificationHelper $notificationHelper, private NotificationHelper $notificationHelper,
AssignmentMapper $assignedUsersMapper, private AssignmentMapper $assignedUsersMapper,
AttachmentService $attachmentService, private AttachmentService $attachmentService,
ActivityManager $activityManager, private ActivityManager $activityManager,
ICommentsManager $commentsManager, private ICommentsManager $commentsManager,
IUserManager $userManager, private IUserManager $userManager,
ChangeHelper $changeHelper, private ChangeHelper $changeHelper,
IEventDispatcher $eventDispatcher, private IEventDispatcher $eventDispatcher,
IURLGenerator $urlGenerator, private IURLGenerator $urlGenerator,
LoggerInterface $logger, private LoggerInterface $logger,
IRequest $request, private IRequest $request,
CardServiceValidator $cardServiceValidator, private CardServiceValidator $cardServiceValidator,
private AssignmentService $assignmentService,
?string $userId, ?string $userId,
) { ) {
$this->cardMapper = $cardMapper;
$this->stackMapper = $stackMapper;
$this->boardMapper = $boardMapper;
$this->labelMapper = $labelMapper;
$this->labelService = $labelService;
$this->permissionService = $permissionService;
$this->boardService = $boardService;
$this->notificationHelper = $notificationHelper;
$this->assignedUsersMapper = $assignedUsersMapper;
$this->attachmentService = $attachmentService;
$this->activityManager = $activityManager;
$this->commentsManager = $commentsManager;
$this->userManager = $userManager;
$this->changeHelper = $changeHelper;
$this->eventDispatcher = $eventDispatcher;
$this->currentUser = $userId; $this->currentUser = $userId;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->request = $request;
$this->cardServiceValidator = $cardServiceValidator;
} }
public function enrichCards($cards) { public function enrichCards($cards) {
@@ -391,6 +355,38 @@ class CardService {
return $card; return $card;
} }
public function cloneCard(int $id, ?int $targetStackId = null):Card {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_READ);
$originCard = $this->cardMapper->find($id);
if ($targetStackId === null) {
$targetStackId = $originCard->getStackId();
}
$this->permissionService->checkPermission($this->stackMapper, $targetStackId, Acl::PERMISSION_EDIT);
$newCard = $this->create($originCard->getTitle(), $targetStackId, $originCard->getType(), $originCard->getOrder(), $originCard->getOwner());
$boardId = $this->stackMapper->findBoardId($targetStackId);
foreach ($this->labelMapper->findAssignedLabelsForCard($id) as $label) {
if ($boardId != $this->stackMapper->findBoardId($originCard->getStackId())) {
try {
$label = $this->labelService->cloneLabelIfNotExists($label->getId(), $boardId);
} catch (NoPermissionException $e) {
break;
}
}
$this->assignLabel($newCard->getId(), $label->getId());
}
foreach ($this->assignedUsersMapper->findAll($id) as $assignement) {
try {
$this->permissionService->checkPermission($this->cardMapper, $newCard->getId(), Acl::PERMISSION_READ, $assignement->getParticipant());
} catch (NoPermissionException $e) {
continue;
}
$this->assignmentService->assignUser($newCard->getId(), $assignement->getParticipant());
}
$newCard->setDescription($originCard->getDescription());
$card = $this->enrichCards([$this->cardMapper->update($newCard)]);
return $card[0];
}
/** /**
* @param $id * @param $id
* @param $title * @param $title

View File

@@ -93,6 +93,18 @@ class LabelService {
return $this->labelMapper->insert($label); return $this->labelMapper->insert($label);
} }
public function cloneLabelIfNotExists(int $labelId, int $targetBoardId): Label {
$this->permissionService->checkPermission(null, $targetBoardId, Acl::PERMISSION_MANAGE);
$boardLabels = $this->boardService->find($targetBoardId)->getLabels();
$originLabel = $this->find($labelId);
$filteredValues = array_values(array_filter($boardLabels, fn ($item) => $item->getTitle() === $originLabel->getTitle()));
if (empty($filteredValues)) {
$label = $this->create($originLabel->getTitle(), $originLabel->getColor(), $targetBoardId);
return $label;
}
return $originLabel;
}
/** /**
* @param $id * @param $id
* @return \OCP\AppFramework\Db\Entity * @return \OCP\AppFramework\Db\Entity

View File

@@ -3,9 +3,8 @@
- SPDX-License-Identifier: AGPL-3.0-or-later - SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
<template> <template>
<NcModal v-if="modalShow" :title="t('deck', 'Move card to another board')" @close="modalShow=false"> <NcDialog :open.sync="modalShow" :name="t('deck', 'Move/copy card')">
<div class="modal__content"> <div class="modal__content">
<h3>{{ t('deck', 'Move card to another board') }}</h3>
<NcSelect v-model="selectedBoard" <NcSelect v-model="selectedBoard"
:input-label="t('deck', 'Select a board')" :input-label="t('deck', 'Select a board')"
:placeholder="t('deck', 'Select a board')" :placeholder="t('deck', 'Select a board')"
@@ -20,26 +19,28 @@
:options="stacksFromBoard" :options="stacksFromBoard"
:max-height="100" :max-height="100"
label="title" /> label="title" />
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
{{ t('deck', 'Move card') }}
</button>
<button @click="modalShow=false">
{{ t('deck', 'Cancel') }}
</button>
</div> </div>
</NcModal> <template #actions>
<NcButton :disabled="!isBoardAndStackChoosen" type="secondary" @click="moveCard">
{{ t('deck', 'Move card') }}
</NcButton>
<NcButton :disabled="!isBoardAndStackChoosen" type="primary" @click="cloneCard">
{{ t('deck', 'Copy card') }}
</NcButton>
</template>
</NcDialog>
</template> </template>
<script> <script>
import { NcModal, NcSelect } from '@nextcloud/vue' import { NcDialog, NcSelect, NcButton } from '@nextcloud/vue'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { subscribe, unsubscribe } from '@nextcloud/event-bus' import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { mapGetters } from 'vuex'
export default { export default {
name: 'CardMoveDialog', name: 'CardMoveDialog',
components: { NcModal, NcSelect }, components: { NcDialog, NcSelect, NcButton },
data() { data() {
return { return {
card: null, card: null,
@@ -50,6 +51,7 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters(['stackById', 'boardById']),
activeBoards() { activeBoards() {
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false) return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
}, },
@@ -66,6 +68,9 @@ export default {
methods: { methods: {
openModal(card) { openModal(card) {
this.card = card this.card = card
this.selectedStack = this.stackById(this.card.stackId)
this.selectedBoard = this.boardById(this.selectedStack.boardId)
this.loadStacksFromBoard(this.selectedBoard)
this.modalShow = true this.modalShow = true
}, },
async loadStacksFromBoard(board) { async loadStacksFromBoard(board) {
@@ -81,33 +86,23 @@ export default {
this.copiedCard = Object.assign({}, this.card) this.copiedCard = Object.assign({}, this.card)
this.copiedCard.stackId = this.selectedStack.id this.copiedCard.stackId = this.selectedStack.id
this.$store.dispatch('moveCard', this.copiedCard) this.$store.dispatch('moveCard', this.copiedCard)
if (parseInt(this.boardId) === parseInt(this.selectedStack.boardId)) { if (parseInt(this.selectedBoard.id) === parseInt(this.selectedStack.boardId)) {
await this.$store.commit('addNewCard', { ...this.copiedCard }) await this.$store.commit('addNewCard', { ...this.copiedCard })
} }
this.modalShow = false this.modalShow = false
}, },
async cloneCard() {
this.$store.dispatch('cloneCard', { cardId: this.card.id, targetStackId: this.selectedStack.id })
this.modalShow = false
},
}, },
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.modal__content { .modal__content {
min-width: 250px;
min-height: 120px;
margin: 20px 20px 100px 20px;
h3 {
font-weight: bold;
text-align: center;
}
.select { .select {
margin-bottom: 12px; margin-bottom: 12px;
} }
} }
.modal__content button {
float: right;
margin-top: 50px;
}
</style> </style>

View File

@@ -31,7 +31,7 @@
icon="icon-external" icon="icon-external"
:close-after-click="true" :close-after-click="true"
@click="openCardMoveDialog"> @click="openCardMoveDialog">
{{ t('deck', 'Move card') }} {{ t('deck', 'Move/copy card') }}
</NcActionButton> </NcActionButton>
<NcActionButton v-for="action in cardActions" <NcActionButton v-for="action in cardActions"
:key="action.label" :key="action.label"

View File

@@ -4,7 +4,7 @@
*/ */
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router' import { generateOcsUrl, generateUrl } from '@nextcloud/router'
export class CardApi { export class CardApi {
@@ -28,6 +28,23 @@ export class CardApi {
}) })
} }
cloneCard(cardId, targetStackId) {
return axios.post(generateOcsUrl(`apps/deck/api/v1.0/cards/${cardId}/clone`), {
targetStackId,
})
.then(
(response) => {
return Promise.resolve(response.data)
},
(err) => {
return Promise.reject(err)
},
)
.catch((err) => {
return Promise.reject(err)
})
}
deleteCard(cardId) { deleteCard(cardId) {
return axios.delete(this.url(`/cards/${cardId}`)) return axios.delete(this.url(`/cards/${cardId}`))
.then( .then(

View File

@@ -272,6 +272,11 @@ export default {
}, },
}, },
actions: { actions: {
async cloneCard({ commit }, { cardId, targetStackId }) {
const createdCard = await apiClient.cloneCard(cardId, targetStackId)
commit('addCard', createdCard)
return createdCard
},
async addCard({ commit }, card) { async addCard({ commit }, card) {
const createdCard = await apiClient.addCard(card) const createdCard = await apiClient.addCard(card)
commit('addCard', createdCard) commit('addCard', createdCard)

View File

@@ -32,6 +32,7 @@ use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Card; use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack; use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper; use OCA\Deck\Db\StackMapper;
@@ -93,6 +94,9 @@ class CardServiceTest extends TestCase {
/** @var CardServiceValidator|MockObject */ /** @var CardServiceValidator|MockObject */
private $cardServiceValidator; private $cardServiceValidator;
/** @var AssignmentService|MockObject */
private $assignmentService;
public function setUp(): void { public function setUp(): void {
parent::setUp(); parent::setUp();
$this->cardMapper = $this->createMock(CardMapper::class); $this->cardMapper = $this->createMock(CardMapper::class);
@@ -114,6 +118,7 @@ class CardServiceTest extends TestCase {
$this->logger = $this->createMock(LoggerInterface::class); $this->logger = $this->createMock(LoggerInterface::class);
$this->request = $this->createMock(IRequest::class); $this->request = $this->createMock(IRequest::class);
$this->cardServiceValidator = $this->createMock(CardServiceValidator::class); $this->cardServiceValidator = $this->createMock(CardServiceValidator::class);
$this->assignmentService = $this->createMock(AssignmentService::class);
$this->logger->expects($this->any())->method('error'); $this->logger->expects($this->any())->method('error');
@@ -137,6 +142,7 @@ class CardServiceTest extends TestCase {
$this->logger, $this->logger,
$this->request, $this->request,
$this->cardServiceValidator, $this->cardServiceValidator,
$this->assignmentService,
'user1' 'user1'
); );
} }
@@ -219,6 +225,61 @@ class CardServiceTest extends TestCase {
$this->assertEquals($b->getStackId(), 123); $this->assertEquals($b->getStackId(), 123);
} }
public function testClone() {
$card = new Card();
$card->setId(1);
$card->setTitle('Card title');
$card->setOwner('admin');
$card->setStackId(12345);
$clonedCard = clone $card;
$clonedCard->setId(2);
$clonedCard->setStackId(1234);
$this->cardMapper->expects($this->exactly(2))
->method('insert')
->willReturn($card, $clonedCard);
$this->cardMapper->expects($this->once())
->method('update')->willReturn($clonedCard);
$this->cardMapper->expects($this->exactly(2))
->method('find')
->willReturn($card, $clonedCard);
// check if users are assigned
$this->assignmentService->expects($this->once())
->method('assignUser')
->with(2, 'admin');
$a1 = new Assignment();
$a1->setCardId(2);
$a1->setType(0);
$a1->setParticipant('admin');
$this->assignedUsersMapper->expects($this->once())
->method('findAll')
->with(1)
->willReturn([$a1]);
// check if labels get cloned
$label = new Label();
$label->setId(1);
$this->labelMapper->expects($this->once())
->method('findAssignedLabelsForCard')
->willReturn([$label]);
$this->cardMapper->expects($this->once())
->method('assignLabel')
->with($clonedCard->getId(), $label->getId())
->willReturn($label);
$stackMock = new Stack();
$stackMock->setBoardId(1234);
$this->stackMapper->expects($this->once())
->method('find')
->willReturn($stackMock);
$b = $this->cardService->create('Card title', 123, 'text', 999, 'admin');
$c = $this->cardService->cloneCard($b->getId(), 1234);
$this->assertEquals($b->getTitle(), $c->getTitle());
$this->assertEquals($b->getOwner(), $c->getOwner());
$this->assertNotEquals($b->getStackId(), $c->getStackId());
}
public function testDelete() { public function testDelete() {
$cardToBeDeleted = new Card(); $cardToBeDeleted = new Card();
$this->cardMapper->expects($this->once()) $this->cardMapper->expects($this->once())

View File

@@ -24,6 +24,7 @@
namespace OCA\Deck\Service; namespace OCA\Deck\Service;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\ChangeHelper;
use OCA\Deck\Db\Label; use OCA\Deck\Db\Label;
use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\LabelMapper;
@@ -105,6 +106,53 @@ class LabelServiceTest extends TestCase {
$this->assertEquals($b->getColor(), 'ffffff'); $this->assertEquals($b->getColor(), 'ffffff');
} }
public function testCloneLabelIfNotExists() {
$label = new Label();
$label->setId(1);
$label->setTitle('title');
$label->setColor('00ff00');
$this->labelMapper->expects($this->once())
->method('find')
->willReturn($label);
$expectedLabel = new Label();
$expectedLabel->setTitle('title');
$expectedLabel->setColor('00ff00');
$expectedLabel->setBoardId(1);
$this->labelMapper->expects($this->once())
->method('insert')
->with($expectedLabel)
->willReturn($label);
$board = new Board();
$board->setLabels([]);
$this->boardService->expects($this->once())
->method('find')
->willReturn($board);
$this->labelService->cloneLabelIfNotExists(1, 1);
}
public function testCloneLabelIfExists() {
$label = new Label();
$label->setId(1);
$label->setTitle('title');
$label->setColor('00ff00');
$this->labelMapper->expects($this->once())
->method('find')
->willReturn($label);
$this->labelMapper->expects($this->never())
->method('insert')
->with($label);
$board = new Board();
$board->setLabels([$label]);
$this->boardService->expects($this->once())
->method('find')
->willReturn($board);
$this->labelService->cloneLabelIfNotExists(1, 1);
}
public function testDelete() { public function testDelete() {
$label = new Label(); $label = new Label();
$label->setId(1); $label->setId(1);