Skip to content

Commit 645b7f7

Browse files
authored
Merge pull request #406 from contentstack/VE-5555-add-reference-instance-multiple-modals
fix(VE-5555): add instance button loading state
2 parents de64cd8 + e98a8e3 commit 645b7f7

7 files changed

Lines changed: 235 additions & 77 deletions

File tree

src/visualBuilder/components/__test__/addInstanceButton.test.tsx

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from "preact/compat";
12
import {
23
act,
34
cleanup,
@@ -7,6 +8,21 @@ import {
78
} from "@testing-library/preact";
89
import { singleLineFieldSchema } from "../../../__test__/data/fields";
910
import AddInstanceButtonComponent from "../addInstanceButton";
11+
import visualBuilderPostMessageActual from "../../utils/visualBuilderPostMessage";
12+
import { getDiscussionIdByFieldMetaData } from "../../utils/getDiscussionIdByFieldMetaData";
13+
14+
const visualBuilderPostMessage = vi.mocked(visualBuilderPostMessageActual);
15+
16+
vi.mock("../../utils/visualBuilderPostMessage", async () => {
17+
return {
18+
default: {
19+
send: vi.fn().mockImplementation((_eventName: string) => {
20+
return Promise.resolve({});
21+
}),
22+
on: vi.fn(),
23+
},
24+
};
25+
});
1026

1127
describe("AddInstanceButtonComponent", () => {
1228
afterEach(cleanup);
@@ -18,10 +34,16 @@ describe("AddInstanceButtonComponent", () => {
1834
<AddInstanceButtonComponent
1935
value={[]}
2036
fieldSchema={singleLineFieldSchema}
37+
// @ts-expect-error mocking fieldMetadata
38+
fieldMetadata={{}}
39+
index={0}
2140
onClick={onClickCallback}
41+
label="Add instance"
42+
// @ts-expect-error mocking signal
43+
loading={{ value: false }}
2244
/>
2345
);
24-
})
46+
});
2547
const buttonElement = getByTestId(
2648
document.body,
2749
"visual-builder-add-instance-button"
@@ -33,22 +55,63 @@ describe("AddInstanceButtonComponent", () => {
3355
expect(buttonElement.querySelector("path")).toBeTruthy();
3456
});
3557

36-
test("calls onClickCallback when button is clicked", async () => {
58+
test("sends add-instance message when clicked", async () => {
3759
const onClickCallback = vi.fn();
3860
await act(() => {
3961
render(
4062
<AddInstanceButtonComponent
4163
value={[]}
4264
fieldSchema={singleLineFieldSchema}
65+
// @ts-expect-error mocking fieldMetadata
66+
fieldMetadata={{}}
67+
index={0}
4368
onClick={onClickCallback}
69+
label="Add instance"
70+
// @ts-expect-error mocking signal
71+
loading={{ value: false }}
4472
/>
4573
);
46-
})
74+
});
4775
const buttonElement = getByTestId(
4876
document.body,
4977
"visual-builder-add-instance-button"
5078
);
51-
fireEvent.click(buttonElement);
79+
await act(() => {
80+
fireEvent.click(buttonElement);
81+
});
82+
expect(visualBuilderPostMessage?.send).toHaveBeenCalledWith(
83+
"add-instance",
84+
{
85+
fieldMetadata: {},
86+
index: 0,
87+
}
88+
);
89+
});
90+
91+
test("calls onClick callback when clicked", async () => {
92+
const onClickCallback = vi.fn();
93+
await act(() => {
94+
render(
95+
<AddInstanceButtonComponent
96+
value={[]}
97+
fieldSchema={singleLineFieldSchema}
98+
// @ts-expect-error mocking fieldMetadata
99+
fieldMetadata={{}}
100+
index={0}
101+
onClick={onClickCallback}
102+
label="Add instance"
103+
// @ts-expect-error mocking signal
104+
loading={{ value: false }}
105+
/>
106+
);
107+
});
108+
const buttonElement = getByTestId(
109+
document.body,
110+
"visual-builder-add-instance-button"
111+
);
112+
await act(() => {
113+
fireEvent.click(buttonElement);
114+
});
52115
expect(onClickCallback).toHaveBeenCalled();
53116
});
54117
});

