fix: keep empty title clicks in h1
This commit is contained in:
@@ -334,4 +334,44 @@ describe('SingleEditorView', () => {
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('routes clicks on the empty title wrapper back into the H1 block', async () => {
|
||||
const editor = createEditor()
|
||||
|
||||
render(
|
||||
<SingleEditorView
|
||||
editor={editor as never}
|
||||
entries={[makeEntry()]}
|
||||
onNavigateWikilink={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
const container = screen.getByTestId('blocknote-view').closest('.editor__blocknote-container')
|
||||
expect(container).toBeTruthy()
|
||||
|
||||
const titleBlockOuter = document.createElement('div')
|
||||
titleBlockOuter.className = 'bn-block-outer'
|
||||
|
||||
const titleBlock = document.createElement('div')
|
||||
titleBlock.className = 'bn-block'
|
||||
|
||||
const titleHeading = document.createElement('div')
|
||||
titleHeading.setAttribute('data-content-type', 'heading')
|
||||
titleHeading.setAttribute('data-level', '1')
|
||||
|
||||
const inlineHeading = document.createElement('div')
|
||||
inlineHeading.className = 'bn-inline-content'
|
||||
titleHeading.appendChild(inlineHeading)
|
||||
titleBlock.appendChild(titleHeading)
|
||||
titleBlockOuter.appendChild(titleBlock)
|
||||
container?.appendChild(titleBlockOuter)
|
||||
|
||||
fireEvent.click(titleBlockOuter)
|
||||
await act(async () => {
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(editor.setTextCursorPosition).toHaveBeenCalledWith('heading-block', 'end')
|
||||
expect(editor.focus).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -141,13 +141,22 @@ function isSelectionInsideElement(element: HTMLElement): boolean {
|
||||
return Boolean(anchorElement && element.contains(anchorElement))
|
||||
}
|
||||
|
||||
const TITLE_HEADING_SELECTOR = 'h1, [data-content-type="heading"][data-level="1"], [data-content-type="heading"]:not([data-level])'
|
||||
const TITLE_HEADING_WRAPPER_SELECTOR = '.bn-block-outer, .bn-block'
|
||||
|
||||
function findTitleHeadingElement(target: HTMLElement): HTMLElement | null {
|
||||
const directHeading = target.closest<HTMLElement>(TITLE_HEADING_SELECTOR)
|
||||
if (directHeading) return directHeading
|
||||
|
||||
const titleWrapper = target.closest<HTMLElement>(TITLE_HEADING_WRAPPER_SELECTOR)
|
||||
return titleWrapper?.querySelector<HTMLElement>(TITLE_HEADING_SELECTOR) ?? null
|
||||
}
|
||||
|
||||
function queueTitleHeadingCursorRepair(
|
||||
target: HTMLElement,
|
||||
editor: ReturnType<typeof useCreateBlockNote>,
|
||||
): boolean {
|
||||
const titleHeading = target.closest<HTMLElement>(
|
||||
'h1, [data-content-type="heading"][data-level="1"], [data-content-type="heading"]:not([data-level])',
|
||||
)
|
||||
const titleHeading = findTitleHeadingElement(target)
|
||||
if (!titleHeading) return false
|
||||
|
||||
queueMicrotask(() => {
|
||||
|
||||
76
tests/smoke/new-note-title-caret-click.spec.ts
Normal file
76
tests/smoke/new-note-title-caret-click.spec.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { test, expect, type Page } from '@playwright/test'
|
||||
import { createFixtureVaultCopy, openFixtureVaultTauri, removeFixtureVaultCopy } from '../helpers/fixtureVault'
|
||||
import { triggerMenuCommand } from './testBridge'
|
||||
|
||||
async function createUntitledNote(page: Page): Promise<void> {
|
||||
await page.locator('body').click()
|
||||
await triggerMenuCommand(page, 'file-new-note')
|
||||
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5_000 })
|
||||
await expect(page.getByTestId('breadcrumb-filename-trigger')).toContainText(/untitled-note-\d+(?:-\d+)?/i, {
|
||||
timeout: 5_000,
|
||||
})
|
||||
}
|
||||
|
||||
async function clearEditorSelection(page: Page): Promise<void> {
|
||||
await page.evaluate(() => {
|
||||
const active = document.activeElement as HTMLElement | null
|
||||
active?.blur()
|
||||
window.getSelection()?.removeAllRanges()
|
||||
})
|
||||
}
|
||||
|
||||
async function clickTitleWrapperPadding(page: Page): Promise<void> {
|
||||
const clickTarget = await page.evaluate(() => {
|
||||
const titleBlockOuter = document.querySelector('.bn-block-outer') as HTMLElement | null
|
||||
if (!titleBlockOuter) return null
|
||||
|
||||
const rect = titleBlockOuter.getBoundingClientRect()
|
||||
return {
|
||||
x: rect.left + Math.min(32, Math.max(12, rect.width / 2)),
|
||||
y: rect.bottom - Math.min(4, Math.max(1, rect.height / 3)),
|
||||
}
|
||||
})
|
||||
|
||||
expect(clickTarget).not.toBeNull()
|
||||
await page.mouse.click(clickTarget!.x, clickTarget!.y)
|
||||
}
|
||||
|
||||
async function activeSelectionBlockType(page: Page): Promise<string | null> {
|
||||
return page.evaluate(() => {
|
||||
const selection = window.getSelection()
|
||||
const anchorNode = selection?.anchorNode ?? null
|
||||
const anchorElement = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement ?? null
|
||||
return anchorElement?.closest('.bn-block-content')?.getAttribute('data-content-type') ?? null
|
||||
})
|
||||
}
|
||||
|
||||
async function editorHasContentEditableFocus(page: Page): Promise<boolean> {
|
||||
return page.evaluate(() => {
|
||||
const active = document.activeElement as HTMLElement | null
|
||||
return Boolean(active?.isContentEditable || active?.closest('[contenteditable="true"]'))
|
||||
})
|
||||
}
|
||||
|
||||
let tempVaultDir: string
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testInfo.setTimeout(90_000)
|
||||
tempVaultDir = createFixtureVaultCopy()
|
||||
await openFixtureVaultTauri(page, tempVaultDir)
|
||||
})
|
||||
|
||||
test.afterEach(async () => {
|
||||
removeFixtureVaultCopy(tempVaultDir)
|
||||
})
|
||||
|
||||
test('@smoke clicking an untitled title wrapper restores the caret to the H1', async ({ page }) => {
|
||||
await createUntitledNote(page)
|
||||
await clearEditorSelection(page)
|
||||
|
||||
await expect.poll(() => editorHasContentEditableFocus(page), { timeout: 5_000 }).toBe(false)
|
||||
|
||||
await clickTitleWrapperPadding(page)
|
||||
|
||||
await expect.poll(() => editorHasContentEditableFocus(page), { timeout: 5_000 }).toBe(true)
|
||||
await expect.poll(() => activeSelectionBlockType(page), { timeout: 5_000 }).toBe('heading')
|
||||
})
|
||||
Reference in New Issue
Block a user