diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4a04976e1..f130d0773 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog +### 2.27.2 + +- `Fix` - `onChange` won't be called when element with data-mutation-free changes some attribute + ### 2.27.1 -- `Fix` - `onChange` will be called on removing the whole text in the last block +- `Fix` - `onChange` will be called on removing the whole text in a block ### 2.27.0 diff --git a/package.json b/package.json index a103238d2..f7d79c5b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.27.1", + "version": "2.27.2", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editorjs.umd.js", "module": "dist/editorjs.mjs", diff --git a/src/components/block/index.ts b/src/components/block/index.ts index e7e25c49c..e059b08bc 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -878,10 +878,11 @@ export default class Block extends EventsDispatcher { * — we should fire 'didMutated' event in that case */ const everyRecordIsMutationFree = mutationsOrInputEvent.length > 0 && mutationsOrInputEvent.every((record) => { - const { addedNodes, removedNodes } = record; + const { addedNodes, removedNodes, target } = record; const changedNodes = [ ...Array.from(addedNodes), ...Array.from(removedNodes), + target, ]; return changedNodes.some((node) => { diff --git a/test/cypress/tests/onchange.cy.ts b/test/cypress/tests/onchange.cy.ts index e05c4054e..13c4fccfa 100644 --- a/test/cypress/tests/onchange.cy.ts +++ b/test/cypress/tests/onchange.cy.ts @@ -509,4 +509,70 @@ describe('onChange callback', () => { }, })); }); + + it('should not be fired when element with the "data-mutation-free" mark changes some attribute', () => { + /** + * Mock for tool wrapper which we will mutate in a test + */ + const toolWrapper = document.createElement('div'); + + /** + * Mark it as mutation-free + */ + toolWrapper.dataset.mutationFree = 'true'; + + /** + * Mock of tool with data-mutation-free attribute + */ + class ToolWithMutationFreeAttribute { + /** + * Simply return mocked element + */ + public render(): HTMLElement { + return toolWrapper; + } + + /** + * Saving logic is not necessary for this test + */ + // eslint-disable-next-line @typescript-eslint/no-empty-function + public save(): void {} + } + + const editorConfig = { + tools: { + testTool: ToolWithMutationFreeAttribute, + }, + onChange: (api, event): void => { + console.log('something changed', event); + }, + data: { + blocks: [ + { + type: 'testTool', + data: {}, + }, + ], + }, + }; + + cy.spy(editorConfig, 'onChange').as('onChange'); + cy.createEditor(editorConfig).as('editorInstance'); + + /** + * Emulate tool's internal attribute mutation + */ + // eslint-disable-next-line cypress/no-unnecessary-waiting, @typescript-eslint/no-magic-numbers + cy.wait(100).then(() => { + toolWrapper.setAttribute('some-changed-attr', 'some-new-value'); + }); + + /** + * Check that onChange callback was not called + */ + // eslint-disable-next-line cypress/no-unnecessary-waiting, @typescript-eslint/no-magic-numbers + cy.wait(500).then(() => { + cy.get('@onChange').should('have.callCount', 0); + }); + }); });