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 fe5298a0e0e0dbb351c7fd5857c610c8fa32a0b2
Author: yasithdev <[email protected]>
AuthorDate: Fri Apr 24 22:33:03 2026 -0400

    feat(launcher): LaunchContainer + entry-launch.ts (tabs scaffolding)
    
    - Add LaunchContainer.vue orchestrating ExperimentMetaHeader, WizardTabs,
      and the three tab panels; tab state mirrored to ?tab= query param
    - Add entry-launch.ts Vite entry (createApp + Pinia) mounting into 
#launch-app
    - Add Tab1ApplicationInputs, Tab2Runtime, Tab3ReviewLaunch empty stubs
    - Register entry-launch.ts in vite.config.js explicit entries map
    - Add LaunchContainer.spec.ts (3 tests: active tab, meta header, one panel 
shown)
    - Use v-if panels (not :hidden) so the test's attribute filter works 
correctly
    - Defensive r?.results ?? [] guards against restoreMocks clearing mock impls
---
 .../js/components/launch/Tab1ApplicationInputs.vue |  2 +
 .../js/components/launch/Tab2Runtime.vue           |  2 +
 .../js/components/launch/Tab3ReviewLaunch.vue      |  2 +
 .../js/containers/LaunchContainer.vue              | 44 ++++++++++++++++++++++
 .../django_airavata_workspace/js/entry-launch.ts   | 10 +++++
 .../unit/components/launch/LaunchContainer.spec.ts | 33 ++++++++++++++++
 .../django_airavata/apps/workspace/vite.config.js  |  1 +
 7 files changed, 94 insertions(+)

diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab1ApplicationInputs.vue
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab1ApplicationInputs.vue
new file mode 100644
index 000000000..25a445eed
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab1ApplicationInputs.vue
@@ -0,0 +1,2 @@
+<template><div /></template>
+<script setup lang="ts"></script>
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab2Runtime.vue
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab2Runtime.vue
new file mode 100644
index 000000000..25a445eed
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab2Runtime.vue
@@ -0,0 +1,2 @@
+<template><div /></template>
+<script setup lang="ts"></script>
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab3ReviewLaunch.vue
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab3ReviewLaunch.vue
new file mode 100644
index 000000000..25a445eed
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/launch/Tab3ReviewLaunch.vue
@@ -0,0 +1,2 @@
+<template><div /></template>
+<script setup lang="ts"></script>
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/LaunchContainer.vue
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/LaunchContainer.vue
new file mode 100644
index 000000000..d9a1d0809
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/LaunchContainer.vue
@@ -0,0 +1,44 @@
+<template>
+  <div class="container-fluid">
+    <ExperimentMetaHeader :projects="projects" />
+    <WizardTabs :active="active" @update:active="onChangeTab" />
+    <div data-test="active-tab" :data-active="active" />
+    <section v-if="active === 1" role="tabpanel">
+      <Tab1ApplicationInputs />
+    </section>
+    <section v-if="active === 2" role="tabpanel">
+      <Tab2Runtime />
+    </section>
+    <section v-if="active === 3" role="tabpanel">
+      <Tab3ReviewLaunch />
+    </section>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from "vue";
+import { launcherService } from 
"django-airavata-common-ui/js/services/launcherService";
+import ExperimentMetaHeader from 
"../components/launch/ExperimentMetaHeader.vue";
+import WizardTabs from "../components/launch/WizardTabs.vue";
+import Tab1ApplicationInputs from 
"../components/launch/Tab1ApplicationInputs.vue";
+import Tab2Runtime from "../components/launch/Tab2Runtime.vue";
+import Tab3ReviewLaunch from "../components/launch/Tab3ReviewLaunch.vue";
+
+const active = ref<1 | 2 | 3>(1);
+const projects = ref<Array<{ project_id: string; name: string }>>([]);
+
+function onChangeTab(n: 1 | 2 | 3) {
+  active.value = n;
+  const url = new URL(window.location.href);
+  url.searchParams.set("tab", String(n));
+  window.history.replaceState({}, "", url);
+}
+
+onMounted(async () => {
+  const url = new URL(window.location.href);
+  const t = Number(url.searchParams.get("tab"));
+  if (t === 1 || t === 2 || t === 3) active.value = t as 1 | 2 | 3;
+  const r = await launcherService.listProjects();
+  projects.value = r?.results ?? [];
+});
+</script>
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-launch.ts
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-launch.ts
new file mode 100644
index 000000000..e5350ed07
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-launch.ts
@@ -0,0 +1,10 @@
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import LaunchContainer from "./containers/LaunchContainer.vue";
+
+const root = document.getElementById("launch-app");
+if (root) {
+  const app = createApp(LaunchContainer);
+  app.use(createPinia());
+  app.mount(root);
+}
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/LaunchContainer.spec.ts
 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/LaunchContainer.spec.ts
new file mode 100644
index 000000000..44d8f5e46
--- /dev/null
+++ 
b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/components/launch/LaunchContainer.spec.ts
@@ -0,0 +1,33 @@
+import { mount, flushPromises } from "@vue/test-utils";
+import { createPinia, setActivePinia } from "pinia";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import LaunchContainer from "../../../../js/containers/LaunchContainer.vue";
+
+vi.mock("django-airavata-common-ui/js/services/launcherService", () => ({
+  launcherService: {
+    listProjects: vi.fn().mockResolvedValue({ results: [] }),
+  },
+}));
+
+describe("LaunchContainer", () => {
+  beforeEach(() => setActivePinia(createPinia()));
+
+  it("starts on tab 1", async () => {
+    const w = mount(LaunchContainer);
+    await flushPromises();
+    
expect(w.find("[data-test='active-tab']").attributes("data-active")).toBe("1");
+  });
+
+  it("renders the meta header", async () => {
+    const w = mount(LaunchContainer);
+    await flushPromises();
+    expect(w.find("input[data-test='exp-name']").exists()).toBe(true);
+  });
+
+  it("only one tab panel is shown at a time", async () => {
+    const w = mount(LaunchContainer);
+    await flushPromises();
+    const visiblePanels = w.findAll("[role='tabpanel']").filter((p) => 
!p.attributes("hidden"));
+    expect(visiblePanels).toHaveLength(1);
+  });
+});
diff --git 
a/airavata-django-portal/django_airavata/apps/workspace/vite.config.js 
b/airavata-django-portal/django_airavata/apps/workspace/vite.config.js
index d0ba14c25..a73d02e6a 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/vite.config.js
+++ b/airavata-django-portal/django_airavata/apps/workspace/vite.config.js
@@ -25,5 +25,6 @@ export default defineAppConfig({
     "compute-detail": resolve(srcDir, "entry-compute-detail.js"),
     "project-overview": resolve(srcDir, "entry-project-overview.js"),
     "application-editor": resolve(srcDir, "entry-application-editor.js"),
+    launch: resolve(srcDir, "entry-launch.ts"),
   },
 });

Reply via email to