;
@@ -33,4 +42,5 @@ export interface TestListItem {
tagName: string;
testCases: number;
execution: TestExecution;
+ fieldsIssues?: Array<{ field: string; message: string }>;
}
diff --git a/packages/editor-ui/src/components/WorkflowSelectorParameterInput/WorkflowSelectorParameterInput.vue b/packages/editor-ui/src/components/WorkflowSelectorParameterInput/WorkflowSelectorParameterInput.vue
index c54b13144d..82d607799e 100644
--- a/packages/editor-ui/src/components/WorkflowSelectorParameterInput/WorkflowSelectorParameterInput.vue
+++ b/packages/editor-ui/src/components/WorkflowSelectorParameterInput/WorkflowSelectorParameterInput.vue
@@ -258,6 +258,7 @@ const onAddResourceClicked = () => {
}"
:width="width"
:event-bus="eventBus"
+ :value="modelValue"
@update:model-value="onListItemSelected"
@filter="onSearchFilter"
@load-more="populateNextWorkflowsPage"
diff --git a/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue b/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue
index db8e7a6ef4..b164731436 100644
--- a/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue
+++ b/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue
@@ -229,6 +229,12 @@ async function handleActionItemClick(commandData: Command) {
+
+
+ {{ i18n.baseText('executionsList.evaluation') }}
+
+
+
diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue
index 4358584dba..14df87fcf4 100644
--- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue
+++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsCard.vue
@@ -167,11 +167,13 @@ function onRetryMenuItemSelect(action: string): void {
{{ locale.baseText('executionsList.test') }}
-
+
+
+
+
+ {{ locale.baseText('executionsList.evaluation') }}
+
+
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index 0c8c5d6b28..d7f38ea912 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -751,6 +751,7 @@
"executionsList.selected": "{count} execution selected: | {count} executions selected:",
"executionsList.selectAll": "Select {executionNum} finished execution | Select all {executionNum} finished executions",
"executionsList.test": "Test execution",
+ "executionsList.evaluation": "Evaluation execution",
"executionsList.showError.handleDeleteSelected.title": "Problem deleting executions",
"executionsList.showError.loadMore.title": "Problem loading executions",
"executionsList.showError.loadWorkflows.title": "Problem loading workflows",
@@ -2799,6 +2800,8 @@
"testDefinition.edit.testSaveFailed": "Failed to save test",
"testDefinition.edit.description": "Description",
"testDefinition.edit.description.description": "Add details about what this test evaluates and what success looks like",
+ "testDefinition.edit.pinNodes.noNodes.title": "No nodes to pin",
+ "testDefinition.edit.pinNodes.noNodes.description": "Your workflow needs to have at least one node to run a test",
"testDefinition.edit.tagName": "Tag name",
"testDefinition.edit.step.intro": "When running a test",
"testDefinition.edit.step.executions": "1. Fetch N past executions tagged | 1. Fetch {count} past execution tagged | 1. Fetch {count} past executions tagged",
@@ -2863,6 +2866,13 @@
"testDefinition.viewDetails": "View Details",
"testDefinition.editTest": "Edit Test",
"testDefinition.deleteTest": "Delete Test",
+ "testDefinition.deleteTest.warning": "The test and all associated runs will be removed. This cannot be undone",
+ "testDefinition.testIsRunning": "Test is running. Please wait for it to finish.",
+ "testDefinition.completeConfig": "Complete the configuration below to run the test:",
+ "testDefinition.configError.noEvaluationTag": "No evaluation tag set",
+ "testDefinition.configError.noExecutionsAddedToTag": "No executions added to this tag",
+ "testDefinition.configError.noEvaluationWorkflow": "No evaluation workflow set",
+ "testDefinition.configError.noMetrics": "No metrics set",
"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/stores/testDefinition.store.ee.test.ts b/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts
index 47f6307d9b..aed705cb91 100644
--- a/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts
+++ b/packages/editor-ui/src/stores/testDefinition.store.ee.test.ts
@@ -2,7 +2,9 @@ import { createPinia, setActivePinia } from 'pinia';
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee'; // Adjust the import path as necessary
import { useRootStore } from '@/stores/root.store';
import { usePostHog } from '@/stores/posthog.store';
+import { useAnnotationTagsStore } from '@/stores/tags.store';
import type { TestDefinitionRecord, TestRunRecord } from '@/api/testDefinition.ee';
+import { mockedStore } from '@/__tests__/utils';
const {
createTestDefinition,
@@ -101,6 +103,7 @@ describe('testDefinition.store.ee', () => {
rootStoreMock = useRootStore();
posthogStoreMock = usePostHog();
+ mockedStore(useAnnotationTagsStore).fetchAll = vi.fn().mockResolvedValue([]);
getTestDefinitions.mockResolvedValue({
count: 2,
testDefinitions: [TEST_DEF_A, TEST_DEF_B],
@@ -114,6 +117,7 @@ describe('testDefinition.store.ee', () => {
getTestRun.mockResolvedValue(TEST_RUN);
startTestRun.mockResolvedValue({ success: true });
deleteTestRun.mockResolvedValue({ success: true });
+ getTestMetrics.mockResolvedValue([TEST_METRIC]);
});
test('Initialization', () => {
@@ -276,8 +280,6 @@ describe('testDefinition.store.ee', () => {
describe('Metrics', () => {
test('Fetching Metrics for a Test Definition', async () => {
- getTestMetrics.mockResolvedValue([TEST_METRIC]);
-
const metrics = await store.fetchMetrics('1');
expect(getTestMetrics).toHaveBeenCalledWith(rootStoreMock.restApiContext, '1');
diff --git a/packages/editor-ui/src/stores/testDefinition.store.ee.ts b/packages/editor-ui/src/stores/testDefinition.store.ee.ts
index bcdbd636ad..d2c5cd94b1 100644
--- a/packages/editor-ui/src/stores/testDefinition.store.ee.ts
+++ b/packages/editor-ui/src/stores/testDefinition.store.ee.ts
@@ -5,6 +5,10 @@ import * as testDefinitionsApi from '@/api/testDefinition.ee';
import type { TestDefinitionRecord, TestRunRecord } from '@/api/testDefinition.ee';
import { usePostHog } from './posthog.store';
import { STORES, WORKFLOW_EVALUATION_EXPERIMENT } from '@/constants';
+import { useAnnotationTagsStore } from './tags.store';
+import { useI18n } from '@/composables/useI18n';
+
+type FieldIssue = { field: string; message: string };
export const useTestDefinitionStore = defineStore(
STORES.TEST_DEFINITION,
@@ -16,11 +20,13 @@ export const useTestDefinitionStore = defineStore(
const metricsById = ref>({});
const testRunsById = ref>({});
const pollingTimeouts = ref>({});
+ const fieldsIssues = ref>({});
// Store instances
const posthogStore = usePostHog();
const rootStore = useRootStore();
-
+ const tagsStore = useAnnotationTagsStore();
+ const locale = useI18n();
// Computed
const allTestDefinitions = computed(() => {
return Object.values(testDefinitionsById.value).sort((a, b) =>
@@ -100,6 +106,8 @@ export const useTestDefinitionStore = defineStore(
);
});
+ const getFieldIssues = (testId: string) => fieldsIssues.value[testId] || [];
+
// Methods
const setAllTestDefinitions = (definitions: TestDefinitionRecord[]) => {
testDefinitionsById.value = definitions.reduce(
@@ -144,13 +152,18 @@ export const useTestDefinitionStore = defineStore(
}
};
+ const fetchMetricsForAllTests = async () => {
+ const testDefinitions = Object.values(testDefinitionsById.value);
+ await Promise.all(testDefinitions.map(async (testDef) => await fetchMetrics(testDef.id)));
+ };
+
const fetchTestDefinition = async (id: string) => {
const testDefinition = await testDefinitionsApi.getTestDefinition(
rootStore.restApiContext,
id,
);
testDefinitionsById.value[testDefinition.id] = testDefinition;
-
+ updateRunFieldIssues(id);
return testDefinition;
};
@@ -178,7 +191,11 @@ export const useTestDefinitionStore = defineStore(
setAllTestDefinitions(retrievedDefinitions.testDefinitions);
fetchedAll.value = true;
- await fetchRunsForAllTests();
+ await Promise.all([
+ tagsStore.fetchAll({ withUsageCount: true }),
+ fetchRunsForAllTests(),
+ fetchMetricsForAllTests(),
+ ]);
return retrievedDefinitions;
} finally {
loading.value = false;
@@ -203,6 +220,7 @@ export const useTestDefinitionStore = defineStore(
params,
);
upsertTestDefinitions([createdDefinition]);
+ updateRunFieldIssues(createdDefinition.id);
return createdDefinition;
};
@@ -216,6 +234,7 @@ export const useTestDefinitionStore = defineStore(
updateParams,
);
upsertTestDefinitions([updatedDefinition]);
+ updateRunFieldIssues(params.id);
return updatedDefinition;
};
@@ -240,9 +259,9 @@ export const useTestDefinitionStore = defineStore(
try {
const metrics = await testDefinitionsApi.getTestMetrics(rootStore.restApiContext, testId);
metrics.forEach((metric) => {
- metricsById.value[metric.id] = metric;
+ metricsById.value[metric.id] = { ...metric, testDefinitionId: testId };
});
- return metrics;
+ return metrics.map((metric) => ({ ...metric, testDefinitionId: testId }));
} finally {
loading.value = false;
}
@@ -253,7 +272,7 @@ export const useTestDefinitionStore = defineStore(
testDefinitionId: string;
}): Promise => {
const metric = await testDefinitionsApi.createTestMetric(rootStore.restApiContext, params);
- metricsById.value[metric.id] = metric;
+ metricsById.value[metric.id] = { ...metric, testDefinitionId: params.testDefinitionId };
return metric;
};
@@ -261,7 +280,9 @@ export const useTestDefinitionStore = defineStore(
params: testDefinitionsApi.TestMetricRecord,
): Promise => {
const metric = await testDefinitionsApi.updateTestMetric(rootStore.restApiContext, params);
- metricsById.value[metric.id] = metric;
+ metricsById.value[metric.id] = { ...metric, testDefinitionId: params.testDefinitionId };
+
+ updateRunFieldIssues(params.testDefinitionId);
return metric;
};
@@ -271,6 +292,8 @@ export const useTestDefinitionStore = defineStore(
await testDefinitionsApi.deleteTestMetric(rootStore.restApiContext, params);
const { [params.id]: deleted, ...rest } = metricsById.value;
metricsById.value = rest;
+
+ updateRunFieldIssues(params.testDefinitionId);
};
// Test Runs Methods
@@ -296,6 +319,7 @@ export const useTestDefinitionStore = defineStore(
const getTestRun = async (params: { testDefinitionId: string; runId: string }) => {
const run = await testDefinitionsApi.getTestRun(rootStore.restApiContext, params);
testRunsById.value[run.id] = run;
+ updateRunFieldIssues(params.testDefinitionId);
return run;
};
@@ -346,6 +370,52 @@ export const useTestDefinitionStore = defineStore(
pollingTimeouts.value = {};
};
+ const updateRunFieldIssues = (testId: string) => {
+ const issues: FieldIssue[] = [];
+ const testDefinition = testDefinitionsById.value[testId];
+
+ if (!testDefinition) {
+ return;
+ }
+
+ if (!testDefinition.annotationTagId) {
+ issues.push({
+ field: 'tags',
+ message: locale.baseText('testDefinition.configError.noEvaluationTag'),
+ });
+ } else {
+ const tagUsageCount = tagsStore.tagsById[testDefinition.annotationTagId]?.usageCount ?? 0;
+
+ if (tagUsageCount === 0) {
+ issues.push({
+ field: 'tags',
+ message: locale.baseText('testDefinition.configError.noExecutionsAddedToTag'),
+ });
+ }
+ }
+
+ if (!testDefinition.evaluationWorkflowId) {
+ issues.push({
+ field: 'evaluationWorkflow',
+ message: locale.baseText('testDefinition.configError.noEvaluationWorkflow'),
+ });
+ }
+
+ const metrics = metricsByTestId.value[testId] || [];
+ if (metrics.filter((metric) => metric.name).length === 0) {
+ issues.push({
+ field: 'metrics',
+ message: locale.baseText('testDefinition.configError.noMetrics'),
+ });
+ }
+
+ fieldsIssues.value = {
+ ...fieldsIssues.value,
+ [testId]: issues,
+ };
+ return issues;
+ };
+
return {
// State
fetchedAll,
@@ -381,6 +451,8 @@ export const useTestDefinitionStore = defineStore(
cancelTestRun,
deleteTestRun,
cleanupPolling,
+ getFieldIssues,
+ updateRunFieldIssues,
};
},
{},
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue
index fd3b9d5d97..4da2923110 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionEditView.vue
@@ -8,19 +8,12 @@ import { useAnnotationTagsStore } from '@/stores/tags.store';
import { useDebounce } from '@/composables/useDebounce';
import { useTestDefinitionForm } from '@/components/TestDefinition/composables/useTestDefinitionForm';
-import EvaluationHeader from '@/components/TestDefinition/EditDefinition/EvaluationHeader.vue';
-import DescriptionInput from '@/components/TestDefinition/EditDefinition/DescriptionInput.vue';
-import EvaluationStep from '@/components/TestDefinition/EditDefinition/EvaluationStep.vue';
-import TagsInput from '@/components/TestDefinition/EditDefinition/TagsInput.vue';
-import WorkflowSelector from '@/components/TestDefinition/EditDefinition/WorkflowSelector.vue';
-import MetricsInput from '@/components/TestDefinition/EditDefinition/MetricsInput.vue';
+import HeaderSection from '@/components/TestDefinition/EditDefinition/sections/HeaderSection.vue';
+import RunsSection from '@/components/TestDefinition/EditDefinition/sections/RunsSection.vue';
import type { TestMetricRecord, TestRunRecord } from '@/api/testDefinition.ee';
-import Modal from '@/components/Modal.vue';
-import type { ModalState } from '@/Interface';
import { useUIStore } from '@/stores/ui.store';
-import TestRunsTable from '@/components/TestDefinition/ListRuns/TestRunsTable.vue';
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
-
+import ConfigSection from '@/components/TestDefinition/EditDefinition/sections/ConfigSection.vue';
const props = defineProps<{
testId?: string;
}>();
@@ -33,9 +26,9 @@ const toast = useToast();
const testDefinitionStore = useTestDefinitionStore();
const tagsStore = useAnnotationTagsStore();
const uiStore = useUIStore();
+
const {
state,
- fieldsIssues,
isSaving,
cancelEditing,
loadTestData,
@@ -58,11 +51,11 @@ const tagUsageCount = computed(
() => tagsStore.tagsById[state.value.tags.value[0]]?.usageCount ?? 0,
);
const hasRuns = computed(() => runs.value.length > 0);
-const nodePinningModal = ref(null);
-const modalContentWidth = ref(0);
const showConfig = ref(true);
const selectedMetric = ref('');
+const fieldsIssues = computed(() => testDefinitionStore.getFieldIssues(testId.value) ?? []);
+
onMounted(async () => {
if (!testDefinitionStore.isFeatureEnabled) {
toast.showMessage({
@@ -76,7 +69,6 @@ onMounted(async () => {
});
return; // Add early return to prevent loading if feature is disabled
}
- void tagsStore.fetchAll({ withUsageCount: true });
if (testId.value) {
await loadTestData(testId.value);
} else {
@@ -103,8 +95,8 @@ async function onSaveTest() {
}
}
-function hasIssues(key: string) {
- return fieldsIssues.value.some((issue) => issue.field === key);
+function getFieldIssues(key: string) {
+ return fieldsIssues.value.filter((issue) => issue.field === key);
}
async function onDeleteMetric(deletedMetric: Partial) {
@@ -138,6 +130,9 @@ const runs = computed(() =>
),
);
+const isRunning = computed(() => runs.value.some((run) => run.status === 'running'));
+const isRunTestEnabled = computed(() => fieldsIssues.value.length === 0 && !isRunning.value);
+
async function onDeleteRuns(toDelete: TestRunRecord[]) {
await Promise.all(
toDelete.map(async (run) => {
@@ -172,196 +167,62 @@ watch(
-
-
-
-
-
-
- {{ locale.baseText('testDefinition.edit.saving') }}
-
- {{ locale.baseText('testDefinition.edit.saved') }}
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {{ locale.baseText('testDefinition.completeConfig') }}
+ - {{ issue.message }}
+
+
+ {{ locale.baseText('testDefinition.testIsRunning') }}
+
+
+
-
-
-
-
-
-
+
-
-
- {{ locale.baseText('testDefinition.edit.step.intro') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
- {{
- locale.baseText('testDefinition.edit.selectNodes')
- }}
-
-
-
-
-
@@ -392,104 +253,4 @@ watch(
overflow-y: auto;
}
}
-
-.headerSection {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- background-color: var(--color-background-light);
- width: 100%;
-}
-
-.headerMeta {
- max-width: 50%;
-}
-
-.name {
- display: flex;
- align-items: center;
-
- .lastSaved {
- font-size: var(--font-size-s);
- color: var(--color-text-light);
- }
-}
-
-.descriptionInput {
- margin-top: var(--spacing-2xs);
-}
-
-.runs {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-m);
- flex: 1;
- padding-top: var(--spacing-3xs);
- overflow: auto;
-
- @media (min-height: 56rem) {
- margin-top: var(--spacing-2xl);
- }
-}
-
-.panelBlock {
- width: var(--evaluation-edit-panel-width);
- display: grid;
- height: 100%;
- overflow-y: auto;
- flex-shrink: 0;
- padding-bottom: var(--spacing-l);
- margin-left: var(--spacing-2xl);
- transition: width 0.2s ease;
-
- &.hidden {
- margin-left: 0;
- width: 0;
- overflow: hidden;
- flex-shrink: 1;
- }
-
- .noRuns & {
- overflow-y: initial;
- }
-}
-
-.panelIntro {
- font-size: var(--font-size-m);
- color: var(--color-text-dark);
-
- justify-self: center;
- position: relative;
- display: block;
-}
-
-.step {
- position: relative;
-
- &:not(:first-child) {
- margin-top: var(--spacing-m);
- }
-}
-
-.introArrow {
- --arrow-height: 1.5rem;
- margin-bottom: -1rem;
- justify-self: center;
-}
-
-.evaluationArrows {
- --arrow-height: 22rem;
- display: flex;
- justify-content: space-between;
- width: 100%;
- max-width: 80%;
- margin: 0 auto;
- margin-bottom: -100%;
- z-index: 0;
-}
-
-.controls {
- display: flex;
- gap: var(--spacing-s);
-}
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue
index 64cf826111..5742060df4 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionListView.vue
@@ -1,15 +1,20 @@
@@ -186,12 +259,9 @@ onMounted(() => {
diff --git a/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunsListView.vue b/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunsListView.vue
index b367f0ba4f..1f6ac49d96 100644
--- a/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunsListView.vue
+++ b/packages/editor-ui/src/views/TestDefinition/TestDefinitionRunsListView.vue
@@ -4,10 +4,11 @@ import { useRouter } from 'vue-router';
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
import type { TestRunRecord } from '@/api/testDefinition.ee';
import TestRunsTable from '@/components/TestDefinition/ListRuns/TestRunsTable.vue';
-import { VIEWS } from '@/constants';
+import { MODAL_CONFIRM, VIEWS } from '@/constants';
import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast';
import { useUIStore } from '@/stores/ui.store';
+import { useMessage } from '@/composables/useMessage';
const router = useRouter();
const testDefinitionStore = useTestDefinitionStore();
@@ -75,6 +76,19 @@ async function runTest() {
}
async function onDeleteRuns(runsToDelete: TestRunRecord[]) {
+ const { confirm } = useMessage();
+
+ const deleteConfirmed = await confirm(locale.baseText('testDefinition.deleteTest'), {
+ type: 'warning',
+ confirmButtonText: locale.baseText(
+ 'settings.log-streaming.destinationDelete.confirmButtonText',
+ ),
+ cancelButtonText: locale.baseText('settings.log-streaming.destinationDelete.cancelButtonText'),
+ });
+
+ if (deleteConfirmed !== MODAL_CONFIRM) {
+ return;
+ }
await Promise.all(
runsToDelete.map(async (run) => {
await testDefinitionStore.deleteTestRun({ testDefinitionId: testId.value, runId: run.id });
@@ -98,7 +112,7 @@ onMounted(async () => {
-
+
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 3e85f86425..9824dda7d9 100644
--- a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionEditView.test.ts
+++ b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionEditView.test.ts
@@ -147,21 +147,6 @@ describe('TestDefinitionEditView', () => {
expect(createTestMock).toHaveBeenCalled();
});
- it('should update test and show success message on save if testId is present', async () => {
- vi.mocked(useRoute).mockReturnValue({
- params: { testId: '1' },
- name: VIEWS.TEST_DEFINITION_EDIT,
- } as unknown as ReturnType );
-
- const { getByTestId } = renderComponentWithFeatureEnabled();
-
- const saveButton = getByTestId('run-test-button');
- saveButton.click();
- await nextTick();
-
- expect(updateTestMock).toHaveBeenCalledWith('1');
- });
-
it('should show error message on failed test creation', async () => {
createTestMock.mockRejectedValue(new Error('Save failed'));
@@ -180,53 +165,39 @@ describe('TestDefinitionEditView', () => {
expect(showErrorMock).toHaveBeenCalledWith(expect.any(Error), expect.any(String));
});
- it('should display "Save Test" button when editing test without eval workflow and tags', async () => {
+ 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 { getByTestId } = renderComponentWithFeatureEnabled();
+ const { getByTestId, mockedTestDefinitionStore } = renderComponentWithFeatureEnabled();
+
+ mockedTestDefinitionStore.getFieldIssues = vi
+ .fn()
+ .mockReturnValue([{ field: 'tags', message: 'Tag is required' }]);
await nextTick();
+
const updateButton = getByTestId('run-test-button');
- expect(updateButton.textContent?.toLowerCase()).toContain('save');
- });
+ expect(updateButton.textContent?.toLowerCase()).toContain('run test');
+ expect(updateButton).toHaveClass('disabled');
- it('should display "Save Test" button when creating new test', async () => {
- vi.mocked(useRoute).mockReturnValue({
- params: {},
- name: VIEWS.NEW_TEST_DEFINITION,
- } as unknown as ReturnType);
-
- const { getByTestId } = renderComponentWithFeatureEnabled();
-
- const saveButton = getByTestId('run-test-button');
- expect(saveButton.textContent?.toLowerCase()).toContain('save test');
+ mockedTestDefinitionStore.getFieldIssues = vi.fn().mockReturnValue([]);
+ await nextTick();
+ expect(updateButton).not.toHaveClass('disabled');
});
it('should apply "has-issues" class to inputs with issues', async () => {
- vi.mocked(useTestDefinitionForm).mockReturnValue({
- ...vi.mocked(useTestDefinitionForm)(),
- fieldsIssues: ref([
- { field: 'name', message: 'Name is required' },
- { field: 'tags', message: 'Tag is required' },
- ]),
- } as unknown as ReturnType);
-
- const { container } = renderComponentWithFeatureEnabled();
-
+ const { container, mockedTestDefinitionStore } = renderComponentWithFeatureEnabled();
+ mockedTestDefinitionStore.getFieldIssues = vi
+ .fn()
+ .mockReturnValue([{ field: 'tags', message: 'Tag is required' }]);
await nextTick();
const issueElements = container.querySelectorAll('.has-issues');
expect(issueElements.length).toBeGreaterThan(0);
});
- it('should fetch all tags on mount', async () => {
- renderComponentWithFeatureEnabled();
- await nextTick();
- expect(mockedStore(useAnnotationTagsStore).fetchAll).toHaveBeenCalled();
- });
-
describe('Test Runs functionality', () => {
it('should display test runs table when runs exist', async () => {
vi.mocked(useRoute).mockReturnValue({
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 bf23b73a9e..c4b427f4bd 100644
--- a/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionListView.test.ts
+++ b/packages/editor-ui/src/views/TestDefinition/tests/TestDefinitionListView.test.ts
@@ -6,21 +6,22 @@ import { createComponentRenderer } from '@/__tests__/render';
import TestDefinitionListView from '@/views/TestDefinition/TestDefinitionListView.vue';
import { useRoute, useRouter } from 'vue-router';
import { useToast } from '@/composables/useToast';
-import { useAnnotationTagsStore } from '@/stores/tags.store';
+import { useMessage } from '@/composables/useMessage';
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
import { nextTick, ref } from 'vue';
import { mockedStore, waitAllPromises } from '@/__tests__/utils';
-import { VIEWS } from '@/constants';
+import { MODAL_CONFIRM, VIEWS } from '@/constants';
import type { TestDefinitionRecord } from '@/api/testDefinition.ee';
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;
@@ -65,6 +66,7 @@ describe('TestDefinitionListView', () => {
showMessageMock = vi.fn();
showErrorMock = vi.fn();
+ confirmMock = vi.fn().mockResolvedValue(MODAL_CONFIRM);
startTestRunMock = vi.fn().mockResolvedValue({ success: true });
fetchTestRunsMock = vi.fn();
deleteByIdMock = vi.fn();
@@ -74,6 +76,10 @@ describe('TestDefinitionListView', () => {
showMessage: showMessageMock,
showError: showErrorMock,
} as unknown as ReturnType);
+
+ vi.mocked(useMessage).mockReturnValue({
+ confirm: confirmMock,
+ } as unknown as ReturnType);
});
afterEach(() => {
@@ -89,7 +95,6 @@ describe('TestDefinitionListView', () => {
setActivePinia(pinia);
const testDefinitionStore = mockedStore(useTestDefinitionStore);
- // const tagsStore = mockedStore(useAnnotationTagsStore);
testDefinitionStore.isFeatureEnabled = true;
testDefinitionStore.fetchAll = fetchAllMock;
testDefinitionStore.startTestRun = startTestRunMock;
@@ -120,7 +125,6 @@ describe('TestDefinitionListView', () => {
expect(testDefinitionStore.fetchAll).toHaveBeenCalledWith({
workflowId: 'workflow1',
});
- expect(mockedStore(useAnnotationTagsStore).fetchAll).toHaveBeenCalled();
});
it('should start test run and show success message', async () => {
@@ -150,13 +154,12 @@ describe('TestDefinitionListView', () => {
});
it('should delete test and show success message', async () => {
- const { getByTestId, testDefinitionStore } = await renderComponentWithFeatureEnabled();
-
+ const { getByTestId } = await renderComponentWithFeatureEnabled();
const deleteButton = getByTestId('delete-test-button-1');
deleteButton.click();
- await nextTick();
+ await waitAllPromises();
- expect(testDefinitionStore.deleteById).toHaveBeenCalledWith('1');
+ expect(deleteByIdMock).toHaveBeenCalledWith('1');
expect(showMessageMock).toHaveBeenCalledWith({
title: expect.any(String),
type: 'success',
|