+
+
+
Fix test configuration.",
+ "testDefinition.listRuns.error.evaluationWorkflowNotFound": "Selected evaluation workflow does not exist. {link}.",
+ "testDefinition.listRuns.error.evaluationWorkflowNotFound.solution": "Fix test configuration",
"testDefinition.runDetail.ranAt": "Ran at",
"testDefinition.runDetail.testCase": "Test case",
"testDefinition.runDetail.testCase.id": "Test case ID",
"testDefinition.runDetail.testCase.status": "Test case status",
"testDefinition.runDetail.totalCases": "Total cases",
- "testDefinition.runDetail.error.mockedNodeMissing": "Output for a mocked node does not exist in benchmark execution.
Fix test configuration.",
- "testDefinition.runDetail.error.executionFailed": "Failed to execute workflow with benchmark trigger.
View execution.",
- "testDefinition.runDetail.error.evaluationFailed": "Failed to execute the evaluation workflow.
View evaluation execution.",
- "testDefinition.runDetail.error.triggerNoLongerExists": "Trigger in benchmark execution no longer exists in workflow.
View benchmark.",
- "testDefinition.runDetail.error.metricsMissing": "Metrics defined in test were not returned by evaluation workflow.
Fix test configuration.",
- "testDefinition.runDetail.error.unknownMetrics": "Evaluation workflow defined metrics that are not defined in the test.
Fix test configuration.",
- "testDefinition.runDetail.error.invalidMetrics": "Evaluation workflow returned invalid metrics. Only numeric values are expected. View evaluation execution.
View evaluation execution.",
+ "testDefinition.runDetail.error.mockedNodeMissing": "Output for a mocked node does not exist in benchmark execution.{link}.",
+ "testDefinition.runDetail.error.mockedNodeMissing.solution": "Fix test configuration",
+ "testDefinition.runDetail.error.executionFailed": "Failed to execute workflow with benchmark trigger. {link}.",
+ "testDefinition.runDetail.error.executionFailed.solution": "View execution",
+ "testDefinition.runDetail.error.evaluationFailed": "Failed to execute the evaluation workflow. {link}.",
+ "testDefinition.runDetail.error.evaluationFailed.solution": "View evaluation execution",
+ "testDefinition.runDetail.error.triggerNoLongerExists": "Trigger in benchmark execution no longer exists in workflow.{link}.",
+ "testDefinition.runDetail.error.triggerNoLongerExists.solution": "View benchmark",
+ "testDefinition.runDetail.error.metricsMissing": "Metrics defined in test were not returned by evaluation workflow {link}.",
+ "testDefinition.runDetail.error.metricsMissing.solution": "Fix test configuration",
+ "testDefinition.runDetail.error.unknownMetrics": "Evaluation workflow defined metrics that are not defined in the test. {link}.",
+ "testDefinition.runDetail.error.unknownMetrics.solution": "Fix test configuration",
+ "testDefinition.runDetail.error.invalidMetrics": "Evaluation workflow returned invalid metrics. Only numeric values are expected. View evaluation execution. {link}.",
+ "testDefinition.runDetail.error.invalidMetrics.solution": "View evaluation execution",
"testDefinition.runTest": "Run Test",
"testDefinition.cancelTestRun": "Cancel Test Run",
"testDefinition.notImplemented": "This feature is not implemented yet!",
@@ -2919,6 +2941,14 @@
"testDefinition.configError.noMetrics": "No metrics set",
"testDefinition.workflowInput.subworkflowName": "Evaluation workflow for {name}",
"testDefinition.workflowInput.subworkflowName.default": "My Evaluation Sub-Workflow",
+ "testDefinition.executions.addTo": "Add to Test",
+ "testDefinition.executions.addTo.new": "Add to Test",
+ "testDefinition.executions.addTo.existing": "Add to \"{name}\"",
+ "testDefinition.executions.addedTo": "Added to \"{name}\"",
+ "testDefinition.executions.toast.addedTo": "1 past execution added as a test case to \"{name}\"",
+ "testDefinition.workflow.createNew": "Create new evaluation workflow",
+ "testDefinition.workflow.createNew.or": "or use existing evaluation sub-workflow",
+ "testDefinition.executions.toast.addedTo.title": "Added to test",
"freeAi.credits.callout.claim.title": "Get {credits} free OpenAI API credits",
"freeAi.credits.callout.claim.button.label": "Claim credits",
"freeAi.credits.callout.success.title.part1": "Claimed {credits} free OpenAI API credits! Please note these free credits are only for the following models:",
diff --git a/packages/editor-ui/src/plugins/icons/index.ts b/packages/editor-ui/src/plugins/icons/index.ts
index 18e8113fca..343a1cec83 100644
--- a/packages/editor-ui/src/plugins/icons/index.ts
+++ b/packages/editor-ui/src/plugins/icons/index.ts
@@ -29,6 +29,7 @@ import {
faCheckSquare,
faChevronDown,
faChevronUp,
+ faCircle,
faChevronLeft,
faChevronRight,
faCode,
@@ -166,6 +167,8 @@ import {
faPowerOff,
faPaperPlane,
faExclamationCircle,
+ faMinusCircle,
+ faAdjust,
} from '@fortawesome/free-solid-svg-icons';
import { faVariable, faXmark, faVault, faRefresh } from './custom';
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
@@ -206,6 +209,7 @@ export const FontAwesomePlugin: Plugin = {
addIcon(faChevronRight);
addIcon(faChevronDown);
addIcon(faChevronUp);
+ addIcon(faCircle);
addIcon(faCode);
addIcon(faCodeBranch);
addIcon(faCog);
@@ -346,6 +350,8 @@ export const FontAwesomePlugin: Plugin = {
addIcon(faPowerOff);
addIcon(faPaperPlane);
addIcon(faRefresh);
+ addIcon(faMinusCircle);
+ addIcon(faAdjust);
app.component('FontAwesomeIcon', FontAwesomeIcon);
},
diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts
index 4e69e40ac3..8a767af742 100644
--- a/packages/editor-ui/src/router.ts
+++ b/packages/editor-ui/src/router.ts
@@ -11,6 +11,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useTemplatesStore } from '@/stores/templates.store';
import { useUIStore } from '@/stores/ui.store';
import { useSSOStore } from '@/stores/sso.store';
+import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
import { EnterpriseEditionFeature, VIEWS, EDITABLE_CANVAS_VIEWS } from '@/constants';
import { useTelemetry } from '@/composables/useTelemetry';
import { middleware } from '@/utils/rbac/middleware';
@@ -18,7 +19,6 @@ import type { RouterMiddleware } from '@/types/router';
import { initializeAuthenticatedFeatures, initializeCore } from '@/init';
import { tryToParseNumber } from '@/utils/typesUtils';
import { projectsRoutes } from '@/routes/projects.routes';
-import TestDefinitionRunsListView from './views/TestDefinition/TestDefinitionRunsListView.vue';
import TestDefinitionRunDetailView from './views/TestDefinition/TestDefinitionRunDetailView.vue';
const ChangePasswordView = async () => await import('./views/ChangePasswordView.vue');
@@ -61,6 +61,8 @@ const WorkflowHistory = async () => await import('@/views/WorkflowHistory.vue');
const WorkflowOnboardingView = async () => await import('@/views/WorkflowOnboardingView.vue');
const TestDefinitionListView = async () =>
await import('./views/TestDefinition/TestDefinitionListView.vue');
+const TestDefinitionNewView = async () =>
+ await import('./views/TestDefinition/TestDefinitionNewView.vue');
const TestDefinitionEditView = async () =>
await import('./views/TestDefinition/TestDefinitionEditView.vue');
const TestDefinitionRootView = async () =>
@@ -264,65 +266,42 @@ export const routes: RouteRecordRaw[] = [
header: MainHeader,
sidebar: MainSidebar,
},
+ props: true,
meta: {
keepWorkflowAlive: true,
- middleware: ['authenticated'],
+ middleware: ['authenticated', 'custom'],
+ middlewareOptions: {
+ custom: () => useTestDefinitionStore().isFeatureEnabled,
+ },
},
children: [
{
path: '',
name: VIEWS.TEST_DEFINITION,
- components: {
- default: TestDefinitionListView,
- },
- meta: {
- keepWorkflowAlive: true,
- middleware: ['authenticated'],
- },
+ component: TestDefinitionListView,
+ props: true,
},
{
path: 'new',
name: VIEWS.NEW_TEST_DEFINITION,
- components: {
- default: TestDefinitionEditView,
- },
- meta: {
- keepWorkflowAlive: true,
- middleware: ['authenticated'],
- },
+ component: TestDefinitionNewView,
+ props: true,
},
{
path: ':testId',
name: VIEWS.TEST_DEFINITION_EDIT,
+ props: true,
components: {
default: TestDefinitionEditView,
},
- meta: {
- keepWorkflowAlive: true,
- middleware: ['authenticated'],
- },
- },
- {
- path: ':testId/runs',
- name: VIEWS.TEST_DEFINITION_RUNS,
- components: {
- default: TestDefinitionRunsListView,
- },
- meta: {
- keepWorkflowAlive: true,
- middleware: ['authenticated'],
- },
},
{
path: ':testId/runs/:runId',
name: VIEWS.TEST_DEFINITION_RUNS_DETAIL,
+ props: true,
components: {
default: TestDefinitionRunDetailView,
},
- meta: {
- keepWorkflowAlive: true,
- middleware: ['authenticated'],
- },
},
],
},
diff --git a/packages/editor-ui/src/stores/tags.store.ts b/packages/editor-ui/src/stores/tags.store.ts
index 00272d96bd..367cee64d7 100644
--- a/packages/editor-ui/src/stores/tags.store.ts
+++ b/packages/editor-ui/src/stores/tags.store.ts
@@ -88,9 +88,22 @@ const createTagsStore = (id: STORES.TAGS | STORES.ANNOTATION_TAGS) => {
return retrievedTags;
};
- const create = async (name: string) => {
- const createdTag = await tagsApi.createTag(rootStore.restApiContext, { name });
+ const create = async (
+ name: string,
+ { incrementExisting }: { incrementExisting?: boolean } = {},
+ ) => {
+ let tagName = name;
+
+ if (incrementExisting) {
+ const tagNameRegex = new RegExp(tagName);
+ const existingTags = allTags.value.filter((tag) => tagNameRegex.test(tag.name));
+ if (existingTags.length > 0) {
+ tagName = `${tagName} (${existingTags.length + 1})`;
+ }
+ }
+ const createdTag = await tagsApi.createTag(rootStore.restApiContext, { name: tagName });
upsertTags([createdTag]);
+
return createdTag;
};
diff --git a/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts b/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts
index aed705cb91..6f43a939bb 100644
--- a/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts
+++ b/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts
@@ -60,24 +60,28 @@ const TEST_DEF_A: TestDefinitionRecord = {
name: 'Test Definition A',
workflowId: '123',
description: 'Description A',
+ createdAt: '2023-01-01T00:00:00.000Z',
};
const TEST_DEF_B: TestDefinitionRecord = {
id: '2',
name: 'Test Definition B',
workflowId: '123',
description: 'Description B',
+ createdAt: '2023-01-01T00:00:00.000Z',
};
const TEST_DEF_NEW: TestDefinitionRecord = {
id: '3',
name: 'New Test Definition',
workflowId: '123',
description: 'New Description',
+ createdAt: '2023-01-01T00:00:00.000Z',
};
const TEST_METRIC = {
id: 'metric1',
name: 'Test Metric',
testDefinitionId: '1',
+ createdAt: '2023-01-01T00:00:00.000Z',
};
const TEST_RUN: TestRunRecord = {
@@ -89,6 +93,9 @@ const TEST_RUN: TestRunRecord = {
updatedAt: '2024-01-01',
runAt: '2024-01-01',
completedAt: '2024-01-01',
+ failedCases: 0,
+ totalCases: 1,
+ passedCases: 1,
};
describe('testDefinition.store.ee', () => {
@@ -140,10 +147,7 @@ describe('testDefinition.store.ee', () => {
'2': TEST_DEF_B,
});
expect(store.isLoading).toBe(false);
- expect(result).toEqual({
- count: 2,
- testDefinitions: [TEST_DEF_A, TEST_DEF_B],
- });
+ expect(result).toEqual([TEST_DEF_A, TEST_DEF_B]);
});
test('Fetching Test Definitions with force flag', async () => {
@@ -159,10 +163,7 @@ describe('testDefinition.store.ee', () => {
'2': TEST_DEF_B,
});
expect(store.isLoading).toBe(false);
- expect(result).toEqual({
- count: 2,
- testDefinitions: [TEST_DEF_A, TEST_DEF_B],
- });
+ expect(result).toEqual([TEST_DEF_A, TEST_DEF_B]);
});
test('Fetching Test Definitions when already fetched', async () => {
@@ -198,6 +199,7 @@ describe('testDefinition.store.ee', () => {
name: 'Updated Test Definition A',
description: 'Updated Description A',
workflowId: '123',
+ createdAt: '2023-01-01T00:00:00.000Z',
};
store.upsertTestDefinitions([updatedDefinition]);
@@ -246,7 +248,7 @@ describe('testDefinition.store.ee', () => {
workflowId: '123',
});
expect(store.testDefinitionsById).toEqual({
- '1': params,
+ '1': { ...TEST_DEF_A, ...params },
'2': TEST_DEF_B,
});
expect(result).toEqual(params);
diff --git a/packages/editor-ui/src/stores/testDefinition.store.ee.ts b/packages/editor-ui/src/stores/testDefinition.store.ee.ts
index fa2cfdf657..283947f4be 100644
--- a/packages/editor-ui/src/stores/testDefinition.store.ee.ts
+++ b/packages/editor-ui/src/stores/testDefinition.store.ee.ts
@@ -172,6 +172,15 @@ export const useTestDefinitionStore = defineStore(
return testDefinition;
};
+ const fetchTestDefinitionsByWorkflowId = async (workflowId: string) => {
+ const testDefinitions = await testDefinitionsApi.getTestDefinitions(
+ rootStore.restApiContext,
+ { workflowId },
+ );
+ setAllTestDefinitions(testDefinitions.testDefinitions);
+ return testDefinitions.testDefinitions;
+ };
+
const fetchTestCaseExecutions = async (params: { testDefinitionId: string; runId: string }) => {
const testCaseExecutions = await testDefinitionsApi.getTestCaseExecutions(
rootStore.restApiContext,
@@ -202,16 +211,15 @@ export const useTestDefinitionStore = defineStore(
loading.value = true;
try {
- const retrievedDefinitions = await testDefinitionsApi.getTestDefinitions(
- rootStore.restApiContext,
- { workflowId },
- );
+ if (!workflowId) {
+ return;
+ }
- setAllTestDefinitions(retrievedDefinitions.testDefinitions);
+ const retrievedDefinitions = await fetchTestDefinitionsByWorkflowId(workflowId);
fetchedAll.value = true;
await Promise.all([
- tagsStore.fetchAll({ withUsageCount: true }),
+ tagsStore.fetchAll({ force: true, withUsageCount: true }),
fetchRunsForAllTests(),
fetchMetricsForAllTests(),
]);
@@ -463,6 +471,7 @@ export const useTestDefinitionStore = defineStore(
// Methods
fetchTestDefinition,
+ fetchTestDefinitionsByWorkflowId,
fetchTestCaseExecutions,
fetchAll,
fetchExampleEvaluationInput,
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue
index 3ef7d28821..d321ad062a 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue
@@ -1,139 +1,98 @@
-
-
-
-
- {{ locale.baseText('testDefinition.completeConfig') }}
- - {{ issue.message }}
-
-
- {{ locale.baseText('testDefinition.testIsRunning') }}
-
-
-
+
+
+
+
+
+ {{ state.name.value }}
+
+
+
+
+ {{
+ isSaving
+ ? locale.baseText('testDefinition.edit.saving')
+ : locale.baseText('testDefinition.edit.saved')
+ }}
+
+
+
+
+
+ {{ locale.baseText('testDefinition.completeConfig') }}
+ - {{ issue.message }}
+
+
+ {{ locale.baseText('testDefinition.testIsRunning') }}
+
+
+
+
+
+
+
+
+ {{ state.description.value }}
+
+
-
-
+
+
-
+ handleUpdateMetricsDebounced(testId)"
+ @update:evaluation-workflow="handleUpdateTestDebounced"
+ @update:mocked-nodes="handleUpdateTestDebounced"
+ @open-pinning-modal="openPinningModal"
+ @delete-metric="onDeleteMetric"
+ @open-executions-view-for-tag="openExecutionsViewForTag"
+ @evaluation-workflow-created="onEvaluationWorkflowCreated($event)"
+ />
+
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue
index 5742060df4..ef557d7f4c 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue
@@ -1,134 +1,86 @@
-
-
-
-
+
+
-
-
+
+
+ {{ locale.baseText('testDefinition.list.tests') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ locale.baseText('testDefinition.completeConfig') }}
+
+ - {{ issue.message }}
+
+
+
+
+
+
+
+
+ handleAction(action, item.id)"
+ >
+
+
+
+
+
@@ -272,6 +254,7 @@ onMounted(async () => {
width: 100%;
max-width: var(--content-container-width);
margin: auto;
+ padding: var(--spacing-xl) var(--spacing-l);
}
.loading {
display: flex;
@@ -279,4 +262,18 @@ onMounted(async () => {
align-items: center;
height: 200px;
}
+
+.testList {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--color-foreground-base);
+ border-radius: var(--border-radius-base);
+ // gap: 8px;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 20px;
+}
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionNewView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionNewView.vue
new file mode 100644
index 0000000000..391303a6dd
--- /dev/null
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionNewView.vue
@@ -0,0 +1,78 @@
+
+
+
+ creating {{ name }}
+
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionRootView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionRootView.vue
index ace36b6814..c2623433f2 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionRootView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionRootView.vue
@@ -1,28 +1,31 @@
-
+
@@ -30,7 +33,5 @@ onMounted(initWorkflow);
.evaluationsView {
width: 100%;
height: 100%;
- margin: auto;
- padding: var(--spacing-xl) var(--spacing-l);
}
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunDetailView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunDetailView.vue
index c67297e5e0..85d1582461 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunDetailView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunDetailView.vue
@@ -1,15 +1,42 @@
-
-
-
-
-
- {{ testDefinition?.name }}
-
-
{{ testDefinition?.description }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionEditView.test.ts b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionEditView.test.ts
index 9824dda7d9..e900649e92 100644
--- a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionEditView.test.ts
+++ b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionEditView.test.ts
@@ -1,105 +1,43 @@
-import type { Mock } from 'vitest';
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 TestDefinitionEditView from '@/views/TestDefinition/TestDefinitionEditView.vue';
-import { useRoute, useRouter } from 'vue-router';
-import { useToast } from '@/composables/useToast';
-import { useTestDefinitionForm } from '@/components/TestDefinition/composables/useTestDefinitionForm';
-import { useAnnotationTagsStore } from '@/stores/tags.store';
-import { ref, nextTick } from 'vue';
+import type { useTestDefinitionForm } from '@/components/TestDefinition/composables/useTestDefinitionForm';
+import { ref } from 'vue';
import { cleanupAppModals, createAppModals, mockedStore } from '@/__tests__/utils';
-import { VIEWS } from '@/constants';
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
-import type { TestRunRecord } from '@/api/testDefinition.ee';
+import userEvent from '@testing-library/user-event';
-vi.mock('vue-router');
-vi.mock('@/composables/useToast');
-vi.mock('@/components/TestDefinition/composables/useTestDefinitionForm');
-vi.mock('@/stores/projects.store');
+const form: Partial
> = {
+ state: ref({
+ name: { value: '', isEditing: false, tempValue: '' },
+ description: { value: '', isEditing: false, tempValue: '' },
+ tags: { value: [], tempValue: [], isEditing: false },
+ evaluationWorkflow: { mode: 'list', value: '', __rl: true },
+ metrics: [],
+ mockedNodes: [],
+ }),
+ loadTestData: vi.fn(),
+ cancelEditing: vi.fn(),
+ updateTest: vi.fn(),
+ startEditing: vi.fn(),
+ saveChanges: vi.fn(),
+ deleteMetric: vi.fn(),
+ updateMetrics: vi.fn(),
+ createTest: vi.fn(),
+};
+vi.mock('@/components/TestDefinition/composables/useTestDefinitionForm', () => ({
+ useTestDefinitionForm: () => form,
+}));
+
+const renderComponent = createComponentRenderer(TestDefinitionEditView, {
+ props: { testId: '1', name: 'workflow-name' },
+});
describe('TestDefinitionEditView', () => {
- const renderComponent = createComponentRenderer(TestDefinitionEditView);
-
- let createTestMock: Mock;
- let updateTestMock: Mock;
- let loadTestDataMock: Mock;
- let deleteMetricMock: Mock;
- let updateMetricsMock: Mock;
- let showMessageMock: Mock;
- let showErrorMock: Mock;
-
- const renderComponentWithFeatureEnabled = ({
- testRunsById = {},
- }: { testRunsById?: Record } = {}) => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const mockedTestDefinitionStore = mockedStore(useTestDefinitionStore);
- mockedTestDefinitionStore.isFeatureEnabled = true;
- mockedTestDefinitionStore.testRunsById = testRunsById;
- return { ...renderComponent({ pinia }), mockedTestDefinitionStore };
- };
-
beforeEach(() => {
- setActivePinia(createPinia());
+ createTestingPinia();
createAppModals();
-
- // Default route mock: no testId
- vi.mocked(useRoute).mockReturnValue({
- params: {},
- name: VIEWS.NEW_TEST_DEFINITION,
- } as ReturnType);
-
- vi.mocked(useRouter).mockReturnValue({
- push: vi.fn(),
- replace: vi.fn(),
- resolve: vi.fn().mockReturnValue({ href: '/test-href' }),
- currentRoute: { value: { params: {} } },
- } as unknown as ReturnType);
-
- createTestMock = vi.fn().mockResolvedValue({ id: 'newTestId' });
- updateTestMock = vi.fn().mockResolvedValue({});
- loadTestDataMock = vi.fn();
- deleteMetricMock = vi.fn();
- updateMetricsMock = vi.fn();
- showMessageMock = vi.fn();
- showErrorMock = vi.fn();
- // const mockedTestDefinitionStore = mockedStore(useTestDefinitionStore);
-
- vi.mocked(useToast).mockReturnValue({
- showMessage: showMessageMock,
- showError: showErrorMock,
- } as unknown as ReturnType);
- vi.mocked(useTestDefinitionForm).mockReturnValue({
- state: ref({
- name: { value: '', isEditing: false, tempValue: '' },
- description: { value: '', isEditing: false, tempValue: '' },
- tags: { value: [], tempValue: [], isEditing: false },
- evaluationWorkflow: { mode: 'list', value: '', __rl: true },
- metrics: [],
- }),
- fieldsIssues: ref([]),
- isSaving: ref(false),
- loadTestData: loadTestDataMock,
- createTest: createTestMock,
- updateTest: updateTestMock,
- startEditing: vi.fn(),
- saveChanges: vi.fn(),
- cancelEditing: vi.fn(),
- handleKeydown: vi.fn(),
- deleteMetric: deleteMetricMock,
- updateMetrics: updateMetricsMock,
- } as unknown as ReturnType);
-
- vi.mock('@/stores/projects.store', () => ({
- useProjectsStore: vi.fn().mockReturnValue({
- isTeamProjectFeatureEnabled: false,
- currentProject: null,
- currentProjectId: null,
- }),
- }));
});
afterEach(() => {
@@ -107,165 +45,72 @@ describe('TestDefinitionEditView', () => {
cleanupAppModals();
});
- it('should load test data when testId is provided', async () => {
- vi.mocked(useRoute).mockReturnValue({
- params: { testId: '1' },
- name: VIEWS.TEST_DEFINITION_EDIT,
- } as unknown as ReturnType);
- renderComponentWithFeatureEnabled();
-
- mockedStore(useAnnotationTagsStore).fetchAll.mockResolvedValue([]);
-
- expect(loadTestDataMock).toHaveBeenCalledWith('1');
- });
-
- it('should not load test data when testId is not provided', async () => {
- // Here route returns no testId
- vi.mocked(useRoute).mockReturnValue({
- params: {},
- name: VIEWS.NEW_TEST_DEFINITION,
- } as unknown as ReturnType);
- renderComponentWithFeatureEnabled();
-
- expect(loadTestDataMock).not.toHaveBeenCalled();
- });
-
- it('should create a new test and show success message on save if no testId is present', async () => {
- vi.mocked(useRoute).mockReturnValue({
- params: {},
- name: VIEWS.NEW_TEST_DEFINITION,
- } as ReturnType);
- const { getByTestId } = renderComponentWithFeatureEnabled();
-
- mockedStore(useAnnotationTagsStore).fetchAll.mockResolvedValue([]);
-
- await nextTick();
- const saveButton = getByTestId('run-test-button');
- saveButton.click();
- await nextTick();
-
- expect(createTestMock).toHaveBeenCalled();
- });
-
- it('should show error message on failed test creation', async () => {
- createTestMock.mockRejectedValue(new Error('Save failed'));
-
- vi.mocked(useRoute).mockReturnValue({
- params: {},
- name: VIEWS.NEW_TEST_DEFINITION,
- } as unknown as ReturnType);
-
- const { getByTestId } = renderComponentWithFeatureEnabled();
-
- const saveButton = getByTestId('run-test-button');
- saveButton.click();
- await nextTick();
-
- expect(createTestMock).toHaveBeenCalled();
- expect(showErrorMock).toHaveBeenCalledWith(expect.any(Error), expect.any(String));
+ it('should load test data', async () => {
+ renderComponent();
+ expect(form.loadTestData).toHaveBeenCalledWith('1', 'workflow-name');
});
it('should display disabled "run test" button when editing test without tags', async () => {
- vi.mocked(useRoute).mockReturnValue({
- params: { testId: '1' },
- name: VIEWS.TEST_DEFINITION_EDIT,
- } as unknown as ReturnType);
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
- const { getByTestId, mockedTestDefinitionStore } = renderComponentWithFeatureEnabled();
+ testDefinitionStore.getFieldIssues.mockReturnValueOnce([
+ { field: 'tags', message: 'Tag is required' },
+ ]);
- mockedTestDefinitionStore.getFieldIssues = vi
- .fn()
- .mockReturnValue([{ field: 'tags', message: 'Tag is required' }]);
-
- await nextTick();
+ const { getByTestId } = renderComponent();
const updateButton = getByTestId('run-test-button');
expect(updateButton.textContent?.toLowerCase()).toContain('run test');
expect(updateButton).toHaveClass('disabled');
-
- mockedTestDefinitionStore.getFieldIssues = vi.fn().mockReturnValue([]);
- await nextTick();
- expect(updateButton).not.toHaveClass('disabled');
});
it('should apply "has-issues" class to inputs with issues', async () => {
- const { container, mockedTestDefinitionStore } = renderComponentWithFeatureEnabled();
- mockedTestDefinitionStore.getFieldIssues = vi
- .fn()
- .mockReturnValue([{ field: 'tags', message: 'Tag is required' }]);
- await nextTick();
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+
+ testDefinitionStore.getFieldIssues.mockReturnValueOnce([
+ { field: 'evaluationWorkflow', message: 'No evaluation workflow set' },
+ ]);
+
+ const { container } = renderComponent();
const issueElements = container.querySelectorAll('.has-issues');
expect(issueElements.length).toBeGreaterThan(0);
});
describe('Test Runs functionality', () => {
it('should display test runs table when runs exist', async () => {
- vi.mocked(useRoute).mockReturnValue({
- params: { testId: '1' },
- name: VIEWS.TEST_DEFINITION_EDIT,
- } as unknown as ReturnType);
-
- const { getByTestId } = renderComponentWithFeatureEnabled({
- testRunsById: {
- run1: {
- id: 'run1',
- testDefinitionId: '1',
- status: 'completed',
- runAt: '2023-01-01',
- createdAt: '2023-01-01',
- updatedAt: '2023-01-01',
- completedAt: '2023-01-01',
- },
- run2: {
- id: 'run2',
- testDefinitionId: '1',
- status: 'running',
- runAt: '2023-01-02',
- createdAt: '2023-01-02',
- updatedAt: '2023-01-02',
- completedAt: '',
- },
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.testRunsById = {
+ run1: {
+ id: 'run1',
+ testDefinitionId: '1',
+ status: 'completed',
+ runAt: '2023-01-01',
+ createdAt: '2023-01-01',
+ updatedAt: '2023-01-01',
+ completedAt: '2023-01-01',
+ failedCases: 0,
+ passedCases: 1,
+ totalCases: 1,
},
- });
+ };
- const runsTable = getByTestId('past-runs-table');
- expect(runsTable).toBeTruthy();
+ const { getByTestId } = renderComponent();
+ expect(getByTestId('past-runs-table')).toBeInTheDocument();
});
it('should not display test runs table when no runs exist', async () => {
- const { container } = renderComponentWithFeatureEnabled();
-
- const runsTable = container.querySelector('[data-test-id="past-runs-table"]');
- expect(runsTable).toBeFalsy();
+ const { queryByTestId } = renderComponent();
+ expect(queryByTestId('past-runs-table')).not.toBeInTheDocument();
});
it('should start a test run when run test button is clicked', async () => {
- vi.mocked(useTestDefinitionForm).mockReturnValue({
- ...vi.mocked(useTestDefinitionForm)(),
- state: ref({
- name: { value: 'Test', isEditing: false, tempValue: '' },
- description: { value: '', isEditing: false, tempValue: '' },
- tags: { value: ['tag1'], tempValue: [], isEditing: false },
- evaluationWorkflow: { mode: 'list', value: 'workflow1', __rl: true },
- metrics: [],
- mockedNodes: [],
- }),
- } as unknown as ReturnType);
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ const { getByTestId } = renderComponent();
- vi.mocked(useRoute).mockReturnValue({
- params: { testId: '1' },
- name: VIEWS.TEST_DEFINITION_EDIT,
- } as unknown as ReturnType);
+ await userEvent.click(getByTestId('run-test-button'));
- const { getByTestId, mockedTestDefinitionStore } = renderComponentWithFeatureEnabled();
- await nextTick();
-
- const runButton = getByTestId('run-test-button');
- runButton.click();
- await nextTick();
-
- expect(mockedTestDefinitionStore.startTestRun).toHaveBeenCalledWith('1');
- expect(mockedTestDefinitionStore.fetchTestRuns).toHaveBeenCalledWith('1');
+ expect(testDefinitionStore.startTestRun).toHaveBeenCalledWith('1');
+ expect(testDefinitionStore.fetchTestRuns).toHaveBeenCalledWith('1');
});
});
});
diff --git a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionListView.test.ts b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionListView.test.ts
index c4b427f4bd..e82bc4174e 100644
--- a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionListView.test.ts
+++ b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionListView.test.ts
@@ -1,173 +1,170 @@
-import type { Mock } from 'vitest';
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 TestDefinitionListView from '@/views/TestDefinition/TestDefinitionListView.vue';
-import { useRoute, useRouter } from 'vue-router';
-import { useToast } from '@/composables/useToast';
-import { useMessage } from '@/composables/useMessage';
+import type { useToast } from '@/composables/useToast';
+import type { useMessage } from '@/composables/useMessage';
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
-import { nextTick, ref } from 'vue';
-import { mockedStore, waitAllPromises } from '@/__tests__/utils';
-import { MODAL_CONFIRM, VIEWS } from '@/constants';
+import { mockedStore } from '@/__tests__/utils';
+import { MODAL_CONFIRM } from '@/constants';
import type { TestDefinitionRecord } from '@/api/testDefinition.ee';
+import userEvent from '@testing-library/user-event';
+import { within, waitFor } from '@testing-library/dom';
+
+const renderComponent = createComponentRenderer(TestDefinitionListView);
+
+const workflowId = 'workflow1';
+const mockTestDefinitions: TestDefinitionRecord[] = [
+ {
+ id: '1',
+ name: 'Test 1',
+ workflowId,
+ updatedAt: '2023-01-01T00:00:00.000Z',
+ createdAt: '2023-01-01T00:00:00.000Z',
+ annotationTagId: 'tag1',
+ },
+ {
+ id: '2',
+ name: 'Test 2',
+ workflowId,
+ updatedAt: '2023-01-02T00:00:00.000Z',
+ createdAt: '2023-01-01T00:00:00.000Z',
+ },
+ {
+ id: '3',
+ name: 'Test 3',
+ workflowId,
+ updatedAt: '2023-01-03T00:00:00.000Z',
+ createdAt: '2023-01-01T00:00:00.000Z',
+ },
+];
+
+const toast = vi.hoisted(
+ () =>
+ ({
+ showMessage: vi.fn(),
+ showError: vi.fn(),
+ }) satisfies Partial>,
+);
+
+vi.mock('@/composables/useToast', () => ({
+ useToast: () => toast,
+}));
+
+const message = vi.hoisted(
+ () =>
+ ({
+ confirm: vi.fn(),
+ }) satisfies Partial>,
+);
+
+vi.mock('@/composables/useMessage', () => ({
+ useMessage: () => message,
+}));
-vi.mock('vue-router');
-vi.mock('@/composables/useToast');
-vi.mock('@/composables/useMessage');
describe('TestDefinitionListView', () => {
- const renderComponent = createComponentRenderer(TestDefinitionListView);
-
- let showMessageMock: Mock;
- let showErrorMock: Mock;
- let confirmMock: Mock;
- let startTestRunMock: Mock;
- let fetchTestRunsMock: Mock;
- let deleteByIdMock: Mock;
- let fetchAllMock: Mock;
-
- const mockTestDefinitions: TestDefinitionRecord[] = [
- {
- id: '1',
- name: 'Test 1',
- workflowId: 'workflow1',
- updatedAt: '2023-01-01T00:00:00.000Z',
- annotationTagId: 'tag1',
- },
- {
- id: '2',
- name: 'Test 2',
- workflowId: 'workflow1',
- updatedAt: '2023-01-02T00:00:00.000Z',
- },
- {
- id: '3',
- name: 'Test 3',
- workflowId: 'workflow1',
- updatedAt: '2023-01-03T00:00:00.000Z',
- },
- ];
-
beforeEach(() => {
- setActivePinia(createPinia());
-
- vi.mocked(useRoute).mockReturnValue(
- ref({
- params: { name: 'workflow1' },
- name: VIEWS.TEST_DEFINITION,
- }) as unknown as ReturnType,
- );
-
- vi.mocked(useRouter).mockReturnValue({
- push: vi.fn(),
- currentRoute: { value: { params: { name: 'workflow1' } } },
- } as unknown as ReturnType);
-
- showMessageMock = vi.fn();
- showErrorMock = vi.fn();
- confirmMock = vi.fn().mockResolvedValue(MODAL_CONFIRM);
- startTestRunMock = vi.fn().mockResolvedValue({ success: true });
- fetchTestRunsMock = vi.fn();
- deleteByIdMock = vi.fn();
- fetchAllMock = vi.fn().mockResolvedValue({ testDefinitions: mockTestDefinitions });
-
- vi.mocked(useToast).mockReturnValue({
- showMessage: showMessageMock,
- showError: showErrorMock,
- } as unknown as ReturnType);
-
- vi.mocked(useMessage).mockReturnValue({
- confirm: confirmMock,
- } as unknown as ReturnType);
+ createTestingPinia();
});
afterEach(() => {
vi.clearAllMocks();
});
- const renderComponentWithFeatureEnabled = async (
- { testDefinitions }: { testDefinitions: TestDefinitionRecord[] } = {
- testDefinitions: mockTestDefinitions,
- },
- ) => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.isFeatureEnabled = true;
- testDefinitionStore.fetchAll = fetchAllMock;
- testDefinitionStore.startTestRun = startTestRunMock;
- testDefinitionStore.fetchTestRuns = fetchTestRunsMock;
- testDefinitionStore.deleteById = deleteByIdMock;
- testDefinitionStore.allTestDefinitionsByWorkflowId = { workflow1: testDefinitions };
-
- const component = renderComponent({ pinia });
- await waitAllPromises();
- return { ...component, testDefinitionStore };
- };
+ it('should render loader', async () => {
+ const { getByTestId } = renderComponent({ props: { name: 'any' } });
+ expect(getByTestId('test-definition-loader')).toBeTruthy();
+ });
it('should render empty state when no tests exist', async () => {
- const { getByTestId } = await renderComponentWithFeatureEnabled({ testDefinitions: [] });
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.allTestDefinitionsByWorkflowId = {};
- expect(getByTestId('test-definition-empty-state')).toBeTruthy();
+ const { getByTestId } = renderComponent({ props: { name: 'any' } });
+ await waitFor(() => expect(getByTestId('test-definition-empty-state')).toBeTruthy());
});
it('should render tests list when tests exist', async () => {
- const { getByTestId } = await renderComponentWithFeatureEnabled();
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.allTestDefinitionsByWorkflowId[workflowId] = mockTestDefinitions;
- expect(getByTestId('test-definition-list')).toBeTruthy();
+ const { getByTestId } = renderComponent({ props: { name: workflowId } });
+
+ await waitFor(() => expect(getByTestId('test-definition-list')).toBeTruthy());
});
- it('should load initial data on mount', async () => {
- const { testDefinitionStore } = await renderComponentWithFeatureEnabled();
-
- expect(testDefinitionStore.fetchAll).toHaveBeenCalledWith({
- workflowId: 'workflow1',
- });
+ it('should load initial base on route param', async () => {
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ renderComponent({ props: { name: workflowId } });
+ expect(testDefinitionStore.fetchAll).toHaveBeenCalledWith({ workflowId });
});
it('should start test run and show success message', async () => {
- const { getByTestId } = await renderComponentWithFeatureEnabled();
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.allTestDefinitionsByWorkflowId[workflowId] = mockTestDefinitions;
+ testDefinitionStore.startTestRun.mockResolvedValueOnce({ success: true });
- const runButton = getByTestId('run-test-button-1');
- runButton.click();
- await nextTick();
+ const { getByTestId } = renderComponent({ props: { name: workflowId } });
- expect(startTestRunMock).toHaveBeenCalledWith('1');
- expect(fetchTestRunsMock).toHaveBeenCalledWith('1');
- expect(showMessageMock).toHaveBeenCalledWith({
- title: expect.any(String),
- type: 'success',
- });
+ await waitFor(() => expect(getByTestId('test-definition-list')).toBeTruthy());
+
+ const testToRun = mockTestDefinitions[0].id;
+ await userEvent.click(getByTestId(`run-test-${testToRun}`));
+
+ expect(testDefinitionStore.startTestRun).toHaveBeenCalledWith(testToRun);
+ expect(toast.showMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
+ expect(testDefinitionStore.fetchTestRuns).toHaveBeenCalledWith(testToRun);
});
it('should show error message on failed test run', async () => {
- const { getByTestId, testDefinitionStore } = await renderComponentWithFeatureEnabled();
- testDefinitionStore.startTestRun = vi.fn().mockRejectedValue(new Error('Run failed'));
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.allTestDefinitionsByWorkflowId[workflowId] = mockTestDefinitions;
+ testDefinitionStore.startTestRun.mockRejectedValueOnce(new Error('Run failed'));
- const runButton = getByTestId('run-test-button-1');
- runButton.click();
- await nextTick();
+ const { getByTestId } = renderComponent({ props: { name: workflowId } });
- expect(showErrorMock).toHaveBeenCalledWith(expect.any(Error), expect.any(String));
+ await waitFor(() => expect(getByTestId('test-definition-list')).toBeTruthy());
+
+ const testToRun = mockTestDefinitions[0].id;
+ await userEvent.click(getByTestId(`run-test-${testToRun}`));
+
+ expect(toast.showError).toHaveBeenCalledWith(expect.any(Error), expect.any(String));
});
it('should delete test and show success message', async () => {
- const { getByTestId } = await renderComponentWithFeatureEnabled();
- const deleteButton = getByTestId('delete-test-button-1');
- deleteButton.click();
- await waitAllPromises();
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.allTestDefinitionsByWorkflowId[workflowId] = mockTestDefinitions;
+ testDefinitionStore.startTestRun.mockRejectedValueOnce(new Error('Run failed'));
- expect(deleteByIdMock).toHaveBeenCalledWith('1');
- expect(showMessageMock).toHaveBeenCalledWith({
- title: expect.any(String),
- type: 'success',
+ message.confirm.mockResolvedValueOnce(MODAL_CONFIRM);
+
+ const { getByTestId } = renderComponent({
+ props: { name: workflowId },
});
+
+ await waitFor(() => expect(getByTestId('test-definition-list')).toBeTruthy());
+
+ const testToDelete = mockTestDefinitions[0].id;
+
+ const trigger = getByTestId(`test-actions-${testToDelete}`);
+ await userEvent.click(trigger);
+
+ const dropdownId = within(trigger).getByRole('button').getAttribute('aria-controls');
+ const dropdown = document.querySelector(`#${dropdownId}`);
+ expect(dropdown).toBeInTheDocument();
+
+ await userEvent.click(await within(dropdown as HTMLElement).findByText('Delete'));
+
+ expect(testDefinitionStore.deleteById).toHaveBeenCalledWith(testToDelete);
+ expect(toast.showMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
});
it('should sort tests by updated date in descending order', async () => {
- const { container } = await renderComponentWithFeatureEnabled();
+ const testDefinitionStore = mockedStore(useTestDefinitionStore);
+ testDefinitionStore.allTestDefinitionsByWorkflowId[workflowId] = mockTestDefinitions;
+
+ const { container, getByTestId } = renderComponent({ props: { name: workflowId } });
+ await waitFor(() => expect(getByTestId('test-definition-list')).toBeTruthy());
const testItems = container.querySelectorAll('[data-test-id^="test-item-"]');
expect(testItems[0].getAttribute('data-test-id')).toBe('test-item-3');
diff --git a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRootView.test.ts b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRootView.test.ts
index e39d53a8d5..6a0fe570b3 100644
--- a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRootView.test.ts
+++ b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRootView.test.ts
@@ -1,16 +1,13 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { createPinia, setActivePinia } from 'pinia';
+import { describe, it, expect, beforeEach } from 'vitest';
import { createTestingPinia } from '@pinia/testing';
import { createComponentRenderer } from '@/__tests__/render';
import TestDefinitionRootView from '../TestDefinitionRootView.vue';
-import { useRouter } from 'vue-router';
+
import { useWorkflowsStore } from '@/stores/workflows.store';
import { mockedStore } from '@/__tests__/utils';
import type { IWorkflowDb } from '@/Interface';
-import * as workflowsApi from '@/api/workflows';
-vi.mock('vue-router');
-vi.mock('@/api/workflows');
+import { waitFor } from '@testing-library/vue';
describe('TestDefinitionRootView', () => {
const renderComponent = createComponentRenderer(TestDefinitionRootView);
@@ -33,55 +30,33 @@ describe('TestDefinitionRootView', () => {
};
beforeEach(() => {
- setActivePinia(createPinia());
-
- vi.mocked(useRouter).mockReturnValue({
- currentRoute: {
- value: {
- params: {
- name: 'workflow123',
- },
- },
- },
- } as unknown as ReturnType);
-
- vi.mocked(workflowsApi.getWorkflow).mockResolvedValue({
- ...mockWorkflow,
- id: 'workflow123',
- });
+ createTestingPinia();
});
it('should initialize workflow on mount if not already initialized', async () => {
- const pinia = createTestingPinia();
const workflowsStore = mockedStore(useWorkflowsStore);
workflowsStore.workflow = mockWorkflow;
+ const newWorkflowId = 'workflow123';
- const newWorkflow = {
- ...mockWorkflow,
- id: 'workflow123',
- };
- workflowsStore.fetchWorkflow.mockResolvedValue(newWorkflow);
+ renderComponent({ props: { name: newWorkflowId } });
- renderComponent({ pinia });
-
- expect(workflowsStore.fetchWorkflow).toHaveBeenCalledWith('workflow123');
+ expect(workflowsStore.fetchWorkflow).toHaveBeenCalledWith(newWorkflowId);
});
it('should not initialize workflow if already loaded', async () => {
- const pinia = createTestingPinia();
const workflowsStore = mockedStore(useWorkflowsStore);
- workflowsStore.workflow = {
- ...mockWorkflow,
- id: 'workflow123',
- };
+ workflowsStore.workflow = mockWorkflow;
- renderComponent({ pinia });
+ renderComponent({ props: { name: mockWorkflow.id } });
expect(workflowsStore.fetchWorkflow).not.toHaveBeenCalled();
});
- it('should render router view', () => {
- const { container } = renderComponent();
- expect(container.querySelector('router-view')).toBeTruthy();
+ it('should render router view', async () => {
+ const workflowsStore = mockedStore(useWorkflowsStore);
+ workflowsStore.fetchWorkflow.mockResolvedValue(mockWorkflow);
+ const { container } = renderComponent({ props: { name: mockWorkflow.id } });
+
+ await waitFor(() => expect(container.querySelector('router-view')).toBeTruthy());
});
});
diff --git a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRunDetailView.test.ts b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRunDetailView.test.ts
deleted file mode 100644
index 63fabd6f5d..0000000000
--- a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionRunDetailView.test.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-import type { Mock } from 'vitest';
-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 TestDefinitionRunDetailView from '@/views/TestDefinition/TestDefinitionRunDetailView.vue';
-import { useRoute, useRouter } from 'vue-router';
-import { useToast } from '@/composables/useToast';
-import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
-import { useWorkflowsStore } from '@/stores/workflows.store';
-import { nextTick, ref } from 'vue';
-import { mockedStore, waitAllPromises } from '@/__tests__/utils';
-import { VIEWS } from '@/constants';
-import type { TestRunRecord } from '@/api/testDefinition.ee';
-import type { IWorkflowDb } from '@/Interface';
-
-vi.mock('vue-router');
-vi.mock('@/composables/useToast');
-
-describe('TestDefinitionRunDetailView', () => {
- const renderComponent = createComponentRenderer(TestDefinitionRunDetailView);
-
- let showErrorMock: Mock;
- let getTestRunMock: Mock;
- let fetchTestCaseExecutionsMock: Mock;
-
- const mockTestRun: TestRunRecord = {
- id: 'run1',
- status: 'completed',
- runAt: '2023-01-01T00:00:00.000Z',
- metrics: {
- accuracy: 0.95,
- precision: 0.88,
- },
- testDefinitionId: 'test1',
- createdAt: '2023-01-01T00:00:00.000Z',
- updatedAt: '2023-01-01T00:00:00.000Z',
- completedAt: '2023-01-01T00:00:00.000Z',
- };
-
- const mockTestDefinition = {
- id: 'test1',
- name: 'Test Definition 1',
- evaluationWorkflowId: 'workflow1',
- workflowId: 'workflow1',
- };
-
- const mockWorkflow = {
- id: 'workflow1',
- name: 'Evaluation Workflow',
- };
-
- const mockTestCaseExecutions = [
- { id: 'exec1', status: 'success' },
- { id: 'exec2', status: 'error' },
- ];
-
- beforeEach(() => {
- setActivePinia(createPinia());
-
- // Mock route with testId and runId
- vi.mocked(useRoute).mockReturnValue(
- ref({
- params: { testId: 'test1', runId: 'run1' },
- name: VIEWS.TEST_DEFINITION_RUNS,
- }) as unknown as ReturnType,
- );
-
- vi.mocked(useRouter).mockReturnValue({
- back: vi.fn(),
- currentRoute: { value: { params: { testId: 'test1', runId: 'run1' } } },
- resolve: vi.fn().mockResolvedValue({ href: 'test-definition-run-detail' }),
- } as unknown as ReturnType);
-
- showErrorMock = vi.fn();
- getTestRunMock = vi.fn().mockResolvedValue(mockTestRun);
- fetchTestCaseExecutionsMock = vi.fn().mockResolvedValue(mockTestCaseExecutions);
-
- vi.mocked(useToast).mockReturnValue({
- showError: showErrorMock,
- } as unknown as ReturnType);
- });
-
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- it('should load run details on mount', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.testRunsById = { run1: mockTestRun };
- testDefinitionStore.testDefinitionsById = { test1: mockTestDefinition };
- testDefinitionStore.getTestRun = getTestRunMock;
-
- const workflowsStore = mockedStore(useWorkflowsStore);
- workflowsStore.workflowsById = { workflow1: mockWorkflow as IWorkflowDb };
-
- const { getByTestId } = renderComponent({ pinia });
- await nextTick();
-
- expect(getTestRunMock).toHaveBeenCalledWith({
- testDefinitionId: 'test1',
- runId: 'run1',
- });
- // expect(fetchExecutionsMock).toHaveBeenCalled();
- expect(getByTestId('test-definition-run-detail')).toBeTruthy();
- });
-
- it('should display test run metrics', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.testRunsById = { run1: mockTestRun };
- testDefinitionStore.testDefinitionsById = { test1: mockTestDefinition };
- testDefinitionStore.getTestRun = getTestRunMock;
-
- const { container } = renderComponent({ pinia });
- await nextTick();
-
- const metricsCards = container.querySelectorAll('.summaryCard');
- expect(metricsCards.length).toBeGreaterThan(0);
- expect(container.textContent).toContain('0.95'); // Check for accuracy metric
- });
-
- it('should handle errors when loading run details', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.getTestRun = vi.fn().mockRejectedValue(new Error('Failed to load'));
-
- renderComponent({ pinia });
- await waitAllPromises();
-
- expect(showErrorMock).toHaveBeenCalledWith(expect.any(Error), 'Failed to load run details');
- });
-
- it('should navigate back when back button is clicked', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const router = useRouter();
- const { getByTestId } = renderComponent({ pinia });
- await nextTick();
-
- const backButton = getByTestId('test-definition-run-detail').querySelector('.backButton');
- backButton?.dispatchEvent(new Event('click'));
-
- expect(router.back).toHaveBeenCalled();
- });
-
- // Test loading states
- it('should show loading state while fetching data', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.getTestRun = vi
- .fn()
- .mockImplementation(async () => await new Promise(() => {})); // Never resolves
-
- const { container } = renderComponent({ pinia });
- await nextTick();
-
- expect(container.querySelector('.loading')).toBeTruthy();
- });
-
- // Test metrics display
- it('should correctly format and display all metrics', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testRunWithMultipleMetrics = {
- ...mockTestRun,
- metrics: {
- accuracy: 0.956789,
- precision: 0.887654,
- recall: 0.923456,
- f1_score: 0.901234,
- },
- };
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.testRunsById = { run1: testRunWithMultipleMetrics };
- testDefinitionStore.testDefinitionsById = { test1: mockTestDefinition };
-
- const { container } = renderComponent({ pinia });
- await nextTick();
-
- // Check if the metrics are displayed correctly with 2 decimal places
- expect(container.textContent).toContain('0.96');
- expect(container.textContent).toContain('0.89');
- expect(container.textContent).toContain('0.92');
- expect(container.textContent).toContain('0.90');
- });
-
- // Test status display
- it('should display correct status with appropriate styling', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testRunWithStatus: TestRunRecord = {
- ...mockTestRun,
- status: 'error',
- };
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
- testDefinitionStore.testRunsById = { run1: testRunWithStatus };
- testDefinitionStore.testDefinitionsById = { test1: mockTestDefinition };
-
- const { container } = renderComponent({ pinia });
- await nextTick();
-
- const statusElement = container.querySelector('.error');
- expect(statusElement).toBeTruthy();
- expect(statusElement?.textContent?.trim()).toBe('error');
- });
-
- // Test table data
- it('should correctly populate the test cases table', async () => {
- const pinia = createTestingPinia();
- setActivePinia(pinia);
-
- const testDefinitionStore = mockedStore(useTestDefinitionStore);
-
- // Mock all required store methods
- testDefinitionStore.testRunsById = { run1: mockTestRun };
- testDefinitionStore.testDefinitionsById = { test1: mockTestDefinition };
- testDefinitionStore.getTestRun = getTestRunMock;
- // Add this mock for fetchTestDefinition
- testDefinitionStore.fetchTestDefinition = vi.fn().mockResolvedValue(mockTestDefinition);
- testDefinitionStore.fetchTestCaseExecutions = fetchTestCaseExecutionsMock;
-
- const { container } = renderComponent({ pinia });
-
- // Wait for all promises to resolve
- await waitAllPromises();
-
- const tableRows = container.querySelectorAll('.el-table__row');
- expect(tableRows.length).toBe(mockTestCaseExecutions.length);
- });
-});
diff --git a/packages/editor-ui/src/views/WorkflowExecutionsView.vue b/packages/editor-ui/src/views/WorkflowExecutionsView.vue
index f2ca2cc363..07cfc4c221 100644
--- a/packages/editor-ui/src/views/WorkflowExecutionsView.vue
+++ b/packages/editor-ui/src/views/WorkflowExecutionsView.vue
@@ -125,6 +125,7 @@ async function initializeRoute() {
.replace({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflow.value.id, executionId: executions.value[0].id },
+ query: route.query,
})
.catch(() => {});
}