2024-04-18 02:48:52 -07:00
|
|
|
import { renderComponent } from '@/__tests__/render';
|
|
|
|
import ParameterInput from '@/components/ParameterInput.vue';
|
|
|
|
import type { useNDVStore } from '@/stores/ndv.store';
|
|
|
|
import type { CompletionResult } from '@codemirror/autocomplete';
|
|
|
|
import { createTestingPinia } from '@pinia/testing';
|
|
|
|
import { faker } from '@faker-js/faker';
|
2024-05-08 01:43:04 -07:00
|
|
|
import { waitFor } from '@testing-library/vue';
|
|
|
|
import userEvent from '@testing-library/user-event';
|
2024-11-01 09:01:13 -07:00
|
|
|
import type { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
2024-08-28 05:01:05 -07:00
|
|
|
import { cleanupAppModals, createAppModals } from '@/__tests__/utils';
|
2024-04-18 02:48:52 -07:00
|
|
|
|
|
|
|
let mockNdvState: Partial<ReturnType<typeof useNDVStore>>;
|
2024-05-08 01:43:04 -07:00
|
|
|
let mockNodeTypesState: Partial<ReturnType<typeof useNodeTypesStore>>;
|
2024-04-18 02:48:52 -07:00
|
|
|
let mockCompletionResult: Partial<CompletionResult>;
|
|
|
|
|
|
|
|
vi.mock('@/stores/ndv.store', () => {
|
|
|
|
return {
|
|
|
|
useNDVStore: vi.fn(() => mockNdvState),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-05-08 01:43:04 -07:00
|
|
|
vi.mock('@/stores/nodeTypes.store', () => {
|
|
|
|
return {
|
|
|
|
useNodeTypesStore: vi.fn(() => mockNodeTypesState),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-04-18 02:48:52 -07:00
|
|
|
vi.mock('@/plugins/codemirror/completions/datatype.completions', () => {
|
|
|
|
return {
|
|
|
|
datatypeCompletions: vi.fn(() => mockCompletionResult),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
vi.mock('vue-router', () => {
|
|
|
|
const push = vi.fn();
|
|
|
|
return {
|
|
|
|
useRouter: () => ({
|
|
|
|
push,
|
|
|
|
}),
|
2024-05-17 01:53:15 -07:00
|
|
|
useRoute: () => ({}),
|
2024-04-18 02:48:52 -07:00
|
|
|
RouterLink: vi.fn(),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('ParameterInput.vue', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
mockNdvState = {
|
|
|
|
hasInputData: true,
|
|
|
|
activeNode: {
|
|
|
|
id: faker.string.uuid(),
|
|
|
|
name: faker.word.words(3),
|
|
|
|
parameters: {},
|
|
|
|
position: [faker.number.int(), faker.number.int()],
|
|
|
|
type: 'test',
|
|
|
|
typeVersion: 1,
|
|
|
|
},
|
2024-11-08 05:11:36 -08:00
|
|
|
isInputPanelEmpty: false,
|
|
|
|
isOutputPanelEmpty: false,
|
2024-04-18 02:48:52 -07:00
|
|
|
};
|
2024-05-08 01:43:04 -07:00
|
|
|
mockNodeTypesState = {
|
|
|
|
allNodeTypes: [],
|
|
|
|
};
|
2024-08-28 05:01:05 -07:00
|
|
|
createAppModals();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
cleanupAppModals();
|
2024-04-18 02:48:52 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
test('should render an options parameter (select)', async () => {
|
|
|
|
const { container, baseElement, emitted } = renderComponent(ParameterInput, {
|
|
|
|
pinia: createTestingPinia(),
|
|
|
|
props: {
|
|
|
|
path: 'operation',
|
|
|
|
parameter: {
|
|
|
|
displayName: 'Operation',
|
|
|
|
name: 'operation',
|
|
|
|
type: 'options',
|
|
|
|
noDataExpression: true,
|
|
|
|
displayOptions: { show: { resource: ['sheet'] } },
|
|
|
|
options: [
|
|
|
|
{
|
|
|
|
name: 'Append or Update Row',
|
|
|
|
value: 'appendOrUpdate',
|
|
|
|
description: 'Append a new row or update an existing one (upsert)',
|
|
|
|
action: 'Append or update row in sheet',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Append Row',
|
|
|
|
value: 'append',
|
|
|
|
description: 'Create a new row in a sheet',
|
|
|
|
action: 'Append row in sheet',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
default: 'appendOrUpdate',
|
|
|
|
},
|
|
|
|
modelValue: 'appendOrUpdate',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const select = container.querySelector('input') as HTMLInputElement;
|
|
|
|
const selectTrigger = container.querySelector('.select-trigger') as HTMLElement;
|
|
|
|
expect(select).toBeInTheDocument();
|
|
|
|
expect(selectTrigger).toBeInTheDocument();
|
|
|
|
await waitFor(() => expect(select).toHaveValue('Append or Update Row'));
|
|
|
|
|
|
|
|
await userEvent.click(selectTrigger);
|
|
|
|
|
|
|
|
const options = baseElement.querySelectorAll('.list-option');
|
|
|
|
expect(options.length).toEqual(2);
|
|
|
|
expect(options[0].querySelector('.option-headline')).toHaveTextContent('Append or Update Row');
|
|
|
|
expect(options[0].querySelector('.option-description')).toHaveTextContent(
|
|
|
|
'Append a new row or update an existing one (upsert)',
|
|
|
|
);
|
|
|
|
expect(options[1].querySelector('.option-headline')).toHaveTextContent('Append Row');
|
|
|
|
expect(options[1].querySelector('.option-description')).toHaveTextContent(
|
|
|
|
'Create a new row in a sheet',
|
|
|
|
);
|
|
|
|
|
|
|
|
await userEvent.click(options[1]);
|
|
|
|
|
|
|
|
expect(emitted('update')).toContainEqual([expect.objectContaining({ value: 'append' })]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should render a string parameter', async () => {
|
|
|
|
const { container, emitted } = renderComponent(ParameterInput, {
|
|
|
|
pinia: createTestingPinia(),
|
|
|
|
props: {
|
|
|
|
path: 'tag',
|
|
|
|
parameter: {
|
|
|
|
displayName: 'Tag',
|
|
|
|
name: 'tag',
|
|
|
|
type: 'string',
|
|
|
|
},
|
|
|
|
modelValue: '',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const input = container.querySelector('input') as HTMLInputElement;
|
|
|
|
expect(input).toBeInTheDocument();
|
|
|
|
|
|
|
|
await userEvent.type(input, 'foo');
|
|
|
|
|
|
|
|
expect(emitted('update')).toContainEqual([expect.objectContaining({ value: 'foo' })]);
|
|
|
|
});
|
2024-05-08 01:43:04 -07:00
|
|
|
|
|
|
|
test('should not reset the value of a multi-select with loadOptionsMethod on load', async () => {
|
|
|
|
mockNodeTypesState.getNodeParameterOptions = vi.fn(async () => [
|
|
|
|
{ name: 'ID', value: 'id' },
|
|
|
|
{ name: 'Title', value: 'title' },
|
|
|
|
{ name: 'Description', value: 'description' },
|
|
|
|
]);
|
|
|
|
|
|
|
|
const { emitted, container } = renderComponent(ParameterInput, {
|
|
|
|
pinia: createTestingPinia(),
|
|
|
|
props: {
|
|
|
|
path: 'columns',
|
|
|
|
parameter: {
|
|
|
|
displayName: 'Columns',
|
|
|
|
name: 'columns',
|
|
|
|
type: 'multiOptions',
|
|
|
|
typeOptions: { loadOptionsMethod: 'getColumnsMultiOptions' },
|
|
|
|
},
|
|
|
|
modelValue: ['id', 'title'],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const input = container.querySelector('input') as HTMLInputElement;
|
|
|
|
expect(input).toBeInTheDocument();
|
|
|
|
|
|
|
|
// Nothing should be emitted
|
|
|
|
expect(emitted('update')).toBeUndefined();
|
|
|
|
});
|
2024-09-26 03:03:29 -07:00
|
|
|
|
|
|
|
test('should show message when can not load options without credentials', async () => {
|
|
|
|
mockNodeTypesState.getNodeParameterOptions = vi.fn(async () => {
|
|
|
|
throw new Error('Node does not have any credentials set');
|
|
|
|
});
|
|
|
|
|
|
|
|
// @ts-expect-error Readonly property
|
|
|
|
mockNodeTypesState.getNodeType = vi.fn().mockReturnValue({
|
|
|
|
displayName: 'Test',
|
|
|
|
credentials: [
|
|
|
|
{
|
|
|
|
name: 'openAiApi',
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
const { emitted, container, getByTestId } = renderComponent(ParameterInput, {
|
|
|
|
pinia: createTestingPinia(),
|
|
|
|
props: {
|
|
|
|
path: 'columns',
|
|
|
|
parameter: {
|
|
|
|
displayName: 'Columns',
|
|
|
|
name: 'columns',
|
|
|
|
type: 'options',
|
|
|
|
typeOptions: { loadOptionsMethod: 'getColumnsMultiOptions' },
|
|
|
|
},
|
|
|
|
modelValue: 'id',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
await waitFor(() => expect(getByTestId('parameter-input-field')).toBeInTheDocument());
|
|
|
|
|
|
|
|
const input = container.querySelector('input') as HTMLInputElement;
|
|
|
|
expect(input).toBeInTheDocument();
|
|
|
|
|
|
|
|
expect(mockNodeTypesState.getNodeParameterOptions).toHaveBeenCalled();
|
|
|
|
|
|
|
|
expect(input.value.toLowerCase()).not.toContain('error');
|
|
|
|
expect(input).toHaveValue('Set up credential to see options');
|
|
|
|
|
|
|
|
expect(emitted('update')).toBeUndefined();
|
|
|
|
});
|
2024-04-18 02:48:52 -07:00
|
|
|
});
|