Skip to content

Commit d285264

Browse files
tink-botkolaente
andauthored
fix(editor): don't strike through nested checklist items of a checked parent (#3715)
Unchecked checklist item nested under checked parent rendered greyed out and struck through, so it looked checked. Cause: `li[data-checked='true']` styled whole list item, including nested task list inside item's content div. `text-decoration` propagates to descendants and cannot be reset by child. Rule now scoped to item's own content. Fixes #3712 ## How to verify 1. Create a task and open it. 2. Edit the description, add a checklist item "Parent item", then press Enter and Tab to nest a second item "Child item" underneath it. 3. Check the checkbox of "Parent item" only, and save. 4. **Expected:** "Parent item" is greyed out and struck through. "Child item" keeps normal text colour with no strikethrough, and its checkbox stays unchecked. **Before this PR:** "Child item" was greyed out and struck through too, making it look checked. --------- Co-authored-by: kolaente <k@knt.li> Co-authored-by: kolaente <hello@kolaente.de>
1 parent 3502c0a commit d285264

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

frontend/src/components/input/editor/TipTap.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ ul[data-type='taskList'] {
10381038
padding: 0;
10391039
margin-inline-start: 0;
10401040
1041-
li[data-checked='true'] {
1041+
li[data-checked='true'] > div > :not(ul, ol) {
10421042
color: var(--grey-500);
10431043
text-decoration: line-through;
10441044
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {test, expect} from '../../support/fixtures'
2+
import {TaskFactory} from '../../factories/task'
3+
import {ProjectFactory} from '../../factories/project'
4+
5+
const NESTED_CHECKLIST = `<ul data-type="taskList">
6+
<li data-checked="true" data-type="taskItem"><label><input type="checkbox" checked><span></span></label>
7+
<div>
8+
<p>Parent item</p>
9+
<ul data-type="taskList">
10+
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
11+
<div><p>Child item</p></div>
12+
</li>
13+
</ul>
14+
</div>
15+
</li>
16+
</ul>`
17+
18+
test.describe('Nested checklist items', () => {
19+
test.beforeEach(async () => {
20+
await ProjectFactory.create(1)
21+
})
22+
23+
/**
24+
* Regression test for https://github.com/go-vikunja/vikunja/issues/3712
25+
*
26+
* Styling the whole `li[data-checked=true]` also hit the nested task list living
27+
* inside the item's content div, so an unchecked child rendered greyed out and
28+
* struck through - it looked checked.
29+
*/
30+
test('Should not strike through an unchecked child of a checked item (issue #3712)', async ({authenticatedPage: page}) => {
31+
const tasks = await TaskFactory.create(1, {
32+
id: 1,
33+
description: NESTED_CHECKLIST,
34+
})
35+
36+
await page.goto(`/tasks/${tasks[0].id}`)
37+
38+
const description = page.locator('.task-view .details.content.description .tiptap')
39+
const checkedItem = description.locator('li[data-checked="true"] > div > p').first()
40+
const uncheckedItem = description.locator('li[data-checked="false"] > div > p').first()
41+
42+
await expect(checkedItem).toHaveText('Parent item')
43+
await expect(uncheckedItem).toHaveText('Child item')
44+
45+
// text-decoration propagates to descendants and cannot be reset by a child, so the
46+
// only reliable check is that no ancestor of the child carries the strikethrough.
47+
const struckAncestors = await uncheckedItem.evaluate(el => {
48+
const decorated: string[] = []
49+
for (let node = el; node; node = node.parentElement) {
50+
if (node.classList.contains('tiptap')) {
51+
break
52+
}
53+
if (getComputedStyle(node).textDecorationLine.includes('line-through')) {
54+
decorated.push(node.tagName)
55+
}
56+
}
57+
return decorated
58+
})
59+
expect(struckAncestors).toEqual([])
60+
61+
// The checked parent still gets its own strikethrough and muted colour.
62+
await expect(checkedItem).toHaveCSS('text-decoration-line', 'line-through')
63+
expect(await uncheckedItem.evaluate(el => getComputedStyle(el).color))
64+
.not.toBe(await checkedItem.evaluate(el => getComputedStyle(el).color))
65+
})
66+
})

0 commit comments

Comments
 (0)