From d3b0328fd70fcb4c351bcc3854141ff05b251b97 Mon Sep 17 00:00:00 2001 From: Leandro Lourenci Date: Wed, 4 Mar 2020 21:09:31 -0300 Subject: [PATCH] feat: Add capability to add a new card (#209) --- README.md | 34 +- jest.config.js | 3 +- .../CardAdder/components/CardForm/index.js | 82 ++++ .../components/CardForm/index.spec.js | 78 ++++ .../Column/components/CardAdder/index.js | 38 ++ .../Column/components/CardAdder/index.spec.js | 78 ++++ .../Board/components/Column/index.js | 13 +- .../Board/components/Column/index.spec.js | 43 ++- src/components/Board/index.js | 18 +- src/components/Board/index.spec.js | 358 ++++++++++++++++++ webpack.dev.js | 3 +- webpack.prod.js | 3 +- 12 files changed, 743 insertions(+), 8 deletions(-) create mode 100644 src/components/Board/components/Column/components/CardAdder/components/CardForm/index.js create mode 100644 src/components/Board/components/Column/components/CardAdder/components/CardForm/index.spec.js create mode 100644 src/components/Board/components/Column/components/CardAdder/index.js create mode 100644 src/components/Board/components/Column/components/CardAdder/index.spec.js diff --git a/README.md b/README.md index 31bac7e7..1823d6ba 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,9 @@ setBoard(newBoard) | [`onColumnRename`](#oncolumnrename) (required if `allowRenameColumn` or when [`renameColumn`](#rendercolumnheader) is called) | Callback that will be called when a column is renamed | ✅ | ✅ | | [`allowRemoveCard`](#allowremovecard) | Allow to remove a card in default card template | ✅ | ✅ | | [`onCardRemove`](#oncardremove) (required if `allowRemoveCard`) | Callback that will be called when a card is removed | ✅ | ✅ | -| [`onCardNew`](#oncardnew) (required if [`addCard`](#rendercolumnheader) is called) | Callback that will be called when a new card is added | 🚫 | ✅ | +| [`allowAddCard`](#allowaddcard) | Allow to add a card. Expect an object with the position to add the card in the column. | 🚫 | ✅ | +| [`onCardNew`](#oncardnew) (required if `allowAddCard` or when [`addCard`](#rendercolumnheader) is called) | Callback that will be called when a new card is added through the default card adder template | 🚫 | ✅ | +| [`onNewCardConfirm`](#onnewcardconfirm) (required if `allowAddCard`) | Callback that will be called when a new card is confirmed by the user through the default card adder template | 🚫 | ✅ | #### `children` @@ -456,6 +458,36 @@ When the user removes a card, this callback will be called passing these paramet | `card` | Card to be added | | `{ on: 'bottom|top' }` | Whether the card will be added on top or bottom of the column (`bottom` is default) | +#### `allowAddCard` + +Allow the user to add a card in the column directly by the board. By default, it adds the card on the bottom of the column, but you can specify whether you want to add at the top or at the bottom of the board by passing an object with 'on' prop. + +E.g.: + // at the bottom by default + // in the bottom of the column + // at the top of the column + +#### `onCardNew` + +When the user adds a new card through the default card adder template, this callback will be called passing the updated board and the new card. + +#### `onNewCardConfirm` + +When the user confirms a new card through the default card adder template, this callback will be called with a draft of a card with the title and the description typed by the user. + +You **must** return the new card with its new id in this callback. + +Ex.: + +```js +function onCardNew (newCard) { + const newCard = { id: ${required-new-unique-cardId}, ...newCard } + return newCard +} + + +``` + #### `removeCard` | Arg | Description | diff --git a/jest.config.js b/jest.config.js index 122f42be..762266ce 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,7 @@ module.exports = { testMatch: ['/src/**/*.spec.js'], setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], moduleNameMapper: { - '^@services/(.*)$': '/src/services/$1' + '^@services/(.*)$': '/src/services/$1', + '^@components/(.*)$': '/src/components/$1' } } diff --git a/src/components/Board/components/Column/components/CardAdder/components/CardForm/index.js b/src/components/Board/components/Column/components/CardAdder/components/CardForm/index.js new file mode 100644 index 00000000..3dda7161 --- /dev/null +++ b/src/components/Board/components/Column/components/CardAdder/components/CardForm/index.js @@ -0,0 +1,82 @@ +import React, { useRef } from 'react' +import styled from 'styled-components' +import { when } from '@services/utils' +import CardSkeleton from '@components/Board/components/CardSkeleton' + +const DefaultCard = styled(CardSkeleton)` + border-radius: 3px; + background-color: #fff; + padding: 10px; + margin-bottom: 7px; + input { + border: 0px; + font-family: inherit; + font-size: inherit; + } +` + +const CardTitle = styled.input` + font-weight: bold; + border-bottom: 1px solid #eee; + padding-bottom: 5px; + font-weight: bold; + display: flex; + justify-content: space-between; + width: 100%; + padding: 0px; +` + +const CardDescription = styled.input` + input { + width: 100%; + } + margin-top: 10px; +` +const StyledFormButtons = styled.div` + display: flex; + justify-content: space-between; + margin-top: 5px; +` + +const StyledButton = styled.button` + background-color: #eee; + border: none; + padding: 5px; + width: 45%; + margin-top: 5px; + border-radius: 3px; + &:hover { + transition: 0.3s; + cursor: pointer; + background-color: #ccc; + } +` + +function CardForm({ onConfirm, onCancel }) { + const inputCardTitle = useRef() + const inputCardDescription = useRef() + + function addCard(event) { + event.preventDefault() + when(inputCardTitle.current.value)(value => { + onConfirm({ title: value, description: inputCardDescription.current.value }) + }) + } + + return ( + +
+ + + + Add + + Cancel + + + +
+ ) +} + +export default CardForm diff --git a/src/components/Board/components/Column/components/CardAdder/components/CardForm/index.spec.js b/src/components/Board/components/Column/components/CardAdder/components/CardForm/index.spec.js new file mode 100644 index 00000000..e97e9155 --- /dev/null +++ b/src/components/Board/components/Column/components/CardAdder/components/CardForm/index.spec.js @@ -0,0 +1,78 @@ +import React from 'react' +import { render, fireEvent } from '@testing-library/react' +import CardForm from './' + +describe('', () => { + let subject, onConfirm, onCancel + + function mount() { + onConfirm = jest.fn() + onCancel = jest.fn() + + subject = render() + } + + beforeEach(mount) + afterEach(() => { + subject = onConfirm = onCancel = undefined + }) + + it('renders the card inputs', () => { + expect(subject.container.querySelector('input[name="title"]')).toBeInTheDocument() + expect(subject.container.querySelector('input[name="description"]')).toBeInTheDocument() + }) + + it('focuses on the title input', () => { + expect(subject.container.querySelector('input[name="title"]')).toHaveFocus() + }) + + describe('when the user clicks confirm the input', () => { + describe('when the user has informed a valid card', () => { + beforeEach(() => { + fireEvent.change(subject.container.querySelector('input[name="title"]'), { target: { value: 'Card title' } }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Description' } + }) + fireEvent.click(subject.queryByText('Add')) + }) + + it('calls the onConfirm prop passing the values', () => { + expect(onConfirm).toHaveBeenCalledTimes(1) + expect(onConfirm).toHaveBeenCalledWith({ title: 'Card title', description: 'Description' }) + }) + + it('does not call the onCancel prop', () => { + expect(onCancel).not.toHaveBeenCalled() + }) + }) + + describe('when the user has not typed a card title', () => { + beforeEach(() => { + fireEvent.change(subject.container.querySelector('input[name="title"]'), { target: { value: '' } }) + fireEvent.click(subject.queryByText('Add')) + }) + + it('does not call the onConfirm prop', () => { + expect(onConfirm).not.toHaveBeenCalled() + }) + + it('does not call the onCancel prop', () => { + expect(onCancel).not.toHaveBeenCalled() + }) + }) + }) + + describe('when the user cancels the input', () => { + beforeEach(() => { + fireEvent.click(subject.queryByText('Cancel')) + }) + + it('calls the onCancel prop', () => { + expect(onCancel).toHaveBeenCalledTimes(1) + }) + + it('does not call the onConfirm prop', () => { + expect(onConfirm).not.toHaveBeenCalled() + }) + }) +}) diff --git a/src/components/Board/components/Column/components/CardAdder/index.js b/src/components/Board/components/Column/components/CardAdder/index.js new file mode 100644 index 00000000..442e9db9 --- /dev/null +++ b/src/components/Board/components/Column/components/CardAdder/index.js @@ -0,0 +1,38 @@ +import React, { useState } from 'react' +import styled from 'styled-components' +import CardForm from './components/CardForm' + +const AddCardButton = styled.button` + width: 100%; + margin-top: 5px; + background-color: transparent; + cursor: pointer; + border: 1px solid #ccc; + transition: 0.3s; + :hover { + background-color: #ccc; + } + border-radius: 3px; + font-size: 20px; + margin-bottom: 10px; + font-weight: bold; +` + +export default function CardAdder({ column, onConfirm }) { + function confirmCard(card) { + onConfirm(column, card) + setAddingCard(false) + } + + const [addingCard, setAddingCard] = useState(false) + + return ( + <> + {addingCard ? ( + setAddingCard(false)} /> + ) : ( + setAddingCard(!addingCard)}>+ + )} + + ) +} diff --git a/src/components/Board/components/Column/components/CardAdder/index.spec.js b/src/components/Board/components/Column/components/CardAdder/index.spec.js new file mode 100644 index 00000000..e75779e3 --- /dev/null +++ b/src/components/Board/components/Column/components/CardAdder/index.spec.js @@ -0,0 +1,78 @@ +import React from 'react' +import { render, fireEvent } from '@testing-library/react' +import CardAdder from './' + +describe('', () => { + let subject, onConfirm + const column = { id: 1 } + function mount() { + onConfirm = jest.fn() + + subject = render() + } + + beforeEach(mount) + afterEach(() => { + subject = onConfirm = undefined + }) + + it('renders the button to add a new card', () => { + expect(subject.queryByText('+')).toBeInTheDocument() + }) + + describe('when the user clicks to add a new card', () => { + beforeEach(() => fireEvent.click(subject.queryByText('+'))) + + it('hides the card placeholder', () => { + expect(subject.queryByText('+')).not.toBeInTheDocument() + }) + + it('renders the card inputs', () => { + expect(subject.container.querySelector('input[name="title"]')).toBeInTheDocument() + expect(subject.container.querySelector('input[name="description"]')).toBeInTheDocument() + }) + + describe('when the user confirms the new card', () => { + beforeEach(() => { + fireEvent.change(subject.container.querySelector('input[name="title"]'), { + target: { value: 'Card Added by user' } + }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Description' } + }) + fireEvent.click(subject.queryByText('Add')) + }) + + it('calls the "onConfirm" prop passing the new card and the column', () => { + expect(onConfirm).toHaveBeenCalledTimes(1) + expect(onConfirm).toHaveBeenCalledWith(column, { title: 'Card Added by user', description: 'Description' }) + }) + + it('hides the input', () => { + expect(subject.container.querySelector('input')).not.toBeInTheDocument() + }) + + it('renders the placeholder to add a new card', () => { + expect(subject.queryByText('+')).toBeInTheDocument() + }) + }) + + describe('when the user cancels the new card', () => { + beforeEach(() => { + fireEvent.click(subject.queryByText('Cancel')) + }) + + it('does not call the "onConfirm" prop', () => { + expect(onConfirm).not.toHaveBeenCalled() + }) + + it('hides the input', () => { + expect(subject.container.querySelector('input')).not.toBeInTheDocument() + }) + + it('renders the placeholder to add a new card', () => { + expect(subject.queryByText('+')).toBeInTheDocument() + }) + }) + }) +}) diff --git a/src/components/Board/components/Column/index.js b/src/components/Board/components/Column/index.js index d0c9658c..27f77a29 100644 --- a/src/components/Board/components/Column/index.js +++ b/src/components/Board/components/Column/index.js @@ -4,6 +4,7 @@ import { Draggable } from 'react-beautiful-dnd' import Card from './components/Card' import CardSkeleton from '../CardSkeleton' import withDroppable from '../../../withDroppable' +import CardAdder from './components/CardAdder' export const StyledColumn = styled.div` height: 100%; @@ -19,7 +20,16 @@ const DroppableColumn = withDroppable(styled.div` min-height: 28px; `) -function Column({ children, index: columnIndex, renderCard, renderColumnHeader, disableColumnDrag, disableCardDrag }) { +function Column({ + children, + index: columnIndex, + renderCard, + renderColumnHeader, + disableColumnDrag, + disableCardDrag, + onCardNew, + allowAddCard +}) { return ( {columnProvided => ( @@ -27,6 +37,7 @@ function Column({ children, index: columnIndex, renderCard, renderColumnHeader,
{renderColumnHeader(children)}
+ {allowAddCard && } {children.cards.length ? ( children.cards.map((card, index) => ( diff --git a/src/components/Board/components/Column/index.spec.js b/src/components/Board/components/Column/index.spec.js index 1a715bf3..538bd606 100644 --- a/src/components/Board/components/Column/index.spec.js +++ b/src/components/Board/components/Column/index.spec.js @@ -1,5 +1,5 @@ import React from 'react' -import { render } from '@testing-library/react' +import { render, fireEvent } from '@testing-library/react' import Column, { StyledColumn } from './' describe('', () => { @@ -62,6 +62,47 @@ describe('', () => { expect(renderCard).toHaveBeenCalledWith(column, expect.objectContaining({ id: 1, title: 'Card title 1' }), false) }) }) + + describe('about the card adding', () => { + const onCardNew = jest.fn() + + describe('when the component does not receive the "allowAddCard" prop', () => { + beforeEach(() => mount({ onCardNew })) + + it('does not show the add card button', () => { + expect(subject.queryByText('+')).not.toBeInTheDocument() + }) + }) + + describe('when the component receives the "allowAddCard" prop', () => { + beforeEach(() => mount({ onCardNew, allowAddCard: true })) + + it('shows the add card button', () => { + expect(subject.queryByText('+')).toBeVisible() + }) + + describe('when the user clicks on the add card button', () => { + beforeEach(() => fireEvent.click(subject.queryByText('+'))) + + describe('when the user confirm a new card', () => { + beforeEach(() => { + fireEvent.change(subject.container.querySelector('input[name="title"]'), { + target: { value: 'Card title' } + }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Description' } + }) + fireEvent.click(subject.queryByText('Add')) + }) + + it('calls the onCardNew prop passing the values', () => { + expect(onCardNew).toHaveBeenCalledTimes(1) + expect(onCardNew).toHaveBeenCalledWith(column, { title: 'Card title', description: 'Description' }) + }) + }) + }) + }) + }) }) describe('', () => { diff --git a/src/components/Board/index.js b/src/components/Board/index.js index 8490711a..2dc880ff 100644 --- a/src/components/Board/index.js +++ b/src/components/Board/index.js @@ -51,7 +51,9 @@ function UncontrolledBoard({ onCardRemove, onColumnNew, disableCardDrag, - disableColumnDrag + disableColumnDrag, + allowAddCard, + onNewCardConfirm }) { const [board, setBoard] = useState(initialBoard) const handleOnCardDragEnd = partialRight(handleOnDragEnd, { moveCallback: moveCard, notifyCallback: onCardDragEnd }) @@ -87,6 +89,7 @@ function UncontrolledBoard({ function handleCardAdd(column, card, options = {}) { const boardWithNewCard = addCard(board, column, card, options) + onCardNew( boardWithNewCard, boardWithNewCard.columns.find(({ id }) => id === column.id), @@ -95,6 +98,11 @@ function UncontrolledBoard({ setBoard(boardWithNewCard) } + async function handleDraftCardAdd(column, card, options = {}) { + const newCard = await onNewCardConfirm(card) + handleCardAdd(column, newCard, options) + } + function handleCardRemove(column, card) { const boardWithoutCard = removeCard(board, column, card) onCardRemove( @@ -141,6 +149,8 @@ function UncontrolledBoard({ onColumnRename={handleColumnRename} disableColumnDrag={disableColumnDrag} disableCardDrag={disableCardDrag} + onCardNew={(column, card) => handleDraftCardAdd(column, card, allowAddCard)} + allowAddCard={allowAddCard && onNewCardConfirm} > {board} @@ -215,7 +225,9 @@ function BoardContainer({ allowRenameColumn, onColumnRename, onColumnDragEnd, - onCardDragEnd + onCardDragEnd, + onCardNew, + allowAddCard }) { function handleOnDragEnd(event) { const coordinates = getCoordinates(event, board) @@ -253,6 +265,8 @@ function BoardContainer({ } disableColumnDrag={disableColumnDrag} disableCardDrag={disableCardDrag} + onCardNew={onCardNew} + allowAddCard={allowAddCard} > {column} diff --git a/src/components/Board/index.spec.js b/src/components/Board/index.spec.js index ac62e982..66e89b3a 100644 --- a/src/components/Board/index.spec.js +++ b/src/components/Board/index.spec.js @@ -39,6 +39,7 @@ describe('', () => { } afterEach(() => { + jest.clearAllMocks() subject = onCardDragEnd = onColumnDragEnd = onColumnRemove = onCardRemove = undefined }) @@ -1492,6 +1493,363 @@ describe('', () => { }) }) }) + + describe('when the component does not receive a custom header column template', () => { + const onCardNew = jest.fn() + const onNewCardConfirm = jest.fn(column => new Promise(resolve => resolve({ id: 999, ...column }))) + + describe('when the component does not receive "allowAddCard" prop', () => { + beforeEach(() => { + mount({ allowAddCard: false, onNewCardConfirm, onCardNew }) + }) + + it('does not render the card adder', () => { + expect(subject.queryByText('+')).not.toBeInTheDocument() + }) + }) + + describe('when the component does not receive the "onNewCardConfirm" prop', () => { + beforeEach(() => { + mount({ allowAddCard: true, onCardNew: () => {} }) + }) + + it('does not render the column adder', () => { + expect(subject.queryByText('+')).not.toBeInTheDocument() + }) + }) + + describe('when the component receives both the "allowAddCard" and "onNewCardConfirm" props', () => { + describe('when the user adds a new card', () => { + beforeEach(async () => { + mount({ allowAddCard: true, onNewCardConfirm, onCardNew }) + + fireEvent.click(subject.queryAllByText('+')[0]) + fireEvent.change(subject.container.querySelector('input[name="title"]'), { + target: { value: 'Card title' } + }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Card description' } + }) + fireEvent.click(subject.queryByText('Add')) + await waitForElement(() => subject.container.querySelector('[data-testid="card"]:nth-child(3)')) + }) + + it('calls the "onNewCardConfirm" passing the new card', () => { + expect(onNewCardConfirm).toHaveBeenCalledTimes(1) + expect(onNewCardConfirm).toHaveBeenCalledWith({ + title: 'Card title', + description: 'Card description' + }) + }) + + it('renders the new card using the id returned on "onNewCardConfirm"', () => { + expect(subject.queryAllByTestId('card')).toHaveLength(4) + }) + + it('renders the card placeholder', () => { + expect(subject.queryAllByText('+')).toHaveLength(2) + }) + + it('adds a new card on column', () => { + const cards = within(subject.queryAllByTestId('column')[0]).queryAllByTestId('card') + expect(cards).toHaveLength(3) + expect(cards[2]).toHaveTextContent('Card title') + }) + + it('calls the "onCardNew" callback passing the updated board, the updated column and the new card', () => { + expect(onCardNew).toHaveBeenCalledTimes(1) + expect(onCardNew).toHaveBeenCalledWith( + { + columns: [ + { + id: 1, + title: 'Column Backlog', + cards: [ + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + }, + { id: 999, title: 'Card title', description: 'Card description' } + ] + }, + { + id: 2, + title: 'Column Doing', + cards: [ + { + id: 3, + title: 'Card title 3', + description: 'Card content' + } + ] + } + ] + }, + { + id: 1, + title: 'Column Backlog', + cards: [ + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + }, + { id: 999, title: 'Card title', description: 'Card description' } + ] + }, + expect.objectContaining({ id: 999 }) + ) + }) + }) + + describe('about the card position when it is added', () => { + describe('when the position is not specified', () => { + beforeEach(async () => { + mount({ allowAddCard: true, onNewCardConfirm, onCardNew }) + fireEvent.click(subject.queryAllByText('+')[0]) + + fireEvent.change(subject.container.querySelector('input[name="title"]'), { + target: { value: 'Card title' } + }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Card description' } + }) + fireEvent.click(subject.queryByText('Add')) + await waitForElement(() => subject.container.querySelector('[data-testid="card"]:nth-child(3)')) + }) + + it('adds a new card on the bottom of the column', () => { + const cards = within(subject.queryAllByTestId('column')[0]).queryAllByTestId('card') + expect(cards).toHaveLength(3) + expect(cards[2]).toHaveTextContent('Card description') + }) + + it('calls the "onCardNew" callback passing the updated board, the updated column and the new card on the end of the card array', () => { + expect(onCardNew).toHaveBeenCalledTimes(1) + expect(onCardNew).toHaveBeenCalledWith( + { + columns: [ + { + id: 1, + title: 'Column Backlog', + cards: [ + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + }, + { id: 999, title: 'Card title', description: 'Card description' } + ] + }, + { + id: 2, + title: 'Column Doing', + cards: [ + { + id: 3, + title: 'Card title 3', + description: 'Card content' + } + ] + } + ] + }, + { + id: 1, + title: 'Column Backlog', + cards: [ + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + }, + { id: 999, title: 'Card title', description: 'Card description' } + ] + }, + expect.objectContaining({ id: 999 }) + ) + }) + }) + + describe('when the position is specified to add the card on the top of the column', () => { + beforeEach(async () => { + mount({ allowAddCard: { on: 'top' }, onNewCardConfirm, onCardNew }) + fireEvent.click(subject.queryAllByText('+')[0]) + + fireEvent.change(subject.container.querySelector('input[name="title"]'), { + target: { value: 'Card title' } + }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Card description' } + }) + fireEvent.click(subject.queryByText('Add')) + await waitForElement(() => subject.container.querySelector('[data-testid="card"]:nth-child(3)')) + }) + + it('adds a new card on the top of the column', () => { + const cards = within(subject.queryAllByTestId('column')[0]).queryAllByTestId('card') + expect(cards).toHaveLength(3) + expect(cards[0]).toHaveTextContent('Card description') + }) + + it('calls the "onCardNew" callback passing the updated board, the updated column and the new card on the start of the array', () => { + expect(onCardNew).toHaveBeenCalledTimes(1) + expect(onCardNew).toHaveBeenCalledWith( + { + columns: [ + { + id: 1, + title: 'Column Backlog', + cards: [ + { id: 999, title: 'Card title', description: 'Card description' }, + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + } + ] + }, + { + id: 2, + title: 'Column Doing', + cards: [ + { + id: 3, + title: 'Card title 3', + description: 'Card content' + } + ] + } + ] + }, + { + id: 1, + title: 'Column Backlog', + cards: [ + { id: 999, title: 'Card title', description: 'Card description' }, + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + } + ] + }, + expect.objectContaining({ id: 999 }) + ) + }) + }) + + describe('when the position is specified to add the card on the bottom of the column', () => { + beforeEach(async () => { + mount({ allowAddCard: { on: 'bottom' }, onNewCardConfirm, onCardNew }) + fireEvent.click(subject.queryAllByText('+')[0]) + + fireEvent.change(subject.container.querySelector('input[name="title"]'), { + target: { value: 'Card title' } + }) + fireEvent.change(subject.container.querySelector('input[name="description"]'), { + target: { value: 'Card description' } + }) + fireEvent.click(subject.queryByText('Add')) + await waitForElement(() => subject.container.querySelector('[data-testid="card"]:nth-child(3)')) + }) + + it('adds a new card on the bottom of the column', () => { + const cards = within(subject.queryAllByTestId('column')[0]).queryAllByTestId('card') + expect(cards).toHaveLength(3) + expect(cards[2]).toHaveTextContent('Card description') + }) + + it('calls the "onCardNew" callback passing the updated board, the updated column and the new card on the end of the array', () => { + expect(onCardNew).toHaveBeenCalledTimes(1) + expect(onCardNew).toHaveBeenCalledWith( + { + columns: [ + { + id: 1, + title: 'Column Backlog', + cards: [ + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + }, + { id: 999, title: 'Card title', description: 'Card description' } + ] + }, + { + id: 2, + title: 'Column Doing', + cards: [ + { + id: 3, + title: 'Card title 3', + description: 'Card content' + } + ] + } + ] + }, + { + id: 1, + title: 'Column Backlog', + cards: [ + { + id: 1, + title: 'Card title 1', + description: 'Card content' + }, + { + id: 2, + title: 'Card title 2', + description: 'Card content' + }, + { id: 999, title: 'Card title', description: 'Card description' } + ] + }, + expect.objectContaining({ id: 999 }) + ) + }) + }) + }) + }) + }) }) }) }) diff --git a/webpack.dev.js b/webpack.dev.js index cba96b2c..be238208 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -14,7 +14,8 @@ module.exports = { plugins: [new HtmlWebpackPlugin({ template: './assets/index.html' })], resolve: { alias: { - '@services': path.resolve(__dirname, 'src/services/') + '@services': path.resolve(__dirname, 'src/services/'), + '@components': path.resolve(__dirname, 'src/components/') } }, devtool: 'inline-source-map', diff --git a/webpack.prod.js b/webpack.prod.js index 640deccf..0ddc4213 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -20,7 +20,8 @@ module.exports = { }, resolve: { alias: { - '@services': path.resolve(__dirname, 'src/services/') + '@services': path.resolve(__dirname, 'src/services/'), + '@components': path.resolve(__dirname, 'src/components/') } } }