@@ -18,6 +18,7 @@ import assert from "node:assert";
1818import { describe , it , beforeEach } from "node:test" ;
1919import { MessageProcessor } from "./message-processor.js" ;
2020import { Catalog , ComponentApi } from "../catalog/types.js" ;
21+ import { z } from "zod" ;
2122
2223describe ( "MessageProcessor" , ( ) => {
2324 let processor : MessageProcessor < ComponentApi > ;
@@ -32,6 +33,196 @@ describe("MessageProcessor", () => {
3233 } ) ;
3334 } ) ;
3435
36+ describe ( "getClientCapabilities" , ( ) => {
37+ it ( "generates basic client capabilities with supportedCatalogIds" , ( ) => {
38+ const caps : any = processor . getClientCapabilities ( ) ;
39+ assert . strictEqual ( ( caps [ "v0.9" ] as any ) . inlineCatalogs , undefined ) ;
40+ assert . deepStrictEqual ( caps , {
41+ "v0.9" : {
42+ supportedCatalogIds : [ "test-catalog" ] ,
43+ } ,
44+ } ) ;
45+ } ) ;
46+
47+ it ( "generates inline catalogs when requested" , ( ) => {
48+ const buttonApi : ComponentApi = {
49+ name : "Button" ,
50+ schema : z . object ( {
51+ label : z . string ( ) . describe ( "The button label" ) ,
52+ } ) ,
53+ } ;
54+ const cat = new Catalog ( "cat-1" , [ buttonApi ] ) ;
55+ const proc = new MessageProcessor ( [ cat ] ) ;
56+
57+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
58+ const inlineCat = caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] ;
59+
60+ assert . strictEqual ( inlineCat . catalogId , "cat-1" ) ;
61+ const buttonSchema = inlineCat . components ! . Button ;
62+
63+ assert . ok ( buttonSchema . allOf ) ;
64+ assert . strictEqual (
65+ buttonSchema . allOf [ 0 ] . $ref ,
66+ "common_types.json#/$defs/ComponentCommon" ,
67+ ) ;
68+ assert . strictEqual ( buttonSchema . allOf [ 1 ] . properties . component . const , "Button" ) ;
69+ assert . strictEqual (
70+ buttonSchema . allOf [ 1 ] . properties . label . description ,
71+ "The button label" ,
72+ ) ;
73+ assert . deepStrictEqual ( buttonSchema . allOf [ 1 ] . required , [ "component" , "label" ] ) ;
74+ } ) ;
75+
76+ it ( "transforms REF: descriptions into valid $ref nodes" , ( ) => {
77+ const customApi : ComponentApi = {
78+ name : "Custom" ,
79+ schema : z . object ( {
80+ title : z
81+ . string ( )
82+ . describe ( "REF:common_types.json#/$defs/DynamicString|The title" ) ,
83+ } ) ,
84+ } ;
85+ const cat = new Catalog ( "cat-ref" , [ customApi ] ) ;
86+ const proc = new MessageProcessor ( [ cat ] ) ;
87+
88+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
89+ const titleSchema =
90+ caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] . components ! . Custom . allOf [ 1 ] . properties . title ;
91+
92+ assert . strictEqual ( titleSchema . $ref , "common_types.json#/$defs/DynamicString" ) ;
93+ assert . strictEqual ( titleSchema . description , "The title" ) ;
94+ // Ensure Zod's 'type: string' was removed
95+ assert . strictEqual ( titleSchema . type , undefined ) ;
96+ } ) ;
97+
98+ it ( "generates inline catalogs with functions and theme schema" , ( ) => {
99+ const buttonApi : ComponentApi = {
100+ name : "Button" ,
101+ schema : z . object ( {
102+ label : z . string ( ) ,
103+ } ) ,
104+ } ;
105+ const addFn = {
106+ name : "add" ,
107+ returnType : "number" as const ,
108+ schema : z . object ( {
109+ a : z . number ( ) . describe ( "First number" ) ,
110+ b : z . number ( ) . describe ( "Second number" ) ,
111+ } ) ,
112+ execute : ( args : any ) => args . a + args . b ,
113+ } ;
114+
115+ const themeSchema = z . object ( {
116+ primaryColor : z . string ( ) . describe ( "REF:common_types.json#/$defs/Color|The main color" ) ,
117+ } ) ;
118+
119+ const cat = new Catalog ( "cat-full" , [ buttonApi ] , [ addFn ] , themeSchema ) ;
120+ const proc = new MessageProcessor ( [ cat ] ) ;
121+
122+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
123+ const inlineCat = caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] ;
124+
125+ assert . strictEqual ( inlineCat . catalogId , "cat-full" ) ;
126+
127+ // Verify Functions
128+ assert . ok ( inlineCat . functions ) ;
129+ assert . strictEqual ( inlineCat . functions . length , 1 ) ;
130+ const fn = inlineCat . functions [ 0 ] ;
131+ assert . strictEqual ( fn . name , "add" ) ;
132+ assert . strictEqual ( fn . returnType , "number" ) ;
133+ assert . strictEqual ( fn . parameters . properties . a . description , "First number" ) ;
134+
135+ // Verify Theme
136+ assert . ok ( inlineCat . theme ) ;
137+ assert . ok ( inlineCat . theme . primaryColor ) ;
138+ assert . strictEqual ( inlineCat . theme . primaryColor . $ref , "common_types.json#/$defs/Color" ) ;
139+ assert . strictEqual ( inlineCat . theme . primaryColor . description , "The main color" ) ;
140+ } ) ;
141+
142+ it ( "omits functions and theme when catalog has none" , ( ) => {
143+ const compApi : ComponentApi = { name : "EmptyComp" , schema : z . object ( { } ) } ;
144+ const cat = new Catalog ( "cat-empty" , [ compApi ] ) ;
145+ const proc = new MessageProcessor ( [ cat ] ) ;
146+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
147+ const inlineCat = caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] ;
148+
149+ assert . strictEqual ( inlineCat . catalogId , "cat-empty" ) ;
150+ assert . strictEqual ( inlineCat . functions , undefined ) ;
151+ assert . strictEqual ( inlineCat . theme , undefined ) ;
152+ } ) ;
153+
154+ it ( "processes REF: tags deeply nested in schema arrays and objects" , ( ) => {
155+ const deepApi : ComponentApi = {
156+ name : "DeepComp" ,
157+ schema : z . object ( {
158+ items : z . array ( z . object ( {
159+ action : z . string ( ) . describe ( "REF:common_types.json#/$defs/Action|The action to perform" )
160+ } ) )
161+ } )
162+ } ;
163+ const cat = new Catalog ( "cat-deep" , [ deepApi ] ) ;
164+ const proc = new MessageProcessor ( [ cat ] ) ;
165+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
166+
167+ const properties = caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] . components ! . DeepComp . allOf [ 1 ] . properties ;
168+ const actionSchema = properties . items . items . properties . action ;
169+
170+ assert . strictEqual ( actionSchema . $ref , "common_types.json#/$defs/Action" ) ;
171+ assert . strictEqual ( actionSchema . description , "The action to perform" ) ;
172+ assert . strictEqual ( actionSchema . type , undefined ) ;
173+ } ) ;
174+
175+ it ( "handles REF: tags without pipes or with multiple pipes" , ( ) => {
176+ const edgeApi : ComponentApi = {
177+ name : "EdgeComp" ,
178+ schema : z . object ( {
179+ noPipe : z . string ( ) . describe ( "REF:common_types.json#/$defs/NoPipe" ) ,
180+ multiPipe : z . string ( ) . describe ( "REF:common_types.json#/$defs/MultiPipe|First|Second" ) ,
181+ } )
182+ } ;
183+ const cat = new Catalog ( "cat-edge" , [ edgeApi ] ) ;
184+ const proc = new MessageProcessor ( [ cat ] ) ;
185+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
186+
187+ const properties = caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] . components ! . EdgeComp . allOf [ 1 ] . properties ;
188+
189+ assert . strictEqual ( properties . noPipe . $ref , "common_types.json#/$defs/NoPipe" ) ;
190+ assert . strictEqual ( properties . noPipe . description , undefined ) ;
191+
192+ assert . strictEqual ( properties . multiPipe . $ref , "common_types.json#/$defs/MultiPipe" ) ;
193+ assert . strictEqual ( properties . multiPipe . description , "First" ) ;
194+ } ) ;
195+
196+ it ( "handles multiple catalogs correctly" , ( ) => {
197+ const compApi : ComponentApi = { name : "C1" , schema : z . object ( { } ) } ;
198+ const cat1 = new Catalog ( "cat-1" , [ compApi ] ) ;
199+
200+ const addFn = {
201+ name : "add" ,
202+ returnType : "number" as const ,
203+ schema : z . object ( { } ) ,
204+ execute : ( ) => 0 ,
205+ } ;
206+ const themeSchema = z . object ( { color : z . string ( ) } ) ;
207+ const cat2 = new Catalog ( "cat-2" , [ ] , [ addFn ] , themeSchema ) ;
208+
209+ const proc = new MessageProcessor ( [ cat1 , cat2 ] ) ;
210+ const caps = proc . getClientCapabilities ( { includeInlineCatalogs : true } ) ;
211+
212+ assert . strictEqual ( caps [ "v0.9" ] . inlineCatalogs ! . length , 2 ) ;
213+
214+ const inlineCat1 = caps [ "v0.9" ] . inlineCatalogs ! [ 0 ] ;
215+ assert . strictEqual ( inlineCat1 . catalogId , "cat-1" ) ;
216+ assert . strictEqual ( inlineCat1 . functions , undefined ) ;
217+ assert . strictEqual ( inlineCat1 . theme , undefined ) ;
218+
219+ const inlineCat2 = caps [ "v0.9" ] . inlineCatalogs ! [ 1 ] ;
220+ assert . strictEqual ( inlineCat2 . catalogId , "cat-2" ) ;
221+ assert . strictEqual ( inlineCat2 . functions ! . length , 1 ) ;
222+ assert . ok ( inlineCat2 . theme ) ;
223+ } ) ;
224+ } ) ;
225+
35226 it ( "creates surface" , ( ) => {
36227 processor . processMessages ( [
37228 {
0 commit comments