diff --git a/config/rollup.js b/config/rollup.js index 1f2337b13..d961e1139 100644 --- a/config/rollup.js +++ b/config/rollup.js @@ -89,6 +89,7 @@ export function createRollupConfig({ pkg, tailwindConfig }) { globals: { react: 'React' }, file: `./${pkg.module}`, exports: 'named', + inlineDynamicImports: true, }, // { // file: `./${pkg.main}`, diff --git a/packages/core/editor/src/YooptaEditor.test.tsx b/packages/core/editor/src/YooptaEditor.test.tsx index 21e78ae38..66d8fa53b 100644 --- a/packages/core/editor/src/YooptaEditor.test.tsx +++ b/packages/core/editor/src/YooptaEditor.test.tsx @@ -1,36 +1,10 @@ -import React from 'react'; import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import { YooptaEditor } from './YooptaEditor'; -import { YooptaPlugin } from './plugins'; -import { createYooptaEditor } from './editor'; - -const fakePlugin = new YooptaPlugin({ - type: 'Test', - elements: { - test: { - render: (props) =>
{props.children}
, - props: { nodeType: 'block' }, - }, - }, -}); - -const paragraphPlugin = new YooptaPlugin({ - type: 'Paragraph', - elements: { - paragraph: { - render: (props) =>

{props.children}

, - props: { nodeType: 'block' }, - }, - }, -}); - -const plugins = [fakePlugin, paragraphPlugin]; +import { screen } from '@testing-library/react'; +import { renderYooptaEditor } from 'test-utils'; describe('YooptaEditor Component', () => { it('renders correctly with minimum props', () => { - const editor = createYooptaEditor(); - render(); + renderYooptaEditor(); expect(screen.getByTestId('yoopta-editor')).toBeInTheDocument(); }); @@ -38,9 +12,7 @@ describe('YooptaEditor Component', () => { it('applies className and style correctly', () => { const style = { width: '500px' }; const className = 'custom-class'; - const editor = createYooptaEditor(); - - render(); + renderYooptaEditor({ className, style }); const editorEl = screen.getByTestId('yoopta-editor'); expect(editorEl).toHaveClass(className); @@ -50,18 +22,15 @@ describe('YooptaEditor Component', () => { it('validates initial value and handles invalid data gracefully', () => { const consoleErrorMock = vi.spyOn(console, 'error').mockImplementation(() => {}); const invalidValue = 'this is not a valid value'; - const editor = createYooptaEditor(); + renderYooptaEditor({ value: invalidValue }); - render(); expect(consoleErrorMock).toHaveBeenCalled(); consoleErrorMock.mockRestore(); }); it('displays a warning for legacy data usage', () => { - const editor = createYooptaEditor(); - const value = [{ id: 'old-format', nodeType: 'old' }]; - render(); + renderYooptaEditor({ value }); expect(screen.getByText(/legacy version of the Yoopta-Editor is used/i)).toBeInTheDocument(); }); diff --git a/packages/core/editor/src/YooptaEditor.tsx b/packages/core/editor/src/YooptaEditor.tsx index 9bced6886..73d195543 100644 --- a/packages/core/editor/src/YooptaEditor.tsx +++ b/packages/core/editor/src/YooptaEditor.tsx @@ -19,7 +19,7 @@ import { YooptaMark } from './marks'; import { FakeSelectionMark } from './marks/FakeSelectionMark'; import { generateId } from './utils/generateId'; -type Props = { +export type YooptaEditorProps = { id?: string; editor: YooEditor; plugins: YooptaPlugin[]; @@ -81,7 +81,7 @@ const YooptaEditor = ({ readOnly, width, style, -}: Props) => { +}: YooptaEditorProps) => { const applyChanges = () => { setEditorState((prev) => ({ ...prev, version: prev.version + 1 })); }; diff --git a/packages/core/editor/src/index.ts b/packages/core/editor/src/index.ts index 16d31ef4d..cfc0419c5 100644 --- a/packages/core/editor/src/index.ts +++ b/packages/core/editor/src/index.ts @@ -7,7 +7,7 @@ export { useYooptaReadOnly, useYooptaPluginOptions, } from './contexts/YooptaContext/YooptaContext'; -import { YooptaEditor } from './YooptaEditor'; +import { YooptaEditor, type YooptaEditorProps } from './YooptaEditor'; // [TODO] - should be in separated package @yoopta/common/ui or @yoopta/ui export { UI } from './UI'; @@ -40,3 +40,4 @@ export { Blocks } from './editor/blocks'; import './styles.css'; export default YooptaEditor; +export { YooptaEditorProps }; diff --git a/packages/plugins/paragraph/src/plugin/ParagraphPlugin.test.tsx b/packages/plugins/paragraph/src/plugin/ParagraphPlugin.test.tsx new file mode 100644 index 000000000..92c4b9427 --- /dev/null +++ b/packages/plugins/paragraph/src/plugin/ParagraphPlugin.test.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Paragraph } from './'; +import { renderYooptaEditor } from 'test-utils'; + +const plugins = [Paragraph]; + +describe('Paragraph Plugin', () => { + it('renders correctly with basic props', () => { + const { container } = renderYooptaEditor({ plugins }); + + const paragraph = screen.getByTestId('paragraph'); + expect(paragraph).toBeInTheDocument(); + expect(paragraph.tagName).toBe('P'); + expect(paragraph.textContent).toBe('Test content'); + }); + + it('applies custom classes and attributes', () => { + const customProps = { + className: 'custom-class', + style: { textAlign: 'center' }, + attributes: { 'data-testid': 'paragraph' }, + }; + render(); + const paragraph = screen.getByTestId('paragraph'); + expect(paragraph).toHaveClass('custom-class'); + expect(paragraph).toHaveStyle({ textAlign: 'center' }); + }); + + it('handles extendRender prop', () => { + const extendedRender = vi.fn(({ children }) =>
{children}
); + render( + , + ); + expect(extendedRender).toHaveBeenCalled(); + const extendedElement = screen.getByTestId('extended'); + expect(extendedElement.textContent).toBe('Extended content'); + }); + + it('serializes to HTML correctly', () => { + const element = { type: 'paragraph', children: [{ text: 'Hello world' }] }; + const text = 'Hello world'; + const serializedOutput = Paragraph.parsers.html.serialize(element, text); + expect(serializedOutput).toBe('

Hello world

'); + }); + + it('deserializes from HTML correctly', () => { + document.body.innerHTML = `

Hello world

`; + const pElement = document.querySelector('p'); + const deserializedOutput = Paragraph.parsers.html.deserialize.nodeNames.includes(pElement.nodeName); + expect(deserializedOutput).toBeTruthy(); + }); + + it('serializes to Markdown correctly', () => { + const element = { type: 'paragraph', children: [{ text: 'Hello world' }] }; + const text = 'Hello world'; + const serializedMarkdown = Paragraph.parsers.markdown.serialize(element, text); + expect(serializedMarkdown).toBe('Hello world\n'); + }); +}); diff --git a/tests/test-utils.tsx b/tests/test-utils.tsx new file mode 100644 index 000000000..b30553c7c --- /dev/null +++ b/tests/test-utils.tsx @@ -0,0 +1,63 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { createYooptaEditor } from '../packages/core/editor/src/editor'; +import { YooptaPlugin } from '../packages/core/editor/src/plugins'; +import { YooptaEditor, YooptaEditorProps } from '../packages/core/editor/src/YooptaEditor'; + +export const InlinePlugin = new YooptaPlugin({ + type: 'Inline', + elements: { + inline: { + render: (props) => {props.children}, + props: { nodeType: 'inline' }, + }, + }, +}); + +export const DefaultParagraph = new YooptaPlugin({ + type: 'Paragraph', + elements: { + paragraph: { + render: (props) =>

{props.children}

, + props: { nodeType: 'block' }, + }, + }, +}); + +export const BlockPluginWithProps = new YooptaPlugin({ + type: 'Block', + elements: { + block: { + render: (props) =>
{props.children}
, + props: { nodeType: 'block', checked: false }, + }, + }, +}); + +export const TEST_PLUGIN_LIST = [InlinePlugin, DefaultParagraph, BlockPluginWithProps]; + +export function renderYooptaEditor(props: Partial = {}) { + const editor = createYooptaEditor(); + + return render( + , + ); +} + +const data = { + 'aafd7597-1e9a-4c80-ab6c-88786595d72a': { + 'aafd7597-1e9a-4c80-ab6c-88786595d72a': { + id: 'aafd7597-1e9a-4c80-ab6c-88786595d72a', + meta: { depth: 0, order: 0 }, + type: 'Paragraph', + value: [ + { + id: '3aff9e2c-5fee-43ff-b426-1e4fee8b303c', + type: 'paragraph', + props: { nodeType: 'block' }, + children: [{ text: '' }], + }, + ], + }, + }, +}; diff --git a/vitest.config.ts b/vitest.config.ts index f703934dd..5c92883fa 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,7 @@ import { defineConfig, transformWithEsbuild } from 'vite'; import react from '@vitejs/plugin-react'; import svgr from 'vite-plugin-svgr'; +import { resolve } from 'path'; export default defineConfig({ plugins: [ @@ -27,4 +28,9 @@ export default defineConfig({ 'packages/development/**/*.{test,spec}.{ts,tsx}', ], }, + resolve: { + alias: { + 'test-utils': resolve(__dirname, 'tests/test-utils.tsx'), + }, + }, });