186 lines
10 KiB
JavaScript
186 lines
10 KiB
JavaScript
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 = normalizeTaskButtonList(inspectorForm.value)");
|
|
expect(source).toContain("data.services = normalizeServiceList(data.services)");
|
|
expect(source).toContain("data.buttons = normalizeTaskButtonList(data)");
|
|
});
|
|
|
|
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, option.id)");
|
|
expect(source).toContain('@change="onTaskButtonSelectionChange(option.id, $event.target.checked)"');
|
|
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(() =>");
|
|
expect(source).toContain("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("<span>Run</span>");
|
|
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('<option value="" disabled>Select lane</option>');
|
|
expect(source).toContain('<option value="" disabled>Select vehicle type</option>');
|
|
});
|
|
|
|
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, data.object_id)"');
|
|
expect(source).toContain('@click.stop="openTaskAttachment(attachment, task.id)"');
|
|
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("filters path editor cases to customer-visible machine start tasks", () => {
|
|
const source = studioSource();
|
|
|
|
expect(source).toContain("const selectedPathEditorLaneHasMachineRelay = computed(() =>");
|
|
expect(source).toContain("selectedSimulatorLaneRow.value?.relay_machine_id");
|
|
expect(source).toContain("const pathCaseShowsMachineStartTask = (path = {}) => {");
|
|
expect(source).toContain("path.all_visible_questions_answered === false");
|
|
expect(source).toContain('normalizeServiceList(task?.services || []).includes("MACHINE")');
|
|
expect(source).toContain(
|
|
"const machineStartPathResultList = computed(() => pathResultList.value.filter(pathCaseShowsMachineStartTask))"
|
|
);
|
|
expect(source).toContain("const pathCaseList = computed(() => machineStartPathResultList.value)");
|
|
expect(source).toContain("const PATH_OUTCOMES_CASE_LIMIT = 2048");
|
|
expect(source).toContain("path_sample_limit: PATH_OUTCOMES_CASE_LIMIT");
|
|
expect(source).toContain('data-testid="studio-path-hidden-non-machine-start-cases"');
|
|
});
|
|
|
|
it("derives path editor machine allowance from the program wheel", () => {
|
|
const source = studioSource();
|
|
|
|
expect(source).toContain(
|
|
"const pathEditorMachineAllowed = computed(() => Boolean(pathEditorProgramWheelPosition.value))"
|
|
);
|
|
expect(source).toContain('return ["MACHINE", "PROGRAM_PICKER"]');
|
|
expect(source).toContain("machine_allowed: pathEditorMachineAllowed.value");
|
|
expect(source).toContain("pathEditorMachineAllowed.value ? pathEditorTaskPayloads.value : []");
|
|
expect(source).toContain('data-testid="studio-path-editor-program"');
|
|
expect(source).not.toContain('data-testid="studio-path-editor-machine-allowed"');
|
|
expect(source).not.toContain('data-testid="studio-path-editor-reset"');
|
|
expect(source).not.toContain('data-testid="studio-path-editor-start"');
|
|
expect(source).not.toContain("const pathEditorServiceOptions = computed");
|
|
expect(source).not.toContain('class="studio-service-picker studio-path-service-picker"');
|
|
expect(source).not.toContain('v-model="pathEditorForm.services"');
|
|
});
|
|
|
|
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)"');
|
|
});
|
|
});
|