src/visualBuilder/components/addInstanceButton.tsx

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,82 @@ import classNames from "classnames";
33
import { visualBuilderStyles } from "../visualBuilder.style";
44
import { PlusIcon } from "./icons";
55
import { ISchemaFieldMap } from "../utils/types/index.types";
6+
import { CslpData } from "../../cslp/types/cslp.types";
7+
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";
8+
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types";
9+
import { Signal } from "@preact/signals";
610

711
interface AddInstanceButtonProps {
812
value: any;
913
onClick: (event: MouseEvent) => void;
1014
label?: string | undefined;
1115
fieldSchema: ISchemaFieldMap | undefined;
16+
fieldMetadata: CslpData;
17+
index: number;
18+
loading: Signal<boolean>;
1219
}
1320

1421
function AddInstanceButtonComponent(
1522
props: AddInstanceButtonProps
1623
): JSX.Element {
1724
const fieldSchema = props.fieldSchema;
18-
const disabled =
19-
fieldSchema && "max_instance" in fieldSchema && fieldSchema.max_instance
20-
? props.value.length >= fieldSchema.max_instance
21-
: false;
25+
const fieldMetadata = props.fieldMetadata;
26+
const index = props.index;
27+
const loading = props.loading;
28+
29+
const onClick = async (event: MouseEvent) => {
30+
loading.value = true;
31+
try {
32+
await visualBuilderPostMessage?.send(
33+
VisualBuilderPostMessageEvents.ADD_INSTANCE,
34+
{
35+
fieldMetadata,
36+
index,
37+
}
38+
);
39+
} catch (error) {
40+
console.error("Visual Builder: Failed to add instance", error);
41+
}
42+
loading.value = false;
43+
props.onClick(event);
44+
};
45+
46+
const buttonClassName = classNames(
47+
"visual-builder__add-button",
48+
visualBuilderStyles()["visual-builder__add-button"],
49+
{
50+
"visual-builder__add-button--with-label": props.label,
51+
},
52+
{
53+
[visualBuilderStyles()["visual-builder__add-button--loading"]]:
54+
loading.value,
55+
},
56+
visualBuilderStyles()["visual-builder__tooltip"]
57+
);
58+
59+
const maxInstances =
60+
fieldSchema && fieldSchema.data_type !== "block"
61+
? fieldSchema.max_instance
62+
: undefined;
63+
const isMaxInstances = maxInstances
64+
? props.value.length >= maxInstances
65+
: false;
66+
const disabled = loading.value || isMaxInstances;
2267

2368
return (
2469
<button
25-
className={classNames(
26-
"visual-builder__add-button",
27-
visualBuilderStyles()["visual-builder__add-button"],
28-
{
29-
"visual-builder__add-button--with-label": props.label,
30-
},
31-
visualBuilderStyles()["visual-builder__tooltip"]
32-
)}
70+
className={buttonClassName}
3371
data-tooltip={"Add section"}
3472
data-testid="visual-builder-add-instance-button"
3573
disabled={disabled}
3674
title={
37-
disabled && fieldSchema && "max_instance" in fieldSchema
38-
? `Max ${fieldSchema.max_instance} instances allowed`
75+
maxInstances && isMaxInstances
76+
? `Max ${maxInstances} instances allowed`
3977
: undefined
4078
}
4179
onClick={(e) => {
4280
const event = e as unknown as MouseEvent;
43-
props.onClick(event);
81+
onClick(event);
4482
}}
4583
>
4684
<PlusIcon />

src/visualBuilder/utils/__test__/instanceButtons.test.ts renamed to src/visualBuilder/generators/__test__/generateAddInstanceButtons.test.tsx

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,67 @@
1+
import React from "preact/compat";
12
import { singleLineFieldSchema } from "../../../__test__/data/fields";
2-
import { ISchemaFieldMap } from "../types/index.types";
33
import {
44
getAddInstanceButtons,
55
generateAddInstanceButton,
6-
} from "./../../generators/generateAddInstanceButtons";
6+
} from "../generateAddInstanceButtons";
7+
import AddInstanceButtonComponentActual from "../../components/addInstanceButton";
8+
9+
const AddInstanceButtonComponent = vi.mocked(AddInstanceButtonComponentActual);
10+
11+
vi.mock("../../components/addInstanceButton", async () => {
12+
return {
13+
default: vi.fn().mockImplementation(() => {
14+
return (
15+
<button data-testid="add-instance-button">
16+
Add instance button
17+
</button>
18+
);
19+
}),
20+
};
21+
});
722

823
describe("generateAddInstanceButton", () => {
9-
test("should generate a button", () => {
24+
afterEach(() => {
25+
vi.clearAllMocks();
26+
});
27+
28+
test("should generate and return a button", () => {
1029
const button = generateAddInstanceButton({
1130
fieldSchema: singleLineFieldSchema,
1231
value: "",
13-
onClick: () => {},
32+
// @ts-expect-error mock field metadata
33+
fieldMetadata: { hello: "world" },
34+
onClick: vi.fn(),
35+
// @ts-expect-error mocking preact signal
36+
loading: { value: false },
37+
index: 0,
38+
label: "Add Instance",
1439
});
1540
expect(button).toBeInstanceOf(HTMLButtonElement);
1641
});
1742

18-
test("should call the callback when clicked", () => {
19-
const callback = vi.fn();
20-
const button = generateAddInstanceButton({
43+
test("should call the AddInstanceButtonComponent with the correct props", () => {
44+
generateAddInstanceButton({
45+
fieldSchema: singleLineFieldSchema,
46+
value: "",
47+
// @ts-expect-error mock field metadata
48+
fieldMetadata: { hello: "world" },
49+
onClick: vi.fn(),
50+
// @ts-expect-error mocking preact signal
51+
loading: { value: false },
52+
index: 0,
53+
label: "Add Instance",
54+
});
55+
const args = AddInstanceButtonComponent.mock.calls[0][0];
56+
expect(args).toStrictEqual({
2157
fieldSchema: singleLineFieldSchema,
2258
value: "",
23-
onClick: callback,
59+
fieldMetadata: { hello: "world" },
60+
onClick: expect.any(Function),
61+
loading: { value: false },
62+
index: 0,
63+
label: "Add Instance",
2464
});
25-
button.click();
26-
expect(callback).toHaveBeenCalledTimes(1);
2765
});
2866
});
2967

src/visualBuilder/generators/generateAddInstanceButtons.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,44 @@
1+
import React from "preact/compat";
12
import { render } from "preact";
23
import AddInstanceButtonComponent from "../components/addInstanceButton";
34
import { ISchemaFieldMap } from "../utils/types/index.types";
5+
import { CslpData } from "../../cslp/types/cslp.types";
6+
import { Signal } from "@preact/signals";
47

58
/**
6-
* Generates a button element, when clicked, triggers the provided callback function.
9+
* Generates a button element, when clicked, sends the add instance message and
10+
* then calls the provided callback function.
711
* @param onClickCallback - The function to be called when the button is clicked.
812
* @returns The generated button element.
913
*/
1014
export function generateAddInstanceButton({
11-
fieldSchema,
1215
value,
16+
fieldSchema,
17+
fieldMetadata,
18+
index,
19+
loading,
1320
onClick,
1421
label,
1522
}: {
23+
fieldSchema: ISchemaFieldMap | undefined;
1624
value: any;
25+
fieldMetadata: CslpData;
26+
index: number;
27+
loading: Signal<boolean>;
1728
onClick: (event: MouseEvent) => void;
1829
label?: string | undefined;
19-
fieldSchema: ISchemaFieldMap | undefined;
2030
}): HTMLButtonElement {
2131
const wrapper = document.createDocumentFragment();
2232

2333
render(
2434
<AddInstanceButtonComponent
35+
loading={loading}
36+
index={index}
2537
value={value}
2638
label={label}
2739
onClick={onClick}
2840
fieldSchema={fieldSchema}
41+
fieldMetadata={fieldMetadata}
2942
/>,
3043
wrapper
3144
);

0 commit comments

Comments
 (0)