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 c5f97a3a426cc0dea70a1366e0d29179835f6e43
Author: yasithdev <[email protected]>
AuthorDate: Fri Apr 24 22:21:53 2026 -0400

    feat(launcher): WizardTabs with strict-forward gating
---
 .../js/components/launch/WizardTabs.vue            | 37 ++++++++++++++++++++
 .../unit/components/launch/WizardTabs.spec.ts      | 40 ++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/WizardTabs.vue
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/WizardTabs.vue
new file mode 100644
index 000000000..7d2b4f76d
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/WizardTabs.vue
@@ -0,0 +1,37 @@
+<template>
+  <div class="nav nav-tabs mb-3" role="tablist">
+    <button
+      v-for="t in tabs"
+      :key="t.idx"
+      role="tab"
+      type="button"
+      class="nav-link"
+      :class="{ active: t.idx === active }"
+      :disabled="t.disabled"
+      @click="onClick(t.idx, t.disabled)"
+    >
+      {{ t.idx }} ยท {{ t.label }}
+    </button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch";
+
+defineProps<{ active: 1 | 2 | 3 }>();
+const emit = defineEmits<{ "update:active": [n: 1 | 2 | 3] }>();
+
+const store = useLaunchStore();
+
+const tabs = computed(() => [
+  { idx: 1 as const, label: "Application & Inputs", disabled: false },
+  { idx: 2 as const, label: "Runtime", disabled: !store.tab1Valid },
+  { idx: 3 as const, label: "Review & Launch", disabled: !store.tab1Valid || 
!store.tab2Valid },
+]);
+
+function onClick(n: 1 | 2 | 3, disabled: boolean) {
+  if (disabled) return;
+  emit("update:active", n);
+}
+</script>
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/WizardTabs.spec.ts
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/WizardTabs.spec.ts
new file mode 100644
index 000000000..41ebbab5f
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/WizardTabs.spec.ts
@@ -0,0 +1,40 @@
+import { mount } from "@vue/test-utils";
+import { setActivePinia, createPinia } from "pinia";
+import { beforeEach, describe, expect, it } from "vitest";
+import WizardTabs from "../../../../js/components/launch/WizardTabs.vue";
+import { useLaunchStore } from "django-airavata-common-ui/js/stores/launch";
+
+describe("WizardTabs", () => {
+  beforeEach(() => setActivePinia(createPinia()));
+
+  function makeMount(active: 1 | 2 | 3 = 1) {
+    return mount(WizardTabs, { props: { active }, global: { stubs: { 
transition: false } } });
+  }
+
+  it("renders three tab buttons", () => {
+    const w = makeMount();
+    expect(w.findAll("button[role='tab']")).toHaveLength(3);
+  });
+
+  it("disables tabs 2 and 3 when tab 1 is invalid", () => {
+    const w = makeMount();
+    const tabs = w.findAll("button[role='tab']");
+    expect(tabs[1].attributes("disabled")).toBeDefined();
+    expect(tabs[2].attributes("disabled")).toBeDefined();
+  });
+
+  it("emits update:active when allowed tab clicked", async () => {
+    const store = useLaunchStore();
+    // Force tab 1 valid by spying on the getter
+    Object.defineProperty(store, "tab1Valid", { value: true });
+    const w = makeMount();
+    await w.findAll("button[role='tab']")[1].trigger("click");
+    expect(w.emitted("update:active")?.[0]).toEqual([2]);
+  });
+
+  it("does not emit update:active for a disabled tab", async () => {
+    const w = makeMount();
+    await w.findAll("button[role='tab']")[1].trigger("click");
+    expect(w.emitted("update:active")).toBeUndefined();
+  });
+});

Reply via email to