Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "preact/compat";
import {
act,
cleanup,
Expand All @@ -7,6 +8,21 @@ import {
} from "@testing-library/preact";
import { singleLineFieldSchema } from "../../../__test__/data/fields";
import AddInstanceButtonComponent from "../addInstanceButton";
import visualBuilderPostMessageActual from "../../utils/visualBuilderPostMessage";
import { getDiscussionIdByFieldMetaData } from "../../utils/getDiscussionIdByFieldMetaData";

const visualBuilderPostMessage = vi.mocked(visualBuilderPostMessageActual);

vi.mock("../../utils/visualBuilderPostMessage", async () => {
return {
default: {
send: vi.fn().mockImplementation((_eventName: string) => {
return Promise.resolve({});
}),
on: vi.fn(),
},
};
});

describe("AddInstanceButtonComponent", () => {
afterEach(cleanup);
Expand All @@ -18,10 +34,16 @@ describe("AddInstanceButtonComponent", () => {
<AddInstanceButtonComponent
value={[]}
fieldSchema={singleLineFieldSchema}
// @ts-expect-error mocking fieldMetadata
fieldMetadata={{}}
index={0}
onClick={onClickCallback}
label="Add instance"
// @ts-expect-error mocking signal
loading={{ value: false }}
/>
);
})
});
const buttonElement = getByTestId(
document.body,
"visual-builder-add-instance-button"
Expand All @@ -33,22 +55,63 @@ describe("AddInstanceButtonComponent", () => {
expect(buttonElement.querySelector("path")).toBeTruthy();
});

test("calls onClickCallback when button is clicked", async () => {
test("sends add-instance message when clicked", async () => {
const onClickCallback = vi.fn();
await act(() => {
render(
<AddInstanceButtonComponent
value={[]}
fieldSchema={singleLineFieldSchema}
// @ts-expect-error mocking fieldMetadata
fieldMetadata={{}}
index={0}
onClick={onClickCallback}
label="Add instance"
// @ts-expect-error mocking signal
loading={{ value: false }}
/>
);
})
});
const buttonElement = getByTestId(
document.body,
"visual-builder-add-instance-button"
);
fireEvent.click(buttonElement);
await act(() => {
fireEvent.click(buttonElement);
});
expect(visualBuilderPostMessage?.send).toHaveBeenCalledWith(
"add-instance",
{
fieldMetadata: {},
index: 0,
}
);
});

test("calls onClick callback when clicked", async () => {
const onClickCallback = vi.fn();
await act(() => {
render(
<AddInstanceButtonComponent
value={[]}
fieldSchema={singleLineFieldSchema}
// @ts-expect-error mocking fieldMetadata
fieldMetadata={{}}
index={0}
onClick={onClickCallback}
label="Add instance"
// @ts-expect-error mocking signal
loading={{ value: false }}
/>
);
});
const buttonElement = getByTestId(
document.body,
"visual-builder-add-instance-button"
);
await act(() => {
fireEvent.click(buttonElement);
});
expect(onClickCallback).toHaveBeenCalled();
});
});
68 changes: 53 additions & 15 deletions src/visualBuilder/components/addInstanceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,82 @@ import classNames from "classnames";
import { visualBuilderStyles } from "../visualBuilder.style";
import { PlusIcon } from "./icons";
import { ISchemaFieldMap } from "../utils/types/index.types";
import { CslpData } from "../../cslp/types/cslp.types";
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types";
import { Signal } from "@preact/signals";

interface AddInstanceButtonProps {
value: any;
onClick: (event: MouseEvent) => void;
label?: string | undefined;
fieldSchema: ISchemaFieldMap | undefined;
fieldMetadata: CslpData;
index: number;
loading: Signal<boolean>;
}

