diff --git a/src/runtime/test/render-vdom.spec.tsx b/src/runtime/test/render-vdom.spec.tsx index c31262d0aea..00e939e5a27 100644 --- a/src/runtime/test/render-vdom.spec.tsx +++ b/src/runtime/test/render-vdom.spec.tsx @@ -1097,5 +1097,100 @@ describe('render-vdom', () => { await waitForChanges(); expect(rootInstance.counter).toEqual(2); }); + + it('should not call ref cb w/ null when children are reordered', async () => { + // this test is a regression test ensuring that the algorithm for matching + // up children across rerenders works correctly when a basic transposition is + // done (the elements at the ends of the children swap places). + @Component({ tag: 'cmp-a' }) + class CmpA { + divRef: HTMLElement; + @Prop() state = true; + + renderA() { + return ( +
(this.divRef = el)}> + A +
+ ); + } + + renderB() { + return
B
; + } + + render() { + return this.state + ? [this.renderB(),
middle
, this.renderA()] + : [this.renderA(),
middle
, this.renderB()]; + } + } + + const { root, rootInstance, waitForChanges } = await newSpecPage({ + components: [CmpA], + html: ``, + }); + // ref should be set correctly after the first render + expect(rootInstance.divRef).toEqual(root.querySelector('.a')); + root.state = false; + await waitForChanges(); + // We've changed the state and forced a re-render. This tests one of the + // ways in which children can be re-ordered that the `updateChildren` algo + // can handle without having `key` attrs set. + expect(rootInstance.divRef).toEqual(root.querySelector('.a')); + }); + + it('should not call ref cb w/ null when children w/ keys are reordered', async () => { + // this test is a regression test ensuring that the algorithm for matching + // up children across rerenders works correctly in a situation in which it + // needs to use the `key` attribute to disambiguate them. At present, if the + // `key` attribute is _not_ present in this case then this test will fail + // because without the `key` Stencil's child-identity heuristic falls over. + @Component({ tag: 'cmp-a' }) + class CmpA { + divRef: HTMLElement; + @Prop() state = true; + + renderA() { + return ( +
(this.divRef = el)}> + A +
+ ); + } + + renderB() { + return
B
; + } + + render() { + return this.state ? [this.renderB(), this.renderA()] : [this.renderA()]; + } + } + + const { root, rootInstance, waitForChanges } = await newSpecPage({ + components: [CmpA], + html: ``, + }); + + // ref should be set correctly after the first render + expect(rootInstance.divRef).toEqual(root.querySelector('.a')); + root.state = false; + await waitForChanges(); + // We've changed the state and forced a re-render where the algorithm for + // reconciling children will have to use the `key` attribute to find the + // equivalent VNode on the re-render. So if that is all working correctly + // then the value of our `divRef` property should be set correctly after + // the rerender. + // + // The reordering that is conditionally done in the `render` method of the + // test component above is specifically the type of edge case that the + // parts of the `updateChildren` algorithm which _don't_ use the `key` attr + // have trouble with. + // + // This is essentially a regression test for the issue described in + // https://github.com/ionic-team/stencil/issues/3253 + expect(rootInstance.divRef).toEqual(root.querySelector('.a')); + }); }); });