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'),
+ },
+ },
});