Skip to content
Merged
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
38 changes: 37 additions & 1 deletion src/components/__tests__/LayoutTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
import { importTextActionCreator as importText } from '../../actions/importText'
import createTestApp, { cleanupTestApp } from '../../test-helpers/createTestApp'
import dispatch from '../../test-helpers/dispatch'
import queryThoughtByText from '../../test-helpers/queries/queryThoughtByText'
import { setCursorFirstMatchActionCreator as setCursor } from '../../test-helpers/setCursorFirstMatch'

beforeEach(createTestApp)
afterEach(cleanupTestApp)
afterEach(async () => {
vi.restoreAllMocks()
await cleanupTestApp()
})

it('unmount TreeNodes on collapse', async () => {
await dispatch(
Expand Down Expand Up @@ -34,3 +38,35 @@
// collapse a and unmount b
expect(document.querySelectorAll('[aria-label="tree-node"]').length).toBe(2)
})

it('unmounts distant thoughts after navigation and remounts them on return', async () => {
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue(new DOMRect(0, 0, 100, 20))

await dispatch(
importText({
text: `
- a
- b
- c
- d
- e
- f
- g
`,
}),
)

await dispatch(setCursor(['a']))
expect(await queryThoughtByText('a')).not.toBeNull()

await dispatch(setCursor(['a', 'b', 'c', 'd', 'e', 'f']))
await act(vi.runOnlyPendingTimersAsync)

expect(await queryThoughtByText('a')).toBeNull()

Check failure on line 65 in src/components/__tests__/LayoutTree.ts

View workflow job for this annotation

GitHub Actions / TDD — Unit tests

[unit] src/components/__tests__/LayoutTree.ts > unmounts distant thoughts after navigation and remounts them on return

AssertionError: expected <div …(9)></div> to be null - Expected: null + Received: <div aria-label="editable-W-OwEZFzwb02M" autocapitalize="sentences" class="editable" contenteditable="true" data-editable="true" placeholder="a" role="button" spellcheck="true" style="opacity: 1;" > a </div> ❯ src/components/__tests__/LayoutTree.ts:65:41
// hide-parent thoughts stay mounted so they can fade back in when navigating up.
expect(await queryThoughtByText('d')).not.toBeNull()
expect(await queryThoughtByText('f')).not.toBeNull()

await dispatch(setCursor(['a']))
expect(await queryThoughtByText('a')).not.toBeNull()
})
22 changes: 20 additions & 2 deletions src/e2e/puppeteer/__tests__/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ import waitUntil from '../helpers/waitUntil'

vi.setConfig({ testTimeout: 20000, hookTimeout: 20000 })

/** Returns the persistent tree node that contains the editable with the given value. */
const getTreeNode = async (value: string) => {
const editable = (await waitForEditable(value)).asElement()
if (!editable) throw new Error(`Editable "${value}" not found.`)

const treeNode = (
await editable.evaluateHandle(element => (element as Element).closest('[aria-label="tree-node"]'))
).asElement()
if (!treeNode) throw new Error(`Tree node for "${value}" not found.`)

return treeNode
}

it('set the cursor to a thought in the home context on load', async () => {
const importText = `
- a
Expand Down Expand Up @@ -92,8 +105,10 @@ it('do nothing when clicking on a hidden ancestor', async () => {
- d`
await paste(importText)
await waitForEditable('d')
const ancestorTreeNode = await getTreeNode('a')
await clickThought('d')
await clickThought('a')
await ancestorTreeNode.waitForSelector('[data-editable]', { hidden: true })
await click(ancestorTreeNode)

const thoughtValue = await getEditingText()
expect(thoughtValue).toBe('d')
Expand All @@ -107,6 +122,8 @@ it('do nothing when clicking on a hidden great uncle', async () => {
- d`
await paste(importText)

const greatUncleTreeNode = await getTreeNode('d')

// click a to expand b and c
await waitForEditable('a')
await clickThought('a')
Expand All @@ -115,7 +132,8 @@ it('do nothing when clicking on a hidden great uncle', async () => {
// for some reason we need to sleep before clicking c, otherwise the cursor is moved to d
await waitForEditable('c')
await clickThought('c')
await clickThought('d')
await greatUncleTreeNode.waitForSelector('[data-editable]', { hidden: true })
await click(greatUncleTreeNode)

const thoughtValue = await getEditingText()
expect(thoughtValue).toBe('c')
Expand Down
45 changes: 45 additions & 0 deletions src/hooks/__tests__/useDelayedAutofocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { renderHook } from '@testing-library/react'
import { act } from 'react'
import Autofocus from '../../@types/Autofocus'
import useDelayedAutofocus from '../useDelayedAutofocus'

beforeEach(() => {
vi.useFakeTimers()
})

afterEach(() => {
vi.useRealTimers()
})

it('updates the selected autofocus after the delay', () => {
const { result, rerender } = renderHook(
({ autofocus }: { autofocus: Autofocus }) =>
useDelayedAutofocus(autofocus, {
delay: 750,
selector: autofocusNew => autofocusNew === 'hide',
}),
{ initialProps: { autofocus: 'show' as Autofocus } },
)

rerender({ autofocus: 'hide' })

act(() => vi.advanceTimersByTime(749))
expect(result.current).toBe(false)

act(() => vi.advanceTimersByTime(1))
expect(result.current).toBe(true)

Check failure on line 30 in src/hooks/__tests__/useDelayedAutofocus.ts

View workflow job for this annotation

GitHub Actions / TDD — Unit tests

[unit] src/hooks/__tests__/useDelayedAutofocus.ts > updates the selected autofocus after the delay

AssertionError: expected false to be true // Object.is equality - Expected + Received - true + false ❯ src/hooks/__tests__/useDelayedAutofocus.ts:30:26
})

it('cancels a delayed update on unmount', () => {
const { rerender, unmount } = renderHook(
({ autofocus }: { autofocus: Autofocus }) =>
useDelayedAutofocus(autofocus, { delay: 750, selector: value => value }),
{ initialProps: { autofocus: 'show' as Autofocus } },
)

rerender({ autofocus: 'hide' })
expect(vi.getTimerCount()).toBe(1)

unmount()
expect(vi.getTimerCount()).toBe(0)

Check failure on line 44 in src/hooks/__tests__/useDelayedAutofocus.ts

View workflow job for this annotation

GitHub Actions / TDD — Unit tests

[unit] src/hooks/__tests__/useDelayedAutofocus.ts > cancels a delayed update on unmount

AssertionError: expected 1 to be +0 // Object.is equality - Expected + Received - 0 + 1 ❯ src/hooks/__tests__/useDelayedAutofocus.ts:44:30
})
4 changes: 1 addition & 3 deletions src/hooks/useDelayedAutofocus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const useDelayedAutofocus = <T = string>(
// This ensures that the component is only re-rendered when the selector result changes, not every time the delayed autofocus value changes.
const [autofocusDelayed, setAutofocusDelayed] = useState(selector(autofocus))
const lastAutofocusRef = useRef(autofocus)
const unmounted = useRef(false)
const autofocusTimerRef = useRef<number>(0)
useEffect(
() => {
Expand All @@ -25,7 +24,6 @@ const useDelayedAutofocus = <T = string>(
(lastAutofocusRef.current === 'show' || lastAutofocusRef.current === 'dim')
) {
autofocusTimerRef.current = setTimeout(() => {
if (unmounted.current) return
setAutofocusDelayed(selector(autofocus))
lastAutofocusRef.current = autofocus
}, delay) as unknown as number
Expand All @@ -35,7 +33,7 @@ const useDelayedAutofocus = <T = string>(
}

return () => {
unmounted.current = true
clearTimeout(autofocusTimerRef.current)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
Loading