@@ -3,7 +3,6 @@ import { castToBrowser } from '@glimmer/debug-util';
33import { GlimmerishComponent } from '../components/emberish-glimmer' ;
44import { RenderTest } from '../render-test' ;
55import { test } from '../test-decorator' ;
6- import { tracked } from '../test-helpers/tracked' ;
76
87export class ShadowDOMSuite extends RenderTest {
98 static suiteName = 'Shadow DOM' ;
@@ -132,9 +131,7 @@ export class ShadowDOMSuite extends RenderTest {
132131
133132 @test
134133 '<template shadowrootmode="open"> as component root with tracked state re-renders into same shadow root' ( ) {
135- class Counter extends GlimmerishComponent {
136- @tracked count = 0 ;
137- }
134+ class Counter extends GlimmerishComponent { }
138135
139136 this . registerComponent (
140137 'Glimmer' ,
@@ -201,4 +198,46 @@ export class ShadowDOMSuite extends RenderTest {
201198 'regular content renders in host after switching from shadow branch'
202199 ) ;
203200 }
201+
202+ @test
203+ 'conditional <template shadowrootmode="open"> re-enables shadow root on toggle back (exercises existingShadowRoot path)' ( ) {
204+ // When a declarative shadow root block is torn down and re-created on the same
205+ // host element, VM_ATTACH_SHADOW_ROOT_OP encounters rawParent.shadowRoot !== null
206+ // (shadow roots cannot be removed from a host). The existingShadowRoot branch in
207+ // the opcode clears and reuses the existing shadow root so that attachShadow() is
208+ // not called again (which would throw DOMException).
209+ this . render (
210+ '<div class="host">{{#if this.useShadow}}<template shadowrootmode="open"><p>{{this.msg}}</p></template>{{else}}<span>off</span>{{/if}}</div>' ,
211+ { useShadow : true , msg : 'first' }
212+ ) ;
213+
214+ const rootEl = castToBrowser ( this . element , 'HTML' ) ;
215+ const host = rootEl . querySelector ( '.host' ) as HTMLElement | null ;
216+ const initialShadowRoot = host ?. shadowRoot ;
217+
218+ this . assert . ok ( initialShadowRoot !== null , 'shadow root attached on first render' ) ;
219+ this . assert . strictEqual (
220+ host ?. shadowRoot ?. querySelector ( 'p' ) ?. textContent ,
221+ 'first' ,
222+ 'initial shadow content correct'
223+ ) ;
224+
225+ // Tear down the shadow DOM block — shadow root stays on the host (platform behavior)
226+ this . rerender ( { useShadow : false , msg : 'first' } ) ;
227+ this . assert . ok ( host ?. querySelector ( 'span' ) !== null , 'else branch rendered' ) ;
228+
229+ // Re-enable shadow DOM — VM_ATTACH_SHADOW_ROOT_OP hits the existingShadowRoot path
230+ this . rerender ( { useShadow : true , msg : 'second' } ) ;
231+ this . assert . ok ( host ?. shadowRoot !== null , 'shadow root still attached after re-enable' ) ;
232+ this . assert . strictEqual (
233+ host ?. shadowRoot ,
234+ initialShadowRoot ,
235+ 'same shadow root instance reused (not recreated)'
236+ ) ;
237+ this . assert . strictEqual (
238+ host ?. shadowRoot ?. querySelector ( 'p' ) ?. textContent ,
239+ 'second' ,
240+ 'shadow content updated correctly after re-enable'
241+ ) ;
242+ }
204243}
0 commit comments