From 083f556f3d38cf890a02c2e47e31161bee77ae2c Mon Sep 17 00:00:00 2001 From: Brunon Blok <43315279+brun0ne@users.noreply.github.com> Date: Fri, 7 Apr 2023 18:48:42 +0000 Subject: [PATCH 1/3] fix step for disabled operations and comments --- src/core/Recipe.mjs | 2 +- src/web/App.mjs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs index 0a2e217d..01438e3a 100755 --- a/src/core/Recipe.mjs +++ b/src/core/Recipe.mjs @@ -46,7 +46,7 @@ class Recipe { module: OperationConfig[c.op].module, ingValues: c.args, breakpoint: c.breakpoint, - disabled: c.disabled || c.op === "Comment", + disabled: c.disabled, }); }); } diff --git a/src/web/App.mjs b/src/web/App.mjs index e84363f8..cd736e4f 100644 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -153,6 +153,13 @@ class App { // Remove all current indicators this.manager.recipe.updateBreakpointIndicator(false); + if (this.getRecipeConfig()[this.progress] && (this.getRecipeConfig()[this.progress].disabled || this.getRecipeConfig()[this.progress].op === "Comment")) { + // Skip disabled operations and comments + // This makes stepping through the recipe work correctly + this.progress++; + return this.bake(step); + } + this.manager.worker.bake( this.getRecipeConfig(), // The configuration of the recipe this.options, // Options set by the user From 388709037ddeaf1c3acbd7fe10d0e0d31b1db83b Mon Sep 17 00:00:00 2001 From: C85297 <95289555+C85297@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:55:17 +0100 Subject: [PATCH 2/3] fix(recipe): keep comments skipped while stepping Addresses review feedback on #1550. --- src/core/Recipe.mjs | 2 +- src/web/App.mjs | 7 --- src/web/waiters/WorkerWaiter.mjs | 21 +++++--- tests/browser/04_step.js | 87 ++++++++++++++++++++++++++++++ tests/operations/tests/Comment.mjs | 19 +++++++ 5 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 tests/browser/04_step.js diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs index 01438e3a..0a2e217d 100755 --- a/src/core/Recipe.mjs +++ b/src/core/Recipe.mjs @@ -46,7 +46,7 @@ class Recipe { module: OperationConfig[c.op].module, ingValues: c.args, breakpoint: c.breakpoint, - disabled: c.disabled, + disabled: c.disabled || c.op === "Comment", }); }); } diff --git a/src/web/App.mjs b/src/web/App.mjs index cd736e4f..e84363f8 100644 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -153,13 +153,6 @@ class App { // Remove all current indicators this.manager.recipe.updateBreakpointIndicator(false); - if (this.getRecipeConfig()[this.progress] && (this.getRecipeConfig()[this.progress].disabled || this.getRecipeConfig()[this.progress].op === "Comment")) { - // Skip disabled operations and comments - // This makes stepping through the recipe work correctly - this.progress++; - return this.bake(step); - } - this.manager.worker.bake( this.getRecipeConfig(), // The configuration of the recipe this.options, // Options set by the user diff --git a/src/web/waiters/WorkerWaiter.mjs b/src/web/waiters/WorkerWaiter.mjs index 296aa956..1cfa39ee 100644 --- a/src/web/waiters/WorkerWaiter.mjs +++ b/src/web/waiters/WorkerWaiter.mjs @@ -477,17 +477,24 @@ class WorkerWaiter { recipeConfig = this.recipeConfig; if (this.step) { + const stepProgress = Number.isInteger(this.app.progress) ? this.app.progress : 0, + breakpointSearchStart = Math.min(stepProgress, recipeConfig.length); + // Remove all breakpoints from the recipe up to progress - if (nextInput.progress !== false) { - for (let i = 0; i < nextInput.progress; i++) { - if ("breakpoint" in recipeConfig[i]) { - delete recipeConfig[i].breakpoint; - } + for (let i = 0; i < breakpointSearchStart; i++) { + if ("breakpoint" in recipeConfig[i]) { + delete recipeConfig[i].breakpoint; } } - // Set a breakpoint at the next operation so we stop baking there - if (recipeConfig[this.app.progress]) recipeConfig[this.app.progress].breakpoint = true; + // Set a breakpoint at the next enabled operation so disabled operations and comments do + // not cause the rest of the recipe to bake in a single step. + for (let i = breakpointSearchStart; i < recipeConfig.length; i++) { + if (!recipeConfig[i].disabled && recipeConfig[i].op !== "Comment") { + recipeConfig[i].breakpoint = true; + break; + } + } } let transferable; diff --git a/tests/browser/04_step.js b/tests/browser/04_step.js new file mode 100644 index 00000000..a0a5a8b4 --- /dev/null +++ b/tests/browser/04_step.js @@ -0,0 +1,87 @@ +/** + * Regression tests for stepping through recipes. + * + * @copyright Crown Copyright + * @license Apache-2.0 + */ + +const utils = require("./browserUtils.js"); + +module.exports = { + before: browser => { + browser + .resizeWindow(1280, 800) + .url(browser.launchUrl) + .useCss() + .waitForElementNotPresent("#preloader", 10000) + .click("#auto-bake-label"); + }, + + "Step skips comments and disabled operations when choosing the next breakpoint": browser => { + const recipeConfig = [ + { + op: "To Upper case", + args: ["All"] + }, + { + op: "Comment", + args: ["Skip while stepping"] + }, + { + op: "ROT13", + args: [true, true, false, 13], + disabled: true + }, + { + op: "To Hex", + args: ["Space", 0] + } + ]; + + utils.setInput(browser, "a", false); + + browser + .urlHash("recipe=" + JSON.stringify(recipeConfig)) + .waitUntil(async function() { + const result = await this.execute(function() { + return document.querySelectorAll("#rec-list li.operation").length; + }); + return result.value === recipeConfig.length; + }, 5000) + .waitForElementNotVisible("#output-loader", 10000) + .click("#step") + .waitForElementNotVisible("#output-loader", 10000) + .waitUntil(async function() { + const result = await this.execute(function() { + return window.app.manager.output.outputEditorView.state.doc.toString(); + }); + return result.value === "A"; + }, 5000); + + browser.execute(function() { + return document.querySelector("#rec-list li.operation.break .op-title")?.textContent; + }, [], function({value}) { + browser.expect(value).to.equal("To Hex"); + }); + + browser + .click("#step") + .waitForElementNotVisible("#output-loader", 10000) + .waitUntil(async function() { + const result = await this.execute(function() { + return window.app.manager.output.outputEditorView.state.doc.toString(); + }); + return result.value === "41"; + }, 5000); + + browser.execute(function() { + return document.querySelector("#rec-list li.operation.break .op-title")?.textContent || null; + }, [], function({value}) { + browser.expect(value).to.equal(null); + }); + }, + + after: browser => { + browser.end(); + } +}; diff --git a/tests/operations/tests/Comment.mjs b/tests/operations/tests/Comment.mjs index 06bf58f2..efee0f03 100644 --- a/tests/operations/tests/Comment.mjs +++ b/tests/operations/tests/Comment.mjs @@ -58,6 +58,25 @@ TestRegister.addTests([ } ] }, + { + name: "Comment: preserves byte array dish type", + input: "00 ff 80 41", + expectedOutput: "00 ff 80 41", + recipeConfig: [ + { + op: "From Hex", + args: ["Space"] + }, + { + op: "Comment", + args: ["Must not translate the byte array to a string"] + }, + { + op: "To Hex", + args: ["Space", 0] + } + ] + }, { name: "Label, Comment: Complex content", input: ALL_BYTES, From 21557e0dccb7c89ea2a1dc4325ab0623f4e1f12f Mon Sep 17 00:00:00 2001 From: C85297 <95289555+C85297@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:06:12 +0100 Subject: [PATCH 3/3] listen for browser hash changes and reload recipe --- src/web/App.mjs | 2 +- src/web/Manager.mjs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/web/App.mjs b/src/web/App.mjs index e84363f8..17810c65 100644 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -828,7 +828,7 @@ class App { /** - * Handler for the history popstate event. + * Handler for history popstate and hashchange events. * Reloads parameters from the URL. * * @param {event} e diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 7cde638d..406c247f 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -125,6 +125,7 @@ class Manager { window.addEventListener("focus", this.window.windowFocus.bind(this.window)); window.addEventListener("statechange", this.app.stateChange.bind(this.app)); window.addEventListener("popstate", this.app.popState.bind(this.app)); + window.addEventListener("hashchange", this.app.popState.bind(this.app)); window.addEventListener("message", this.input.handlePostMessage.bind(this.input)); // Controls