import React from "react";
import { render, screen, waitFor, fireEvent } from "test/testUtils";
import DropDownControl from "./DropDownControl";
import { reduxForm } from "redux-form";
import "@testing-library/jest-dom";
import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
import type { SelectOptionProps } from "@appsmith/ads";
import type { ReduxAction } from "actions/ReduxActionTypes";
import { PluginType } from "entities/Plugin";
const mockStore = configureStore([]);
const initialValues = {
actionConfiguration: { testPath: ["option1", "option2"] },
};
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function TestForm(props: any) {
return
{props.children}
;
}
const ReduxFormDecorator = reduxForm({ form: "TestForm", initialValues })(
TestForm,
);
const mockOptions = [
{ label: "Option 1", value: "option1", children: "Option 1" },
{ label: "Option 2", value: "option2", children: "Option 2" },
{ label: "Option 3", value: "option3", children: "Option 3" },
];
const mockAction = {
type: "API_ACTION",
name: "Test API Action",
datasource: { id: "datasource1", name: "Datasource 1" },
actionConfiguration: {
body: "",
headers: [],
testPath: ["option1", "option2"],
},
};
const dropDownProps = {
options: mockOptions,
placeholderText: "Select Columns",
isMultiSelect: true,
configProperty: "actionConfiguration.testPath",
controlType: "PROJECTION",
propertyValue: "",
label: "Columns",
id: "column",
formName: "",
isValid: true,
formValues: mockAction,
isLoading: false,
maxTagCount: 3,
};
const EXTERNAL_SAAS_PLUGIN_ID = "external-saas-pluginid";
describe("DropDownControl", () => {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let store: any;
beforeEach(() => {
store = mockStore({
form: { TestForm: { values: initialValues } },
appState: {},
});
});
afterEach(() => {
jest.clearAllMocks();
});
it("should renders dropdownControl and options properly", async () => {
render(
,
);
const dropdownSelect = await waitFor(async () =>
screen.findByTestId("t--dropdown-actionConfiguration.testPath"),
);
expect(dropdownSelect).toBeInTheDocument();
const options = screen.getAllByText(/Optio.../);
const optionCount = options.length;
expect(optionCount).toBe(2);
});
it("should clear all selected options", async () => {
render(
,
);
const clearAllButton = document.querySelector(".rc-select-clear");
expect(clearAllButton).toBeInTheDocument();
fireEvent.click(clearAllButton!);
await waitFor(() => {
const options = screen.queryAllByText(/Option.../);
expect(options.length).toBe(0);
});
});
it("should handle single select mode correctly", async () => {
const singleSelectProps = {
...dropDownProps,
isMultiSelect: false,
configProperty: "actionConfiguration.singlePath",
inputIcon: undefined,
showArrow: undefined,
};
render(
,
);
// Wait for component to be fully rendered
const dropdownSelect = await screen.findByTestId(
"t--dropdown-actionConfiguration.singlePath",
);
// Open dropdown
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Wait for dropdown to be visible
await waitFor(() => {
expect(screen.getByRole("listbox")).toBeVisible();
});
// Select Option 1
const option = screen.getByRole("option", { name: "Option 1" });
fireEvent.click(option);
// Wait for selection to be applied and verify
await waitFor(() => {
// Check if the selection is displayed
const selectedItem = screen.getByText("Option 1", {
selector: ".rc-select-selection-item",
});
expect(selectedItem).toBeInTheDocument();
});
// Check if the option is marked as selected
const selectedOption = screen.getByRole("option", { name: "Option 1" });
expect(selectedOption).toHaveClass("rc-select-item-option-selected");
// Check that other options are not selected
const options = screen.getAllByRole("option");
const selectedCount = options.filter((opt: SelectOptionProps) =>
opt.classList.contains("rc-select-item-option-selected"),
).length;
expect(selectedCount).toBe(1);
});
it("should handle multi-select mode correctly", async () => {
render(
,
);
const dropdownSelect = await screen.findByTestId(
"t--dropdown-actionConfiguration.testPath",
);
// Open dropdown
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
await waitFor(() => {
expect(screen.getByRole("listbox")).toBeVisible();
});
// Verify initial selections (Option 1 and Option 2 are selected from initialValues)
const initialSelectedOptions = screen
.getAllByRole("option")
.filter((opt) => opt.getAttribute("aria-selected") === "true");
expect(initialSelectedOptions).toHaveLength(2);
});
it("should show placeholder if no option is selected", async () => {
const updatedProps = { ...dropDownProps, options: [] };
render(
,
);
expect(screen.getByText("Select Columns")).toBeInTheDocument();
});
});
describe("DropDownControl grouping tests", () => {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let store: any;
const formName = "GroupingTestForm";
beforeEach(() => {
store = mockStore({
form: {
[formName]: {
values: { actionConfiguration: { testPath: { data: "" } } },
},
},
});
});
it("should render grouped options correctly", async () => {
const mockOptionGroupConfig = {
group1: { label: "Group 1", children: [] },
group2: { label: "Group 2", children: [] },
};
const mockOptions = [
{
label: "Option 1",
value: "1",
optionGroupType: "group1",
children: [],
},
{
label: "Option 2",
value: "2",
children: [],
// No group - should go to Others
},
{
label: "Option 3",
value: "3",
optionGroupType: "group2",
children: [],
},
];
const props = {
...dropDownProps,
options: mockOptions,
optionGroupConfig: mockOptionGroupConfig,
};
render(
,
);
// 1. Grab the dropdown container
const dropdownSelect = await waitFor(async () =>
screen.findByTestId("t--dropdown-actionConfiguration.testPath"),
);
// Open dropdown
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Verify group headers are present
await waitFor(() => {
expect(screen.getByText("Group 1")).toBeInTheDocument();
});
expect(screen.getByText("Group 2")).toBeInTheDocument();
expect(screen.getByText("Others")).toBeInTheDocument();
// Verify options are in correct groups
const group1Option = screen.getByText("Option 1");
const group2Option = screen.getByText("Option 3");
const othersOption = screen.getByText("Option 2");
expect(group1Option).toBeInTheDocument();
expect(group2Option).toBeInTheDocument();
expect(othersOption).toBeInTheDocument();
});
it("should append group identifiers to values when appendGroupIdentifierToValue is true", async () => {
const mockOptionGroupConfig = {
group1: { label: "Group 1", children: [] },
group2: { label: "Group 2", children: [] },
};
const mockOptions = [
{
label: "Option 1",
value: "1",
optionGroupType: "group1",
children: [],
},
{
label: "Option 2",
value: "2",
children: [],
},
{
label: "Option 3",
value: "3",
optionGroupType: "group2",
children: [],
},
];
const props = {
...dropDownProps,
options: mockOptions,
optionGroupConfig: mockOptionGroupConfig,
appendGroupIdentifierToValue: true,
isMultiSelect: false,
};
render(
,
);
// Open dropdown
const dropdownSelect = await waitFor(async () =>
screen.findByTestId("t--dropdown-actionConfiguration.testPath"),
);
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Select Option 1 (from group1)
const option1 = screen.getByText("Option 1");
fireEvent.click(option1);
const actions = store.getActions();
expect(actions[actions.length - 1].payload).toEqual("group1:1");
// // Select Option 2 (from others group)
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
const option2 = screen.getByText("Option 2");
fireEvent.click(option2);
expect(actions[actions.length - 1].payload).toEqual("others:2");
});
it("should handle multi-select with group identifiers correctly", async () => {
const mockOptionGroupConfig = {
group1: { label: "Group 1", children: [] },
group2: { label: "Group 2", children: [] },
};
const mockOptions = [
{
label: "Option 1",
value: "1",
optionGroupType: "group1",
children: [],
},
{
label: "Option 2",
value: "2",
children: [],
},
{
label: "Option 3",
value: "3",
optionGroupType: "group2",
children: [],
},
];
const props = {
...dropDownProps,
options: mockOptions,
optionGroupConfig: mockOptionGroupConfig,
appendGroupIdentifierToValue: true,
isMultiSelect: true,
};
render(
,
);
// Open dropdown
const dropdownSelect = await waitFor(async () =>
screen.findByTestId("t--dropdown-actionConfiguration.testPath"),
);
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Select multiple options
const option1 = screen.getByText("Option 1");
const option2 = screen.getByText("Option 2");
fireEvent.click(option1);
fireEvent.click(option2);
// Verify the stored values include group identifiers
let actions = store.getActions();
// store the values of last 2 actions in a variable
const lastTwoActions = actions
.slice(-2)
.map((action: ReduxAction) => action.payload);
expect(lastTwoActions).toEqual([["group1:1"], ["others:2"]]);
// Test removal of an option
const selectedOption1 = screen.getByText("Option 1");
fireEvent.click(selectedOption1);
actions = store.getActions();
// Verify the option was removed correctly
expect(actions[actions.length - 1].payload).toEqual(["group1:1"]);
});
it("should handle edge cases with appendGroupIdentifierToValue", async () => {
const mockOptionGroupConfig = {
group1: { label: "Group 1", children: [] },
};
const mockOptions = [
{
label: "Option with colon",
value: "value:with:colon",
optionGroupType: "group1",
children: [],
},
{
label: "Option without group",
value: "no_group_value",
children: [],
},
];
const props = {
...dropDownProps,
options: mockOptions,
optionGroupConfig: mockOptionGroupConfig,
appendGroupIdentifierToValue: true,
isMultiSelect: true,
};
render(
,
);
// Open dropdown
const dropdownSelect = await waitFor(async () =>
screen.findByTestId("t--dropdown-actionConfiguration.testPath"),
);
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Select both options
const option1 = screen.getByText("Option with colon");
const option2 = screen.getByText("Option without group");
fireEvent.click(option1);
fireEvent.click(option2);
// Verify the stored values handle special cases correctly
let actions = store.getActions();
const lastTwoActions = actions
.slice(-2)
.map((action: ReduxAction) => action.payload);
expect(lastTwoActions).toEqual([
["group1:value:with:colon"],
["others:no_group_value"],
]);
// Test removal of an option
const selectedOption1 = screen.getByText("Option with colon");
fireEvent.click(selectedOption1);
actions = store.getActions();
expect(actions[actions.length - 1].payload).toEqual([
"group1:value:with:colon",
]);
});
});
describe("DropdownControl Single select tests", () => {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let store: any;
const initialValuesSingleSelect = {
actionConfiguration: {
testPath: "option1",
},
pluginId: EXTERNAL_SAAS_PLUGIN_ID,
};
const mockActionSingleSelect = {
type: "API_ACTION",
name: "Test API Action",
datasource: {
id: "datasource1",
name: "Datasource 1",
},
actionConfiguration: {
body: "",
headers: [],
testPath: "option1",
},
};
const dropDownPropsSingleSelect = {
options: mockOptions,
placeholderText: "Select Columns",
configProperty: "actionConfiguration.testPath",
controlType: "PROJECTION",
propertyValue: "",
label: "Columns",
id: "column",
formName: "",
isValid: true,
formValues: mockActionSingleSelect,
isLoading: false,
maxTagCount: 3,
isAllowClear: true,
isSearchable: true,
};
beforeEach(() => {
store = mockStore({
form: {
TestForm: {
values: initialValuesSingleSelect,
},
},
entities: {
plugins: {
list: [
{
id: EXTERNAL_SAAS_PLUGIN_ID,
type: PluginType.EXTERNAL_SAAS,
},
],
},
},
appState: {},
});
});
it("should clear selected option", async () => {
render(
,
);
const clearAllButton = document.querySelector(".rc-select-clear");
expect(clearAllButton).toBeInTheDocument();
fireEvent.click(clearAllButton!);
await waitFor(() => {
const options = screen.queryAllByText(/Option.../);
expect(options.length).toBe(0);
});
});
it("should filter options when searching", async () => {
render(
,
);
// Find and click the dropdown to open it
const dropdownSelect = await screen.findByTestId(
"t--dropdown-actionConfiguration.testPath",
);
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Find the search input and type "Option 2"
const searchInput = screen.getByPlaceholderText("Type to search...");
fireEvent.change(searchInput, { target: { value: "Option 2" } });
// Get all visible options
const visibleOptions = screen.getAllByRole("option");
// // Should only show one option
expect(visibleOptions).toHaveLength(1);
// The visible option should be "Option 1"
expect(visibleOptions[0]).toHaveTextContent("Option 2");
});
it("should have no command found ui for custom action for external saas plugin", async () => {
const externalSaasDropdownprops = {
...dropDownPropsSingleSelect,
configProperty: "actionConfiguration.command",
options: [
...mockOptions,
{
label: "Custom Action",
value: "custom action",
children: "Custom Action",
},
],
formName: "TestForm",
};
render(
,
);
// Find and click the dropdown to open it
const dropdownSelect = await screen.findByTestId(
"t--dropdown-actionConfiguration.command",
);
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Find the search input and type "Option 2"
const searchInput = screen.getByPlaceholderText("Type to search...");
fireEvent.change(searchInput, { target: { value: "Test" } });
expect(screen.getByTestId("t--select-custom--action")).toBeInTheDocument();
});
it("should have not found ui dropdown with no command property and no custom action present", async () => {
render(
,
);
// Find and click the dropdown to open it
const dropdownSelect = await screen.findByTestId(
"t--dropdown-actionConfiguration.testPath",
);
fireEvent.mouseDown(dropdownSelect.querySelector(".rc-select-selector")!);
// Find the search input and type "Option 2"
const searchInput = screen.getByPlaceholderText("Type to search...");
fireEvent.change(searchInput, { target: { value: "Test" } });
expect(screen.getByText("Not found")).toBeInTheDocument();
});
});