Files
JS-MOCHA-SE-AL/qkf/stepRegistry.js
2026-02-08 20:37:34 -06:00

88 lines
2.9 KiB
JavaScript

// qkf/stepRegistry.js
function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function normalizeText(t) {
return String(t || "").trim().replace(/\s+/g, " ");
}
function patternToRegex(pattern) {
const parts = String(pattern).split("{string}");
const escaped = parts.map((p) => escapeRegex(normalizeText(p)));
const joined = escaped.join("\\s+(.+?)\\s+");
const final = "^" + joined.replace(/\s+/g, "\\s+") + "$";
return new RegExp(final, "i");
}
function createStepRegistry(initialCtx = {}) {
const defs = [];
function register(pattern, fn, meta = {}) {
defs.push({
pattern: String(pattern),
regex: patternToRegex(pattern),
fn,
meta,
});
}
function WHEN(pattern, fn) { register(pattern, fn, { keyword: "WHEN" }); }
function AND(pattern, fn) { register(pattern, fn, { keyword: "AND" }); }
function THEN(pattern, fn) { register(pattern, fn, { keyword: "THEN" }); }
function GIVEN(pattern, fn){ register(pattern, fn, { keyword: "GIVEN" }); }
function BUT(pattern, fn) { register(pattern, fn, { keyword: "BUT" }); }
async function run(stepText, extraCtx = {}) {
const text = normalizeText(stepText);
for (const d of defs) {
const m = d.regex.exec(text);
if (!m) continue;
const captures = m.slice(1).map((x) => normalizeText(x));
const ctx = { ...initialCtx, ...extraCtx };
// ✅ Begin step tracking so QKF calls are awaited even if step defs don't `await`
if (ctx.qkf && typeof ctx.qkf.__beginStep === "function") ctx.qkf.__beginStep();
try {
// Step defs get (...captures, ctx)
const res = d.fn.apply(null, [...captures, ctx]);
// Await the step function itself (if it's async)
await res;
// ✅ Then await all QKF actions triggered during the step
if (ctx.qkf && typeof ctx.qkf.__endStep === "function") await ctx.qkf.__endStep();
return;
} catch (err) {
// try to flush pending actions so failures surface cleanly
try {
if (ctx.qkf && typeof ctx.qkf.__endStep === "function") await ctx.qkf.__endStep();
} catch (_) {
// ignore secondary errors
}
throw err;
}
}
const available = defs.map((d) => `- ${d.pattern}`).join("\n");
throw new Error(
`No step definition matched:\n "${text}"\n\nAvailable definitions:\n${available}`
);
}
return {
defs,
register,
run,
WHEN,
AND,
THEN,
GIVEN,
BUT,
};
}
module.exports = { createStepRegistry };