From 9b0d58d318e27eb456613a6d1843edb60fba19a0 Mon Sep 17 00:00:00 2001
From: Amal K Joy <153802538+amal-k-joy@users.noreply.github.com>
Date: Wed, 17 Jul 2024 18:56:27 +0530
Subject: [PATCH] feat(ConditionBuilder): adding test cases and issue fixes
(#5685)
* feat(ConditionBuilder): adding testcases and issue fixes
* feat(ConditionBuilder): adding testcases for tree variant
---
.../ConditionBuilder.stories.jsx | 2 +-
.../ConditionBuilder/ConditionBuilder.test.js | 1227 ++++++++++++++++-
.../ConditionBuilderContent.js | 106 +-
.../translationObject.js | 1 +
.../ConditionGroupBuilder.js | 22 +-
.../ConditionBuilder/assets/sampleInput.js | 82 +-
.../utils/handleKeyboardEvents.js | 5 +-
.../components/ConditionBuilder/utils/util.js | 14 -
8 files changed, 1314 insertions(+), 145 deletions(-)
diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx
index 88ac2fdacc..43eb5e3144 100644
--- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx
+++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.stories.jsx
@@ -181,7 +181,7 @@ const getOptions = async (conditionState, { property }) => {
}
};
const requiredProps = {
- startConditionLabel: 'Add Condition',
+ startConditionLabel: 'Add condition',
popOverSearchThreshold: 4,
getConditionState: (rootState) => {
console.log(rootState);
diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js
index c1bf302bec..1a1c9de579 100644
--- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js
+++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilder.test.js
@@ -5,14 +5,22 @@
* LICENSE file in the root directory of this source tree.
*/
-import React from 'react';
-import { render, screen } from '@testing-library/react'; // https://testing-library.com/docs/react-testing-library/intro
+import React, { act } from 'react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react'; // https://testing-library.com/docs/react-testing-library/intro
import { pkg } from '../../settings';
import uuidv4 from '../../global/js/utils/uuidv4';
+import { Earth } from '@carbon/react/icons';
import { ConditionBuilder } from '.';
import cx from 'classnames';
+import userEvent from '@testing-library/user-event';
+
+import { inputData, inputDataDynamicOptions } from './assets/sampleInput';
+import {
+ sampleDataStructure_sentence,
+ // sampleDataStructure_tree,
+} from './assets/SampleData';
const blockClass = `${pkg.prefix}--condition-builder`;
const componentName = ConditionBuilder.displayName;
@@ -22,68 +30,1223 @@ const className = `class-${uuidv4()}`;
const dataTestId = uuidv4();
const defaultProps = {
inputConfig: {},
- startConditionLabel: 'Add Condition',
+ startConditionLabel: 'Add condition',
popOverSearchThreshold: 4,
getConditionState: () => {},
variant: 'sentence',
};
+const inputConfigOptionType = {
+ properties: [
+ {
+ id: 'continent',
+ label: 'Continent',
+ icon: Earth,
+ type: 'option',
+ config: {
+ options: [
+ {
+ label: 'Africa',
+ id: 'Africa',
+ },
+ {
+ label: 'Antarctica',
+ id: 'Antarctica',
+ },
+ ],
+ },
+ },
+ ],
+};
+
+const getContinents = () => {
+ return [
+ {
+ label: 'Africa',
+ id: 'Africa',
+ },
+ {
+ label: 'Antarctica',
+ id: 'Antarctica',
+ },
+ {
+ label: 'Asia',
+ id: 'Asia',
+ },
+ {
+ label: 'Australia',
+ id: 'Australia',
+ },
+ {
+ label: 'Europe',
+ id: 'Europe',
+ },
+ ];
+};
+
+const getOptions = async (conditionState, { property }) => {
+ switch (property) {
+ case 'continent':
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(getContinents());
+ }, 2000);
+ });
+
+ default:
+ return [];
+ }
+};
+
describe(componentName, () => {
it('renders a component ConditionBuilder', async () => {
- render( );
+ render();
expect(screen.getByRole('main')).toHaveClass(cx(blockClass));
});
it('has no accessibility violations', async () => {
- const { container } = render(
-
- );
+ const { container } = render();
expect(container).toBeAccessible(componentName);
expect(container).toHaveNoAxeViolations();
});
- // it(`renders children`, async () => {
- // render({children});
- // screen.getByText(children);
- // });
-
it('applies className to the containing node', async () => {
- render(
-
- {' '}
-
- );
+ render();
expect(screen.getByRole('main')).toHaveClass(className);
});
it('adds additional props to the containing node', async () => {
- render(
-
- {' '}
-
- );
+ render();
screen.getByTestId(dataTestId);
});
it('forwards a ref to an appropriate node', async () => {
const ref = React.createRef();
- render(
-
- {' '}
-
- );
+ render();
expect(ref.current).toHaveClass(blockClass);
});
it('adds the Devtools attribute to the containing node', async () => {
- render(
-
- {' '}
-
- );
+ render();
expect(screen.getByTestId(dataTestId)).toHaveDevtoolsAttribute(
componentName
);
});
+
+ //test cases for sentence variant
+ it('should render the component with provided label to start condition builder', async () => {
+ const startConditionLabel = 'Add condition';
+ render(
+
+ );
+
+ expect(screen.getByText(startConditionLabel));
+ });
+
+ it('render the component with input type as single select option', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getByRole('option', { name: 'Continent' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Continent' }))
+ );
+
+ expect(screen.getByRole('option', { name: 'is' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'is' }))
+ );
+
+ expect(screen.getByRole('option', { name: 'Africa' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Africa' }))
+ );
+
+ const selectedItem = screen.getByRole('button', { name: 'Africa' });
+
+ expect(selectedItem);
+ });
+
+ it('render the component with input type as multiselect option', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getByRole('option', { name: 'Continent' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Continent' }))
+ );
+
+ expect(screen.getByRole('option', { name: 'is one of' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'is one of' }))
+ );
+
+ //selection option 1
+ expect(screen.getByRole('option', { name: 'Africa' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Africa' }))
+ );
+
+ //selection option 2
+
+ expect(screen.getByRole('option', { name: 'Antarctica' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Antarctica' }))
+ );
+
+ //selecting and deselecting option 3
+ expect(screen.getByRole('option', { name: 'Asia' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Asia' }))
+ );
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Asia' }))
+ );
+
+ //clicking outside
+ const container = document.querySelector(`.${blockClass}`);
+ await act(() => userEvent.click(container));
+
+ const selectedItem = screen.getByRole('button', {
+ name: 'Africa, Antarctica',
+ });
+ expect(selectedItem);
+ });
+
+ it('checking select/deselect all functionality for the input type option with multiselect', async () => {
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getByRole('option', { name: 'Continent' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'Continent' }))
+ );
+
+ expect(screen.getByRole('option', { name: 'is one of' }));
+
+ await act(() =>
+ userEvent.click(screen.getByRole('option', { name: 'is one of' }))
+ );
+
+ //selecting all
+ const selectAllButton = screen.getByRole('button', {
+ name: 'Select all',
+ });
+
+ await act(() => userEvent.click(selectAllButton));
+
+ const selectedItems = screen.getByRole('button', {
+ name: 'Africa, Antarctica',
+ });
+ expect(selectedItems);
+
+ //de-selecting all
+ const deSelectAllButton = screen.getByRole('button', {
+ name: 'Deselect all',
+ });
+
+ await act(() => userEvent.click(deSelectAllButton));
+
+ //selecting one
+ expect(screen.getByText('Antarctica'));
+
+ await act(() => userEvent.click(screen.getByText('Antarctica')));
+
+ //clicking outside
+ const container = document.querySelector(`.${blockClass}`);
+ await act(() => userEvent.click(container));
+
+ const selectedItem = screen.getByRole('button', {
+ name: 'Antarctica',
+ });
+ expect(selectedItem);
+ });
+
+ it('check search feature is functioning in popover', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getByText('Continent'));
+
+ await act(() => userEvent.click(screen.getByText('Continent')));
+
+ expect(screen.getByText('is one of'));
+
+ await act(() => userEvent.click(screen.getByText('is one of')));
+
+ const searchInput = screen.getByRole('searchbox');
+ expect(searchInput);
+
+ fireEvent.change(searchInput, { target: { value: 'Antarctica' } });
+
+ expect(screen.getByText('Antarctica')).toBeInTheDocument();
+ });
+
+ it('checking Add condition and close condition functionality', async () => {
+ render(
+
+ );
+ //start builder
+ fireEvent.click(screen.getByText('Add condition'));
+
+ //add first condition
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Continent',
+ })
+ )
+ );
+
+ fireEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ );
+
+ fireEvent.click(
+ screen.getByRole('option', {
+ name: 'Africa',
+ })
+ );
+
+ const addButton = document.querySelector(`.${blockClass}__add-button`);
+ expect(addButton);
+ await act(() => userEvent.click(addButton));
+
+ //add second condition
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Continent',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() => userEvent.click(screen.getByText('Antarctica')));
+
+ const selectedItem2 = screen.getByRole('button', { name: 'Antarctica' });
+
+ expect(selectedItem2);
+
+ const firstCloseButton = document.querySelector(
+ `.${blockClass}__close-condition`
+ );
+ expect(firstCloseButton);
+ fireEvent.click(firstCloseButton);
+
+ expect(screen.queryByText('Africa')).not.toBeInTheDocument();
+ });
+
+ it('changing condition connector and all connectors within a group should be synchronize', async () => {
+ render(
+
+ );
+ //start builder
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ await act(() =>
+ userEvent.click(screen.getAllByRole('button', { name: 'and' })[0])
+ );
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'or',
+ })
+ )
+ );
+
+ expect(screen.queryByText('and')).not.toBeInTheDocument();
+ });
+
+ it('render the component with input type text', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'ID',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ const inputText = document.querySelector('#id');
+ fireEvent.change(inputText, { target: { value: 'testID123' } });
+
+ const container = document.querySelector(`.${blockClass}`);
+ await act(() => userEvent.click(container));
+
+ const selectedItem = screen.getByRole('button', { name: 'testID123' });
+
+ expect(selectedItem);
+ });
+
+ it('render the component with input type textarea', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Id Long',
+ })
+ )
+ );
+
+ const isOperator = screen.getByRole('option', {
+ name: 'is',
+ });
+ await act(() => userEvent.click(isOperator));
+
+ const inputText = document.querySelector('#id_long');
+ fireEvent.change(inputText, { target: { value: 'testID123' } });
+
+ const container = document.querySelector(`.${blockClass}`);
+ await act(() => userEvent.click(container));
+
+ const selectedItem = screen.getByRole('button', { name: 'testID123' });
+
+ expect(selectedItem);
+ });
+
+ it('render the component with input type number', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Price',
+ })
+ )
+ );
+
+ const isOperator = screen.getByRole('option', {
+ name: 'is',
+ });
+ await act(() => userEvent.click(isOperator));
+
+ let inputText = document.querySelector('#price');
+ fireEvent.change(inputText, { target: { value: '123' } });
+
+ const container = document.querySelector(`.${blockClass}`);
+ await act(() => userEvent.click(container));
+
+ const selectedItem = screen.getByRole('button', { name: '123 Dollars' });
+
+ expect(selectedItem);
+
+ await act(() => userEvent.click(selectedItem));
+ inputText = document.querySelector('#price');
+ fireEvent.change(inputText, { target: { value: '-123' } });
+
+ await act(() => userEvent.click(container));
+
+ expect(screen.getByRole('button', { name: 'Incomplete' }));
+ });
+
+ it('render the component with input type date', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Date',
+ })
+ )
+ );
+
+ const isOperator = screen.getByRole('option', {
+ name: 'is',
+ });
+ await act(() => userEvent.click(isOperator));
+
+ const inputElement = document.querySelector('#datePicker');
+ await act(() => userEvent.type(inputElement, '12/06/2024{enter}'));
+
+ await act(() => userEvent.keyboard('{escape}'));
+
+ const selectedItem = screen.getByRole('button', { name: '12/06/2024' });
+
+ expect(selectedItem);
+ });
+
+ it('render the component with input type time', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Time',
+ })
+ )
+ );
+
+ const isOperator = screen.getByRole('option', {
+ name: 'is',
+ });
+ await act(() => userEvent.click(isOperator));
+
+ const timeElement = document.querySelector('#time-picker');
+ await act(() => userEvent.type(timeElement, '12:30'));
+
+ const dayZoneElement = document.querySelector('#time-picker-day-zone');
+ await act(() =>
+ fireEvent.change(dayZoneElement, { target: { value: 'PM' } })
+ );
+ expect(dayZoneElement).toHaveValue('PM');
+
+ const timeZoneElement = document.querySelector('#time-picker-time-zone');
+ await act(() =>
+ fireEvent.change(timeZoneElement, { target: { value: 'UTC' } })
+ );
+ expect(timeZoneElement).toHaveValue('UTC');
+
+ await act(() => userEvent.keyboard('{escape}'));
+
+ const selectedItem = screen.getByRole('button', { name: '12:30 PM UTC' });
+
+ expect(selectedItem);
+ });
+
+ it('fetch options dynamically', async () => {
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getByText('Continent'));
+
+ await act(() => userEvent.click(screen.getByText('Continent')));
+
+ expect(screen.getByText('is'));
+
+ await act(() => userEvent.click(screen.getByText('is')));
+
+ //fetching the options dynamically and it will be resolved after 2 seconds
+
+ await waitFor(() => screen.getByText('Africa'), { timeout: 2500 });
+
+ await act(() => userEvent.click(screen.getByText('Africa')));
+
+ const selectedItem = screen.getByRole('button', { name: 'Africa' });
+
+ expect(selectedItem);
+ });
+
+ it('check translation are working as expected', async () => {
+ const translateWithId = (key) => {
+ const translationsObject = {
+ conditionHeadingText: 'Condition Heading',
+ };
+
+ return translationsObject[key];
+ };
+
+ render(
+
+ );
+ //start builder
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getByText('Condition Heading'));
+ });
+
+ //test cases for tree variant
+ it('render the tree variant with 3 conditions and 1 subgroup', async () => {
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ //adding condition 1
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Continent',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() => userEvent.click(screen.getByText('Africa')));
+
+ //adding condition 2
+
+ let addButton = document.querySelector(`.${blockClass}__add-button`);
+ expect(addButton);
+ await act(() => userEvent.click(addButton));
+
+ const regionOption = screen.getByRole('option', {
+ name: 'Region',
+ });
+ await act(() => userEvent.click(regionOption));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'India',
+ })
+ )
+ );
+
+ //adding a subgroup
+
+ let addSubGroupButton = document.querySelector(
+ `.${blockClass}__add-condition-sub-group`
+ );
+ expect(addSubGroupButton);
+ await act(() => userEvent.click(addSubGroupButton));
+
+ //add third condition
+
+ const colorOption = screen.getByRole('option', {
+ name: 'Color',
+ });
+ await act(() => userEvent.click(colorOption));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'black',
+ })
+ )
+ );
+ const subGroups = screen.getAllByText('if');
+ expect(subGroups).toHaveLength(2);
+ });
+
+ it('render the tree variant with 2 groups', async () => {
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+ //group 1
+ //adding condition 1
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Continent',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() => userEvent.click(screen.getByText('Africa')));
+
+ //adding condition 2
+
+ let addButton = document.querySelector(`.${blockClass}__add-button`);
+ expect(addButton);
+ await act(() => userEvent.click(addButton));
+
+ const regionOption = screen.getByRole('option', {
+ name: 'Region',
+ });
+ await act(() => userEvent.click(regionOption));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'India',
+ })
+ )
+ );
+
+ //adding a subgroup
+
+ let addSubGroupButton = document.querySelector(
+ `.${blockClass}__add-condition-sub-group`
+ );
+ expect(addSubGroupButton);
+ await act(() => userEvent.click(addSubGroupButton));
+
+ //add third condition
+
+ const colorOption = screen.getByRole('option', {
+ name: 'Color',
+ });
+ await act(() => userEvent.click(colorOption));
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'black',
+ })
+ )
+ );
+ const subGroups = screen.getAllByText('if');
+ expect(subGroups).toHaveLength(2);
+
+ //group 2
+
+ const addGroupButton = document.querySelector(
+ `.${blockClass}__add-condition-group`
+ );
+ expect(addGroupButton);
+ await act(() => userEvent.click(addGroupButton));
+ //adding condition 1
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Continent',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Africa',
+ })
+ )
+ );
+
+ const ifStatements = screen.getAllByRole('button', { name: 'if' });
+ expect(ifStatements).toHaveLength(3);
+
+ const groupConnector = screen.getAllByRole('button', { name: 'or' });
+ expect(groupConnector).toHaveLength(1);
+ });
+
+ it('check the next/previous close button is focussed on remove condition', async () => {
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ let closeButtons = document.querySelectorAll(
+ `.${blockClass}__close-condition`
+ );
+ expect(closeButtons).toHaveLength(3);
+ //click first close button
+ await act(() => userEvent.click(closeButtons[0]));
+
+ closeButtons = document.querySelectorAll(`.${blockClass}__close-condition`);
+ expect(closeButtons).toHaveLength(2);
+
+ expect(closeButtons[0]).toHaveFocus();
+
+ //click last close button
+
+ await act(() => userEvent.click(closeButtons[1]));
+ closeButtons = document.querySelectorAll(`.${blockClass}__close-condition`);
+ expect(closeButtons).toHaveLength(1);
+ expect(closeButtons[0]).toHaveFocus();
+ });
+
+ it('check the next/previous close button is focussed on remove condition for tree variant', async () => {
+ const sampleDataStructure = {
+ operator: 'or',
+ groups: [
+ {
+ groupOperator: 'and', //'and|or',
+ statement: 'if', // 'if|exclude if',
+ id: uuidv4(),
+ conditions: [
+ {
+ property: 'region',
+ operator: 'is',
+ value: 'IL',
+ id: uuidv4(),
+ },
+ {
+ property: 'delivery',
+ operator: 'is',
+ value: 'processing',
+ id: uuidv4(),
+ },
+ {
+ property: 'delivery',
+ operator: 'is',
+ value: 'processing',
+ id: uuidv4(),
+ },
+ {
+ property: 'delivery',
+ operator: 'is',
+ value: 'processing',
+ id: uuidv4(),
+ },
+ {
+ groupOperator: 'and',
+ statement: 'if',
+ id: uuidv4(),
+ conditions: [
+ {
+ property: 'region',
+ operator: 'is',
+ value: 'IL',
+ id: uuidv4(),
+ },
+ {
+ property: 'delivery',
+ operator: 'is',
+ value: 'processing',
+ id: uuidv4(),
+ },
+ {
+ property: 'delivery',
+ operator: 'is',
+ value: 'processing',
+ id: uuidv4(),
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ let closeButtons = document.querySelectorAll(
+ `.${blockClass}__close-condition`
+ );
+ expect(closeButtons).toHaveLength(7);
+ //click first close button
+ await act(() => userEvent.click(closeButtons[0]));
+
+ closeButtons = document.querySelectorAll(`.${blockClass}__close-condition`);
+ expect(closeButtons).toHaveLength(6);
+
+ expect(closeButtons[0]).toHaveFocus();
+
+ //click 4th(first condition in first subgroup) close button
+
+ await act(() => userEvent.click(closeButtons[3]));
+ closeButtons = document.querySelectorAll(`.${blockClass}__close-condition`);
+ expect(closeButtons).toHaveLength(5);
+ expect(closeButtons[3]).toHaveFocus();
+
+ //close all conditions of the subgroup
+
+ await act(() => userEvent.click(closeButtons[4]));
+ closeButtons = document.querySelectorAll(`.${blockClass}__close-condition`);
+ await act(() => userEvent.click(closeButtons[3]));
+ closeButtons = document.querySelectorAll(`.${blockClass}__close-condition`);
+ expect(closeButtons).toHaveLength(3);
+
+ //when all conditions of a subgroup is closed , it will focus the previous row
+ const row = document.querySelectorAll(
+ '[role="row"][aria-level="2"][aria-posinset="3"]'
+ );
+ expect(row).toHaveLength(1);
+ expect(row[0]).toHaveFocus();
+ });
+
+ it('check the add/remove actions ', async () => {
+ const sampleDataStructure = {
+ operator: 'or',
+ groups: [
+ {
+ groupOperator: 'and', //'and|or',
+ statement: 'if', // 'if|exclude if',
+ id: uuidv4(),
+ conditions: [
+ {
+ property: 'region',
+ operator: 'is',
+ value: 'IL',
+ id: uuidv4(),
+ },
+ ],
+ },
+ ],
+ };
+
+ const actions = [
+ {
+ id: uuidv4(),
+ label: 'Add item to cart',
+ },
+ { id: uuidv4(), label: 'Proceed item to checkout' },
+ ];
+
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ //click on add action button
+ await act(() =>
+ userEvent.click(
+ document.querySelector(
+ `.${blockClass}__actions-container .${blockClass}__add-button`
+ )
+ )
+ );
+
+ expect(
+ screen.getByRole('option', {
+ name: 'Add item to cart',
+ })
+ );
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Add item to cart',
+ })
+ )
+ );
+
+ expect(
+ screen.getByRole('button', {
+ name: 'Add item to cart',
+ })
+ );
+
+ //add second action
+ await act(() =>
+ userEvent.click(
+ document.querySelector(
+ `.${blockClass}__actions-container .${blockClass}__add-button`
+ )
+ )
+ );
+
+ expect(
+ screen.getByRole('option', {
+ name: 'Proceed item to checkout',
+ })
+ );
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Proceed item to checkout',
+ })
+ )
+ );
+
+ expect(
+ screen.getByRole('button', {
+ name: 'Proceed item to checkout',
+ })
+ );
+
+ //add third action
+ await act(() =>
+ userEvent.click(
+ document.querySelector(
+ `.${blockClass}__actions-container .${blockClass}__add-button`
+ )
+ )
+ );
+
+ expect(
+ screen.getByRole('option', {
+ name: 'Add item to cart',
+ })
+ );
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Add item to cart',
+ })
+ )
+ );
+
+ expect(
+ screen.getAllByRole('button', {
+ name: 'Add item to cart',
+ })
+ ).toHaveLength(2);
+
+ //removing conditions
+ let closeConditions = document.querySelectorAll(
+ `.${blockClass}__actions-container .${blockClass}__close-condition`
+ );
+ expect(closeConditions).toHaveLength(3);
+
+ await act(() => userEvent.click(closeConditions[2]));
+
+ closeConditions = document.querySelectorAll(
+ `.${blockClass}__actions-container .${blockClass}__close-condition`
+ );
+ expect(closeConditions).toHaveLength(2);
+ expect(
+ screen.getAllByRole('button', {
+ name: 'Add item to cart',
+ })
+ ).toHaveLength(1);
+ expect(
+ screen.getAllByRole('button', {
+ name: 'Proceed item to checkout',
+ })
+ ).toHaveLength(1);
+
+ await act(() => userEvent.click(closeConditions[1]));
+
+ closeConditions = document.querySelectorAll(
+ `.${blockClass}__actions-container .${blockClass}__close-condition`
+ );
+ expect(closeConditions).toHaveLength(1);
+ expect(
+ screen.getAllByRole('button', {
+ name: 'Add item to cart',
+ })
+ ).toHaveLength(1);
+ expect(
+ screen.queryByText('Proceed item to checkout')
+ ).not.toBeInTheDocument();
+ });
+
+ it(' remove all conditions in a group keeping only subgroups', async () => {
+ const sampleDataStructure = {
+ operator: 'or',
+ groups: [
+ {
+ groupOperator: 'and',
+ statement: 'if',
+ id: '686c62a9-e33d-4e31-817b-4fd319168935',
+ conditions: [
+ {
+ property: 'region',
+ operator: 'is',
+ value: {
+ label: 'Afghanistan',
+ id: 'AF',
+ icon: {
+ propTypes: {},
+ },
+ },
+ id: '87b6cc99-b463-45e2-ab88-44a2a2069a25',
+ },
+ {
+ groupOperator: 'and',
+ statement: 'if',
+ conditions: [
+ {
+ property: 'region',
+ operator: 'is',
+ value: {
+ label: 'Afghanistan',
+ id: 'AF',
+ icon: {
+ propTypes: {},
+ },
+ },
+ id: 'b1ab21df-1791-4955-a9f4-5e257b1d8ee2',
+ },
+ {
+ groupOperator: 'and',
+ statement: 'if',
+ conditions: [
+ {
+ property: 'color',
+ operator: 'is',
+ value: {
+ label: 'black',
+ id: 'black',
+ },
+ id: '3dc4a2d9-c83d-4b56-8e24-d0dd0ec1e7a4',
+ },
+ ],
+ id: '88fe784e-d748-4dfd-818c-63d1167bf60e',
+ },
+ ],
+ id: '09e9feb8-a4a6-485f-9ac0-5b52d1dc82e4',
+ },
+ ],
+ },
+ ],
+ };
+ render(
+
+ );
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+
+ expect(screen.getAllByRole('button', { name: 'if' })).toHaveLength(3);
+
+ await act(() =>
+ userEvent.click(document.querySelector(`.${blockClass}__close-condition`))
+ );
+
+ expect(screen.getAllByRole('button', { name: 'if' })).toHaveLength(2);
+ });
+
+ it('check the custom input type', async () => {
+ render();
+
+ await act(() => userEvent.click(screen.getByText('Add condition')));
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'Product',
+ })
+ )
+ );
+
+ await act(() =>
+ userEvent.click(
+ screen.getByRole('option', {
+ name: 'is greater than',
+ })
+ )
+ );
+
+ const inputText = document.querySelector('#customInput');
+ fireEvent.change(inputText, { target: { value: 'testID123' } });
+
+ const container = document.querySelector(`.${blockClass}`);
+ await act(() => userEvent.click(container));
+
+ const selectedItem = screen.getByRole('button', { name: 'testID123' });
+
+ expect(selectedItem);
+ });
});
diff --git a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.js b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.js
index 1574cc9c7d..22ffbb79a2 100644
--- a/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.js
+++ b/packages/ibm-products/src/components/ConditionBuilder/ConditionBuilderContent/ConditionBuilderContent.js
@@ -32,7 +32,10 @@ const ConditionBuilderContent = ({
const [showConditionGroupPreview, setShowConditionGroupPreview] =
useState(false);
- const [addConditionGroupText] = useTranslations(['addConditionGroupText']);
+ const [addConditionGroupText, conditionHeadingText] = useTranslations([
+ 'addConditionGroupText',
+ 'conditionHeadingText',
+ ]);
const showConditionGroupPreviewHandler = () => {
setShowConditionGroupPreview(true);
};
@@ -113,25 +116,26 @@ const ConditionBuilderContent = ({
});
};
+ if (!isConditionBuilderActive) {
+ return (
+
+ );
+ }
+
return (
<>
- {!isConditionBuilderActive && (
-
- )}
- {isConditionBuilderActive && (
-
- )}
+
+ {conditionHeadingText}
+
- {variant == 'tree' && (
-
- {
-
- }
-
- )}
- {showConditionGroupPreview && (
-
+ {
+
- )}
- >
+ }
+
+ )}
+ {showConditionGroupPreview && (
+
)}
- {isConditionBuilderActive && actions && (
+ {actions && (
{
if (item.conditions) {
@@ -184,9 +189,20 @@ const ConditionGroupBuilder = ({
`[aria-level="${Number(currentLevel) + 1}"][role="row"]`
);
if (nextRow) {
- manageTabIndexAndFocus(nextRow, conditionBuilderRef);
+ //since there are no condition in current group, this group will move one level up
+
+ const rowIdentity = {
+ ariaLevel: Number(nextRow.ariaLevel) - 1,
+ ariaPosInSet: nextRow.ariaPosInSet,
+ };
+ setTimeout(() => {
+ const currentRowToFocus =
+ conditionBuilderContentRef.current.querySelector(
+ `[role="row"][aria-level="${rowIdentity.ariaLevel}"][aria-posinset="${rowIdentity.ariaPosInSet}"]`
+ );
+ manageTabIndexAndFocus(currentRowToFocus, conditionBuilderRef);
+ }, 0);
} else if (prevRows?.length > 1) {
- // prevRows[prevRows.length - 2].setAttribute('tabindex', '0');
manageTabIndexAndFocus(
prevRows[prevRows.length - 2],
conditionBuilderRef
diff --git a/packages/ibm-products/src/components/ConditionBuilder/assets/sampleInput.js b/packages/ibm-products/src/components/ConditionBuilder/assets/sampleInput.js
index e1ce850c4a..ad14e3928c 100644
--- a/packages/ibm-products/src/components/ConditionBuilder/assets/sampleInput.js
+++ b/packages/ibm-products/src/components/ConditionBuilder/assets/sampleInput.js
@@ -14,47 +14,47 @@ import CustomInput from './CustomInput';
//keeping this , an alternative way to give support for dynamic options.
//instead of supplying getOptions callback, we keep option property in inputConfig always as a async method instead to array as below.
-export const inputDataForAsyncOptions = {
- properties: [
- {
- id: 'continent',
- label: 'Continent',
- icon: Earth,
- type: 'option',
- config: {
- options: async () => {
- let returnVal = [
- {
- label: 'Africa',
- id: 'Africa',
- },
- {
- label: 'Antarctica',
- id: 'Antarctica',
- },
- {
- label: 'Asia',
- id: 'Asia',
- },
- {
- label: 'Australia',
- id: 'Australia',
- },
- {
- label: 'Europe',
- id: 'Europe',
- },
- ];
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(returnVal);
- }, 2000);
- });
- },
- },
- },
- ],
-};
+// export const inputDataForAsyncOptions = {
+// properties: [
+// {
+// id: 'continent',
+// label: 'Continent',
+// icon: Earth,
+// type: 'option',
+// config: {
+// options: async () => {
+// let returnVal = [
+// {
+// label: 'Africa',
+// id: 'Africa',
+// },
+// {
+// label: 'Antarctica',
+// id: 'Antarctica',
+// },
+// {
+// label: 'Asia',
+// id: 'Asia',
+// },
+// {
+// label: 'Australia',
+// id: 'Australia',
+// },
+// {
+// label: 'Europe',
+// id: 'Europe',
+// },
+// ];
+// return new Promise((resolve) => {
+// setTimeout(() => {
+// resolve(returnVal);
+// }, 2000);
+// });
+// },
+// },
+// },
+// ],
+// };
const customOperators = [
{
diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js b/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js
index 7fa0553e53..417dcc2e55 100644
--- a/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js
+++ b/packages/ibm-products/src/components/ConditionBuilder/utils/handleKeyboardEvents.js
@@ -311,7 +311,10 @@ const navigateToNextRowCell = (
const nextRow = rows[nextRowIndex];
const itemName = evt.target.dataset.name;
if (nextRow?.querySelector(`[data-name="${itemName}"]`)) {
- nextRow?.querySelector(`[data-name="${itemName}"]`)?.focus();
+ manageTabIndexAndFocus(
+ nextRow?.querySelector(`[data-name="${itemName}"]`),
+ conditionBuilderRef
+ );
} else if (variant === 'tree') {
//when the next row is a if statement , then that row is focused. From any cell of last row of an group , arrow down select the next row (if)
manageTabIndexAndFocus(nextRow, conditionBuilderRef);
diff --git a/packages/ibm-products/src/components/ConditionBuilder/utils/util.js b/packages/ibm-products/src/components/ConditionBuilder/utils/util.js
index 818c3f6769..3eee3fb201 100644
--- a/packages/ibm-products/src/components/ConditionBuilder/utils/util.js
+++ b/packages/ibm-products/src/components/ConditionBuilder/utils/util.js
@@ -76,20 +76,6 @@ export const checkForHoldingKey = (evt, key) => {
return evt[key];
};
-export const checkDuplicateAction = (
- actionState,
- selectedId,
- currentActionId
-) => {
- if (
- selectedId !== currentActionId &&
- actionState.find((eachAction) => eachAction.id === selectedId)
- ) {
- return true;
- }
- return false;
-};
-
export const checkIsValid = (item) => {
return item && item !== 'INVALID';
};