import React, { useEffect } from 'react' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import type { Command, EditorState } from 'prosemirror-state' import { vi } from 'vitest' import { testEditorState } from '../fixtures/testState' import { useRichTextEditor } from './useRichTextEditor' const user = userEvent.setup() const Scenario = ({ onChange = () => undefined, editable = true, }: { onChange?: (editorState: EditorState) => void editable?: boolean }): JSX.Element => { const command: Command = (state, dispatch) => { // Insert text at the current selection point, which is the start because // we don’t have a selection yet. if (!dispatch) return false dispatch(state.tr.insertText('Prepended content. ')) return true } const [ref, editorState, dispatchTransaction, setEditableStatus] = useRichTextEditor( testEditorState, { 'aria-labelledby': 'label-ref-id', 'data-testid': '12345678' }, { editable }, ) useEffect(() => { // Propagate changes to the editorState onChange(editorState) }, [editorState, onChange]) return (
) } describe('useRichTextEditor()', () => { it('binds the editor to the DOM', async () => { render() await waitFor(() => { expect(screen.getByText('Example content')).toBeInTheDocument() }) }) it('updates the DOM when commands are dispatched', async () => { render() await waitFor(() => { expect(screen.getByText('Example content')).toBeInTheDocument() }) await user.click(screen.getByText('Prepend button')) await waitFor(() => { expect(screen.getByText('Prepended content. Example content')).toBeInTheDocument() }) }) it('updates the editorState when commands are dispatched', async () => { const onChange = vi.fn() render() await waitFor(() => { expect(screen.getByText('Example content')).toBeInTheDocument() }) await user.click(screen.getByText('Prepend button')) const updatedEditorState = onChange.mock.calls[1][0] as EditorState const expected = { doc: { content: [ { content: [ { text: 'Prepended content. Example content', type: 'text', }, ], type: 'paragraph', }, ], type: 'doc', }, selection: { anchor: 20, head: 20, type: 'text', }, } expect(updatedEditorState.toJSON()).toStrictEqual(expected) }) it('defaults to editable', async () => { render() await waitFor(() => { const editor = screen.getByTestId('testid--editor') expect(editor.children[0]).toHaveAttribute('contenteditable', 'true') }) }) it('respects initial editable status', async () => { render() await waitFor(() => { const editor = screen.getByTestId('testid--editor') expect(editor.children[0]).toHaveAttribute('contenteditable', 'false') }) }) it('updates editable status', async () => { render() const editor = screen.getByTestId('testid--editor') await waitFor(() => { expect(editor.children[0]).toHaveAttribute('contenteditable', 'true') }) const disableEditButton = screen.getByText('Editable: false') await user.click(disableEditButton) await waitFor(() => { expect(editor.children[0]).toHaveAttribute('contenteditable', 'false') }) const enableEditButton = screen.getByText('Editable: true') await user.click(enableEditButton) await waitFor(() => { expect(editor.children[0]).toHaveAttribute('contenteditable', 'true') }) }) it('is removed from DOM when unmounted', async () => { const { unmount } = render() const editor = screen.getByTestId('testid--editor') expect(editor).toBeInTheDocument() unmount() expect(screen.queryByTestId('testid--editor')).not.toBeInTheDocument() }) })