11import { ModeToggle } from '@/components/ui/mode-toggle' ;
22import { ThemeProvider } from '@/components/ui/theme-provider' ;
33import { APP_THEMES } from '@/lib/themes' ;
4+ import { registerAnalyticsClient } from '@/lib/analytics' ;
45
56const ICONS : Record < string , string > = {
67 light : 'lucide-sun' ,
@@ -12,13 +13,10 @@ const ICONS: Record<string, string> = {
1213 halo : 'lucide-shield' ,
1314} ;
1415
15- function pick ( theme : string ) {
16- cy . get ( '[data-testid="theme-toggle"]' ) . click ( ) ;
17- cy . get ( `[data-testid="theme-option-${ theme } "]` ) . click ( ) ;
18- }
19-
2016describe ( 'ModeToggle' , ( ) => {
2117 beforeEach ( ( ) => {
18+ cy . window ( ) . then ( ( win ) => win . localStorage . setItem ( 'theme' , 'light' ) ) ;
19+ registerAnalyticsClient ( { capture : cy . stub ( ) . as ( 'capture' ) } ) ;
2220 cy . mount (
2321 < ThemeProvider
2422 attribute = "class"
@@ -29,56 +27,36 @@ describe('ModeToggle', () => {
2927 < ModeToggle />
3028 </ ThemeProvider > ,
3129 ) ;
30+ cy . get ( '[data-testid="theme-toggle"]' ) . should (
31+ 'have.attr' ,
32+ 'aria-label' ,
33+ 'Switch theme (currently light mode)' ,
34+ ) ;
3235 } ) ;
3336
34- it ( 'expands an icon-only menu with one option per theme' , ( ) => {
35- cy . get ( '[data-testid="theme-menu"]' ) . should ( 'not.exist' ) ;
36- cy . get ( '[data-testid="theme-toggle"]' )
37- . should ( 'have.attr' , 'aria-expanded' , 'false' )
38- . click ( )
39- . should ( 'have.attr' , 'aria-expanded' , 'true' ) ;
40- cy . get ( '[data-testid="theme-menu"] [role="radio"]' ) . should ( 'have.length' , APP_THEMES . length ) ;
41- cy . get ( '[data-testid="theme-option-kart"]' ) . should ( 'not.exist' ) ;
42- for ( const theme of APP_THEMES ) {
43- cy . get ( `[data-testid="theme-option-${ theme } "]` )
44- . should ( 'have.attr' , 'aria-label' )
45- . and ( 'not.be.empty' ) ;
46- // Icons only: no visible text inside an option.
47- cy . get ( `[data-testid="theme-option-${ theme } "]` ) . should ( 'have.text' , '' ) ;
48- cy . get ( `[data-testid="theme-option-${ theme } "] svg` ) . should ( 'have.class' , ICONS [ theme ] ) ;
49- }
50- cy . get ( '[data-testid="theme-option-light"]' )
51- . should ( 'have.attr' , 'aria-checked' , 'true' )
52- . and ( 'have.focus' ) ;
53- } ) ;
54-
55- it ( 'selects any theme directly and closes the menu' , ( ) => {
56- for ( const theme of [ 'doom' , 'halo' , 'gta' , 'minecraft' , 'dark' , 'csgo' , 'light' ] ) {
57- pick ( theme ) ;
37+ it ( 'cycles every theme with one click and wraps back to light without a menu' , ( ) => {
38+ for ( const theme of [ ...APP_THEMES . slice ( 1 ) , 'light' ] ) {
39+ cy . get ( '[data-testid="theme-toggle"]' ) . click ( ) ;
5840 cy . get ( 'html' ) . should ( 'have.class' , theme ) ;
5941 cy . get ( '[data-testid="theme-menu"]' ) . should ( 'not.exist' ) ;
42+ cy . get ( '[data-testid="theme-toggle"]' ) . should ( 'not.have.attr' , 'aria-expanded' ) ;
6043 cy . get ( '[data-testid="theme-toggle"]' )
6144 . should ( 'have.attr' , 'aria-label' , `Switch theme (currently ${ theme } mode)` )
6245 . find ( 'svg' )
6346 . should ( 'have.class' , ICONS [ theme ] ) ;
47+ cy . window ( ) . should ( ( win ) => expect ( win . localStorage . getItem ( 'theme' ) ) . to . equal ( theme ) ) ;
48+ cy . get ( '@capture' ) . should ( 'have.been.calledWith' , 'theme_toggled' , { theme } ) ;
6449 }
65- cy . get ( 'html' ) . should ( 'not.have.class' , 'gta ' ) ;
50+ cy . get ( 'html' ) . should ( 'not.have.class' , 'halo ' ) ;
6651 } ) ;
6752
68- it ( 'supports arrow-key navigation and Escape' , ( ) => {
69- cy . get ( '[data-testid="theme-toggle"]' ) . click ( ) ;
70- cy . focused ( ) . should ( 'have.attr' , 'data-testid' , 'theme-option-light' ) ;
71- cy . focused ( ) . type ( '{downArrow}' ) ;
72- cy . focused ( ) . should ( 'have.attr' , 'data-testid' , 'theme-option-dark' ) ;
73- cy . focused ( ) . type ( '{upArrow}{upArrow}' ) ;
74- cy . focused ( ) . should ( 'have.attr' , 'data-testid' , `theme-option-${ APP_THEMES . at ( - 1 ) } ` ) ;
75- cy . focused ( ) . click ( ) ;
76- cy . get ( 'html' ) . should ( 'have.class' , APP_THEMES . at ( - 1 ) ! ) ;
77- cy . get ( '[data-testid="theme-menu"]' ) . should ( 'not.exist' ) ;
78- cy . get ( '[data-testid="theme-toggle"]' ) . click ( ) ;
79- cy . focused ( ) . should ( 'have.attr' , 'data-testid' , `theme-option-${ APP_THEMES . at ( - 1 ) } ` ) ;
80- cy . focused ( ) . type ( '{esc}' ) ;
53+ it ( 'keeps a native button and retains focus after activation' , ( ) => {
54+ cy . get ( '[data-testid="theme-toggle"]' )
55+ . should ( 'have.prop' , 'tagName' , 'BUTTON' )
56+ . and ( 'have.attr' , 'type' , 'button' ) ;
57+ cy . get ( '[data-testid="theme-toggle"]' ) . focus ( ) . click ( ) ;
58+ cy . get ( 'html' ) . should ( 'have.class' , 'dark' ) ;
59+ cy . focused ( ) . should ( 'have.attr' , 'data-testid' , 'theme-toggle' ) ;
8160 cy . get ( '[data-testid="theme-menu"]' ) . should ( 'not.exist' ) ;
82- cy . get ( 'html' ) . should ( 'have.class' , APP_THEMES . at ( - 1 ) ! ) ;
8361 } ) ;
8462} ) ;
0 commit comments