@bpmnkit/plugins 0.0.19 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/config-panel-bpmn/index.d.ts +18 -0
- package/dist/config-panel-bpmn/index.js +534 -19
- package/dist/config-panel-bpmn/util.d.ts +14 -0
- package/dist/config-panel-bpmn/util.js +21 -0
- package/dist/connector-catalog/builtin-templates.d.ts +10 -0
- package/dist/connector-catalog/builtin-templates.js +585 -0
- package/dist/connector-catalog/css.d.ts +1 -1
- package/dist/connector-catalog/css.js +282 -0
- package/dist/connector-catalog/index.d.ts +27 -14
- package/dist/connector-catalog/index.js +76 -17
- package/dist/connector-catalog/panel.d.ts +34 -0
- package/dist/connector-catalog/panel.js +223 -0
- package/dist/process-runner/css.js +237 -0
- package/dist/process-runner/index.d.ts +56 -0
- package/dist/process-runner/index.js +519 -36
- package/package.json +7 -7
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findValidationStructure, getValidationInputNames } from "@bpmnkit/core";
|
|
1
2
|
import { injectProcessRunnerStyles } from "./css.js";
|
|
2
3
|
// ── IndexedDB persistence for input variables ───────────────────────────────
|
|
3
4
|
function openRunnerDb() {
|
|
@@ -122,6 +123,27 @@ export function createProcessRunnerPlugin(options) {
|
|
|
122
123
|
let currentProcessId;
|
|
123
124
|
/** The project ID used to scope input variable persistence. */
|
|
124
125
|
let currentProjectId = null;
|
|
126
|
+
// Wrappers that prefer the caller-provided callbacks over internal IndexedDB.
|
|
127
|
+
async function persistScenarios(scenarios) {
|
|
128
|
+
if (options.onSaveScenarios)
|
|
129
|
+
return options.onSaveScenarios(scenarios);
|
|
130
|
+
return saveScenarios(currentProjectId, scenarios);
|
|
131
|
+
}
|
|
132
|
+
async function fetchScenarios() {
|
|
133
|
+
if (options.onLoadScenarios)
|
|
134
|
+
return options.onLoadScenarios();
|
|
135
|
+
return loadScenarios(currentProjectId);
|
|
136
|
+
}
|
|
137
|
+
async function persistInputVars(vars) {
|
|
138
|
+
if (options.onSaveInputVars)
|
|
139
|
+
return options.onSaveInputVars(vars);
|
|
140
|
+
return saveInputVars(currentProjectId, vars);
|
|
141
|
+
}
|
|
142
|
+
async function fetchInputVars() {
|
|
143
|
+
if (options.onLoadInputVars)
|
|
144
|
+
return options.onLoadInputVars();
|
|
145
|
+
return loadInputVars(currentProjectId);
|
|
146
|
+
}
|
|
125
147
|
/** Pending step resolvers — each represents a paused beforeComplete call. */
|
|
126
148
|
const stepQueue = [];
|
|
127
149
|
/** Accumulated FEEL expression evaluations for the current run. */
|
|
@@ -146,6 +168,11 @@ export function createProcessRunnerPlugin(options) {
|
|
|
146
168
|
let lastChaosRunCompleted = null;
|
|
147
169
|
/** Input variables configured by the user (persisted in IndexedDB). */
|
|
148
170
|
const inputVars = [];
|
|
171
|
+
/** Which scenario is open in the editor (null = list view). */
|
|
172
|
+
let editingScenarioId = null;
|
|
173
|
+
/** Element ID last clicked in the canvas while editing a scenario. */
|
|
174
|
+
let focusedElementId = null;
|
|
175
|
+
const MOCKABLE_TASK_TYPES = new Set(["serviceTask", "sendTask", "businessRuleTask", "userTask"]);
|
|
149
176
|
const toolbarEl = document.createElement("div");
|
|
150
177
|
toolbarEl.className = "bpmnkit-runner-toolbar";
|
|
151
178
|
/** Entry button placed in the HUD action bar (styled by initEditorHud). */
|
|
@@ -170,12 +197,14 @@ export function createProcessRunnerPlugin(options) {
|
|
|
170
197
|
const feelTabBtn = makeTabBtn("FEEL", false);
|
|
171
198
|
const errorsTabBtn = makeTabBtn("Errors", false);
|
|
172
199
|
const inputTabBtn = makeTabBtn("Input", false);
|
|
173
|
-
|
|
200
|
+
// Tests sub-tab only shown when there is no dedicated testsContainer
|
|
201
|
+
const testsTabBtn = options.testsContainer === undefined ? makeTabBtn("Tests", false) : null;
|
|
174
202
|
playTabBarEl.appendChild(varTabBtn);
|
|
175
203
|
playTabBarEl.appendChild(feelTabBtn);
|
|
176
204
|
playTabBarEl.appendChild(errorsTabBtn);
|
|
177
205
|
playTabBarEl.appendChild(inputTabBtn);
|
|
178
|
-
|
|
206
|
+
if (testsTabBtn !== null)
|
|
207
|
+
playTabBarEl.appendChild(testsTabBtn);
|
|
179
208
|
function makePaneEl(hidden) {
|
|
180
209
|
const d = document.createElement("div");
|
|
181
210
|
d.className = hidden
|
|
@@ -217,24 +246,34 @@ export function createProcessRunnerPlugin(options) {
|
|
|
217
246
|
playPanelEl.appendChild(feelPaneEl);
|
|
218
247
|
playPanelEl.appendChild(errorsPaneEl);
|
|
219
248
|
playPanelEl.appendChild(ivarsPaneEl);
|
|
220
|
-
|
|
249
|
+
// Mount tests pane into dedicated container when provided, otherwise keep as sub-tab
|
|
250
|
+
if (options.testsContainer !== undefined) {
|
|
251
|
+
testsPaneEl.classList.remove("bpmnkit-runner-play-pane--hidden");
|
|
252
|
+
options.testsContainer.appendChild(testsPaneEl);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
playPanelEl.appendChild(testsPaneEl);
|
|
256
|
+
}
|
|
221
257
|
function switchPlayTab(tab) {
|
|
222
258
|
varTabBtn.classList.toggle("bpmnkit-runner-play-tab--active", tab === "variables");
|
|
223
259
|
feelTabBtn.classList.toggle("bpmnkit-runner-play-tab--active", tab === "feel");
|
|
224
260
|
errorsTabBtn.classList.toggle("bpmnkit-runner-play-tab--active", tab === "errors");
|
|
225
261
|
inputTabBtn.classList.toggle("bpmnkit-runner-play-tab--active", tab === "input");
|
|
226
|
-
testsTabBtn
|
|
262
|
+
testsTabBtn?.classList.toggle("bpmnkit-runner-play-tab--active", tab === "tests");
|
|
227
263
|
varsPaneEl.classList.toggle("bpmnkit-runner-play-pane--hidden", tab !== "variables");
|
|
228
264
|
feelPaneEl.classList.toggle("bpmnkit-runner-play-pane--hidden", tab !== "feel");
|
|
229
265
|
errorsPaneEl.classList.toggle("bpmnkit-runner-play-pane--hidden", tab !== "errors");
|
|
230
266
|
ivarsPaneEl.classList.toggle("bpmnkit-runner-play-pane--hidden", tab !== "input");
|
|
231
|
-
testsPaneEl
|
|
267
|
+
// testsPaneEl lives in the dock when testsContainer is set — don't hide/show it here
|
|
268
|
+
if (options.testsContainer === undefined) {
|
|
269
|
+
testsPaneEl.classList.toggle("bpmnkit-runner-play-pane--hidden", tab !== "tests");
|
|
270
|
+
}
|
|
232
271
|
}
|
|
233
272
|
varTabBtn.addEventListener("click", () => switchPlayTab("variables"));
|
|
234
273
|
feelTabBtn.addEventListener("click", () => switchPlayTab("feel"));
|
|
235
274
|
errorsTabBtn.addEventListener("click", () => switchPlayTab("errors"));
|
|
236
275
|
inputTabBtn.addEventListener("click", () => switchPlayTab("input"));
|
|
237
|
-
testsTabBtn
|
|
276
|
+
testsTabBtn?.addEventListener("click", () => switchPlayTab("tests"));
|
|
238
277
|
function computeStateAt(idx) {
|
|
239
278
|
const vars = new Map();
|
|
240
279
|
const feels = [];
|
|
@@ -450,6 +489,25 @@ export function createProcessRunnerPlugin(options) {
|
|
|
450
489
|
}
|
|
451
490
|
function renderInputVars() {
|
|
452
491
|
clearEl(ivarsPaneEl);
|
|
492
|
+
// ── Validation hints ────────────────────────────────────────────────────
|
|
493
|
+
if (options.getValidationDmn && options.getDefinitions) {
|
|
494
|
+
const defs = options.getDefinitions();
|
|
495
|
+
const startEvent = defs?.processes[0]?.flowElements.find((e) => e.type === "startEvent");
|
|
496
|
+
if (startEvent && defs) {
|
|
497
|
+
// getDefinitions returns a minimal interface; cast to BpmnDefinitions-compatible shape
|
|
498
|
+
const structure = findValidationStructure(defs, startEvent.id);
|
|
499
|
+
if (structure) {
|
|
500
|
+
const dmnXml = options.getValidationDmn(structure.decisionId);
|
|
501
|
+
const names = dmnXml ? getValidationInputNames(dmnXml) : [];
|
|
502
|
+
if (names.length > 0) {
|
|
503
|
+
const hintsEl = document.createElement("div");
|
|
504
|
+
hintsEl.className = "bpmnkit-runner-play-ivar-hints";
|
|
505
|
+
hintsEl.innerHTML = `<span class="bpmnkit-runner-play-ivar-hints-label">Expected:</span> ${names.map((n) => `<span class="bpmnkit-runner-play-ivar-hint-chip">${n}</span>`).join("")}`;
|
|
506
|
+
ivarsPaneEl.appendChild(hintsEl);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
453
511
|
for (let i = 0; i < inputVars.length; i++) {
|
|
454
512
|
const entry = inputVars[i];
|
|
455
513
|
if (entry === undefined)
|
|
@@ -464,7 +522,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
464
522
|
const v = inputVars[i];
|
|
465
523
|
if (v !== undefined) {
|
|
466
524
|
v.name = nameInput.value;
|
|
467
|
-
void
|
|
525
|
+
void persistInputVars(inputVars);
|
|
468
526
|
}
|
|
469
527
|
});
|
|
470
528
|
const eqEl = document.createElement("span");
|
|
@@ -478,7 +536,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
478
536
|
const v = inputVars[i];
|
|
479
537
|
if (v !== undefined) {
|
|
480
538
|
v.value = valueInput.value;
|
|
481
|
-
void
|
|
539
|
+
void persistInputVars(inputVars);
|
|
482
540
|
}
|
|
483
541
|
});
|
|
484
542
|
const delBtn = document.createElement("button");
|
|
@@ -487,7 +545,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
487
545
|
delBtn.addEventListener("click", () => {
|
|
488
546
|
inputVars.splice(i, 1);
|
|
489
547
|
renderInputVars();
|
|
490
|
-
void
|
|
548
|
+
void persistInputVars(inputVars);
|
|
491
549
|
});
|
|
492
550
|
row.appendChild(nameInput);
|
|
493
551
|
row.appendChild(eqEl);
|
|
@@ -501,23 +559,129 @@ export function createProcessRunnerPlugin(options) {
|
|
|
501
559
|
addBtn.addEventListener("click", () => {
|
|
502
560
|
inputVars.push({ name: "", value: "" });
|
|
503
561
|
renderInputVars();
|
|
504
|
-
void
|
|
562
|
+
void persistInputVars(inputVars);
|
|
505
563
|
// Focus the name field of the new row
|
|
506
564
|
const rows = ivarsPaneEl.querySelectorAll(".bpmnkit-runner-play-ivar-name");
|
|
507
565
|
rows[rows.length - 1]?.focus();
|
|
508
566
|
});
|
|
509
567
|
ivarsPaneEl.appendChild(addBtn);
|
|
510
568
|
}
|
|
569
|
+
// ── Tests helpers ─────────────────────────────────────────────────────────
|
|
570
|
+
function parseVarValue(str) {
|
|
571
|
+
const t = str.trim();
|
|
572
|
+
if (!t)
|
|
573
|
+
return "";
|
|
574
|
+
try {
|
|
575
|
+
return JSON.parse(t);
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
return t;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function formatVarValue(val) {
|
|
582
|
+
return typeof val === "string" ? val : JSON.stringify(val);
|
|
583
|
+
}
|
|
584
|
+
function makeSectionTitle(text) {
|
|
585
|
+
const el = document.createElement("div");
|
|
586
|
+
el.className = "bpmnkit-runner-tests-section-title";
|
|
587
|
+
el.textContent = text;
|
|
588
|
+
return el;
|
|
589
|
+
}
|
|
590
|
+
/** Renders a key=value editor widget. Calls onUpdate whenever entries change. */
|
|
591
|
+
function makeVarList(vars, addLabel, onUpdate) {
|
|
592
|
+
const wrap = document.createElement("div");
|
|
593
|
+
wrap.className = "bpmnkit-runner-tests-varlist";
|
|
594
|
+
const entries = Object.entries(vars).map(([k, v]) => ({
|
|
595
|
+
key: k,
|
|
596
|
+
val: formatVarValue(v),
|
|
597
|
+
}));
|
|
598
|
+
function save() {
|
|
599
|
+
const r = {};
|
|
600
|
+
for (const e of entries) {
|
|
601
|
+
if (e.key.trim())
|
|
602
|
+
r[e.key.trim()] = parseVarValue(e.val);
|
|
603
|
+
}
|
|
604
|
+
onUpdate(r);
|
|
605
|
+
}
|
|
606
|
+
function renderList() {
|
|
607
|
+
clearEl(wrap);
|
|
608
|
+
for (let i = 0; i < entries.length; i++) {
|
|
609
|
+
const entry = entries[i];
|
|
610
|
+
if (entry === undefined)
|
|
611
|
+
continue;
|
|
612
|
+
const row = document.createElement("div");
|
|
613
|
+
row.className = "bpmnkit-runner-play-ivar-row";
|
|
614
|
+
const nameInput = document.createElement("input");
|
|
615
|
+
nameInput.className = "bpmnkit-runner-play-ivar-name";
|
|
616
|
+
nameInput.placeholder = "name";
|
|
617
|
+
nameInput.value = entry.key;
|
|
618
|
+
nameInput.addEventListener("input", () => {
|
|
619
|
+
if (entries[i] !== undefined) {
|
|
620
|
+
;
|
|
621
|
+
entries[i].key = nameInput.value;
|
|
622
|
+
save();
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
const eq = document.createElement("span");
|
|
626
|
+
eq.className = "bpmnkit-runner-play-ivar-eq";
|
|
627
|
+
eq.textContent = "=";
|
|
628
|
+
const valInput = document.createElement("input");
|
|
629
|
+
valInput.className = "bpmnkit-runner-play-ivar-value";
|
|
630
|
+
valInput.placeholder = "value";
|
|
631
|
+
valInput.value = entry.val;
|
|
632
|
+
valInput.addEventListener("input", () => {
|
|
633
|
+
if (entries[i] !== undefined) {
|
|
634
|
+
;
|
|
635
|
+
entries[i].val = valInput.value;
|
|
636
|
+
save();
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
const del = document.createElement("button");
|
|
640
|
+
del.className = "bpmnkit-runner-play-ivar-del";
|
|
641
|
+
del.textContent = "\u00D7";
|
|
642
|
+
del.addEventListener("click", () => {
|
|
643
|
+
entries.splice(i, 1);
|
|
644
|
+
renderList();
|
|
645
|
+
save();
|
|
646
|
+
});
|
|
647
|
+
row.appendChild(nameInput);
|
|
648
|
+
row.appendChild(eq);
|
|
649
|
+
row.appendChild(valInput);
|
|
650
|
+
row.appendChild(del);
|
|
651
|
+
wrap.appendChild(row);
|
|
652
|
+
}
|
|
653
|
+
const addBtn = document.createElement("button");
|
|
654
|
+
addBtn.className = "bpmnkit-runner-play-ivar-add";
|
|
655
|
+
addBtn.textContent = addLabel;
|
|
656
|
+
addBtn.addEventListener("click", () => {
|
|
657
|
+
entries.push({ key: "", val: "" });
|
|
658
|
+
renderList();
|
|
659
|
+
const inputs = wrap.querySelectorAll(".bpmnkit-runner-play-ivar-name");
|
|
660
|
+
inputs[inputs.length - 1]?.focus();
|
|
661
|
+
});
|
|
662
|
+
wrap.appendChild(addBtn);
|
|
663
|
+
}
|
|
664
|
+
renderList();
|
|
665
|
+
return wrap;
|
|
666
|
+
}
|
|
511
667
|
// ── Tests tab ────────────────────────────────────────────────────────────
|
|
512
668
|
const scenarios = [];
|
|
513
669
|
const scenarioResults = new Map();
|
|
514
670
|
function renderTests() {
|
|
671
|
+
if (editingScenarioId !== null) {
|
|
672
|
+
renderScenarioEditor();
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
renderScenarioList();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function renderScenarioList() {
|
|
515
679
|
clearEl(testsPaneEl);
|
|
516
680
|
if (options.runScenario === undefined) {
|
|
517
681
|
testsPaneEl.appendChild(emptyEl("Pass runScenario in options to enable the Tests tab."));
|
|
518
682
|
return;
|
|
519
683
|
}
|
|
520
|
-
//
|
|
684
|
+
// Header
|
|
521
685
|
const headerEl = document.createElement("div");
|
|
522
686
|
headerEl.className = "bpmnkit-runner-tests-header";
|
|
523
687
|
const runAllBtn = document.createElement("button");
|
|
@@ -535,7 +699,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
535
699
|
headerEl.appendChild(runAllBtn);
|
|
536
700
|
const addBtn = document.createElement("button");
|
|
537
701
|
addBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-add";
|
|
538
|
-
addBtn.textContent = "+ New
|
|
702
|
+
addBtn.textContent = "+ New";
|
|
539
703
|
addBtn.addEventListener("click", () => {
|
|
540
704
|
const id = `scenario-${Date.now()}`;
|
|
541
705
|
scenarios.push({
|
|
@@ -545,11 +709,12 @@ export function createProcessRunnerPlugin(options) {
|
|
|
545
709
|
mocks: {},
|
|
546
710
|
expect: {},
|
|
547
711
|
});
|
|
548
|
-
void
|
|
712
|
+
void persistScenarios(scenarios);
|
|
713
|
+
editingScenarioId = id;
|
|
714
|
+
focusedElementId = null;
|
|
549
715
|
renderTests();
|
|
550
716
|
});
|
|
551
717
|
headerEl.appendChild(addBtn);
|
|
552
|
-
// AI generate scenarios button
|
|
553
718
|
if (options.generateScenarios !== undefined) {
|
|
554
719
|
const genBtn = document.createElement("button");
|
|
555
720
|
genBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-gen";
|
|
@@ -561,14 +726,13 @@ export function createProcessRunnerPlugin(options) {
|
|
|
561
726
|
void options.generateScenarios()
|
|
562
727
|
.then((newScenarios) => {
|
|
563
728
|
scenarios.push(...newScenarios);
|
|
564
|
-
void
|
|
729
|
+
void persistScenarios(scenarios);
|
|
565
730
|
})
|
|
566
731
|
.catch(() => undefined)
|
|
567
732
|
.finally(() => renderTests());
|
|
568
733
|
});
|
|
569
734
|
headerEl.appendChild(genBtn);
|
|
570
735
|
}
|
|
571
|
-
// Import chaos findings as draft scenarios
|
|
572
736
|
if (lastChaosInjections.length > 0 && options.getJobType !== undefined) {
|
|
573
737
|
const importChaosBtn = document.createElement("button");
|
|
574
738
|
importChaosBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-chaos-import";
|
|
@@ -592,7 +756,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
592
756
|
}
|
|
593
757
|
if (newScenarios.length > 0) {
|
|
594
758
|
scenarios.push(...newScenarios);
|
|
595
|
-
void
|
|
759
|
+
void persistScenarios(scenarios);
|
|
596
760
|
renderTests();
|
|
597
761
|
}
|
|
598
762
|
});
|
|
@@ -600,7 +764,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
600
764
|
}
|
|
601
765
|
testsPaneEl.appendChild(headerEl);
|
|
602
766
|
if (scenarios.length === 0) {
|
|
603
|
-
testsPaneEl.appendChild(emptyEl("No test scenarios yet. Click + New
|
|
767
|
+
testsPaneEl.appendChild(emptyEl("No test scenarios yet. Click + New."));
|
|
604
768
|
return;
|
|
605
769
|
}
|
|
606
770
|
for (let i = 0; i < scenarios.length; i++) {
|
|
@@ -616,15 +780,17 @@ export function createProcessRunnerPlugin(options) {
|
|
|
616
780
|
const statusEl = document.createElement("span");
|
|
617
781
|
statusEl.className = "bpmnkit-runner-tests-status";
|
|
618
782
|
statusEl.textContent = result === undefined ? "\u25CB" : result.passed ? "\u2713" : "\u2717";
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
783
|
+
const nameEl = document.createElement("span");
|
|
784
|
+
nameEl.className = "bpmnkit-runner-tests-name-label";
|
|
785
|
+
nameEl.textContent = scenario.name;
|
|
786
|
+
const editBtn = document.createElement("button");
|
|
787
|
+
editBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-edit";
|
|
788
|
+
editBtn.textContent = "\u270E";
|
|
789
|
+
editBtn.title = "Edit scenario";
|
|
790
|
+
editBtn.addEventListener("click", () => {
|
|
791
|
+
editingScenarioId = scenario.id;
|
|
792
|
+
focusedElementId = null;
|
|
793
|
+
renderTests();
|
|
628
794
|
});
|
|
629
795
|
const runOneBtn = document.createElement("button");
|
|
630
796
|
runOneBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-run-one";
|
|
@@ -643,15 +809,16 @@ export function createProcessRunnerPlugin(options) {
|
|
|
643
809
|
delBtn.addEventListener("click", () => {
|
|
644
810
|
scenarios.splice(i, 1);
|
|
645
811
|
scenarioResults.delete(scenario.id);
|
|
646
|
-
void
|
|
812
|
+
void persistScenarios(scenarios);
|
|
647
813
|
renderTests();
|
|
648
814
|
});
|
|
649
815
|
rowEl.appendChild(statusEl);
|
|
650
|
-
rowEl.appendChild(
|
|
816
|
+
rowEl.appendChild(nameEl);
|
|
817
|
+
rowEl.appendChild(editBtn);
|
|
651
818
|
rowEl.appendChild(runOneBtn);
|
|
652
819
|
rowEl.appendChild(delBtn);
|
|
653
820
|
testsPaneEl.appendChild(rowEl);
|
|
654
|
-
//
|
|
821
|
+
// Failure diff
|
|
655
822
|
if (result !== undefined && !result.passed) {
|
|
656
823
|
const diffEl = document.createElement("div");
|
|
657
824
|
diffEl.className = "bpmnkit-runner-tests-diff";
|
|
@@ -671,6 +838,310 @@ export function createProcessRunnerPlugin(options) {
|
|
|
671
838
|
}
|
|
672
839
|
}
|
|
673
840
|
}
|
|
841
|
+
function renderScenarioEditor() {
|
|
842
|
+
clearEl(testsPaneEl);
|
|
843
|
+
const scenario = scenarios.find((s) => s.id === editingScenarioId);
|
|
844
|
+
if (scenario === undefined) {
|
|
845
|
+
editingScenarioId = null;
|
|
846
|
+
renderScenarioList();
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const result = scenarioResults.get(scenario.id);
|
|
850
|
+
// ── Editor header ───────────────────────────────────────────────────────
|
|
851
|
+
const headerEl = document.createElement("div");
|
|
852
|
+
headerEl.className = "bpmnkit-runner-tests-editor-header";
|
|
853
|
+
const backBtn = document.createElement("button");
|
|
854
|
+
backBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-back";
|
|
855
|
+
backBtn.textContent = "\u2190 Back";
|
|
856
|
+
backBtn.addEventListener("click", () => {
|
|
857
|
+
editingScenarioId = null;
|
|
858
|
+
focusedElementId = null;
|
|
859
|
+
renderTests();
|
|
860
|
+
});
|
|
861
|
+
headerEl.appendChild(backBtn);
|
|
862
|
+
const nameInput = document.createElement("input");
|
|
863
|
+
nameInput.className = "bpmnkit-runner-tests-editor-name";
|
|
864
|
+
nameInput.value = scenario.name;
|
|
865
|
+
nameInput.placeholder = "Scenario name";
|
|
866
|
+
nameInput.addEventListener("input", () => {
|
|
867
|
+
scenario.name = nameInput.value;
|
|
868
|
+
void persistScenarios(scenarios);
|
|
869
|
+
});
|
|
870
|
+
headerEl.appendChild(nameInput);
|
|
871
|
+
const runBtn = document.createElement("button");
|
|
872
|
+
runBtn.className = "bpmnkit-runner-btn bpmnkit-runner-tests-run-one";
|
|
873
|
+
runBtn.textContent = "\u25B6 Run";
|
|
874
|
+
runBtn.title = "Run this scenario";
|
|
875
|
+
if (options.runScenario !== undefined) {
|
|
876
|
+
runBtn.addEventListener("click", () => {
|
|
877
|
+
runBtn.disabled = true;
|
|
878
|
+
void options.runScenario(scenario).then((r) => {
|
|
879
|
+
scenarioResults.set(scenario.id, r);
|
|
880
|
+
renderTests();
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
runBtn.disabled = true;
|
|
886
|
+
}
|
|
887
|
+
headerEl.appendChild(runBtn);
|
|
888
|
+
if (result !== undefined) {
|
|
889
|
+
const badge = document.createElement("span");
|
|
890
|
+
badge.className = `bpmnkit-runner-tests-editor-badge ${result.passed ? "bpmnkit-runner-tests-editor-badge--pass" : "bpmnkit-runner-tests-editor-badge--fail"}`;
|
|
891
|
+
badge.textContent = result.passed ? "\u2713 passed" : "\u2717 failed";
|
|
892
|
+
headerEl.appendChild(badge);
|
|
893
|
+
}
|
|
894
|
+
testsPaneEl.appendChild(headerEl);
|
|
895
|
+
// ── Start Variables ──────────────────────────────────────────────────────
|
|
896
|
+
testsPaneEl.appendChild(makeSectionTitle("Start Variables"));
|
|
897
|
+
testsPaneEl.appendChild(makeVarList(scenario.inputs ?? {}, "+ Add variable", (updated) => {
|
|
898
|
+
scenario.inputs = updated;
|
|
899
|
+
void persistScenarios(scenarios);
|
|
900
|
+
}));
|
|
901
|
+
// ── Task Mocks ──────────────────────────────────────────────────────────
|
|
902
|
+
const defs = options.getDefinitions?.() ?? null;
|
|
903
|
+
// Decisions referenced by BRTs — used both for filtering mocks and for missing-DMN warnings
|
|
904
|
+
const referencedDecisions = defs?.processes.flatMap((p) => p.flowElements.flatMap((e) => e.type === "businessRuleTask" && e.decisionId ? [e.decisionId] : [])) ?? [];
|
|
905
|
+
const missingDmns = options.getValidationDmn
|
|
906
|
+
? referencedDecisions.filter((id) => options.getValidationDmn?.(id) === null)
|
|
907
|
+
: [];
|
|
908
|
+
const mockableTasks = defs?.processes.flatMap((p) => p.flowElements.filter((e) => {
|
|
909
|
+
if (!MOCKABLE_TASK_TYPES.has(e.type))
|
|
910
|
+
return false;
|
|
911
|
+
// BRTs with calledDecision run internally via DMN — no external job, no mock needed
|
|
912
|
+
if (e.type === "businessRuleTask" && e.decisionId)
|
|
913
|
+
return false;
|
|
914
|
+
return true;
|
|
915
|
+
})) ?? [];
|
|
916
|
+
if (mockableTasks.length > 0) {
|
|
917
|
+
testsPaneEl.appendChild(makeSectionTitle("Task Outputs"));
|
|
918
|
+
const hintEl = document.createElement("div");
|
|
919
|
+
hintEl.className = "bpmnkit-runner-tests-hint";
|
|
920
|
+
hintEl.textContent = "Click a task in the diagram to configure its mock output.";
|
|
921
|
+
testsPaneEl.appendChild(hintEl);
|
|
922
|
+
for (const task of mockableTasks) {
|
|
923
|
+
const jobType = options.getJobType?.(task.id) ?? task.id;
|
|
924
|
+
const mock = scenario.mocks?.[jobType] ?? {};
|
|
925
|
+
const isFocused = focusedElementId === task.id;
|
|
926
|
+
const taskEl = document.createElement("div");
|
|
927
|
+
taskEl.className = `bpmnkit-runner-tests-task${isFocused ? " bpmnkit-runner-tests-task--focused" : ""}`;
|
|
928
|
+
taskEl.dataset.elementId = task.id;
|
|
929
|
+
const taskHeaderEl = document.createElement("div");
|
|
930
|
+
taskHeaderEl.className = "bpmnkit-runner-tests-task-header";
|
|
931
|
+
const taskNameEl = document.createElement("span");
|
|
932
|
+
taskNameEl.className = "bpmnkit-runner-tests-task-name";
|
|
933
|
+
taskNameEl.textContent = task.name ?? task.id;
|
|
934
|
+
const typeEl = document.createElement("span");
|
|
935
|
+
typeEl.className = "bpmnkit-runner-tests-task-badge";
|
|
936
|
+
typeEl.textContent = task.type.replace("Task", "");
|
|
937
|
+
taskHeaderEl.appendChild(taskNameEl);
|
|
938
|
+
taskHeaderEl.appendChild(typeEl);
|
|
939
|
+
taskEl.appendChild(taskHeaderEl);
|
|
940
|
+
// Click header to focus/unfocus
|
|
941
|
+
taskHeaderEl.addEventListener("click", () => {
|
|
942
|
+
focusedElementId = isFocused ? null : task.id;
|
|
943
|
+
renderTests();
|
|
944
|
+
});
|
|
945
|
+
if (isFocused) {
|
|
946
|
+
const bodyEl = document.createElement("div");
|
|
947
|
+
bodyEl.className = "bpmnkit-runner-tests-task-body";
|
|
948
|
+
// Output variables
|
|
949
|
+
bodyEl.appendChild(makeVarList(mock.outputs ?? {}, "+ Add output", (updated) => {
|
|
950
|
+
if (scenario.mocks === undefined)
|
|
951
|
+
scenario.mocks = {};
|
|
952
|
+
scenario.mocks[jobType] = { ...mock, outputs: updated };
|
|
953
|
+
void persistScenarios(scenarios);
|
|
954
|
+
}));
|
|
955
|
+
// Error field
|
|
956
|
+
const errorRow = document.createElement("div");
|
|
957
|
+
errorRow.className = "bpmnkit-runner-tests-error-row";
|
|
958
|
+
const errorLabel = document.createElement("label");
|
|
959
|
+
errorLabel.className = "bpmnkit-runner-tests-error-label";
|
|
960
|
+
errorLabel.textContent = "Fail with error:";
|
|
961
|
+
const errorInput = document.createElement("input");
|
|
962
|
+
errorInput.className = "bpmnkit-runner-tests-error-input";
|
|
963
|
+
errorInput.placeholder = "error message (leave blank to complete)";
|
|
964
|
+
errorInput.value = mock.error ?? "";
|
|
965
|
+
errorInput.addEventListener("input", () => {
|
|
966
|
+
if (scenario.mocks === undefined)
|
|
967
|
+
scenario.mocks = {};
|
|
968
|
+
const err = errorInput.value.trim() || undefined;
|
|
969
|
+
scenario.mocks[jobType] = { ...mock, error: err };
|
|
970
|
+
void persistScenarios(scenarios);
|
|
971
|
+
});
|
|
972
|
+
errorRow.appendChild(errorLabel);
|
|
973
|
+
errorRow.appendChild(errorInput);
|
|
974
|
+
bodyEl.appendChild(errorRow);
|
|
975
|
+
taskEl.appendChild(bodyEl);
|
|
976
|
+
}
|
|
977
|
+
testsPaneEl.appendChild(taskEl);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// ── Missing DMN warning ──────────────────────────────────────────────────
|
|
981
|
+
if (missingDmns.length > 0) {
|
|
982
|
+
const warnEl = document.createElement("div");
|
|
983
|
+
warnEl.className = "bpmnkit-runner-tests-missing-dmn";
|
|
984
|
+
warnEl.textContent = `⚠ Decision model${missingDmns.length > 1 ? "s" : ""} not found: ${missingDmns.join(", ")}. Import the DMN in the Models view.`;
|
|
985
|
+
testsPaneEl.appendChild(warnEl);
|
|
986
|
+
}
|
|
987
|
+
// ── Expected Variables ──────────────────────────────────────────────────
|
|
988
|
+
testsPaneEl.appendChild(makeSectionTitle("Expected Variables"));
|
|
989
|
+
testsPaneEl.appendChild(makeVarList(scenario.expect?.variables ?? {}, "+ Add assertion", (updated) => {
|
|
990
|
+
if (scenario.expect === undefined)
|
|
991
|
+
scenario.expect = {};
|
|
992
|
+
scenario.expect.variables = updated;
|
|
993
|
+
void persistScenarios(scenarios);
|
|
994
|
+
}));
|
|
995
|
+
// ── Failure details ─────────────────────────────────────────────────────
|
|
996
|
+
if (result !== undefined && (result.failures.length > 0 || result.errors.length > 0)) {
|
|
997
|
+
testsPaneEl.appendChild(makeSectionTitle("Last Run Failures"));
|
|
998
|
+
const diffEl = document.createElement("div");
|
|
999
|
+
diffEl.className = "bpmnkit-runner-tests-diff";
|
|
1000
|
+
for (const f of result.failures) {
|
|
1001
|
+
const row = document.createElement("div");
|
|
1002
|
+
row.className = "bpmnkit-runner-tests-diff-row";
|
|
1003
|
+
row.textContent = `${f.field}: expected ${JSON.stringify(f.expected)}, got ${JSON.stringify(f.actual)}`;
|
|
1004
|
+
diffEl.appendChild(row);
|
|
1005
|
+
}
|
|
1006
|
+
for (const e of result.errors) {
|
|
1007
|
+
const row = document.createElement("div");
|
|
1008
|
+
row.className = "bpmnkit-runner-tests-diff-row bpmnkit-runner-tests-diff-error";
|
|
1009
|
+
row.textContent = `Error${e.elementId !== undefined ? ` (${e.elementId})` : ""}: ${e.message}`;
|
|
1010
|
+
diffEl.appendChild(row);
|
|
1011
|
+
}
|
|
1012
|
+
testsPaneEl.appendChild(diffEl);
|
|
1013
|
+
}
|
|
1014
|
+
// ── Last Run Trace ───────────────────────────────────────────────────────
|
|
1015
|
+
if (result !== undefined) {
|
|
1016
|
+
const traceResult = result;
|
|
1017
|
+
testsPaneEl.appendChild(makeSectionTitle("Last Run Trace"));
|
|
1018
|
+
const traceTabsEl = document.createElement("div");
|
|
1019
|
+
traceTabsEl.className = "bpmnkit-runner-play-tabs bpmnkit-runner-tests-trace-tabs";
|
|
1020
|
+
const tracePaneEl = document.createElement("div");
|
|
1021
|
+
tracePaneEl.className = "bpmnkit-runner-tests-trace-pane";
|
|
1022
|
+
let activeTraceTab = "vars";
|
|
1023
|
+
const tVarBtn = makeTabBtn("Variables", true);
|
|
1024
|
+
const tFeelBtn = makeTabBtn("FEEL", false);
|
|
1025
|
+
const tElemBtn = makeTabBtn("Elements", false);
|
|
1026
|
+
function renderTraceTab() {
|
|
1027
|
+
clearEl(tracePaneEl);
|
|
1028
|
+
tVarBtn.className =
|
|
1029
|
+
activeTraceTab === "vars"
|
|
1030
|
+
? "bpmnkit-runner-play-tab bpmnkit-runner-play-tab--active"
|
|
1031
|
+
: "bpmnkit-runner-play-tab";
|
|
1032
|
+
tFeelBtn.className =
|
|
1033
|
+
activeTraceTab === "feel"
|
|
1034
|
+
? "bpmnkit-runner-play-tab bpmnkit-runner-play-tab--active"
|
|
1035
|
+
: "bpmnkit-runner-play-tab";
|
|
1036
|
+
tElemBtn.className =
|
|
1037
|
+
activeTraceTab === "elements"
|
|
1038
|
+
? "bpmnkit-runner-play-tab bpmnkit-runner-play-tab--active"
|
|
1039
|
+
: "bpmnkit-runner-play-tab";
|
|
1040
|
+
if (activeTraceTab === "vars") {
|
|
1041
|
+
const entries = Object.entries(traceResult.finalVariables);
|
|
1042
|
+
if (entries.length === 0) {
|
|
1043
|
+
tracePaneEl.appendChild(emptyEl("No variables."));
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
for (const [name, value] of entries) {
|
|
1047
|
+
const row = document.createElement("div");
|
|
1048
|
+
row.className = "bpmnkit-runner-play-var-row";
|
|
1049
|
+
const nameEl = document.createElement("span");
|
|
1050
|
+
nameEl.className = "bpmnkit-runner-play-var-name";
|
|
1051
|
+
nameEl.textContent = name;
|
|
1052
|
+
const valueEl = document.createElement("span");
|
|
1053
|
+
valueEl.className = "bpmnkit-runner-play-var-value";
|
|
1054
|
+
valueEl.textContent = JSON.stringify(value);
|
|
1055
|
+
row.append(nameEl, valueEl);
|
|
1056
|
+
tracePaneEl.appendChild(row);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
else if (activeTraceTab === "feel") {
|
|
1061
|
+
const evals = traceResult.feelEvals;
|
|
1062
|
+
if (evals.length === 0) {
|
|
1063
|
+
tracePaneEl.appendChild(emptyEl("No FEEL evaluations recorded."));
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
const groups = new Map();
|
|
1067
|
+
for (const ev of evals) {
|
|
1068
|
+
let arr = groups.get(ev.elementId);
|
|
1069
|
+
if (arr === undefined) {
|
|
1070
|
+
arr = [];
|
|
1071
|
+
groups.set(ev.elementId, arr);
|
|
1072
|
+
}
|
|
1073
|
+
arr.push({ property: ev.property, expression: ev.expression, result: ev.result });
|
|
1074
|
+
}
|
|
1075
|
+
for (const [elementId, evArr] of groups) {
|
|
1076
|
+
const groupEl = document.createElement("div");
|
|
1077
|
+
groupEl.className = "bpmnkit-runner-play-feel-group";
|
|
1078
|
+
const headerEl = document.createElement("div");
|
|
1079
|
+
headerEl.className = "bpmnkit-runner-play-feel-header";
|
|
1080
|
+
headerEl.textContent = elementId;
|
|
1081
|
+
groupEl.appendChild(headerEl);
|
|
1082
|
+
for (const ev of evArr) {
|
|
1083
|
+
const rowEl = document.createElement("div");
|
|
1084
|
+
rowEl.className = "bpmnkit-runner-play-feel-row";
|
|
1085
|
+
const propEl = document.createElement("div");
|
|
1086
|
+
propEl.className = "bpmnkit-runner-play-feel-prop";
|
|
1087
|
+
propEl.textContent = ev.property;
|
|
1088
|
+
const exprEl = document.createElement("code");
|
|
1089
|
+
exprEl.className = "bpmnkit-runner-play-feel-expr";
|
|
1090
|
+
exprEl.textContent = ev.expression;
|
|
1091
|
+
const resultRowEl = document.createElement("div");
|
|
1092
|
+
resultRowEl.className = "bpmnkit-runner-play-feel-result-row";
|
|
1093
|
+
const arrowEl = document.createElement("span");
|
|
1094
|
+
arrowEl.className = "bpmnkit-runner-play-feel-arrow";
|
|
1095
|
+
arrowEl.textContent = "\u2192";
|
|
1096
|
+
const resultEl = document.createElement("span");
|
|
1097
|
+
resultEl.className = "bpmnkit-runner-play-feel-result";
|
|
1098
|
+
resultEl.textContent = JSON.stringify(ev.result);
|
|
1099
|
+
resultRowEl.append(arrowEl, resultEl);
|
|
1100
|
+
rowEl.append(propEl, exprEl, resultRowEl);
|
|
1101
|
+
groupEl.appendChild(rowEl);
|
|
1102
|
+
}
|
|
1103
|
+
tracePaneEl.appendChild(groupEl);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
const elems = traceResult.visitedElements;
|
|
1109
|
+
if (elems.length === 0) {
|
|
1110
|
+
tracePaneEl.appendChild(emptyEl("No elements visited."));
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
for (let i = 0; i < elems.length; i++) {
|
|
1114
|
+
const row = document.createElement("div");
|
|
1115
|
+
row.className = "bpmnkit-runner-tests-trace-elem-row";
|
|
1116
|
+
const idxEl = document.createElement("span");
|
|
1117
|
+
idxEl.className = "bpmnkit-runner-tests-trace-elem-idx";
|
|
1118
|
+
idxEl.textContent = String(i + 1);
|
|
1119
|
+
const idEl = document.createElement("span");
|
|
1120
|
+
idEl.className = "bpmnkit-runner-tests-trace-elem-id";
|
|
1121
|
+
idEl.textContent = elems[i] ?? "";
|
|
1122
|
+
row.append(idxEl, idEl);
|
|
1123
|
+
tracePaneEl.appendChild(row);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
tVarBtn.addEventListener("click", () => {
|
|
1129
|
+
activeTraceTab = "vars";
|
|
1130
|
+
renderTraceTab();
|
|
1131
|
+
});
|
|
1132
|
+
tFeelBtn.addEventListener("click", () => {
|
|
1133
|
+
activeTraceTab = "feel";
|
|
1134
|
+
renderTraceTab();
|
|
1135
|
+
});
|
|
1136
|
+
tElemBtn.addEventListener("click", () => {
|
|
1137
|
+
activeTraceTab = "elements";
|
|
1138
|
+
renderTraceTab();
|
|
1139
|
+
});
|
|
1140
|
+
traceTabsEl.append(tVarBtn, tFeelBtn, tElemBtn);
|
|
1141
|
+
renderTraceTab();
|
|
1142
|
+
testsPaneEl.append(traceTabsEl, tracePaneEl);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
674
1145
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
675
1146
|
function getPrimaryProcessId() {
|
|
676
1147
|
return currentProcessId;
|
|
@@ -950,13 +1421,13 @@ export function createProcessRunnerPlugin(options) {
|
|
|
950
1421
|
}
|
|
951
1422
|
// Load persisted input variables and scenarios for the initial project
|
|
952
1423
|
currentProjectId = options.getProjectId?.() ?? null;
|
|
953
|
-
void
|
|
1424
|
+
void fetchInputVars().then((loaded) => {
|
|
954
1425
|
inputVars.length = 0;
|
|
955
1426
|
for (const v of loaded)
|
|
956
1427
|
inputVars.push(v);
|
|
957
1428
|
renderInputVars();
|
|
958
1429
|
});
|
|
959
|
-
void
|
|
1430
|
+
void fetchScenarios().then((loaded) => {
|
|
960
1431
|
scenarios.length = 0;
|
|
961
1432
|
for (const s of loaded)
|
|
962
1433
|
scenarios.push(s);
|
|
@@ -969,7 +1440,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
969
1440
|
scenarios.length = 0;
|
|
970
1441
|
for (const s of sidecar)
|
|
971
1442
|
scenarios.push(s);
|
|
972
|
-
void
|
|
1443
|
+
void persistScenarios(scenarios);
|
|
973
1444
|
renderTests();
|
|
974
1445
|
}
|
|
975
1446
|
});
|
|
@@ -984,13 +1455,13 @@ export function createProcessRunnerPlugin(options) {
|
|
|
984
1455
|
const pid = options.getProjectId?.() ?? null;
|
|
985
1456
|
if (pid !== currentProjectId) {
|
|
986
1457
|
currentProjectId = pid;
|
|
987
|
-
void
|
|
1458
|
+
void fetchInputVars().then((loaded) => {
|
|
988
1459
|
inputVars.length = 0;
|
|
989
1460
|
for (const v of loaded)
|
|
990
1461
|
inputVars.push(v);
|
|
991
1462
|
renderInputVars();
|
|
992
1463
|
});
|
|
993
|
-
void
|
|
1464
|
+
void fetchScenarios().then((loaded) => {
|
|
994
1465
|
scenarios.length = 0;
|
|
995
1466
|
for (const s of loaded)
|
|
996
1467
|
scenarios.push(s);
|
|
@@ -1005,7 +1476,7 @@ export function createProcessRunnerPlugin(options) {
|
|
|
1005
1476
|
for (const s of sidecar)
|
|
1006
1477
|
scenarios.push(s);
|
|
1007
1478
|
scenarioResults.clear();
|
|
1008
|
-
void
|
|
1479
|
+
void persistScenarios(scenarios);
|
|
1009
1480
|
renderTests();
|
|
1010
1481
|
}
|
|
1011
1482
|
});
|
|
@@ -1023,6 +1494,18 @@ export function createProcessRunnerPlugin(options) {
|
|
|
1023
1494
|
engine.deploy({ bpmn: defs });
|
|
1024
1495
|
if (currentInstance !== null)
|
|
1025
1496
|
cleanup();
|
|
1497
|
+
}), onAny("element:click", (evt) => {
|
|
1498
|
+
if (editingScenarioId === null)
|
|
1499
|
+
return;
|
|
1500
|
+
const typed = evt;
|
|
1501
|
+
const elementId = typed.element?.id;
|
|
1502
|
+
if (elementId === undefined)
|
|
1503
|
+
return;
|
|
1504
|
+
const elementType = typed.element?.type ?? "";
|
|
1505
|
+
if (!MOCKABLE_TASK_TYPES.has(elementType))
|
|
1506
|
+
return;
|
|
1507
|
+
focusedElementId = focusedElementId === elementId ? null : elementId;
|
|
1508
|
+
renderTests();
|
|
1026
1509
|
}));
|
|
1027
1510
|
},
|
|
1028
1511
|
uninstall() {
|