import { createTestingPinia } from "@pinia/testing"; import { getFakeRegisteredUser } from "@tests/test-data"; import { getLocalVue } from "@tests/vitest/helpers"; import { shallowMount } from "@vue/test-utils"; import flushPromises from "flush-promises"; import { describe, expect, it, vi } from "vitest"; import sampleInvocation from "@/components/Workflow/test/json/invocation.json"; import { useUserStore } from "@/stores/userStore"; import WorkflowNavigationTitle from "./WorkflowNavigationTitle.vue"; // Constants const WORKFLOW_OWNER = "test-user"; const OTHER_USER = "other-user"; const UNIMPORTABLE_WORKFLOW_ID = "invalid-workflow-id"; const UNIMPORTABLE_WORKFLOW_INSTANCE_ID = "invalid-instance-id"; const SAMPLE_WORKFLOW = { id: "workflow-id", name: "workflow-name", owner: WORKFLOW_OWNER, version: 1, }; const IMPORT_ERROR_MESSAGE = "Failed to import workflow"; const SELECTORS = { WORKFLOW_HEADING: "[data-description='workflow heading']", ACTIONS_BUTTON_GROUP: "[data-button-group]", EDIT_WORKFLOW_BUTTON: `[data-button-edit][title='Edit Workflow']`, IMPORT_WORKFLOW_BUTTON: "[data-description='import workflow button']", EXECUTE_WORKFLOW_BUTTON: "[data-description='execute workflow button']", ROUTE_TO_RERUN_BUTTON: "[data-button-rerun][title='Rerun Workflow with same inputs']", ALERT_MESSAGE: "balert-stub", }; // Mock the copyWorkflow function for importing a workflow vi.mock("./workflows.services", () => ({ copyWorkflow: vi.fn().mockImplementation(async (workflowId: string) => { if (workflowId === UNIMPORTABLE_WORKFLOW_ID) { throw new Error(IMPORT_ERROR_MESSAGE); } return SAMPLE_WORKFLOW; }), })); // Mock the workflow store to return the sample workflow vi.mock("@/stores/workflowStore", async () => { const originalModule = (await vi.importActual("@/stores/workflowStore")) as any; return { ...originalModule, useWorkflowStore: () => ({ ...originalModule.useWorkflowStore(), getStoredWorkflowByInstanceId: vi.fn().mockImplementation((instanceId: string) => { if (instanceId === UNIMPORTABLE_WORKFLOW_INSTANCE_ID) { return { ...SAMPLE_WORKFLOW, id: UNIMPORTABLE_WORKFLOW_ID }; } return SAMPLE_WORKFLOW; }), }), }; }); const localVue = getLocalVue(); /** * Mounts the WorkflowNavigationTitle component with props/stores adjusted given the parameters * @param version The version of the component to mount (`run_form` or `invocation` view) * @param ownsWorkflow Whether the user owns the workflow associated with the invocation * @param unimportableWorkflow Whether the workflow import should fail * @returns The wrapper object */ async function mountWorkflowNavigationTitle( version: "run_form" | "invocation", ownsWorkflow = true, unimportableWorkflow = false, ) { let workflowId: string; let invocation; if (version === "invocation") { workflowId = !unimportableWorkflow ? sampleInvocation.workflow_id : UNIMPORTABLE_WORKFLOW_INSTANCE_ID; invocation = { ...sampleInvocation, workflow_id: workflowId, }; } else { workflowId = !unimportableWorkflow ? SAMPLE_WORKFLOW.id : UNIMPORTABLE_WORKFLOW_INSTANCE_ID; invocation = undefined; } const wrapper = shallowMount(WorkflowNavigationTitle as object, { propsData: { invocation, workflowId, }, localVue, pinia: createTestingPinia({ createSpy: vi.fn }), }); const userStore = useUserStore(); userStore.currentUser = getFakeRegisteredUser({ username: ownsWorkflow ? WORKFLOW_OWNER : OTHER_USER, }); return { wrapper }; } describe("WorkflowNavigationTitle renders", () => { it("the workflow name in header and run button in actions; invocation version", async () => { const { wrapper } = await mountWorkflowNavigationTitle("invocation"); const heading = wrapper.find(SELECTORS.WORKFLOW_HEADING); expect(heading.text()).toContain(`Invoked Workflow: ${SAMPLE_WORKFLOW.name}`); expect(heading.text()).toContain(`(Version: ${SAMPLE_WORKFLOW.version + 1})`); const rerunButton = wrapper.find(SELECTORS.ROUTE_TO_RERUN_BUTTON); expect(rerunButton.attributes("title")).toContain("Rerun"); }); it("the workflow name in header and run button in actions; run form version", async () => { const { wrapper } = await mountWorkflowNavigationTitle("run_form"); const heading = wrapper.find(SELECTORS.WORKFLOW_HEADING); expect(heading.text()).toContain(`Workflow: ${SAMPLE_WORKFLOW.name}`); expect(heading.text()).toContain(`(Version: ${SAMPLE_WORKFLOW.version + 1})`); const runButton = wrapper.find(SELECTORS.EXECUTE_WORKFLOW_BUTTON); expect(runButton.attributes("title")).toContain("Run"); }); it("edit button if user owns the workflow", async () => { async function findEditButton(version: "invocation" | "run_form") { const { wrapper } = await mountWorkflowNavigationTitle(version); const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP); const editButton = actionsGroup.find(SELECTORS.EDIT_WORKFLOW_BUTTON); expect(editButton.attributes("to")).toBe( `/workflows/edit?id=${SAMPLE_WORKFLOW.id}&version=${SAMPLE_WORKFLOW.version}`, ); } await findEditButton("invocation"); await findEditButton("run_form"); }); it("import button instead if user does not own the workflow", async () => { async function findImportButton(version: "invocation" | "run_form") { const { wrapper } = await mountWorkflowNavigationTitle(version, false); const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP); const importButton = actionsGroup.find(SELECTORS.IMPORT_WORKFLOW_BUTTON); expect(importButton.exists()).toBe(true); } await findImportButton("invocation"); await findImportButton("run_form"); }); }); describe("Importing a workflow in WorkflowNavigationTitle", () => { // We only need to test the `invocation` version because the button is the same in both versions it("should show a confirmation dialog when the import is successful", async () => { const { wrapper } = await mountWorkflowNavigationTitle("invocation", false); const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP); const importButton = actionsGroup.find(SELECTORS.IMPORT_WORKFLOW_BUTTON); // Cannot `.trigger("click")` on `AsyncButton` because it is a stubbed custom component await importButton.props().action(); await flushPromises(); const alert = wrapper.find(SELECTORS.ALERT_MESSAGE); expect(alert.attributes("variant")).toBe("info"); expect(alert.text()).toContain(`Workflow ${SAMPLE_WORKFLOW.name} imported successfully`); }); it("should show an error dialog when the import fails", async () => { const { wrapper } = await mountWorkflowNavigationTitle("invocation", false, true); const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP); const importButton = actionsGroup.find(SELECTORS.IMPORT_WORKFLOW_BUTTON); // Cannot `.trigger("click")` on `AsyncButton` because it is a stubbed custom component await importButton.props().action(); await flushPromises(); const alert = wrapper.find(SELECTORS.ALERT_MESSAGE); expect(alert.attributes("variant")).toBe("danger"); expect(alert.text()).toContain(IMPORT_ERROR_MESSAGE); }); });