mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
✅ Adding RLC unit tests
This commit is contained in:
parent
9ba9443460
commit
891ecb798f
|
@ -0,0 +1,84 @@
|
||||||
|
import type { INode, INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const TEST_MODEL_VALUE: INodeParameterResourceLocator = {
|
||||||
|
__rl: true,
|
||||||
|
value: 'test',
|
||||||
|
mode: 'list',
|
||||||
|
cachedResultName: 'table',
|
||||||
|
cachedResultUrl: 'https://test.com/test',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_PARAMETER_MULTI_MODE: INodeProperties = {
|
||||||
|
displayName: 'Test Parameter',
|
||||||
|
name: 'testParamMultiMode',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: { searchListMethod: 'testSearch', searchable: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://test.com/test',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_PARAMETER_SINGLE_MODE: INodeProperties = {
|
||||||
|
...TEST_PARAMETER_MULTI_MODE,
|
||||||
|
name: 'testParameterSingleMode',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: { searchListMethod: 'testSearch', searchable: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_NODE_MULTI_MODE: INode = {
|
||||||
|
type: 'n8n-nodes-base.airtable',
|
||||||
|
typeVersion: 2.1,
|
||||||
|
position: [80, -260],
|
||||||
|
id: '377e4287-b1e0-44cc-ba0f-7bb3d676d60c',
|
||||||
|
name: 'Test Node - Multi Mode',
|
||||||
|
parameters: {
|
||||||
|
authentication: 'testAuth',
|
||||||
|
resource: 'test',
|
||||||
|
operation: 'get',
|
||||||
|
testParamMultiMode: TEST_MODEL_VALUE,
|
||||||
|
id: '',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
credentials: {
|
||||||
|
testAuth: {
|
||||||
|
id: '1234',
|
||||||
|
name: 'Test Account',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_NODE_SINGLE_MODE: INode = {
|
||||||
|
...TEST_NODE_MULTI_MODE,
|
||||||
|
parameters: {
|
||||||
|
authentication: 'testAuth',
|
||||||
|
resource: 'test',
|
||||||
|
operation: 'get',
|
||||||
|
testParameterSingleMode: TEST_MODEL_VALUE,
|
||||||
|
id: '',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,189 @@
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
import ResourceLocator from './ResourceLocator.vue';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { screen, waitFor } from '@testing-library/vue';
|
||||||
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
|
import {
|
||||||
|
TEST_MODEL_VALUE,
|
||||||
|
TEST_NODE_MULTI_MODE,
|
||||||
|
TEST_NODE_SINGLE_MODE,
|
||||||
|
TEST_PARAMETER_MULTI_MODE,
|
||||||
|
TEST_PARAMETER_SINGLE_MODE,
|
||||||
|
} from './ResourceLocator.test.constants';
|
||||||
|
|
||||||
|
vi.mock('vue-router', async () => {
|
||||||
|
const actual = await vi.importActual('vue-router');
|
||||||
|
const params = {};
|
||||||
|
const location = {};
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useRouter: () => ({
|
||||||
|
push: vi.fn(),
|
||||||
|
}),
|
||||||
|
useRoute: () => ({
|
||||||
|
params,
|
||||||
|
location,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
vi.mock('@/composables/useWorkflowHelpers', () => {
|
||||||
|
return {
|
||||||
|
useWorkflowHelpers: vi.fn(() => ({
|
||||||
|
resolveExpression: vi.fn().mockImplementation((val) => val),
|
||||||
|
resolveRequiredParameters: vi.fn().mockImplementation((_, params) => params),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
vi.mock('@/composables/useTelemetry', () => ({
|
||||||
|
useTelemetry: () => ({ track: vi.fn() }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let nodeTypesStore: ReturnType<typeof mockedStore<typeof useNodeTypesStore>>;
|
||||||
|
|
||||||
|
const renderComponent = createComponentRenderer(ResourceLocator, {
|
||||||
|
props: {
|
||||||
|
modelValue: TEST_MODEL_VALUE,
|
||||||
|
parameter: TEST_PARAMETER_MULTI_MODE,
|
||||||
|
path: `parameters.${TEST_PARAMETER_MULTI_MODE.name}`,
|
||||||
|
node: TEST_NODE_MULTI_MODE,
|
||||||
|
displayTitle: 'Test Resource Locator',
|
||||||
|
expressionComputedValue: '',
|
||||||
|
isValueExpression: false,
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
ResourceLocatorDropdown: false,
|
||||||
|
ExpressionParameterInput: true,
|
||||||
|
ParameterIssues: true,
|
||||||
|
N8nCallout: true,
|
||||||
|
'font-awesome-icon': true,
|
||||||
|
FromAiOverrideField: true,
|
||||||
|
FromAiOverrideButton: true,
|
||||||
|
ParameterOverrideSelectableList: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ResourceLocator', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createTestingPinia();
|
||||||
|
nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||||
|
nodeTypesStore.getNodeType = vi.fn().mockReturnValue({ displayName: 'Test Node' });
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders multi-mode correctly', async () => {
|
||||||
|
const { getByTestId } = renderComponent();
|
||||||
|
expect(getByTestId(`resource-locator-${TEST_PARAMETER_MULTI_MODE.name}`)).toBeInTheDocument();
|
||||||
|
// Should render mode selector with all available modes
|
||||||
|
expect(getByTestId('rlc-mode-selector')).toBeInTheDocument();
|
||||||
|
await userEvent.click(getByTestId('rlc-mode-selector'));
|
||||||
|
TEST_PARAMETER_MULTI_MODE.modes?.forEach((mode) => {
|
||||||
|
expect(screen.getByTestId(`mode-${mode.name}`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders single mode correctly', async () => {
|
||||||
|
const { getByTestId, queryByTestId } = renderComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: TEST_MODEL_VALUE,
|
||||||
|
parameter: TEST_PARAMETER_SINGLE_MODE,
|
||||||
|
path: `parameters.${TEST_PARAMETER_SINGLE_MODE.name}`,
|
||||||
|
node: TEST_NODE_SINGLE_MODE,
|
||||||
|
displayTitle: 'Test Resource Locator',
|
||||||
|
expressionComputedValue: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(getByTestId(`resource-locator-${TEST_PARAMETER_SINGLE_MODE.name}`)).toBeInTheDocument();
|
||||||
|
// Should not render mode selector
|
||||||
|
expect(queryByTestId('rlc-mode-selector')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders fetched resources correctly', async () => {
|
||||||
|
const TEST_ITEMS = [
|
||||||
|
{ name: 'Test Resource', value: 'test-resource', url: 'https://test.com/test-resource' },
|
||||||
|
{
|
||||||
|
name: 'Test Resource 2',
|
||||||
|
value: 'test-resource-2',
|
||||||
|
url: 'https://test.com/test-resource-2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
nodeTypesStore.getResourceLocatorResults.mockResolvedValue({
|
||||||
|
results: TEST_ITEMS,
|
||||||
|
paginationToken: null,
|
||||||
|
});
|
||||||
|
const { getByTestId, getByText, getAllByTestId } = renderComponent();
|
||||||
|
|
||||||
|
expect(getByTestId(`resource-locator-${TEST_PARAMETER_MULTI_MODE.name}`)).toBeInTheDocument();
|
||||||
|
// Click on the input to fetch resources
|
||||||
|
await userEvent.click(getByTestId('rlc-input'));
|
||||||
|
// Wait for the resources to be fetched
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nodeTypesStore.getResourceLocatorResults).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
// Expect the items to be rendered
|
||||||
|
expect(getAllByTestId('rlc-item')).toHaveLength(TEST_ITEMS.length);
|
||||||
|
// We should be getting one item for each result
|
||||||
|
TEST_ITEMS.forEach((item) => {
|
||||||
|
expect(getByText(item.name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders permission error correctly', async () => {
|
||||||
|
const TEST_401_ERROR = {
|
||||||
|
message: 'Failed to load resources',
|
||||||
|
httpCode: '401',
|
||||||
|
description: 'Authentication failed. Please check your credentials.',
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeTypesStore.getResourceLocatorResults.mockRejectedValue(TEST_401_ERROR);
|
||||||
|
|
||||||
|
const { getByTestId, findByTestId } = renderComponent();
|
||||||
|
|
||||||
|
expect(getByTestId(`resource-locator-${TEST_PARAMETER_MULTI_MODE.name}`)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(getByTestId('rlc-input'));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nodeTypesStore.getResourceLocatorResults).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorContainer = await findByTestId('rlc-error-container');
|
||||||
|
expect(errorContainer).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(errorContainer).toHaveTextContent(TEST_401_ERROR.httpCode);
|
||||||
|
expect(errorContainer).toHaveTextContent(TEST_401_ERROR.message);
|
||||||
|
|
||||||
|
expect(getByTestId('permission-error-link')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders generic error correctly', async () => {
|
||||||
|
const TEST_500_ERROR = {
|
||||||
|
message: 'Whoops',
|
||||||
|
httpCode: '500',
|
||||||
|
description: 'Something went wrong. Please try again later.',
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeTypesStore.getResourceLocatorResults.mockRejectedValue(TEST_500_ERROR);
|
||||||
|
const { getByTestId, findByTestId, queryByTestId } = renderComponent();
|
||||||
|
|
||||||
|
expect(getByTestId(`resource-locator-${TEST_PARAMETER_MULTI_MODE.name}`)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(getByTestId('rlc-input'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nodeTypesStore.getResourceLocatorResults).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorContainer = await findByTestId('rlc-error-container');
|
||||||
|
expect(errorContainer).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(errorContainer).toHaveTextContent(TEST_500_ERROR.httpCode);
|
||||||
|
expect(errorContainer).toHaveTextContent(TEST_500_ERROR.message);
|
||||||
|
|
||||||
|
expect(queryByTestId('permission-error-link')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue