From 964de315e8af258f9b8a95a94cf0dba28766546d Mon Sep 17 00:00:00 2001 From: priyanshu-git-hub Date: Fri, 13 Nov 2026 17:06:56 +0530 Subject: [PATCH 1/2] fix(tools): invoke tool reset methods on destroy --- docs/CHANGELOG.md | 1 + src/components/modules/tools.ts | 2 +- test/cypress/tests/modules/Tools.cy.ts | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 480a12c7f..0f48d6151 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,7 @@ - `Improvement` - Removed "Narrow" ("Thin") mode. On non-mobile viewports, Plus and Block Tunes controls now use a responsive action rail beside the editor content. - `Improvement` - `paste` config supports filter functions for tag-based substitution - `Fix` - "Convert to" is not shown in the Inline Toolbar when there are no tools to convert to +- `Fix` - Invoke tool `reset()` methods on `Tools.destroy()` ### 2.31.7 diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index 618242697..a9611ac4f 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -164,7 +164,7 @@ export default class Tools extends Module { * Calls each Tool reset method to clean up anything set by Tool */ public destroy(): void { - Object.values(this.available).forEach(async tool => { + Array.from(this.available.values()).forEach(async tool => { if (_.isFunction(tool.reset)) { await tool.reset(); } diff --git a/test/cypress/tests/modules/Tools.cy.ts b/test/cypress/tests/modules/Tools.cy.ts index b4d7770b4..2a163687a 100644 --- a/test/cypress/tests/modules/Tools.cy.ts +++ b/test/cypress/tests/modules/Tools.cy.ts @@ -104,6 +104,31 @@ describe('Tools module', () => { }); }); + context('.destroy()', () => { + it('should call Tools reset method on destroy', () => { + const resetStub = cy.stub(); + + class WithReset { + public static reset = resetStub; + } + + const module = constructModule({ + defaultBlock: 'withReset', + tools: { + withReset: { + class: WithReset as any, + }, + }, + }); + + return module.prepare().then(() => { + module.destroy(); + + expect(resetStub).to.be.calledOnce; + }); + }); + }); + context('collection accessors', () => { let module: Tools; From 8aeaff86b77455ff777ee373097a6ee3379ea645 Mon Sep 17 00:00:00 2001 From: priyanshu-git-hub Date: Sat, 14 Nov 2026 20:45:02 +0530 Subject: [PATCH 2/2] fix(tools): catch and log errors from tool reset on destroy --- docs/CHANGELOG.md | 2 +- src/components/modules/tools.ts | 6 ++++- test/cypress/tests/modules/Tools.cy.ts | 31 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0f48d6151..72acee3cb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,7 +5,7 @@ - `Improvement` - Removed "Narrow" ("Thin") mode. On non-mobile viewports, Plus and Block Tunes controls now use a responsive action rail beside the editor content. - `Improvement` - `paste` config supports filter functions for tag-based substitution - `Fix` - "Convert to" is not shown in the Inline Toolbar when there are no tools to convert to -- `Fix` - Invoke tool `reset()` methods on `Tools.destroy()` +- `Fix` - Invoke tool `reset()` methods on `Tools.destroy()`, catching and logging any errors as warnings ### 2.31.7 diff --git a/src/components/modules/tools.ts b/src/components/modules/tools.ts index a9611ac4f..14827e151 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -166,7 +166,11 @@ export default class Tools extends Module { public destroy(): void { Array.from(this.available.values()).forEach(async tool => { if (_.isFunction(tool.reset)) { - await tool.reset(); + try { + await tool.reset(); + } catch (e) { + _.log(`Tool «${tool.name}» reset method throws an Error %o`, 'warn', e); + } } }); } diff --git a/test/cypress/tests/modules/Tools.cy.ts b/test/cypress/tests/modules/Tools.cy.ts index 2a163687a..07081a11c 100644 --- a/test/cypress/tests/modules/Tools.cy.ts +++ b/test/cypress/tests/modules/Tools.cy.ts @@ -127,6 +127,37 @@ describe('Tools module', () => { expect(resetStub).to.be.calledOnce; }); }); + + it('should catch and log warning if tool reset rejects', () => { + const resetStub = cy.stub().rejects(new Error('Reset error')); + const warnSpy = cy.spy(console, 'warn'); + + class WithFailingReset { + public static reset = resetStub; + } + + const module = constructModule({ + defaultBlock: 'withFailingReset', + tools: { + withFailingReset: { + class: WithFailingReset as any, + }, + }, + }); + + return module.prepare() + .then(() => { + expect(() => { + module.destroy(); + }).not.to.throw(); + + expect(resetStub).to.be.calledOnce; + }) + .then(() => new Promise((resolve) => setTimeout(resolve, 10))) + .then(() => { + expect(warnSpy).to.be.called; + }); + }); }); context('collection accessors', () => {