Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions packages/core/src/extensions/code-block.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isFirefox } from '@meowdown/vitest/helpers'
import { DOMParser, DOMSerializer } from '@prosekit/pm/model'
import { describe, expect, it } from 'vitest'
import { userEvent } from 'vitest/browser'
import { describe, expect, it, vi } from 'vitest'
import { page, userEvent } from 'vitest/browser'

import { setupFixture } from '../testing/index.ts'

Expand Down Expand Up @@ -95,3 +96,38 @@ describe('dollar fence rules', () => {
expect(fixture.doc.child(0).type.name).toBe('paragraph')
})
})

const codeTokens = page.locate('.ProseMirror pre code [class*="tok-"]')

describe('typing over code block selections', () => {
it('keeps the typed text over a partial selection', async () => {
using fixture = setupFixture()
const { n } = fixture
fixture.set(n.doc(n.codeBlock({ language: 'js' }, '<a>const answer = 4<b>2')))
// The bug only triggers once highlight token spans wrap the code text.
await expect.element(codeTokens.first(), { timeout: 15000 }).toBeInTheDocument()
fixture.view.focus()
await userEvent.keyboard('X')
const expected = n.doc(n.codeBlock({ language: 'js' }, 'X2'))
await vi.waitFor(() => {
expect(fixture.doc.eq(expected)).toBe(true)
})
})

it.skipIf(
// Firefox edits the text node in place and is not affected.
isFirefox(),
).fails('keeps the typed text over the full code text', async () => {
using fixture = setupFixture()
const { n } = fixture
fixture.set(n.doc(n.codeBlock({ language: 'js' }, '<a>const answer = 42<b>')))
// The bug only triggers once highlight token spans wrap the code text.
await expect.element(codeTokens.first(), { timeout: 15000 }).toBeInTheDocument()
fixture.view.focus()
await userEvent.keyboard('X')
const expected = n.doc(n.codeBlock({ language: 'js' }, 'X'))
await vi.waitFor(() => {
expect(fixture.doc.eq(expected)).toBe(true)
})
})
})
68 changes: 67 additions & 1 deletion packages/react/src/components/code-block-view.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import '../testing/index.ts'

import { isSafari } from '@meowdown/vitest/helpers'
import { isFirefox, isSafari } from '@meowdown/vitest/helpers'
import { TextSelection } from '@prosekit/pm/state'
import type { EditorView } from '@prosekit/pm/view'
import { createRef } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { render } from 'vitest-browser-react'
Expand Down Expand Up @@ -160,3 +162,67 @@ describe('code block math preview', () => {
})
})
})

describe('typing over code block selections', () => {
async function setupHighlightedCodeBlock(onDocChange?: VoidFunction) {
const ref = createRef<EditorHandle>()
await render(
<ProseKitEditor ref={ref} initialMarkdown={CODE_BLOCK_MD} onDocChange={onDocChange} />,
)
// The bug only triggers once highlight token spans wrap the code text.
await expect.element(tokens.first(), { timeout: 15000 }).toBeInTheDocument()
const view = ref.current?.editor?.view
if (!view) throw new Error('editor not mounted')
const codeStart = 1
const codeEnd = codeStart + view.state.doc.child(0).content.size
return { ref, view, codeStart, codeEnd }
}

function selectCodeRange(view: EditorView, anchor: number, head: number) {
view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, anchor, head)))
view.focus()
}

it('updates the markdown when typing at a caret', async () => {
const { ref, view, codeEnd } = await setupHighlightedCodeBlock()
selectCodeRange(view, codeEnd, codeEnd)
await userEvent.keyboard('X')
await vi.waitFor(() => {
expect(ref.current?.getMarkdown()).toContain('fn main() {}X')
})
})

it('updates the markdown when typing over a partial selection', async () => {
const { ref, view, codeStart, codeEnd } = await setupHighlightedCodeBlock()
selectCodeRange(view, codeStart, codeEnd - 1)
await userEvent.keyboard('X')
await vi.waitFor(() => {
expect(ref.current?.getMarkdown()).toContain('```rust\nX}\n```')
})
})

it.skipIf(
// Firefox edits the text node in place and is not affected.
isFirefox(),
).fails('updates the markdown when typing over the full code text', async () => {
const { ref, view, codeStart, codeEnd } = await setupHighlightedCodeBlock()
selectCodeRange(view, codeStart, codeEnd)
await userEvent.keyboard('X')
await vi.waitFor(() => {
expect(ref.current?.getMarkdown()).toContain('```rust\nX\n```')
})
})

it.skipIf(
// Firefox edits the text node in place and is not affected.
isFirefox(),
).fails('notifies onDocChange when typing over the full code text', async () => {
const onDocChange = vi.fn()
const { view, codeStart, codeEnd } = await setupHighlightedCodeBlock(onDocChange)
selectCodeRange(view, codeStart, codeEnd)
await userEvent.keyboard('X')
await vi.waitFor(() => {
expect(onDocChange).toHaveBeenCalled()
})
})
})
Loading