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:
Oleg Ivaniv 2024-11-12 22:48:34 +01:00
parent 1417fe4719
commit c063235682
No known key found for this signature in database
3 changed files with 375 additions and 0 deletions

View file

@ -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);
});
});

View file

@ -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);
});
});

View file

@ -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();
});
});