diff --git a/src/components/Note.tsx b/src/components/Note.tsx index 03e1802f7a..c52ad50a60 100644 --- a/src/components/Note.tsx +++ b/src/components/Note.tsx @@ -114,11 +114,23 @@ const Note = React.memo( ) /** Set editing to false onBlur, if keyboard is closed. */ - const onBlur = useCallback(() => { - if (isTouch && !selection.isActive()) { - setTimeout(() => dispatch(editing({ value: false }))) - } - }, [dispatch]) + const onBlur = useCallback( + (e: React.FocusEvent) => { + if (isTouch) { + // if we know that the focus is changing to another editable or note then do not set editing to false + // (does not work when clicking a bullet as it is set to null) + const isRelatedTargetEditableOrNote = + e.relatedTarget && + ((e.relatedTarget as Element).hasAttribute?.('data-editable') || + !!(e.relatedTarget as Element).querySelector('[aria-label="note-editable"]')) + + if (!isRelatedTargetEditableOrNote) { + setTimeout(() => dispatch(editing({ value: false }))) + } + } + }, + [dispatch], + ) if (note === null) return null diff --git a/src/e2e/puppeteer/__tests__/caret.ts b/src/e2e/puppeteer/__tests__/caret.ts index 7cffe1cd98..33e13517ca 100644 --- a/src/e2e/puppeteer/__tests__/caret.ts +++ b/src/e2e/puppeteer/__tests__/caret.ts @@ -1,6 +1,7 @@ import { KnownDevices } from 'puppeteer' import click from '../helpers/click' import clickBullet from '../helpers/clickBullet' +import clickNote from '../helpers/clickNote' import clickThought from '../helpers/clickThought' import emulate from '../helpers/emulate' import getEditingText from '../helpers/getEditingText' @@ -8,6 +9,7 @@ import getSelection from '../helpers/getSelection' import paste from '../helpers/paste' import press from '../helpers/press' import refresh from '../helpers/refresh' +import swipe from '../helpers/swipe' import waitForEditable from '../helpers/waitForEditable' import waitForHiddenEditable from '../helpers/waitForHiddenEditable' import waitForSelector from '../helpers/waitForSelector' @@ -272,4 +274,22 @@ describe('mobile only', () => { const focusNode = await getSelection().focusNode expect(focusNode).toBeUndefined() }) + + describe('when caret moves from inside a note', () => { + it('cursorForward should move the cursor to the next thought', async () => { + const importText = ` + - a + - b + - =note + - Hello world` + await paste(importText) + await clickNote('Hello world') + await clickThought('a') + + await swipe('l') + + const textContext = await getSelection().focusNode?.textContent + expect(textContext).toBe('b') + }) + }) }) diff --git a/src/e2e/puppeteer/__tests__/commandPalette.ts b/src/e2e/puppeteer/__tests__/commandPalette.ts index 58e658e56b..e8ee13fb82 100644 --- a/src/e2e/puppeteer/__tests__/commandPalette.ts +++ b/src/e2e/puppeteer/__tests__/commandPalette.ts @@ -33,7 +33,7 @@ it('open with gesture', async () => { await page.emulate(KnownDevices['iPhone 15 Pro']) await paste(importText) - await swipe('r') + await swipe('r', false) // the command palette should open const popupValue = await page.locator('[data-testid=popup-value]').wait() diff --git a/src/e2e/puppeteer/helpers/clickNote.ts b/src/e2e/puppeteer/helpers/clickNote.ts new file mode 100644 index 0000000000..df421c0c8a --- /dev/null +++ b/src/e2e/puppeteer/helpers/clickNote.ts @@ -0,0 +1,24 @@ +import { page } from '../setup' + +/** + * Click the note for the given note value. Waits for the note at the beginning in case it hasn't been rendered yet. + */ +const clickNote = async (value: string) => { + // use a short timeout to make time for a render and async page communication + // precede clickNote by a longer waitForEditable for steps that are known to take time, such as refreshing the page + const editableNode = await page.waitForFunction( + (value: string) => { + return Array.from(document.querySelectorAll('[aria-label=note-editable]')).find( + element => element.innerHTML === value, + ) + }, + { + timeout: 1000, + }, + value, + ) + // @ts-expect-error - https://github.com/puppeteer/puppeteer/issues/8852 + await editableNode.asElement()?.click() +} + +export default clickNote diff --git a/src/e2e/puppeteer/helpers/swipe.ts b/src/e2e/puppeteer/helpers/swipe.ts index e991ff6af9..a42bf71bec 100644 --- a/src/e2e/puppeteer/helpers/swipe.ts +++ b/src/e2e/puppeteer/helpers/swipe.ts @@ -17,33 +17,13 @@ const swipePoints = async (points: { x: number; y: number }[], complete: boolean /** Swipe right. */ // TODO: Support other directions and multiple swipes. -const swipe = async (direction: 'r') => { +const swipe = async (direction: 'r' | 'l', complete: boolean = true) => { + const anchor = direction === 'l' ? 250 : 100 + const delta = direction === 'l' ? -10 : 10 const y = 100 await swipePoints( - [ - { x: 100, y }, - { x: 110, y }, - { x: 120, y }, - { x: 130, y }, - { x: 140, y }, - { x: 150, y }, - { x: 160, y }, - { x: 170, y }, - { x: 180, y }, - { x: 190, y }, - { x: 200, y }, - { x: 210, y }, - { x: 220, y }, - { x: 230, y }, - { x: 240, y }, - { x: 250, y }, - { x: 260, y }, - { x: 270, y }, - { x: 280, y }, - { x: 290, y }, - { x: 300, y }, - ], - false, + new Array(15).fill(0).map((_, i) => ({ x: anchor + delta * i, y })), + complete, ) }