@@ -62,38 +62,64 @@ describe("Prompts", () => {
6262 expect ( screen . getByText ( "Loading prompts, please wait..." ) ) . toBeInTheDocument ( ) ;
6363 } ) ;
6464
65- it ( "renders populated prompt cards with label fallbacks, tag and argument badges, filtered descriptions, and actions " , async ( ) => {
65+ it ( "groups prompts from the same MCP server into one card with prompt-name badges " , async ( ) => {
6666 const user = userEvent . setup ( ) ;
6767 const prompts : Prompt [ ] = [
6868 createMockPrompt ( {
6969 id : "prompt-display-name" ,
7070 name : "summarize" ,
7171 displayName : "Summarize document" ,
7272 originalName : "summarize_document" ,
73- description : "Summarizes uploaded documents." ,
74- tags : [ { id : "tag-summary" , label : "summary" } ] ,
75- arguments : [
76- { name : "topic" , required : true } ,
77- { name : "tone" , required : false } ,
78- ] ,
73+ gatewayId : "gw-github" ,
74+ gatewaySlug : "gh-repo-tasks" ,
7975 } ) ,
8076 createMockPrompt ( {
8177 id : "prompt-original-name" ,
8278 name : "translate" ,
8379 displayName : "" ,
8480 originalName : "translate_text" ,
85- description : "None" ,
86- tags : [ { id : "tag-language" , label : "language" } ] ,
87- arguments : [ { name : "locale" , required : true } ] ,
81+ gatewayId : "gw-github" ,
82+ gatewaySlug : "gh-repo-tasks" ,
8883 } ) ,
84+ ] ;
85+
86+ server . use ( http . get ( "/prompts" , ( ) => HttpResponse . json ( { prompts } ) ) ) ;
87+
88+ renderWithProviders ( < Prompts /> ) ;
89+
90+ await waitFor ( ( ) => {
91+ expect ( screen . getByText ( "gh-repo-tasks" ) ) . toBeInTheDocument ( ) ;
92+ } ) ;
93+
94+ // A single group card holds both prompts as name badges.
95+ const groupCard = getPromptCard ( "gh-repo-tasks" ) ;
96+ expect ( within ( groupCard ) . getByText ( "Summarize document" ) ) . toBeInTheDocument ( ) ;
97+ expect ( within ( groupCard ) . getByText ( "translate_text" ) ) . toBeInTheDocument ( ) ;
98+
99+ // Only one "More options" trigger for the whole group.
100+ expect ( screen . getAllByRole ( "button" , { name : / M o r e o p t i o n s f o r / i } ) ) . toHaveLength ( 1 ) ;
101+
102+ await user . click ( screen . getByRole ( "button" , { name : "More options for gh-repo-tasks" } ) ) ;
103+ expect ( await screen . findByRole ( "menuitem" , { name : "View details" } ) ) . toBeInTheDocument ( ) ;
104+ } ) ;
105+
106+ it ( "collapses gateway-less prompts into a single REST prompts card" , async ( ) => {
107+ const prompts : Prompt [ ] = [
89108 createMockPrompt ( {
90- id : "prompt-name" ,
109+ id : "prompt-local" ,
110+ name : "doc_processor" ,
111+ displayName : "doc-processor" ,
112+ originalName : "doc_processor" ,
113+ gatewayId : null ,
114+ gatewaySlug : null ,
115+ } ) ,
116+ createMockPrompt ( {
117+ id : "prompt-fallback" ,
91118 name : "fallback_prompt" ,
92119 displayName : undefined ,
93120 originalName : undefined ,
94- description : " " ,
95- tags : [ ] ,
96- arguments : [ ] ,
121+ gatewayId : null ,
122+ gatewaySlug : null ,
97123 } ) ,
98124 ] ;
99125
@@ -102,25 +128,56 @@ describe("Prompts", () => {
102128 renderWithProviders ( < Prompts /> ) ;
103129
104130 await waitFor ( ( ) => {
105- expect ( screen . getByText ( "Summarize document " ) ) . toBeInTheDocument ( ) ;
131+ expect ( screen . getByText ( "REST prompts " ) ) . toBeInTheDocument ( ) ;
106132 } ) ;
107133
108- expect ( screen . getByText ( "translate_text" ) ) . toBeInTheDocument ( ) ;
109- expect ( screen . getByText ( "fallback_prompt" ) ) . toBeInTheDocument ( ) ;
110- expect ( screen . getByText ( "Summarizes uploaded documents. " ) ) . toBeInTheDocument ( ) ;
111- expect ( screen . queryByText ( "None ") ) . not . toBeInTheDocument ( ) ;
134+ // Both gateway-less prompts share one card, shown as name badges.
135+ const restCard = getPromptCard ( "REST prompts" ) ;
136+ expect ( within ( restCard ) . getByText ( "doc-processor " ) ) . toBeInTheDocument ( ) ;
137+ expect ( within ( restCard ) . getByText ( "fallback_prompt ") ) . toBeInTheDocument ( ) ;
112138
113- const summarizeCard = getPromptCard ( "Summarize document" ) ;
114- expect ( within ( summarizeCard ) . getByText ( "summary" ) ) . toBeInTheDocument ( ) ;
115- expect ( within ( summarizeCard ) . getByText ( "topic" ) ) . toBeInTheDocument ( ) ;
116- expect ( within ( summarizeCard ) . getByText ( "tone" ) ) . toBeInTheDocument ( ) ;
139+ // A single "More options" trigger for the whole REST group.
140+ expect ( screen . getAllByRole ( "button" , { name : / M o r e o p t i o n s f o r / i } ) ) . toHaveLength ( 1 ) ;
141+ } ) ;
117142
118- const originalNameCard = getPromptCard ( "translate_text" ) ;
119- expect ( within ( originalNameCard ) . getByText ( "language" ) ) . toBeInTheDocument ( ) ;
120- expect ( within ( originalNameCard ) . getByText ( "locale" ) ) . toBeInTheDocument ( ) ;
143+ it ( "truncates a large group to a +N overflow badge and uses descriptions as badge tooltips" , async ( ) => {
144+ // 10 prompts in one gateway group: 8 visible + a "+2" overflow badge.
145+ const prompts : Prompt [ ] = Array . from ( { length : 10 } , ( _ , i ) =>
146+ createMockPrompt ( {
147+ id : `prompt-${ i } ` ,
148+ name : `prompt_${ i } ` ,
149+ displayName : `Prompt ${ i } ` ,
150+ originalName : `prompt_${ i } ` ,
151+ gatewayId : "gw-bulk" ,
152+ gatewaySlug : "bulk-gateway" ,
153+ description : i === 0 ? "First prompt description." : "None" ,
154+ } ) ,
155+ ) ;
121156
122- await user . click ( screen . getByRole ( "button" , { name : "More options for Summarize document" } ) ) ;
123- expect ( await screen . findByRole ( "menuitem" , { name : "View details" } ) ) . toBeInTheDocument ( ) ;
157+ server . use ( http . get ( "/prompts" , ( ) => HttpResponse . json ( { prompts } ) ) ) ;
158+
159+ renderWithProviders ( < Prompts /> ) ;
160+
161+ await waitFor ( ( ) => {
162+ expect ( screen . getByText ( "bulk-gateway" ) ) . toBeInTheDocument ( ) ;
163+ } ) ;
164+
165+ const card = getPromptCard ( "bulk-gateway" ) ;
166+ expect ( within ( card ) . getByText ( "Prompt 0" ) ) . toBeInTheDocument ( ) ;
167+ expect ( within ( card ) . getByText ( "Prompt 7" ) ) . toBeInTheDocument ( ) ;
168+ // 9th and 10th prompts are hidden behind the overflow badge.
169+ expect ( within ( card ) . queryByText ( "Prompt 8" ) ) . not . toBeInTheDocument ( ) ;
170+ expect ( within ( card ) . getByText ( "+2" ) ) . toBeInTheDocument ( ) ;
171+
172+ // A real description becomes an accessible tooltip; filtered "None" descriptions do not.
173+ const describedTrigger = within ( card ) . getByText ( "Prompt 0" ) . closest ( "button" ) ;
174+ expect ( describedTrigger ) . not . toBeNull ( ) ;
175+ // Radix opens the tooltip on focus, giving keyboard users the description.
176+ describedTrigger ?. focus ( ) ;
177+ expect ( await screen . findByRole ( "tooltip" ) ) . toHaveTextContent ( "First prompt description." ) ;
178+
179+ // "Prompt 1" has description "None", so it renders as a plain tag with no tooltip trigger.
180+ expect ( within ( card ) . getByText ( "Prompt 1" ) . closest ( "button" ) ) . toBeNull ( ) ;
124181 } ) ;
125182
126183 it ( "renders error state when prompts fail to load" , async ( ) => {
0 commit comments