Skip to content

Commit a99fb3e

Browse files
committed
Make check for code fences more robust per spec
1 parent b456e32 commit a99fb3e

File tree

2 files changed

+64
-8
lines changed

2 files changed

+64
-8
lines changed

src/drafts/MarkdownViewer/MarkdownViewer.test.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,32 @@ text before list
5151
- [x] item 1
5252
- [ ] item 2
5353
54+
text after list`
55+
const hierarchyBeforeTaskListNoItemsCheckedTildes = `
56+
text before list
57+
58+
~~~[tasklist]
59+
- [ ] item A
60+
- [ ] item B
61+
\`\`\`
62+
~~~~~~
63+
64+
- [ ] item 1
65+
- [ ] item 2
66+
67+
text after list`
68+
const hierarchyBeforeTaskListOneItemCheckedTildes = `
69+
text before list
70+
71+
~~~[tasklist]
72+
- [ ] item A
73+
- [ ] item B
74+
\`\`\`
75+
~~~~~~
76+
77+
- [x] item 1
78+
- [ ] item 2
79+
5480
text after list`
5581

5682
it('enables checklists by default', () => {
@@ -107,13 +133,28 @@ text after list`
107133
markdownValue={hierarchyBeforeTaskListNoItemsChecked}
108134
onChange={onChangeMock}
109135
disabled
110-
/>
136+
/>,
111137
)
112138
const items = getAllByRole('checkbox')
113139
fireEvent.change(items[0])
114140
await waitFor(() => expect(onChangeMock).toHaveBeenCalledWith(hierarchyBeforeTaskListOneItemChecked))
115141
})
116142

143+
it('calls `onChange` with the updated Markdown when a task is checked and hierarchy is present with tildes', async () => {
144+
const onChangeMock = jest.fn()
145+
const {getAllByRole} = render(
146+
<MarkdownViewer
147+
dangerousRenderedHTML={htmlObject}
148+
markdownValue={hierarchyBeforeTaskListNoItemsCheckedTildes}
149+
onChange={onChangeMock}
150+
disabled
151+
/>,
152+
)
153+
const items = getAllByRole('checkbox')
154+
fireEvent.change(items[0])
155+
await waitFor(() => expect(onChangeMock).toHaveBeenCalledWith(hierarchyBeforeTaskListOneItemCheckedTildes))
156+
})
157+
117158
it('calls `onChange` with the updated Markdown when a task is unchecked', async () => {
118159
const onChangeMock = jest.fn()
119160
const {getAllByRole} = render(
@@ -199,7 +240,7 @@ text after list`
199240
expect(spy).toHaveBeenCalledTimes(1)
200241
expect(spy).toHaveBeenCalledWith(
201242
expect.objectContaining({
202-
message: 'Not implemented: navigation (except hash changes)',
243+
message: 'Not implemented: navigation (except hash changes)'
203244
}),
204245
)
205246
spy.mockRestore()

src/drafts/MarkdownViewer/_useListInteraction.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@ import {ListItem, listItemToString, parseListItem} from '../MarkdownEditor/_useL
44

55
type TaskListItem = ListItem & {taskBox: '[ ]' | '[x]'}
66

7-
const isCodeBlockDelimiter = (line: string) => line.trimStart().startsWith('```')
7+
// Make check for code fences more robust per spec: https://github.github.com/gfm/#fenced-code-blocks
8+
const parseCodeFenceBegin = (line: string) => {
9+
const match = line.match(/^ {0,3}(`{3,}|~{3,})[^`]*$/)
10+
return match ? match[1] : null
11+
}
12+
13+
const isCodeFenceEnd = (line: string, fence: string) => {
14+
const match = line.match(new RegExp(`^ {0,3}${fence}${fence[0]}* *$`))
15+
return match !== null
16+
}
817

918
const isTaskListItem = (item: ListItem | null): item is TaskListItem => typeof item?.taskBox === 'string'
1019

@@ -45,17 +54,23 @@ export const useListInteraction = ({
4554
const onToggleItem = useCallback(
4655
(toggledItemIndex: number) => () => {
4756
const lines = markdownRef.current.split('\n')
48-
let inCodeBlock = false
57+
let currentCodeFence: string | null = null
4958

5059
for (let lineIndex = 0, taskIndex = 0; lineIndex < lines.length; lineIndex++) {
51-
if (isCodeBlockDelimiter(lines[lineIndex])) {
52-
inCodeBlock = !inCodeBlock
60+
const line = lines[lineIndex]
61+
62+
if (!currentCodeFence) {
63+
currentCodeFence = parseCodeFenceBegin(line)
64+
} else if (isCodeFenceEnd(line, currentCodeFence)) {
65+
currentCodeFence = null
5366
continue
5467
}
5568

56-
const parsedLine = parseListItem(lines[lineIndex])
69+
if (currentCodeFence) continue
70+
71+
const parsedLine = parseListItem(line)
5772

58-
if (!isTaskListItem(parsedLine) || inCodeBlock) continue
73+
if (!isTaskListItem(parsedLine)) continue
5974

6075
if (taskIndex === toggledItemIndex) {
6176
const updatedLine = listItemToString(toggleTaskListItem(parsedLine))

0 commit comments

Comments
 (0)