function AddInstanceButtonComponent(
props: AddInstanceButtonProps
): JSX.Element {
const fieldSchema = props.fieldSchema;
const disabled =
fieldSchema && "max_instance" in fieldSchema && fieldSchema.max_instance
? props.value.length >= fieldSchema.max_instance
: false;
const fieldMetadata = props.fieldMetadata;
const index = props.index;
const loading = props.loading;

const onClick = async (event: MouseEvent) => {
loading.value = true;
try {
await visualBuilderPostMessage?.send(
VisualBuilderPostMessageEvents.ADD_INSTANCE,
{
fieldMetadata,
index,
}
);
} catch (error) {
console.error("Visual Builder: Failed to add instance", error);
}
loading.value = false;
props.onClick(event);
};

const buttonClassName = classNames(
"visual-builder__add-button",
visualBuilderStyles()["visual-builder__add-button"],
{
"visual-builder__add-button--with-label": props.label,
},
{
[visualBuilderStyles()["visual-builder__add-button--loading"]]:
loading.value,
},
visualBuilderStyles()["visual-builder__tooltip"]
);

const maxInstances =
fieldSchema && fieldSchema.data_type !== "block"
? fieldSchema.max_instance
: undefined;
const isMaxInstances = maxInstances
? props.value.length >= maxInstances
: false;
const disabled = loading.value || isMaxInstances;

return (
<button
className={classNames(
"visual-builder__add-button",
visualBuilderStyles()["visual-builder__add-button"],
{
"visual-builder__add-button--with-label": props.label,
},
visualBuilderStyles()["visual-builder__tooltip"]
)}
className={buttonClassName}
data-tooltip={"Add section"}
data-testid="visual-builder-add-instance-button"
disabled={disabled}
title={
disabled && fieldSchema && "max_instance" in fieldSchema
? `Max ${fieldSchema.max_instance} instances allowed`
maxInstances && isMaxInstances
? `Max ${maxInstances} instances allowed`
: undefined
}
onClick={(e) => {
const event = e as unknown as MouseEvent;
props.onClick(event);
onClick(event);
}}
>
<PlusIcon />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,67 @@
import React from "preact/compat";
import { singleLineFieldSchema } from "../../../__test__/data/fields";
import { ISchemaFieldMap } from "../types/index.types";
import {
getAddInstanceButtons,
generateAddInstanceButton,
} from "./../../generators/generateAddInstanceButtons";
} from "../generateAddInstanceButtons";
import AddInstanceButtonComponentActual from "../../components/addInstanceButton";

const AddInstanceButtonComponent = vi.mocked(AddInstanceButtonComponentActual);

vi.mock("../../components/addInstanceButton", async () => {
return {
default: vi.fn().mockImplementation(() => {
return (
<button data-testid="add-instance-button">
Add instance button
</button>
);
}),
};
});

describe("generateAddInstanceButton", () => {
test("should generate a button", () => {
afterEach(() => {
vi.clearAllMocks();
});

test("should generate and return a button", () => {
const button = generateAddInstanceButton({
fieldSchema: singleLineFieldSchema,
value: "",
onClick: () => {},
// @ts-expect-error mock field metadata
fieldMetadata: { hello: "world" },
onClick: vi.fn(),
// @ts-expect-error mocking preact signal
loading: { value: false },
index: 0,
label: "Add Instance",
});
expect(button).toBeInstanceOf(HTMLButtonElement);
});

test("should call the callback when clicked", () => {
const callback = vi.fn();
const button = generateAddInstanceButton({
test("should call the AddInstanceButtonComponent with the correct props", () => {
generateAddInstanceButton({
fieldSchema: singleLineFieldSchema,
value: "",
// @ts-expect-error mock field metadata
fieldMetadata: { hello: "world" },
onClick: vi.fn(),
// @ts-expect-error mocking preact signal
loading: { value: false },
index: 0,
label: "Add Instance",
});
const args = AddInstanceButtonComponent.mock.calls[0][0];
expect(args).toStrictEqual({
fieldSchema: singleLineFieldSchema,
value: "",
onClick: callback,
fieldMetadata: { hello: "world" },
onClick: expect.any(Function),
loading: { value: false },
index: 0,
label: "Add Instance",
});
button.click();
expect(callback).toHaveBeenCalledTimes(1);
});
});

Expand Down
19 changes: 16 additions & 3 deletions src/visualBuilder/generators/generateAddInstanceButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
import React from "preact/compat";
import { render } from "preact";
import AddInstanceButtonComponent from "../components/addInstanceButton";
import { ISchemaFieldMap } from "../utils/types/index.types";
import { CslpData } from "../../cslp/types/cslp.types";
import { Signal } from "@preact/signals";

/**
* Generates a button element, when clicked, triggers the provided callback function.
* Generates a button element, when clicked, sends the add instance message and
* then calls the provided callback function.
* @param onClickCallback - The function to be called when the button is clicked.
* @returns The generated button element.
*/
export function generateAddInstanceButton({
fieldSchema,
value,
fieldSchema,
fieldMetadata,
index,
loading,
onClick,
label,
}: {
fieldSchema: ISchemaFieldMap | undefined;
value: any;
fieldMetadata: CslpData;
index: number;
loading: Signal<boolean>;
onClick: (event: MouseEvent) => void;
label?: string | undefined;
fieldSchema: ISchemaFieldMap | undefined;
}): HTMLButtonElement {
const wrapper = document.createDocumentFragment();

render(
<AddInstanceButtonComponent
loading={loading}
index={index}
value={value}
label={label}
onClick={onClick}
fieldSchema={fieldSchema}
fieldMetadata={fieldMetadata}
/>,
wrapper
);
Expand Down
Loading