import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; const readSource = (relativePath) => readFileSync(join(process.cwd(), relativePath), "utf8"); describe("self-serve studio task editing", () => { const studioSource = () => readSource("src/views/dashboards/departmentDashboard/modules/self-serve/DepartmentSelfServeStudio.vue"); it("creates tasks with scoped runtime fields and dynamic image defaults", () => { const source = studioSource(); expect(source).toContain("const createNode = async (kind) => {"); expect(source).toContain('task: "New task"'); expect(source).toContain('gate_type: "ALWAYS"'); expect(source).toContain("lane: parseNullableInt(filters.value.lane_id) || 0"); expect(source).toContain("product: parseNullableInt(filters.value.vehicle_type_id) || 0"); expect(source).toContain("machine_type_id: parseNullableInt(filters.value.machine_type_id)"); expect(source).toContain("services: []"); expect(source).toContain("buttons: []"); }); it("normalizes task services and mapped buttons before saving inspector edits", () => { const source = studioSource(); expect(source).toContain('inspectorForm.value.gate_type ||= "ALWAYS"'); expect(source).toContain("inspectorForm.value.services = normalizeServiceList(inspectorForm.value.services)"); expect(source).toContain("inspectorForm.value.buttons = normalizeButtonList(inspectorForm.value.buttons)"); expect(source).toContain("data.services = normalizeServiceList(data.services)"); expect(source).toContain("data.buttons = normalizeButtonList(data.buttons)"); }); it("renders mapped dynamic image task buttons in the inspector", () => { const source = studioSource(); expect(source).toContain("SELF_SERVE_TASK_BUTTON_OPTIONS"); expect(source).toContain('data-testid="studio-task-button-picker"'); expect(source).toContain('v-model="inspectorForm.buttons"'); expect(source).toContain("taskButtonIsSelected(inspectorForm.buttons, option.id)"); expect(source).toContain("taskButtonSummary(rawFor(data).buttons)"); }); it("renders dynamic image previews in simulator results", () => { const source = studioSource(); expect(source).toContain("getSelfServeDynamicImageButtonsToPress(simulatorPreviewTasks.value)"); expect(source).toContain( "const simulatorDynamicImageCurrentStep = computed(() => getSelfServeCompletedDynamicImageStep(" ); expect(source).toContain("const simulatorDynamicImageUrl = computed(() => {"); expect(source).toContain("buildSelfServeDynamicImageUrl({"); expect(source).toContain("currentStep: simulatorDynamicImageCurrentStep.value"); expect(source).toContain("const buildSimulatorPayload = () => {"); expect(source).toContain('const scheduleSimulatorRun = (reason = "change", delayMs = 250) => {'); expect(source).toContain('data-testid="studio-simulator-steps"'); expect(source).toContain('data-testid="studio-simulator-dynamic-image"'); expect(source).toContain('data-testid="studio-simulator-dynamic-image-reason"'); expect(source).toContain('@error="onSimulatorDynamicImageError"'); }); it("renders the simulator as an auto-running full-width workspace", () => { const source = studioSource(); expect(source).toContain("'is-simulator-panel': activePanel === 'simulator'"); expect(source).toContain('data-testid="studio-simulator-workspace"'); expect(source).toContain("const focusSimulatorNodes = async (nodeIds = [], openInspector = false) => {"); expect(source).toContain('activePanel.value = openInspector ? "inspector" : "flow";'); expect(source).toContain('const scheduleSimulatorRun = (reason = "change", delayMs = 250) => {'); expect(source).toContain('aria-label="Simulator controls"'); expect(source).not.toContain("Run"); expect(source).not.toContain('@submit.prevent="runSimulator'); }); it("requires lane and vehicle scope with URL and simulator local persistence", () => { const source = studioSource(); expect(source).toContain("const scopeFiltersFromRoute = () => ({"); expect(source).toContain("const showScopeGuide = computed(() => ("); expect(source).toContain("const applyScopeGuideSelection = () => {"); expect(source).toContain("const writeScopeToRoute = () => {"); expect(source).toContain("const simulatorStorageKey = computed(() => ["); expect(source).toContain("const restoreSimulatorStateFromStorage = () => {"); expect(source).toContain("const persistSimulatorStateToStorage = () => {"); expect(source).toContain('data-testid="studio-scope-guide"'); expect(source).toContain('data-testid="studio-scope-apply"'); expect(source).toContain(''); expect(source).toContain(''); }); it("renders task attachments in the flow nodes and simulator without saving them into draft tasks", () => { const source = studioSource(); expect(source).toContain("const taskAttachmentList = (dataOrRaw) => {"); expect(source).toContain("const selectedTaskAttachments = computed(() => ("); expect(source).toContain("const uploadSelectedTaskAttachment = async (event) => {"); expect(source).toContain("const deleteSelectedTaskAttachment = async (attachment) => {"); expect(source).toContain('data-testid="studio-task-node-attachments"'); expect(source).toContain('data-testid="studio-task-attachment-manager"'); expect(source).toContain('data-testid="studio-task-attachment-upload"'); expect(source).toContain('class="studio-debug-attachment-list"'); expect(source).toContain('@click.stop="openTaskAttachment(attachment)"'); expect(source).toContain("delete data.attachments"); }); it("renders machine type and dynamic image filters in the studio toolbar", () => { const source = studioSource(); expect(source).toContain('dynamic_image_id: ""'); expect(source).toContain('data-testid="studio-filter-machine-type"'); expect(source).toContain('data-testid="studio-filter-dynamic-image"'); expect(source).toContain("lookupRows('dynamic_images')"); expect(source).toContain( 'matchesScopeFilter(node, "machine_type", normalizeFilterValue(filters.value.machine_type_id))' ); expect(source).toContain( 'matchesScopeFilter(node, "dynamic_image", normalizeFilterValue(filters.value.dynamic_image_id))' ); expect(source).toContain("laneLookupForNode(node)?.machine_type_id"); expect(source).toContain("laneLookupForNode(node)?.dynamic_image_id"); }); it("manages lanes and previews the selected lane dynamic image", () => { const source = studioSource(); expect(source).toContain('{ id: "lanes", label: "Lanes"'); expect(source).toContain("const saveLane = async () => {"); expect(source).toContain('entity: "lane"'); expect(source).toContain("selfserve_enabled: true"); expect(source).toContain("selfserve_enabled: normalizeSelfServeEnabledFlag(form?.selfserve_enabled)"); expect(source).toContain("const buildLaneDynamicImagePreviewUrl = (laneId, dynamicImageId) => {"); expect(source).toContain("dynamicImageId: normalizedDynamicImageId"); expect(source).toContain('data-testid="studio-lane-selfserve-enabled"'); expect(source).toContain('data-testid="studio-inspector-lane-selfserve-enabled"'); expect(source).toContain('data-testid="studio-lane-dynamic-image"'); expect(source).toContain('data-testid="studio-lane-dynamic-image-preview"'); expect(source).toContain('data-testid="studio-inspector-lane-dynamic-image-preview"'); }); it("supports task priority reordering from the task panel", () => { const source = studioSource(); expect(source).toContain("const reorderTask = async (taskNode, direction) => {"); expect(source).toContain("order_priority: index + 1"); expect(source).toContain('action: "reorder"'); expect(source).toContain('@click="reorderTask(node, -1)"'); expect(source).toContain('@click="reorderTask(node, 1)"'); }); });