Skip to content

Commit

Permalink
feat(CanvasToolbar): Add DSL Selector
Browse files Browse the repository at this point in the history
In order to create new entities, a DSL Slector component needs to be
added.

This commit adds said component divided in 3 sections:

1. `ChangeDSLModal`: A modal to warn the user whenever the DSL is being
   changed since the existing integration will be removed
2. `DSLSelectorToggle`: The DSL Button itself
3. `DSLSelector`: The main entrypoint for the DSL Selector, this
   component is in charge of combining both `DSLSelectorToggle` and the
`ChangeDSLModal` components

relates: KaotoIO#1030
  • Loading branch information
lordrip committed Apr 25, 2024
1 parent 7ab49c5 commit 8938c21
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { fireEvent, render } from '@testing-library/react';
import { ChangeDSLModal } from './ChangeDSLModal';

describe('ChangeDSLModal', () => {
it('should be hidden when isOpen is false', () => {
const wrapper = render(<ChangeDSLModal isOpen={false} onConfirm={jest.fn()} onCancel={jest.fn()} />);

expect(wrapper.queryByTestId('confirmation-modal')).not.toBeInTheDocument();
});

it('should be visible when isOpen is true', () => {
const wrapper = render(<ChangeDSLModal isOpen={true} onConfirm={jest.fn()} onCancel={jest.fn()} />);

expect(wrapper.queryByTestId('confirmation-modal')).toBeInTheDocument();
});

it('should call onConfirm when confirm button is clicked', () => {
const onConfirm = jest.fn();
const wrapper = render(<ChangeDSLModal isOpen={true} onConfirm={onConfirm} onCancel={jest.fn()} />);

fireEvent.click(wrapper.getByTestId('confirmation-modal-confirm'));

expect(onConfirm).toBeCalled();
});

it('should call onCancel when cancel button is clicked', () => {
const onCancel = jest.fn();
const wrapper = render(<ChangeDSLModal isOpen={true} onConfirm={jest.fn()} onCancel={onCancel} />);

fireEvent.click(wrapper.getByTestId('confirmation-modal-cancel'));

expect(onCancel).toBeCalled();
});

it('should call onCancel when close button is clicked', () => {
const onCancel = jest.fn();
const wrapper = render(<ChangeDSLModal isOpen={true} onConfirm={jest.fn()} onCancel={onCancel} />);

fireEvent.click(wrapper.getByLabelText('Close'));

expect(onCancel).toBeCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Button, Modal, ModalVariant } from '@patternfly/react-core';
import { FunctionComponent } from 'react';

interface ChangeDSLModalProps {
isOpen: boolean;
onConfirm: () => void;
onCancel: () => void;
}

export const ChangeDSLModal: FunctionComponent<ChangeDSLModalProps> = (props) => {
return (
<Modal
variant={ModalVariant.small}
title="Warning"
data-testid="confirmation-modal"
titleIconVariant="warning"
onClose={props.onCancel}
actions={[
<Button key="confirm" variant="primary" data-testid="confirmation-modal-confirm" onClick={props.onConfirm}>
Confirm
</Button>,
<Button key="cancel" variant="link" data-testid="confirmation-modal-cancel" onClick={props.onCancel}>
Cancel
</Button>,
]}
isOpen={props.isOpen}
>
<p>
This will remove any existing integration and you will lose your current work. Are you sure you would like to
proceed?
</p>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { act, fireEvent, render } from '@testing-library/react';
import { EntitiesContextResult } from '../../../../hooks';
import { KaotoSchemaDefinition } from '../../../../models';
import { SourceSchemaType, sourceSchemaConfig } from '../../../../models/camel';
import { EntitiesContext } from '../../../../providers/entities.provider';
import { SourceCodeApiContext } from '../../../../providers/source-code.provider';
import { DSLSelector } from './DSLSelector';

describe('DSLSelector.tsx', () => {
const config = sourceSchemaConfig;
config.config[SourceSchemaType.Integration].schema = {
schema: { name: 'Integration', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;
config.config[SourceSchemaType.Pipe].schema = {
schema: { name: 'Pipe', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;
config.config[SourceSchemaType.Kamelet].schema = {
schema: { name: 'Kamelet', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;
config.config[SourceSchemaType.KameletBinding].schema = {
name: 'kameletBinding',
schema: { description: 'desc' },
} as KaotoSchemaDefinition;
config.config[SourceSchemaType.Route].schema = {
schema: { name: 'route', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;

const renderWithContext = () => {
return render(
<SourceCodeApiContext.Provider
value={{
setCodeAndNotify: jest.fn(),
}}
>
<EntitiesContext.Provider
value={
{
currentSchemaType: SourceSchemaType.Integration,
} as unknown as EntitiesContextResult
}
>
<DSLSelector />
</EntitiesContext.Provider>
</SourceCodeApiContext.Provider>,
);
};

it('should render all of the types', async () => {
const wrapper = renderWithContext();
const trigger = await wrapper.findByTestId('dsl-list-dropdown');

/** Open Select */
act(() => {
fireEvent.click(trigger);
});

for (const name of ['Pipe', 'Camel Route']) {
const element = await wrapper.findByText(name);
expect(element).toBeInTheDocument();
}
});

it('should warn the user when adding a different type of flow', async () => {
const wrapper = renderWithContext();
const trigger = await wrapper.findByTestId('dsl-list-dropdown');

/** Open Select */
act(() => {
fireEvent.click(trigger);
});

/** Select an option */
act(() => {
const element = wrapper.getByText('Pipe');
fireEvent.click(element);
});

const modal = await wrapper.findByTestId('confirmation-modal');
expect(modal).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { FunctionComponent, PropsWithChildren, useCallback, useContext, useState } from 'react';
import { SourceSchemaType } from '../../../../models/camel';
import { FlowTemplateService } from '../../../../models/visualization/flows/support/flow-templates-service';
import { SourceCodeApiContext } from '../../../../providers';
import { ChangeDSLModal } from './ChangeDSLModal/ChangeDSLModal';
import { DSLSelectorToggle } from './DSLSelectorToggle/DSLSelectorToggle';

export const DSLSelector: FunctionComponent<PropsWithChildren> = () => {
const sourceCodeContextApi = useContext(SourceCodeApiContext);
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
const [proposedFlowType, setProposedFlowType] = useState<SourceSchemaType>();

const checkBeforeAddNewFlow = useCallback((flowType: SourceSchemaType) => {
/**
* If it is not the same DSL, this operation might result in
* removing the existing flows, so then we warn the user first
*/
setProposedFlowType(flowType);
setIsConfirmationModalOpen(true);
}, []);

const onConfirm = useCallback(() => {
if (proposedFlowType) {
sourceCodeContextApi.setCodeAndNotify(FlowTemplateService.getFlowYamlTemplate(proposedFlowType));
setIsConfirmationModalOpen(false);
}
}, [proposedFlowType, sourceCodeContextApi]);

const onCancel = useCallback(() => {
setIsConfirmationModalOpen(false);
}, []);

return (
<>
<DSLSelectorToggle onSelect={checkBeforeAddNewFlow} />
<ChangeDSLModal isOpen={isConfirmationModalOpen} onConfirm={onConfirm} onCancel={onCancel} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { FunctionComponent } from 'react';
import { EntitiesContextResult } from '../../../../../hooks';
import { KaotoSchemaDefinition } from '../../../../../models';
import { SourceSchemaType, sourceSchemaConfig } from '../../../../../models/camel';
import { EntitiesContext } from '../../../../../providers/entities.provider';
import { DSLSelectorToggle } from './DSLSelectorToggle';

const config = sourceSchemaConfig;
config.config[SourceSchemaType.Pipe].schema = {
name: 'Pipe',
schema: { name: 'Pipe', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;
config.config[SourceSchemaType.Kamelet].schema = {
name: 'Kamelet',
schema: { name: 'Kamelet', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;
config.config[SourceSchemaType.Route].schema = {
name: 'route',
schema: { name: 'route', description: 'desc' } as KaotoSchemaDefinition['schema'],
} as KaotoSchemaDefinition;

describe('DSLSelectorToggle.tsx', () => {
let onSelect: () => void;
beforeEach(() => {
onSelect = jest.fn();
});

const DSLSelectorWithContext: FunctionComponent<{ currentSchemaType?: SourceSchemaType }> = (props) => {
const currentSchemaType = props.currentSchemaType ?? SourceSchemaType.Route;
return (
<EntitiesContext.Provider key={Date.now()} value={{ currentSchemaType } as unknown as EntitiesContextResult}>
<DSLSelectorToggle onSelect={onSelect} />
</EntitiesContext.Provider>
);
};

it('component renders', () => {
const wrapper = render(<DSLSelectorWithContext />);
const toggle = wrapper.queryByTestId('dsl-list-dropdown');
expect(toggle).toBeInTheDocument();
});

it('should call onSelect when clicking on the MenuToggleAction', async () => {
const wrapper = render(<DSLSelectorWithContext />);

/** Click on toggle */
const toggle = await wrapper.findByTestId('dsl-list-dropdown');
act(() => {
fireEvent.click(toggle);
});

/** Click on first element */
const element = await wrapper.findByText('Pipe');
act(() => {
fireEvent.click(element);
});

await waitFor(() => {
expect(onSelect).toHaveBeenCalled();
});
});

it('should disable the MenuToggleAction if the DSL is already selected', async () => {
const wrapper = render(<DSLSelectorWithContext currentSchemaType={SourceSchemaType.Route} />);

/** Click on toggle */
const toggle = await wrapper.findByTestId('dsl-list-dropdown');
act(() => {
fireEvent.click(toggle);
});

/** Click on first element */
const element = await wrapper.findByText('Camel Route');
// act(() => {
// fireEvent.click(element);
// });

waitFor(() => {
expect(element).toBeDisabled();
});
});

it('should toggle list of DSLs', async () => {
const wrapper = render(<DSLSelectorWithContext />);
const toggle = await wrapper.findByTestId('dsl-list-dropdown');

/** Click on toggle */
act(() => {
fireEvent.click(toggle);
});

const element = await wrapper.findByText('Pipe');
expect(element).toBeInTheDocument();

/** Close Select */
act(() => {
fireEvent.click(toggle);
});

waitFor(() => {
expect(element).not.toBeInTheDocument();
});
});

it('should show selected value', async () => {
const wrapper = render(<DSLSelectorWithContext />);
const toggle = await wrapper.findByTestId('dsl-list-dropdown');

/** Open Select */
act(() => {
fireEvent.click(toggle);
});

/** Click on first element */
act(() => {
const element = wrapper.getByText('Camel Route');
fireEvent.click(element);
});

/** Open Select again */
act(() => {
fireEvent.click(toggle);
});

const element = await wrapper.findByRole('option', { selected: true });
expect(element).toBeInTheDocument();
expect(element).toHaveTextContent('Camel Route');
});

it('should have selected DSL if provided', async () => {
const wrapper = render(<DSLSelectorWithContext />);
const toggle = await wrapper.findByTestId('dsl-list-dropdown');

/** Open Select */
act(() => {
fireEvent.click(toggle);
});

waitFor(() => {
const element = wrapper.queryByRole('option', { selected: true });
expect(element).toBeInTheDocument();
expect(element).toHaveTextContent('Pipe');
});
});

it('should close Select when pressing ESC', async () => {
const wrapper = render(<DSLSelectorWithContext />);
const toggle = await wrapper.findByTestId('dsl-list-dropdown');

/** Open Select */
act(() => {
fireEvent.click(toggle);
});

const menu = await wrapper.findByRole('listbox');

expect(menu).toBeInTheDocument();

/** Press Escape key to close the menu */
act(() => {
fireEvent.focus(menu);
fireEvent.keyDown(menu, { key: 'Escape', code: 'Escape', charCode: 27 });
});

waitFor(() => {
/** The close panel is an async process */
expect(menu).not.toBeInTheDocument();
});

waitFor(() => {
const element = wrapper.queryByRole('option', { selected: true });
expect(element).toBeInTheDocument();
expect(element).toHaveTextContent('Camel Route');
});
});
});

0 comments on commit 8938c21

Please sign in to comment.