This is an automated email from the ASF dual-hosted git repository. yasithdev pushed a commit to branch feat/generic-experiment-launcher in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
commit 91509ab9cf18d22be88ef0115c9d1ab96ac14c1a Author: yasithdev <[email protected]> AuthorDate: Fri Apr 24 22:39:31 2026 -0400 feat(launcher): InterfacePicker verb cards --- .../js/components/launch/InterfacePicker.vue | 34 +++++++++++++++++++ .../unit/components/launch/InterfacePicker.spec.ts | 38 ++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue new file mode 100644 index 000000000..5216b013a --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/InterfacePicker.vue @@ -0,0 +1,34 @@ +<template> + <div v-if="store.pickedApp" class="row g-2"> + <div + v-for="iface in store.pickedApp.interfaces" + :key="iface.name" + class="col-6 col-md-3" + data-test="iface-card" + > + <button + type="button" + class="card w-100 text-start p-2" + :class="{ 'border-primary': store.draft.interface_name === iface.name }" + :data-test="`iface-card-${iface.name}`" + @click="store.pickInterface(iface.name)" + > + <code class="d-block fw-bold">{{ iface.name }}</code> + <small class="text-muted" :data-test="`iface-sig-${iface.name}`"> + ({{ formatList(iface.inputs) }}) → {{ formatList(iface.outputs) || "void" }} + </small> + </button> + </div> + </div> +</template> + +<script setup lang="ts"> +import type { IODescriptor } from "django-airavata-common-ui/js/stores/launch-types"; +import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch"; + +const store = useLaunchStore(); + +function formatList(io: IODescriptor[]): string { + return io.map((x) => `${x.name}: ${x.type}`).join(", "); +} +</script> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/InterfacePicker.spec.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/InterfacePicker.spec.ts new file mode 100644 index 000000000..201db1b9a --- /dev/null +++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/InterfacePicker.spec.ts @@ -0,0 +1,38 @@ +import { mount } from "@vue/test-utils"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, it } from "vitest"; +import InterfacePicker from "../../../../js/components/launch/InterfacePicker.vue"; +import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch"; + +const APP = { + app_id: "namd", name: "NAMD", category: "MD", + content: { kind: "github" as const, url: "g" }, + interfaces: [ + { name: "compile", inputs: [], outputs: [] }, + { name: "run", inputs: [{ name: "x", type: "int" as const, required: true }], outputs: [] }, + ], +}; + +describe("InterfacePicker", () => { + beforeEach(() => { + setActivePinia(createPinia()); + useLaunchStore().pickApp(APP); + }); + + it("renders one card per interface", () => { + const w = mount(InterfacePicker); + expect(w.findAll("[data-test='iface-card']")).toHaveLength(2); + }); + + it("clicking a card sets interface_name in the store", async () => { + const w = mount(InterfacePicker); + await w.find("[data-test='iface-card-run']").trigger("click"); + expect(useLaunchStore().draft.interface_name).toBe("run"); + }); + + it("renders an input/output signature line per card", () => { + const w = mount(InterfacePicker); + const sig = w.find("[data-test='iface-sig-run']").text(); + expect(sig).toContain("x: int"); + }); +});
