zero-query 1.2.17 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -616,6 +616,93 @@ describe('morph - keyed edge cases', () => {
616
616
  });
617
617
 
618
618
 
619
+ // ---------------------------------------------------------------------------
620
+ // Duplicate-key collision fallback
621
+ //
622
+ // Auto-keys (id / data-id / data-key) can collide among siblings - the
623
+ // canonical case is event delegation, where several action buttons in one
624
+ // row all carry data-id="<record-id>". Duplicate keys make keyed
625
+ // reconciliation ill-defined (later keyMap entries overwrite earlier ones,
626
+ // so nodes get dropped, duplicated, and reordered). _morphChildren detects
627
+ // a duplicate on either side and falls back to positional morphing, which
628
+ // is always well-defined for colliding sets.
629
+ // ---------------------------------------------------------------------------
630
+
631
+ describe('morph - duplicate-key collision fallback', () => {
632
+ it('morphs delegation buttons sharing one data-id without drops or dupes', () => {
633
+ // Faithful to the molexcloud uploads-row repro: a row-actions span whose
634
+ // buttons all carry the same data-id for event delegation. Growing the
635
+ // button set must produce exactly the new buttons, in order, once each.
636
+ const root = el('<span><button data-id="42">scrap</button></span>');
637
+ morph(
638
+ root,
639
+ '<span>' +
640
+ '<button data-id="42">pause</button>' +
641
+ '<button data-id="42">resume</button>' +
642
+ '<button data-id="42">scrap</button>' +
643
+ '<button data-id="42">link</button>' +
644
+ '</span>'
645
+ );
646
+ const buttons = [...root.querySelector('span').children];
647
+ expect(buttons.map(b => b.textContent)).toEqual(['pause', 'resume', 'scrap', 'link']);
648
+ // Every button carries the shared id, none dropped, none duplicated
649
+ expect(buttons.every(b => b.dataset.id === '42')).toBe(true);
650
+ });
651
+
652
+ it('shrinks a shared-data-id button set positionally', () => {
653
+ const root = el(
654
+ '<span>' +
655
+ '<button data-id="7">pause</button>' +
656
+ '<button data-id="7">resume</button>' +
657
+ '<button data-id="7">scrap</button>' +
658
+ '</span>'
659
+ );
660
+ morph(
661
+ root,
662
+ '<span>' +
663
+ '<button data-id="7">scrap</button>' +
664
+ '<button data-id="7">link</button>' +
665
+ '</span>'
666
+ );
667
+ const buttons = [...root.querySelector('span').children];
668
+ expect(buttons.map(b => b.textContent)).toEqual(['scrap', 'link']);
669
+ });
670
+
671
+ it('falls back cleanly when explicit z-key values collide', () => {
672
+ const root = el('<div z-key="dup">A</div><div z-key="dup">B</div>');
673
+ morph(
674
+ root,
675
+ '<div z-key="dup">X</div><div z-key="dup">Y</div><div z-key="dup">Z</div>'
676
+ );
677
+ // Positional morph: exact new content, no drops/dupes despite the collision
678
+ expect([...root.children].map(c => c.textContent)).toEqual(['X', 'Y', 'Z']);
679
+ expect(root.children.length).toBe(3);
680
+ });
681
+
682
+ it('falls back when the collision is only on the new side', () => {
683
+ const root = el('<div data-id="1">A</div><div data-id="2">B</div>');
684
+ morph(root, '<div data-id="9">X</div><div data-id="9">Y</div>');
685
+ expect([...root.children].map(c => c.textContent)).toEqual(['X', 'Y']);
686
+ expect(root.children.length).toBe(2);
687
+ });
688
+
689
+ it('still uses the keyed path (preserving identity) when data-ids are unique', () => {
690
+ // Contrast case: unique data-ids keep the LIS-optimised keyed path, so
691
+ // node identity survives a reorder rather than being positionally rebuilt.
692
+ const root = el(
693
+ '<li data-id="1">One</li><li data-id="2">Two</li><li data-id="3">Three</li>'
694
+ );
695
+ const ref1 = root.children[0];
696
+ const ref2 = root.children[1];
697
+ const ref3 = root.children[2];
698
+ morph(root, '<li data-id="3">Three</li><li data-id="1">One</li><li data-id="2">Two</li>');
699
+ expect(root.children[0]).toBe(ref3);
700
+ expect(root.children[1]).toBe(ref1);
701
+ expect(root.children[2]).toBe(ref2);
702
+ });
703
+ });
704
+
705
+
619
706
  // ---------------------------------------------------------------------------
620
707
  // Boolean attribute morphing
621
708
  // ---------------------------------------------------------------------------