reference-activation.client.spec.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. // @vitest-environment jsdom
  2. /** Composer clicks use the owning Lexical node while selection gestures remain editable. */
  3. import { afterEach, describe, expect, it, vi } from 'vitest'
  4. import { $createParagraphNode, $createTextNode, $getRoot, createEditor } from 'lexical'
  5. import { registerPlainText } from '@lexical/plain-text'
  6. import { ReferenceChipNode } from '../src/client/input/editor/chip-node.tsx'
  7. import { TextRefNode } from '../src/client/input/editor/text-ref.ts'
  8. import { registerReferenceActivation } from '../src/client/input/editor/reference-activation.ts'
  9. const cleanups: Array<() => void> = []
  10. afterEach(() => { for (const cleanup of cleanups.splice(0).reverse()) cleanup() })
  11. function bench() {
  12. const editor = createEditor({ nodes: [ReferenceChipNode, TextRefNode], onError: (error) => { throw error } })
  13. const root = document.createElement('div')
  14. root.contentEditable = 'true'
  15. document.body.append(root)
  16. editor.setRootElement(root)
  17. cleanups.push(() => { editor.setRootElement(null); root.remove() })
  18. cleanups.push(registerPlainText(editor))
  19. const open = vi.fn(() => true)
  20. const off = registerReferenceActivation(editor, open)
  21. cleanups.push(off)
  22. let chip!: ReferenceChipNode
  23. let text!: TextRefNode
  24. editor.update(() => {
  25. chip = new ReferenceChipNode({ source: 'reference', ref: '@a.md', label: 'a.md', appearance: 'file', clipboardText: '@a.md' })
  26. text = new TextRefNode('/review')
  27. $getRoot().append($createParagraphNode().append(chip, $createTextNode(' '), text))
  28. }, { discrete: true })
  29. const click = (key: string, detail = 1) => {
  30. const target = editor.getElementByKey(key)!
  31. target.dispatchEvent(new MouseEvent('click', { bubbles: true, detail }))
  32. }
  33. return { editor, root, chip, text, open, click, off }
  34. }
  35. describe('reference activation', () => {
  36. it('opens chip and editable skill references without changing draft content', () => {
  37. const { editor, chip, text, open, click, off } = bench()
  38. click(chip.getKey())
  39. expect(open).toHaveBeenLastCalledWith('reference', { ref: '@a.md', appearance: 'file' })
  40. click(text.getKey())
  41. expect(open).toHaveBeenLastCalledWith(undefined, { ref: '/review' })
  42. expect(editor.getEditorState().read(() => $getRoot().getTextContent())).toBe('@a.md /review')
  43. off()
  44. click(text.getKey())
  45. expect(open).toHaveBeenCalledTimes(2)
  46. })
  47. it('opens once in a double-click sequence and leaves selections, invalid chips, and ordinary text to the editor', () => {
  48. const { editor, root, chip, text, open, click } = bench()
  49. click(text.getKey(), 1)
  50. click(text.getKey(), 2)
  51. expect(open).toHaveBeenCalledTimes(1)
  52. open.mockClear()
  53. root.dispatchEvent(new MouseEvent('click', { bubbles: true }))
  54. editor.update(() => { text.select(0, 3) }, { discrete: true })
  55. click(text.getKey())
  56. editor.update(() => { text.select(0, 0); chip.setInvalid(true) }, { discrete: true })
  57. click(chip.getKey())
  58. expect(open).not.toHaveBeenCalled()
  59. })
  60. })