@@ -350,26 +350,39 @@ describe('Theme Toggle', () => {
350350 // ── theme-transition class (add/remove on toggle) ────────────────────────────
351351
352352 describe ( 'theme-transition class' , ( ) => {
353+ function createToggleHandler ( ) {
354+ let transitionTimer = null ;
355+ return function toggle ( ) {
356+ const current = document . documentElement . getAttribute ( 'data-theme' ) || LIGHT ;
357+ const next = current === DARK ? LIGHT : DARK ;
358+ document . documentElement . classList . add ( 'theme-transition' ) ;
359+ applyTheme ( next , undefined , storage ) ;
360+ updateButton ( next ) ;
361+ if ( transitionTimer ) { clearTimeout ( transitionTimer ) ; }
362+ transitionTimer = setTimeout ( function ( ) {
363+ document . documentElement . classList . remove ( 'theme-transition' ) ;
364+ transitionTimer = null ;
365+ } , 350 ) ;
366+ } ;
367+ }
368+
353369 it ( 'adds theme-transition class on toggle' , ( ) => {
354370 buildButton ( ) ;
355371 applyTheme ( 'light' , false , storage ) ;
356- const current = document . documentElement . getAttribute ( 'data-theme' ) || LIGHT ;
357- const next = current === DARK ? LIGHT : DARK ;
358- document . documentElement . classList . add ( 'theme-transition' ) ;
359- applyTheme ( next , undefined , storage ) ;
360- updateButton ( next ) ;
372+ const toggle = createToggleHandler ( ) ;
373+
374+ expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( false ) ;
375+ toggle ( ) ;
361376 expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( true ) ;
362377 } ) ;
363378
364379 it ( 'removes theme-transition class after timeout' , ( ) => {
365380 vi . useFakeTimers ( ) ;
366381 buildButton ( ) ;
367382 applyTheme ( 'light' , false , storage ) ;
368- document . documentElement . classList . add ( 'theme-transition' ) ;
369- // Simulate the setTimeout cleanup from the toggle handler
370- setTimeout ( function ( ) {
371- document . documentElement . classList . remove ( 'theme-transition' ) ;
372- } , 350 ) ;
383+ const toggle = createToggleHandler ( ) ;
384+
385+ toggle ( ) ;
373386 expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( true ) ;
374387 vi . advanceTimersByTime ( 350 ) ;
375388 expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( false ) ;
@@ -380,30 +393,22 @@ describe('Theme Toggle', () => {
380393 vi . useFakeTimers ( ) ;
381394 buildButton ( ) ;
382395 applyTheme ( 'light' , false , storage ) ;
383-
384- // Simulate rapid toggle with clearTimeout guard
385- let timer = null ;
396+ const toggle = createToggleHandler ( ) ;
386397
387398 // First toggle
388- document . documentElement . classList . add ( 'theme-transition' ) ;
389- if ( timer ) { clearTimeout ( timer ) ; }
390- timer = setTimeout ( function ( ) {
391- document . documentElement . classList . remove ( 'theme-transition' ) ;
392- timer = null ;
393- } , 350 ) ;
394-
399+ toggle ( ) ;
395400 vi . advanceTimersByTime ( 100 ) ;
396401 expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( true ) ;
397402
398403 // Second rapid toggle — clears the first timer
399- document . documentElement . classList . add ( 'theme-transition' ) ;
400- if ( timer ) { clearTimeout ( timer ) ; }
401- setTimeout ( function ( ) {
402- document . documentElement . classList . remove ( 'theme-transition' ) ;
403- } , 350 ) ;
404+ toggle ( ) ;
405+
406+ // After 350ms from first toggle (250ms after second), class should still be present
407+ vi . advanceTimersByTime ( 250 ) ;
408+ expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( true ) ;
404409
405410 // After 350ms from second toggle, class should be removed
406- vi . advanceTimersByTime ( 350 ) ;
411+ vi . advanceTimersByTime ( 100 ) ;
407412 expect ( document . documentElement . classList . contains ( 'theme-transition' ) ) . toBe ( false ) ;
408413 vi . useRealTimers ( ) ;
409414 } ) ;
0 commit comments