346 lines
10 KiB
TypeScript
346 lines
10 KiB
TypeScript
import {
|
|
DEFAULT_SETUP,
|
|
MAPPING_COLUMNS_RESPONSE,
|
|
NODE_PARAMETER_VALUES,
|
|
UPDATED_SCHEMA,
|
|
getLatestValueChangeEvent,
|
|
} from './utils/ResourceMapper.utils';
|
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
import { waitAllPromises } from '@/__tests__/utils';
|
|
import * as workflowHelpers from '@/mixins/workflowHelpers';
|
|
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { createComponentRenderer } from '@/__tests__/render';
|
|
import type { SpyInstance } from 'vitest';
|
|
|
|
let nodeTypeStore: ReturnType<typeof useNodeTypesStore>;
|
|
let fetchFieldsSpy: SpyInstance;
|
|
let resolveParameterSpy: SpyInstance;
|
|
|
|
const renderComponent = createComponentRenderer(ResourceMapper, DEFAULT_SETUP);
|
|
|
|
describe('ResourceMapper.vue', () => {
|
|
beforeAll(() => {
|
|
nodeTypeStore = useNodeTypesStore();
|
|
fetchFieldsSpy = vi
|
|
.spyOn(nodeTypeStore, 'getResourceMapperFields')
|
|
.mockResolvedValue(MAPPING_COLUMNS_RESPONSE);
|
|
resolveParameterSpy = vi
|
|
.spyOn(workflowHelpers, 'resolveRequiredParameters')
|
|
.mockReturnValue(NODE_PARAMETER_VALUES);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renders default configuration properly', async () => {
|
|
const { getByTestId } = renderComponent();
|
|
await waitAllPromises();
|
|
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
|
|
expect(getByTestId('mapping-mode-select')).toBeInTheDocument();
|
|
expect(getByTestId('matching-column-select')).toBeInTheDocument();
|
|
expect(getByTestId('mapping-fields-container')).toBeInTheDocument();
|
|
// Should render one parameter input for each fetched column
|
|
expect(
|
|
getByTestId('mapping-fields-container').querySelectorAll('.parameter-input').length,
|
|
).toBe(MAPPING_COLUMNS_RESPONSE.fields.length);
|
|
});
|
|
|
|
it('renders add mode properly', async () => {
|
|
const { getByTestId, queryByTestId } = renderComponent(
|
|
{
|
|
props: {
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
mode: 'add',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
|
|
// This mode doesn't render matching column selector
|
|
expect(queryByTestId('matching-column-select')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('renders multi-key match selector properly', async () => {
|
|
const { container, getByTestId } = renderComponent(
|
|
{
|
|
props: {
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
mode: 'upsert',
|
|
multiKeyMatch: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
|
|
expect(container.querySelector('.el-select__tags')).toBeInTheDocument();
|
|
});
|
|
|
|
it('does not render mapping mode selector if it is disabled', async () => {
|
|
const { getByTestId, queryByTestId } = renderComponent(
|
|
{
|
|
props: {
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
supportAutoMap: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
|
|
expect(queryByTestId('mapping-mode-select')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('renders field on top of the list when they are selected for matching', async () => {
|
|
const { container, getByTestId } = renderComponent(
|
|
{
|
|
props: {
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
supportAutoMap: true,
|
|
mode: 'upsert',
|
|
multiKeyMatch: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
|
|
// Id should be the first field in the list
|
|
expect(container.querySelector('.parameter-item')).toContainHTML('id (using to match)');
|
|
// Select Last Name as matching column
|
|
await userEvent.click(getByTestId('matching-column-option-Last name'));
|
|
// Now, last name should be the first field in the list
|
|
expect(container.querySelector('.parameter-item div.title')).toHaveTextContent(
|
|
'Last name (using to match)',
|
|
);
|
|
});
|
|
|
|
it('renders selected matching columns properly when multiple key matching is enabled', async () => {
|
|
const { getByTestId, getAllByText, queryByText } = renderComponent(
|
|
{
|
|
props: {
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
supportAutoMap: true,
|
|
mode: 'upsert',
|
|
multiKeyMatch: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
expect(getByTestId('resource-mapper-container')).toBeInTheDocument();
|
|
await userEvent.click(getByTestId('matching-column-option-Username'));
|
|
|
|
// Both matching columns (id and Username) should be rendered in the dropdown
|
|
expect(
|
|
getByTestId('matching-column-select').querySelector('.el-select > div'),
|
|
).toHaveTextContent('idUsername');
|
|
// All selected columns should have correct labels
|
|
expect(getAllByText('id (using to match)')[0]).toBeInTheDocument();
|
|
expect(getAllByText('Username (using to match)')[0]).toBeInTheDocument();
|
|
expect(queryByText('First Name (using to match)')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('uses field words defined in node definition', async () => {
|
|
const { getByText } = renderComponent(
|
|
{
|
|
props: {
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
fieldWords: {
|
|
singular: 'foo',
|
|
plural: 'foos',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
expect(getByText('Set the value for each foo')).toBeInTheDocument();
|
|
expect(
|
|
getByText('Look for incoming data that matches the foos in the service'),
|
|
).toBeInTheDocument();
|
|
expect(getByText('Foos to Match On')).toBeInTheDocument();
|
|
expect(getByText('The foos that identify the row(s) to modify')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render correct fields based on saved schema', async () => {
|
|
const { getByTestId, queryAllByTestId } = renderComponent(
|
|
{
|
|
props: {
|
|
node: {
|
|
parameters: {
|
|
columns: {
|
|
schema: UPDATED_SCHEMA,
|
|
},
|
|
},
|
|
},
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
mode: 'add',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
// There should be 4 fields rendered and only 1 of them should have remove button
|
|
expect(
|
|
getByTestId('mapping-fields-container').querySelectorAll('.parameter-input').length,
|
|
).toBe(4);
|
|
expect(queryAllByTestId('remove-field-button').length).toBe(1);
|
|
});
|
|
|
|
it('should render correct options based on saved schema', async () => {
|
|
const { getByTestId } = renderComponent(
|
|
{
|
|
props: {
|
|
node: {
|
|
parameters: {
|
|
columns: {
|
|
schema: UPDATED_SCHEMA,
|
|
},
|
|
},
|
|
},
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
mode: 'add',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ merge: true },
|
|
);
|
|
await waitAllPromises();
|
|
// Should have one option in the bottom dropdown for one removed field
|
|
expect(getByTestId('add-fields-select').querySelectorAll('li').length).toBe(1);
|
|
});
|
|
|
|
it('should fetch fields if there is no cached schema', async () => {
|
|
renderComponent({
|
|
props: {
|
|
node: {
|
|
parameters: {
|
|
columns: {
|
|
schema: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await waitAllPromises();
|
|
expect(fetchFieldsSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should not fetch fields if schema is already fetched', async () => {
|
|
renderComponent({
|
|
props: {
|
|
node: {
|
|
parameters: {
|
|
columns: {
|
|
schema: UPDATED_SCHEMA,
|
|
},
|
|
},
|
|
},
|
|
parameter: {
|
|
typeOptions: {
|
|
resourceMapper: {
|
|
mode: 'add',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await waitAllPromises();
|
|
expect(fetchFieldsSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.skip('should delete fields from UI and parameter value when they are deleted', async () => {
|
|
const { getByTestId, emitted } = renderComponent({
|
|
props: {
|
|
node: {
|
|
parameters: {
|
|
columns: {
|
|
schema: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await waitAllPromises();
|
|
// Add some values so we can test if they are gone after deletion
|
|
const idInput = getByTestId('parameter-input-value["id"]').querySelector('input');
|
|
const firstNameInput = getByTestId('parameter-input-value["First name"]').querySelector(
|
|
'input',
|
|
);
|
|
const lastNameInput = getByTestId('parameter-input-value["Last name"]').querySelector('input');
|
|
const usernameInput = getByTestId('parameter-input-value["Username"]').querySelector('input');
|
|
const addressInput = getByTestId('parameter-input-value["Address"]').querySelector('input');
|
|
if (idInput && firstNameInput && lastNameInput && usernameInput && addressInput) {
|
|
await userEvent.type(idInput, '123');
|
|
await userEvent.type(firstNameInput, 'John');
|
|
await userEvent.type(lastNameInput, 'Doe');
|
|
await userEvent.type(usernameInput, 'johndoe');
|
|
await userEvent.type(addressInput, '123 Main St');
|
|
// All field values should be in parameter value
|
|
const valueBeforeRemove = getLatestValueChangeEvent(emitted());
|
|
expect(valueBeforeRemove[0].value.value).toHaveProperty('id');
|
|
expect(valueBeforeRemove[0].value.value).toHaveProperty('First name');
|
|
expect(valueBeforeRemove[0].value.value).toHaveProperty('Last name');
|
|
expect(valueBeforeRemove[0].value.value).toHaveProperty('Username');
|
|
expect(valueBeforeRemove[0].value.value).toHaveProperty('Address');
|
|
// Click on 'Remove all fields' option
|
|
await userEvent.click(getByTestId('columns-parameter-input-options-container'));
|
|
await userEvent.click(getByTestId('action-removeAllFields'));
|
|
// Should delete all non-mandatory fields:
|
|
// 1. From UI
|
|
expect(
|
|
getByTestId('resource-mapper-container').querySelectorAll('.parameter-item').length,
|
|
).toBe(3);
|
|
// 2. And their values from parameter value
|
|
const valueAfterRemove = getLatestValueChangeEvent(emitted());
|
|
expect(valueAfterRemove[0].value.value).not.toHaveProperty('Username');
|
|
expect(valueAfterRemove[0].value.value).not.toHaveProperty('Address');
|
|
} else {
|
|
throw new Error('Could not find input fields');
|
|
}
|
|
});
|
|
});
|