mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
Add tests for WorkflowEvaluation components and EvaluationEditView
- Add MetricsInput component tests - Add TagsInput component tests - Add EvaluationEditView tests - Include snapshot test - Update test utilities and mocks
This commit is contained in:
parent
1417fe4719
commit
c063235682
|
@ -0,0 +1,56 @@
|
||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import MetricsInput from '../EditEvaluation/MetricsInput.vue';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
const renderComponent = createComponentRenderer(MetricsInput);
|
||||||
|
|
||||||
|
describe('MetricsInput', () => {
|
||||||
|
let props: { modelValue: string[] };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
props = {
|
||||||
|
modelValue: ['Metric 1', 'Metric 2'],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly with initial metrics', () => {
|
||||||
|
const { getAllByPlaceholderText } = renderComponent({ props });
|
||||||
|
const inputs = getAllByPlaceholderText('Enter metric name');
|
||||||
|
expect(inputs).toHaveLength(2);
|
||||||
|
expect(inputs[0]).toHaveValue('Metric 1');
|
||||||
|
expect(inputs[1]).toHaveValue('Metric 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a metric when typing in the input', async () => {
|
||||||
|
const { getAllByPlaceholderText, emitted } = renderComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: [''],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const inputs = getAllByPlaceholderText('Enter metric name');
|
||||||
|
await userEvent.type(inputs[0], 'Updated Metric 1');
|
||||||
|
|
||||||
|
expect(emitted('update:modelValue')).toBeTruthy();
|
||||||
|
expect(emitted('update:modelValue')).toEqual('Updated Metric 1'.split('').map((c) => [[c]]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly with no initial metrics', () => {
|
||||||
|
props.modelValue = [];
|
||||||
|
const { queryAllByRole, getByText } = renderComponent({ props });
|
||||||
|
const inputs = queryAllByRole('textbox');
|
||||||
|
expect(inputs).toHaveLength(0);
|
||||||
|
expect(getByText('New metric')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle adding multiple metrics', async () => {
|
||||||
|
const { getByText, emitted } = renderComponent({ props });
|
||||||
|
const addButton = getByText('New metric');
|
||||||
|
|
||||||
|
addButton.click();
|
||||||
|
addButton.click();
|
||||||
|
addButton.click();
|
||||||
|
|
||||||
|
expect(emitted('update:modelValue')).toHaveProperty('length', 3);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import TagsInput from '../EditEvaluation/TagsInput.vue';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import type { ITag } from '@/Interface';
|
||||||
|
|
||||||
|
describe('TagsInput', () => {
|
||||||
|
const mockTags: ITag[] = [
|
||||||
|
{ id: '1', name: 'Tag 1' },
|
||||||
|
{ id: '2', name: 'Tag 2' },
|
||||||
|
{ id: '3', name: 'Tag 3' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTagsById: Record<string, ITag> = {
|
||||||
|
'1': mockTags[0],
|
||||||
|
'2': mockTags[1],
|
||||||
|
'3': mockTags[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
allTags: mockTags,
|
||||||
|
tagsById: mockTagsById,
|
||||||
|
isLoading: false,
|
||||||
|
startEditing: vi.fn(),
|
||||||
|
saveChanges: vi.fn(),
|
||||||
|
cancelEditing: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correctly with default props', () => {
|
||||||
|
const wrapper = mount(TagsInput, {
|
||||||
|
props: defaultProps,
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia()],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('computes tag name correctly', async () => {
|
||||||
|
const wrapper = mount(TagsInput, {
|
||||||
|
props: {
|
||||||
|
...defaultProps,
|
||||||
|
modelValue: {
|
||||||
|
isEditing: false,
|
||||||
|
appliedTagIds: ['1'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia()],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const vm = wrapper.vm;
|
||||||
|
expect(vm.getTagName('1')).toBe('Tag 1');
|
||||||
|
expect(vm.getTagName('4')).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only('updates tags correctly', async () => {
|
||||||
|
const wrapper = mount(TagsInput, {
|
||||||
|
props: {
|
||||||
|
...defaultProps,
|
||||||
|
'onUpdate:modelValue': async (e) => await wrapper.setProps({ modelValue: e }),
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia()],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.find('[data-test-id=workflow-tags-dropdown]').setValue('test');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
||||||
|
expect(wrapper.emitted('update:modelValue')![0][0]).toEqual({
|
||||||
|
isEditing: false,
|
||||||
|
appliedTagIds: ['2'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears tags when empty array is passed', async () => {
|
||||||
|
const wrapper = mount(TagsInput, {
|
||||||
|
props: defaultProps,
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia()],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.vm.updateTags([]);
|
||||||
|
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
||||||
|
expect(wrapper.emitted('update:modelValue')![0][0]).toEqual({
|
||||||
|
isEditing: false,
|
||||||
|
appliedTagIds: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles editing state correctly', async () => {
|
||||||
|
const wrapper = mount(TagsInput, {
|
||||||
|
props: {
|
||||||
|
...defaultProps,
|
||||||
|
modelValue: {
|
||||||
|
isEditing: true,
|
||||||
|
appliedTagIds: ['1'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia()],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.modelValue.isEditing).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import EvaluationEditView from '@/views/WorkflowEvaluation/EvaluationEditView.vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useEvaluationForm } from '@/components/WorkflowEvaluation/composables/useEvaluationForm';
|
||||||
|
import { useAnnotationTagsStore } from '@/stores/tags.store';
|
||||||
|
import { ref, nextTick } from 'vue';
|
||||||
|
import { VIEWS } from '@/constants';
|
||||||
|
|
||||||
|
vi.mock('vue-router');
|
||||||
|
vi.mock('@/composables/useToast');
|
||||||
|
vi.mock('@/components/WorkflowEvaluation/composables/useEvaluationForm');
|
||||||
|
vi.mock('@/stores/tags.store');
|
||||||
|
|
||||||
|
describe('EvaluationEditView', () => {
|
||||||
|
const renderComponent = createComponentRenderer(EvaluationEditView);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
|
||||||
|
vi.mocked(useRoute).mockReturnValue({
|
||||||
|
params: {},
|
||||||
|
path: '/test-path',
|
||||||
|
name: 'test-route',
|
||||||
|
} as ReturnType<typeof useRoute>);
|
||||||
|
|
||||||
|
vi.mocked(useRouter).mockReturnValue({
|
||||||
|
push: vi.fn(),
|
||||||
|
resolve: vi.fn().mockReturnValue({ href: '/test-href' }),
|
||||||
|
} as unknown as ReturnType<typeof useRouter>);
|
||||||
|
|
||||||
|
vi.mocked(useToast).mockReturnValue({
|
||||||
|
showMessage: vi.fn(),
|
||||||
|
showError: vi.fn(),
|
||||||
|
} as unknown as ReturnType<typeof useToast>);
|
||||||
|
vi.mocked(useEvaluationForm).mockReturnValue({
|
||||||
|
state: ref({
|
||||||
|
name: { value: '', isEditing: false, tempValue: '' },
|
||||||
|
description: '',
|
||||||
|
tags: { appliedTagIds: [], isEditing: false },
|
||||||
|
evaluationWorkflow: { id: '1', name: 'Test Workflow' },
|
||||||
|
metrics: [],
|
||||||
|
}),
|
||||||
|
fieldsIssues: ref([]),
|
||||||
|
isSaving: ref(false),
|
||||||
|
loadTestData: vi.fn(),
|
||||||
|
saveTest: vi.fn(),
|
||||||
|
startEditing: vi.fn(),
|
||||||
|
saveChanges: vi.fn(),
|
||||||
|
cancelEditing: vi.fn(),
|
||||||
|
handleKeydown: vi.fn(),
|
||||||
|
} as unknown as ReturnType<typeof useEvaluationForm>);
|
||||||
|
vi.mocked(useAnnotationTagsStore).mockReturnValue({
|
||||||
|
isLoading: ref(false),
|
||||||
|
allTags: ref([]),
|
||||||
|
tagsById: ref({}),
|
||||||
|
fetchAll: vi.fn(),
|
||||||
|
} as unknown as ReturnType<typeof useAnnotationTagsStore>);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load test data when testId is provided', async () => {
|
||||||
|
vi.mocked(useRoute).mockReturnValue({
|
||||||
|
params: { testId: '1' },
|
||||||
|
path: '/test-path',
|
||||||
|
name: 'test-route',
|
||||||
|
} as unknown as ReturnType<typeof useRoute>);
|
||||||
|
const loadTestDataMock = vi.fn();
|
||||||
|
vi.mocked(useEvaluationForm).mockReturnValue({
|
||||||
|
...vi.mocked(useEvaluationForm)(),
|
||||||
|
loadTestData: loadTestDataMock,
|
||||||
|
} as unknown as ReturnType<typeof useEvaluationForm>);
|
||||||
|
|
||||||
|
renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
expect(loadTestDataMock).toHaveBeenCalledWith('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not load test data when testId is not provided', async () => {
|
||||||
|
const loadTestDataMock = vi.fn();
|
||||||
|
vi.mocked(useEvaluationForm).mockReturnValue({
|
||||||
|
...vi.mocked(useEvaluationForm)(),
|
||||||
|
loadTestData: loadTestDataMock,
|
||||||
|
} as unknown as ReturnType<typeof useEvaluationForm>);
|
||||||
|
|
||||||
|
renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
expect(loadTestDataMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save test and show success message on successful save', async () => {
|
||||||
|
const saveTestMock = vi.fn().mockResolvedValue({});
|
||||||
|
const showMessageMock = vi.fn();
|
||||||
|
const routerPushMock = vi.fn();
|
||||||
|
const routerResolveMock = vi.fn().mockReturnValue({ href: '/test-href' });
|
||||||
|
vi.mocked(useEvaluationForm).mockReturnValue({
|
||||||
|
...vi.mocked(useEvaluationForm)(),
|
||||||
|
saveTest: saveTestMock,
|
||||||
|
} as unknown as ReturnType<typeof useEvaluationForm>);
|
||||||
|
vi.mocked(useToast).mockReturnValue({ showMessage: showMessageMock } as unknown as ReturnType<
|
||||||
|
typeof useToast
|
||||||
|
>);
|
||||||
|
vi.mocked(useRouter).mockReturnValue({
|
||||||
|
push: routerPushMock,
|
||||||
|
resolve: routerResolveMock,
|
||||||
|
} as unknown as ReturnType<typeof useRouter>);
|
||||||
|
|
||||||
|
const { getByTestId } = renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
const saveButton = getByTestId('run-test-button');
|
||||||
|
saveButton.click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(saveTestMock).toHaveBeenCalled();
|
||||||
|
expect(showMessageMock).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
|
||||||
|
expect(routerPushMock).toHaveBeenCalledWith({ name: VIEWS.WORKFLOW_EVALUATION });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error message on failed save', async () => {
|
||||||
|
const saveTestMock = vi.fn().mockRejectedValue(new Error('Save failed'));
|
||||||
|
const showErrorMock = vi.fn();
|
||||||
|
vi.mocked(useEvaluationForm).mockReturnValue({
|
||||||
|
...vi.mocked(useEvaluationForm)(),
|
||||||
|
saveTest: saveTestMock,
|
||||||
|
} as unknown as ReturnType<typeof useEvaluationForm>);
|
||||||
|
vi.mocked(useToast).mockReturnValue({ showError: showErrorMock } as unknown as ReturnType<
|
||||||
|
typeof useToast
|
||||||
|
>);
|
||||||
|
|
||||||
|
const { getByTestId } = renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
const saveButton = getByTestId('run-test-button');
|
||||||
|
saveButton.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(saveTestMock).toHaveBeenCalled();
|
||||||
|
expect(showErrorMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display "Update Test" button when editing existing test', async () => {
|
||||||
|
vi.mocked(useRoute).mockReturnValue({
|
||||||
|
params: { testId: '1' },
|
||||||
|
path: '/test-path',
|
||||||
|
name: 'test-route',
|
||||||
|
} as unknown as ReturnType<typeof useRoute>);
|
||||||
|
const { getByTestId } = renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
const updateButton = getByTestId('run-test-button');
|
||||||
|
expect(updateButton.textContent).toContain('Update test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display "Run Test" button when creating new test', async () => {
|
||||||
|
const { getByTestId } = renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
const saveButton = getByTestId('run-test-button');
|
||||||
|
expect(saveButton).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply "has-issues" class to inputs with issues', async () => {
|
||||||
|
vi.mocked(useEvaluationForm).mockReturnValue({
|
||||||
|
...vi.mocked(useEvaluationForm)(),
|
||||||
|
fieldsIssues: ref([{ field: 'name' }, { field: 'tags' }]),
|
||||||
|
} as unknown as ReturnType<typeof useEvaluationForm>);
|
||||||
|
|
||||||
|
const { container } = renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
expect(container.querySelector('.has-issues')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch all tags on mount', async () => {
|
||||||
|
const fetchAllMock = vi.fn();
|
||||||
|
vi.mocked(useAnnotationTagsStore).mockReturnValue({
|
||||||
|
...vi.mocked(useAnnotationTagsStore)(),
|
||||||
|
fetchAll: fetchAllMock,
|
||||||
|
} as unknown as ReturnType<typeof useAnnotationTagsStore>);
|
||||||
|
|
||||||
|
renderComponent({
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
expect(fetchAllMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue