diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 480a12c7f..72acee3cb 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()`, 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 618242697..14827e151 100644 --- a/src/components/modules/tools.ts +++ b/src/components/modules/tools.ts @@ -164,9 +164,13 @@ 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(); + 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 b4d7770b4..07081a11c 100644 --- a/test/cypress/tests/modules/Tools.cy.ts +++ b/test/cypress/tests/modules/Tools.cy.ts @@ -104,6 +104,62 @@ 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; + }); + }); + + 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', () => { let module: Tools;