diff --git a/CHANGELOG.md b/CHANGELOG.md index 105b566..fd4d17e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * Remove AMD publish target since its EOL: https://github.com/requirejs/requirejs/issues/1816#issuecomment-707503323 * Remove CommonJS publish target. `require("idiomorph")` no longer resolves; use `import "idiomorph"` instead. (@botandrose) #122 +* Added: + * Warn in the console when duplicate ids are detected during a morph, since they can cause subtle state loss (@botandrose) #142 + ## [0.7.4] - 2025-09-29 * Fixed: diff --git a/src/idiomorph.js b/src/idiomorph.js index 257c040..f0f4729 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -1189,6 +1189,12 @@ var Idiomorph = (function () { for (const id of duplicateIds) { persistentIds.delete(id); } + if (duplicateIds.size) { + console.warn( + "[Warning] duplicate ids found during morph, state loss within these elements is possible:", + Array.from(duplicateIds), + ); + } return persistentIds; } diff --git a/test/core.js b/test/core.js index 5e10210..632f7a6 100644 --- a/test/core.js +++ b/test/core.js @@ -606,4 +606,50 @@ describe("Core morphing tests", function () { // included in the persistent ID set or it will pantry the id'ed node in error initial.outerHTML.should.equal("Bar"); }); + + describe("duplicate id warnings", function () { + let warn; + + beforeEach(function () { + warn = sinon.stub(console, "warn"); + }); + + afterEach(function () { + warn.restore(); + }); + + it("warns when the old content has duplicate ids", function () { + let initial = make("

Foo

Bar

"); + Idiomorph.morph(initial, "

Baz

"); + warn.calledOnce.should.equal(true); + warn.firstCall.args[1].should.eql(["a"]); + }); + + it("warns when the new content has duplicate ids", function () { + let initial = make("

Foo

"); + Idiomorph.morph(initial, "

Bar

Baz

"); + warn.calledOnce.should.equal(true); + warn.firstCall.args[1].should.eql(["a"]); + }); + + it("reports every duplicated id", function () { + let initial = make( + "

Foo

Bar

Baz

Qux

", + ); + Idiomorph.morph(initial, "

Foo

Baz

"); + warn.firstCall.args[1].should.eql(["a", "b"]); + }); + + it("does not warn when all ids are unique", function () { + let initial = make("

Foo

Bar

"); + Idiomorph.morph(initial, "

Baz

Qux

"); + warn.called.should.equal(false); + }); + + it("does not warn when the content has no ids at all", function () { + let initial = make("

Foo

Bar

"); + Idiomorph.morph(initial, "

Baz

Qux

"); + warn.called.should.equal(false); + }); + }); });