@@ -583,3 +583,143 @@ test.describe('Workshop flow filtering: Copilot scenario-d substitution', () =>
583583 expect ( keys ) . not . toContain ( '11c-build-pr-reviewer-ui' ) ;
584584 } ) ;
585585} ) ;
586+
587+ // ---------------------------------------------------------------------------
588+ // Journey block visibility tests — verify that CSS-driven journey section
589+ // visibility works correctly: marker rewriting, attribute state, and
590+ // visibility transitions when switching paths.
591+ // ---------------------------------------------------------------------------
592+
593+ test . describe ( 'Workshop journey block visibility' , ( ) => {
594+ test ( 'data-workshop-visible-journeys contains "all" and journey-specific IDs after starting the tutorial' , async ( { page } ) => {
595+ // ui-learner maps to the github journey, which has contentJourneyIds: ['ui']
596+ await startWorkshop ( page ) ;
597+
598+ const visibleJourneys = await page . evaluate ( ( ) =>
599+ document . querySelector ( '[data-workshop-root]' ) ?. getAttribute ( 'data-workshop-visible-journeys' ) ?? '' ,
600+ ) ;
601+ // 'all' is always included; 'ui' comes from the github journey's contentJourneyIds
602+ expect ( visibleJourneys . split ( ' ' ) ) . toContain ( 'all' ) ;
603+ expect ( visibleJourneys . split ( ' ' ) ) . toContain ( 'ui' ) ;
604+ } ) ;
605+
606+ test ( 'data-workshop-visible-journeys updates when switching to a different entry path' , async ( { page } ) => {
607+ // Start with ui-learner (github journey → contentJourneyIds: ['ui'])
608+ await startWorkshop ( page ) ;
609+
610+ const initialJourneys = await page . evaluate ( ( ) =>
611+ document . querySelector ( '[data-workshop-root]' ) ?. getAttribute ( 'data-workshop-visible-journeys' ) ?? '' ,
612+ ) ;
613+ expect ( initialJourneys . split ( ' ' ) ) . toContain ( 'ui' ) ;
614+
615+ // Switch to the terminal path (contentJourneyIds: ['terminal', 'local'])
616+ await page . getByRole ( 'button' , { name : / C h a n g e r o u t e / i } ) . click ( ) ;
617+ await page . locator ( '[data-workshop-entry-path="cli-user"]' ) . click ( ) ;
618+ await page . locator ( '[data-workshop-scenario="daily-status"]' ) . click ( ) ;
619+ await expect ( page . locator ( '[data-workshop-tutorial]' ) ) . toBeVisible ( ) ;
620+
621+ const updatedJourneys = await page . evaluate ( ( ) =>
622+ document . querySelector ( '[data-workshop-root]' ) ?. getAttribute ( 'data-workshop-visible-journeys' ) ?? '' ,
623+ ) ;
624+ expect ( updatedJourneys . split ( ' ' ) ) . toContain ( 'all' ) ;
625+ expect ( updatedJourneys . split ( ' ' ) ) . toContain ( 'terminal' ) ;
626+ // 'ui' should no longer be listed (terminal journey does not map to ui content)
627+ expect ( updatedJourneys . split ( ' ' ) ) . not . toContain ( 'ui' ) ;
628+ } ) ;
629+
630+ test ( 'no raw journey comment markers survive HTML rewriting in step data' , async ( { page } ) => {
631+ await startWorkshop ( page ) ;
632+
633+ // Any <!-- journey: ... --> or <!-- /journey --> remaining in rendered HTML
634+ // means rewriteJourneyBlocks did not run or failed. Raw markers must never
635+ // appear in the embedded step-data JSON.
636+ const hasRawMarkers = await page . evaluate ( ( ) => {
637+ const node = document . getElementById ( 'aw-workshop-step-data' ) ;
638+ if ( ! node ) return false ;
639+ const steps = JSON . parse ( node . textContent ?. trim ( ) || '[]' ) as Array < { html : string } > ;
640+ return steps . some ( ( s ) => / < ! - - \s * \/ ? j o u r n e y [: \s] / i. test ( s . html ) ) ;
641+ } ) ;
642+
643+ expect ( hasRawMarkers ) . toBe ( false ) ;
644+ } ) ;
645+
646+ test ( 'journey blocks in step data have base class and one class per comma-separated token' , async ( { page } ) => {
647+ await startWorkshop ( page ) ;
648+
649+ // If any step HTML contains journey blocks, verify each wrapper carries the
650+ // base class plus a distinct class per token. Passes vacuously when the
651+ // current workshop build has no journey-tagged sections.
652+ const result = await page . evaluate ( ( ) => {
653+ const node = document . getElementById ( 'aw-workshop-step-data' ) ;
654+ if ( ! node ) return { hasJourneyBlocks : false , allValid : true } ;
655+ const steps = JSON . parse ( node . textContent ?. trim ( ) || '[]' ) as Array < { html : string } > ;
656+
657+ const wrapperPattern = / c l a s s = " ( [ ^ " ] * a w - w o r k s h o p - j o u r n e y - b l o c k [ ^ " ] * ) " / g;
658+ let hasJourneyBlocks = false ;
659+ let allValid = true ;
660+
661+ for ( const step of steps ) {
662+ for ( const match of step . html . matchAll ( wrapperPattern ) ) {
663+ hasJourneyBlocks = true ;
664+ const classList = match [ 1 ] . split ( / \s + / ) ;
665+ // Must have the base class
666+ if ( ! classList . includes ( 'aw-workshop-journey-block' ) ) {
667+ allValid = false ;
668+ }
669+ // Must have at least one journey-specific class
670+ if ( ! classList . some ( ( c ) => c . startsWith ( 'aw-workshop-journey-block-' ) && c !== 'aw-workshop-journey-block' ) ) {
671+ allValid = false ;
672+ }
673+ }
674+ }
675+
676+ return { hasJourneyBlocks, allValid } ;
677+ } ) ;
678+
679+ expect ( result . allValid ) . toBe ( true ) ;
680+ } ) ;
681+
682+ test ( 'journey blocks are hidden by default and revealed via CSS when the active journey matches' , async ( { page } ) => {
683+ await startWorkshop ( page ) ;
684+
685+ // Verify CSS-driven visibility using an injected probe element.
686+ // The github journey (ui-learner path) maps to contentJourneyIds: ['ui'],
687+ // so a block tagged 'ui' should be visible and one tagged 'terminal' should be hidden.
688+ const visibility = await page . evaluate ( ( ) => {
689+ const stepContent = document . querySelector ( '[data-workshop-step-content]' ) as HTMLElement | null ;
690+ if ( ! stepContent ) return null ;
691+
692+ const uiBlock = document . createElement ( 'div' ) ;
693+ uiBlock . className = 'aw-workshop-journey-block aw-workshop-journey-block-ui' ;
694+ uiBlock . setAttribute ( 'data-test-probe' , 'ui' ) ;
695+
696+ const terminalBlock = document . createElement ( 'div' ) ;
697+ terminalBlock . className = 'aw-workshop-journey-block aw-workshop-journey-block-terminal' ;
698+ terminalBlock . setAttribute ( 'data-test-probe' , 'terminal' ) ;
699+
700+ const allBlock = document . createElement ( 'div' ) ;
701+ allBlock . className = 'aw-workshop-journey-block aw-workshop-journey-block-all' ;
702+ allBlock . setAttribute ( 'data-test-probe' , 'all' ) ;
703+
704+ stepContent . appendChild ( uiBlock ) ;
705+ stepContent . appendChild ( terminalBlock ) ;
706+ stepContent . appendChild ( allBlock ) ;
707+
708+ return {
709+ uiDisplay : window . getComputedStyle ( uiBlock ) . display ,
710+ terminalDisplay : window . getComputedStyle ( terminalBlock ) . display ,
711+ allDisplay : window . getComputedStyle ( allBlock ) . display ,
712+ } ;
713+ } ) ;
714+
715+ expect ( visibility ) . not . toBeNull ( ) ;
716+ if ( visibility ) {
717+ // 'all' blocks are always visible regardless of journey
718+ expect ( visibility . allDisplay ) . toBe ( 'contents' ) ;
719+ // 'ui' block is visible because the github journey includes 'ui' in contentJourneyIds
720+ expect ( visibility . uiDisplay ) . toBe ( 'contents' ) ;
721+ // 'terminal' block is hidden because the github journey does not include 'terminal'
722+ expect ( visibility . terminalDisplay ) . toBe ( 'none' ) ;
723+ }
724+ } ) ;
725+ } ) ;
0 commit comments