@@ -712,6 +712,116 @@ describe("dispatch", () => {
712712 } ) ;
713713} ) ;
714714
715+ // ─── Regression: x-data access pattern (issue #5155) ─────────────────────────
716+ //
717+ // The bug: admin.html used window.Admin?.overflowMenu?.('roots-table-wrapper')
718+ // as the x-data expression. Since overflowMenu is registered via Alpine.data()
719+ // (not attached to window.Admin), optional chaining returned undefined and
720+ // Alpine initialized the component with an empty scope — menuOpen and openMenu
721+ // were never defined, causing "Undefined variable: openMenu" on click.
722+ //
723+ // All other sections (gateways, tools, agents, resources, servers, prompts)
724+ // use the Alpine-registered name directly: x-data="overflowMenu(...)".
725+ // These tests confirm that pattern works and the broken one does not.
726+
727+ describe ( "Regression: x-data access pattern (issue #5155)" , ( ) => {
728+ afterEach ( ( ) => {
729+ delete window . Admin ;
730+ } ) ;
731+
732+ test ( "overflowMenu is NOT on window.Admin (Alpine.data, not window.Admin)" , ( ) => {
733+ window . Admin = { } ;
734+ expect ( window . Admin . overflowMenu ) . toBeUndefined ( ) ;
735+ } ) ;
736+
737+ test ( "window.Admin?.overflowMenu?.() evaluates to undefined (the bug)" , ( ) => {
738+ window . Admin = { } ;
739+ const result = window . Admin ?. overflowMenu ?. ( "roots-table-wrapper" ) ;
740+ expect ( result ) . toBeUndefined ( ) ;
741+ } ) ;
742+
743+ test ( "window.Admin?.overflowMenu?.() evaluates to undefined even with null Admin" , ( ) => {
744+ window . Admin = null ;
745+ const result = window . Admin ?. overflowMenu ?. ( "roots-table-wrapper" ) ;
746+ expect ( result ) . toBeUndefined ( ) ;
747+ } ) ;
748+
749+ test ( "window.Admin?.overflowMenu?.() evaluates to undefined when Admin is absent" , ( ) => {
750+ delete window . Admin ;
751+ const result = window . Admin ?. overflowMenu ?. ( "roots-table-wrapper" ) ;
752+ expect ( result ) . toBeUndefined ( ) ;
753+ } ) ;
754+
755+ test ( "overflowMenu('...') called directly returns a valid component with menuOpen/openMenu" , ( ) => {
756+ const component = overflowMenu ( "roots-table-wrapper" ) ;
757+ expect ( component ) . toBeDefined ( ) ;
758+ expect ( component . menuOpen ) . toBe ( false ) ;
759+ expect ( typeof component . openMenu ) . toBe ( "function" ) ;
760+ expect ( typeof component . init ) . toBe ( "function" ) ;
761+ } ) ;
762+
763+ test ( "overflowMenu('...') called directly works when window.Admin is absent (full isolation)" , ( ) => {
764+ delete window . Admin ;
765+ const component = overflowMenu ( "roots-table-wrapper" ) ;
766+ expect ( component ) . toBeDefined ( ) ;
767+ expect ( component . menuOpen ) . toBe ( false ) ;
768+ expect ( typeof component . openMenu ) . toBe ( "function" ) ;
769+
770+ // Simulate a real click flow (as Alpine would)
771+ const { menu } = createMenuItems ( 1 ) ;
772+ const trigger = document . createElement ( "button" ) ;
773+ trigger . getBoundingClientRect = vi . fn ( ( ) => ( { bottom : 100 , left : 50 , top : 80 } ) ) ;
774+ component . $refs = { trigger, menu } ;
775+ component . $watch = vi . fn ( ) ;
776+ component . $nextTick = vi . fn ( ( cb ) => cb ( ) ) ;
777+ component . init ( ) ;
778+
779+ component . openMenu ( ) ;
780+ expect ( component . menuOpen ) . toBe ( true ) ;
781+ expect ( component . menuTop ) . toBe ( 104 ) ;
782+ } ) ;
783+
784+ test ( "overflowMenu('...') returns component with closeMenu and navigate" , ( ) => {
785+ // These are used in the template markup alongside overflowMenu
786+ const component = overflowMenu ( "roots-table-wrapper" ) ;
787+ expect ( typeof component . closeMenu ) . toBe ( "function" ) ;
788+ expect ( typeof component . navigate ) . toBe ( "function" ) ;
789+ expect ( typeof component . dispatch ) . toBe ( "function" ) ;
790+ expect ( typeof component . destroy ) . toBe ( "function" ) ;
791+ } ) ;
792+
793+ test ( "BUG REPRODUCTION: inline x-data expression matches broken pattern" , ( ) => {
794+ // This is what admin.html line 7938 *was* before the fix:
795+ // x-data="window.Admin?.overflowMenu?.('roots-table-wrapper')"
796+ // Alpine evaluates that as a JS expression. We confirm the result is
797+ // always undefined regardless of window.Admin state.
798+ window . Admin = { } ;
799+ const expression1 = window . Admin ?. overflowMenu ?. ( "roots-table-wrapper" ) ;
800+ expect ( expression1 ) . toBeUndefined ( ) ;
801+
802+ window . Admin = { overflowMenu : undefined } ;
803+ const expression2 = window . Admin ?. overflowMenu ?. ( "roots-table-wrapper" ) ;
804+ expect ( expression2 ) . toBeUndefined ( ) ;
805+
806+ // With a real function on window.Admin.overflowMenu it would work,
807+ // but that's not how overflowMenu is registered.
808+ } ) ;
809+
810+ test ( "BUG REPRODUCTION: undefined x-data scope cannot provide openMenu" , ( ) => {
811+ // When x-data="window.Admin?.overflowMenu?.('...')" evaluates to undefined,
812+ // Alpine initializes the component with an empty/undefined scope.
813+ // In that scenario, calling openMenu is impossible because it doesn't exist.
814+ const brokenExpression = window . Admin ?. overflowMenu ?. ( "roots-table-wrapper" ) ;
815+ expect ( brokenExpression ) . toBeUndefined ( ) ;
816+
817+ // In contrast, the correct pattern returns a component with openMenu.
818+ const fixedExpression = overflowMenu ( "roots-table-wrapper" ) ;
819+ expect ( fixedExpression ) . toBeDefined ( ) ;
820+ expect ( typeof fixedExpression . openMenu ) . toBe ( "function" ) ;
821+ expect ( typeof fixedExpression . menuOpen ) . toBe ( "boolean" ) ;
822+ } ) ;
823+ } ) ;
824+
715825// ─── Multi-menu coordination ──────────────────────────────────────────────────
716826
717827describe ( "Multi-menu coordination" , ( ) => {
0 commit comments