diff --git a/src/components/form/texteditor/TextEditorActions.test.tsx b/src/components/form/texteditor/TextEditorActions.test.tsx
new file mode 100644
index 000000000..bbf89bf76
--- /dev/null
+++ b/src/components/form/texteditor/TextEditorActions.test.tsx
@@ -0,0 +1,79 @@
+import TextEditorActions, {
+ TextEditorActionProps,
+} from 'components/form/texteditor/TextEditorActions';
+import * as React from 'react';
+import { render, fireEvent, fireCustomEvent } from 'test/utils';
+
+const baseProps: TextEditorActionProps = {
+ entry: { value: '' },
+ maxLength: 100,
+ onAddEmoji: vi.fn(),
+ onFormat: vi.fn(),
+};
+
+describe(TextEditorActions.name, () => {
+ it('renders', () => {
+ const { baseElement } = render();
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('should toggle emoji picker', () => {
+ const { baseElement, getByTestId } = render(
+ ,
+ );
+
+ const emojiButton = getByTestId('emoji-button');
+ expect(baseElement).toMatchSnapshot();
+ fireEvent.click(emojiButton);
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('should emit add emoji', () => {
+ const { getByTestId } = render();
+
+ const emojiButton = getByTestId('emoji-button');
+ fireEvent.click(emojiButton);
+
+ expect(baseProps.onAddEmoji).not.toHaveBeenCalled();
+ const emojiPicker = getByTestId('emoji-picker');
+
+ fireCustomEvent(emojiPicker, 'emoji-selected', { emoji: '👍' });
+ expect(baseProps.onAddEmoji).toHaveBeenCalled();
+ });
+
+ it('should close emoji picker on click outside', () => {
+ const { baseElement, getByTestId } = render(
+ ,
+ );
+
+ const emojiButton = getByTestId('emoji-button');
+ fireEvent.click(emojiButton);
+ expect(baseElement).toMatchSnapshot();
+
+ const emojiPicker = getByTestId('emoji-picker');
+ fireCustomEvent(emojiPicker, 'click-outside', true);
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('should emit formats', () => {
+ const { getByTestId } = render();
+
+ const boldButton = getByTestId('bold-button');
+ const italicButton = getByTestId('italic-button');
+ const codeButton = getByTestId('code-button');
+ const strikeButton = getByTestId('strike-button');
+
+ expect(baseProps.onFormat).not.toHaveBeenCalled();
+ fireEvent.click(boldButton);
+ expect(baseProps.onFormat).toHaveBeenCalledWith('*');
+
+ fireEvent.click(italicButton);
+ expect(baseProps.onFormat).toHaveBeenCalledWith('_');
+
+ fireEvent.click(codeButton);
+ expect(baseProps.onFormat).toHaveBeenCalledWith('```');
+
+ fireEvent.click(strikeButton);
+ expect(baseProps.onFormat).toHaveBeenCalledWith('~');
+ });
+});
diff --git a/src/components/form/texteditor/TextEditorActions.tsx b/src/components/form/texteditor/TextEditorActions.tsx
index 5f709125c..ad380577b 100644
--- a/src/components/form/texteditor/TextEditorActions.tsx
+++ b/src/components/form/texteditor/TextEditorActions.tsx
@@ -10,7 +10,7 @@ import styles from './TextEditorActions.module.scss';
const UnnnicButton = applyVueInReact(Unnnic.unnnicButton);
const UnnnicEmojiPicker = applyVueInReact(Unnnic.unnnicEmojiPicker);
-export interface TextEditorProps {
+export interface TextEditorActionProps {
entry: StringEntry;
maxLength: number;
onAddEmoji: (emoji: any) => void;
@@ -22,10 +22,10 @@ export interface TextEditorState {
}
export default class TextEditorActions extends React.Component<
- TextEditorProps,
+ TextEditorActionProps,
TextEditorState
> {
- constructor(props: TextEditorProps) {
+ constructor(props: TextEditorActionProps) {
super(props);
this.state = {
@@ -49,6 +49,7 @@ export default class TextEditorActions extends React.Component<
this.addEmoji(e)}
onClickOutside={() => this.toggleEmojiPicker()}
/>
@@ -65,6 +67,7 @@ export default class TextEditorActions extends React.Component<
{
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders', () => {
+ const { baseElement } = render();
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('renders with initial value', () => {
+ const { baseElement } = render(
+ ,
+ );
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('renders with autocomplete', () => {
+ const { baseElement } = render(
+ ,
+ );
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('renders with label', () => {
+ const { baseElement } = render(
+ ,
+ );
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('renders with autocomplete and label', () => {
+ const { baseElement } = render(
+ ,
+ );
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('renders with error', () => {
+ const { baseElement } = render(
+ ,
+ );
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ it('should call onChange with correct value', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const input = getByTestId('Message');
+
+ fireUnnnicTextAreaChangeText(input, 'Hello');
+ expect(baseProps.onChange).toHaveBeenCalledWith('Hello', 'Message');
+ });
+
+ it('should not call onChange if undefined', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const input = getByTestId('Message');
+
+ fireUnnnicTextAreaChangeText(input, 'hello');
+ expect(baseProps.onChange).not.toHaveBeenCalled();
+ });
+
+ it('should call on change when emoji is added', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ const emojiButton = getByTestId('emoji-button');
+ fireEvent.click(emojiButton);
+
+ expect(baseProps.onChange).not.toHaveBeenCalled();
+ const emojiPicker = getByTestId('emoji-picker');
+
+ fireCustomEvent(emojiPicker, 'emoji-selected', { emoji: '👍' });
+ expect(baseProps.onChange).toHaveBeenCalledTimes(1);
+ });
+
+ it('should emit on change formatted value', () => {
+ const { getByTestId, rerender } = render(
+ ,
+ );
+
+ const input = getByTestId('Message');
+ fireUnnnicTextAreaChangeText(input, 'Hello');
+ expect(baseProps.onChange).toHaveBeenCalledWith('Hello', 'Message');
+
+ rerender(
+ ,
+ );
+
+ const boldButton = getByTestId('bold-button');
+ fireEvent.click(boldButton);
+ expect(baseProps.onChange).toHaveBeenCalledWith('Hello**', 'Message');
+
+ const italicButton = getByTestId('italic-button');
+ fireEvent.click(italicButton);
+ expect(baseProps.onChange).toHaveBeenCalledWith('Hello__', 'Message');
+
+ const codeButton = getByTestId('code-button');
+ fireEvent.click(codeButton);
+ expect(baseProps.onChange).toHaveBeenCalledWith('Hello``````', 'Message');
+
+ const strikeButton = getByTestId('strike-button');
+ fireEvent.click(strikeButton);
+ expect(baseProps.onChange).toHaveBeenCalledWith('Hello~~', 'Message');
+ });
+
+ it('should not emit formats if disabled', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ const boldButton = getByTestId('bold-button');
+ const italicButton = getByTestId('italic-button');
+ const codeButton = getByTestId('code-button');
+ const strikeButton = getByTestId('strike-button');
+
+ expect(baseProps.onChange).not.toHaveBeenCalled();
+ fireEvent.click(boldButton);
+ fireEvent.click(italicButton);
+ fireEvent.click(codeButton);
+ fireEvent.click(strikeButton);
+ expect(baseProps.onChange).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/components/form/texteditor/__snapshots__/TextEditorActions.test.tsx.snap b/src/components/form/texteditor/__snapshots__/TextEditorActions.test.tsx.snap
new file mode 100644
index 000000000..032b65b50
--- /dev/null
+++ b/src/components/form/texteditor/__snapshots__/TextEditorActions.test.tsx.snap
@@ -0,0 +1,895 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`TextEditorActions > renders 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+ 100
+
+
+
+
+`;
+
+exports[`TextEditorActions > should close emoji picker on click outside 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+ 100
+
+
+
+
+`;
+
+exports[`TextEditorActions > should close emoji picker on click outside 2`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+ 100
+
+
+
+
+`;
+
+exports[`TextEditorActions > should toggle emoji picker 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+ 100
+
+
+
+
+`;
+
+exports[`TextEditorActions > should toggle emoji picker 2`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+ 100
+
+
+
+
+`;
diff --git a/src/components/form/texteditor/__snapshots__/TextEditorElement.test.tsx.snap b/src/components/form/texteditor/__snapshots__/TextEditorElement.test.tsx.snap
new file mode 100644
index 000000000..20978103f
--- /dev/null
+++ b/src/components/form/texteditor/__snapshots__/TextEditorElement.test.tsx.snap
@@ -0,0 +1,1210 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`TextEditorElement > renders 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+
+
+
+
+
+`;
+
+exports[`TextEditorElement > renders with autocomplete 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+
+
+
+
+
+`;
+
+exports[`TextEditorElement > renders with autocomplete and label 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+
+
+
+
+
+`;
+
+exports[`TextEditorElement > renders with error 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ Error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+
+
+
+
+
+`;
+
+exports[`TextEditorElement > renders with initial value 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+ /
+
+
+
+
+
+`;
+
+exports[`TextEditorElement > renders with label 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ /
+
+
+
+
+
+`;
diff --git a/src/test/utils.tsx b/src/test/utils.tsx
index 08787c698..e2e5e0282 100644
--- a/src/test/utils.tsx
+++ b/src/test/utils.tsx
@@ -151,6 +151,15 @@ export const fireTembaSelect = (ele: HTMLElement, value: any) => {
ele.dispatchEvent(evt);
};
+export const fireCustomEvent = (
+ ele: HTMLElement,
+ eventName: string,
+ eventData: any,
+) => {
+ const event = new CustomEvent(eventName, { detail: eventData });
+ fireEvent(ele, event);
+};
+
export const mock = (
object: T,
property: K,
diff --git a/src/vitest-setup.ts b/src/vitest-setup.ts
index 07a53265d..2c23a0c93 100644
--- a/src/vitest-setup.ts
+++ b/src/vitest-setup.ts
@@ -31,6 +31,14 @@ global.console = new Console(process.stderr, process.stderr);
setTimeout(callback, 0);
};
+const MockIntersectionObserver = vi.fn(() => ({
+ disconnect: vi.fn(),
+ observe: vi.fn(),
+ takeRecords: vi.fn(),
+ unobserve: vi.fn(),
+}));
+vi.stubGlobal(`IntersectionObserver`, MockIntersectionObserver);
+
const endpoints = config.endpoints;
export const restHandlers = [
http.get(endpoints.flows, () => {