mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -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