Skip to content

Commit 3cb93f7

Browse files
authored
Merge pull request #561 from Chris0Jeky/fix/517-wip-limit-blocking
fix: enforce WIP limit on card creation (#517)
2 parents d26b399 + fb9fa38 commit 3cb93f7

File tree

3 files changed

+348
-7
lines changed

3 files changed

+348
-7
lines changed

frontend/taskdeck-web/src/components/board/ColumnLane.vue

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue'
2+
import { ref, computed } from 'vue'
33
import { useBoardStore } from '../../store/boardStore'
4+
import { useToastStore } from '../../store/toastStore'
5+
import { getErrorDisplay } from '../../composables/useErrorMapper'
46
import CardItem from './CardItem.vue'
57
import CardModal from './CardModal.vue'
68
import ColumnEditModal from './ColumnEditModal.vue'
@@ -21,13 +23,20 @@ const emit = defineEmits<{
2123
}>()
2224
2325
const boardStore = useBoardStore()
26+
const toast = useToastStore()
2427
const newCardTitle = ref('')
2528
const showCardForm = ref(false)
2629
const selectedCard = ref<Card | null>(null)
2730
const showCardModal = ref(false)
2831
const showColumnEdit = ref(false)
2932
const isDragOver = ref(false)
3033
34+
/** True when cards.length > wipLimit (already over limit) */
35+
const isWipLimitExceeded = computed(() => {
36+
// Treat null or <= 0 as "no limit" — mirrors backend's SetWipLimit guard (must be > 0)
37+
return props.column.wipLimit != null && props.column.wipLimit > 0 && props.cards.length > props.column.wipLimit
38+
})
39+
3140
function handleCardClick(card: Card) {
3241
selectedCard.value = card
3342
showCardModal.value = true
@@ -54,14 +63,12 @@ async function createCard() {
5463
newCardTitle.value = ''
5564
showCardForm.value = false
5665
} catch (error) {
66+
const { message } = getErrorDisplay(error, 'Failed to create card')
67+
toast.error(message)
5768
console.error('Failed to create card:', error)
5869
}
5970
}
6071
61-
const isWipLimitExceeded = () => {
62-
return props.column.wipLimit !== null && props.cards.length > props.column.wipLimit
63-
}
64-
6572
function handleCardDragStart(card: Card) {
6673
emit('card-drag-start', card)
6774
}
@@ -187,7 +194,7 @@ function handleCardDragOver(event: DragEvent) {
187194
</button>
188195
<span
189196
class="td-column-lane__count"
190-
:class="isWipLimitExceeded() ? 'td-column-lane__count--exceeded' : ''"
197+
:class="isWipLimitExceeded ? 'td-column-lane__count--exceeded' : ''"
191198
>
192199
{{ cards.length }}{{ column.wipLimit ? `/${column.wipLimit}` : '' }}
193200
</span>
@@ -204,13 +211,14 @@ function handleCardDragOver(event: DragEvent) {
204211
</div>
205212
</div>
206213

207-
<div v-if="isWipLimitExceeded()" class="td-column-lane__wip-warning">
214+
<div v-if="isWipLimitExceeded" class="td-column-lane__wip-warning">
208215
WIP limit exceeded
209216
</div>
210217

211218
<button
212219
data-action="toggle-add-card"
213220
@click="openCardForm"
221+
title="Add a card"
214222
class="td-column-lane__add-card-btn"
215223
>
216224
<span>+</span>

frontend/taskdeck-web/src/tests/components/CardItem.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ function createCard(): Card {
2121
}
2222
}
2323

24+
describe('CardItem — date display', () => {
25+
it('renders formatted due date and overdue indicator when dueDate is in the past', () => {
26+
const card = createCard()
27+
card.dueDate = '2020-01-01T00:00:00.000Z' // definitely in the past
28+
const wrapper = mount(CardItem, { props: { card } })
29+
expect(wrapper.find('.td-board-card__due').exists()).toBe(true)
30+
expect(wrapper.find('.td-board-card__due--overdue').exists()).toBe(true)
31+
expect(wrapper.text()).toContain('Overdue')
32+
})
33+
34+
it('renders due date without overdue indicator when dueDate is in the future', () => {
35+
const card = createCard()
36+
card.dueDate = '2099-12-31T00:00:00.000Z'
37+
const wrapper = mount(CardItem, { props: { card } })
38+
expect(wrapper.find('.td-board-card__due').exists()).toBe(true)
39+
expect(wrapper.find('.td-board-card__due--overdue').exists()).toBe(false)
40+
})
41+
})
42+
2443
describe('CardItem drag guardrails', () => {
2544
it('exposes an explicit enlarged drag handle control', () => {
2645
const wrapper = mount(CardItem, {
@@ -71,6 +90,13 @@ describe('CardItem drag guardrails', () => {
7190
expect(setData).toHaveBeenCalledWith('text/plain', card.id)
7291
expect(wrapper.emitted('dragstart')).toEqual([[card]])
7392
})
93+
94+
it('emits dragend and clears dragging state on dragend', async () => {
95+
const card = createCard()
96+
const wrapper = mount(CardItem, { props: { card } })
97+
await wrapper.get('[data-action="drag-card-handle"]').trigger('dragend')
98+
expect(wrapper.emitted('dragend')).toHaveLength(1)
99+
})
74100
})
75101

76102
describe('CardItem drag handle — text selection clearing', () => {

0 commit comments

Comments
 (0)