@@ -637,4 +637,171 @@ describe("ToolsTab", () => {
637637 expect ( screen . getByText ( / v e r s i o n / i) ) . toBeInTheDocument ( ) ;
638638 } ) ;
639639 } ) ;
640+
641+ describe ( "JSON Validation Integration" , ( ) => {
642+ const toolWithJsonParams : Tool = {
643+ name : "jsonTool" ,
644+ description : "Tool with JSON parameters" ,
645+ inputSchema : {
646+ type : "object" as const ,
647+ properties : {
648+ config : {
649+ type : "object" as const ,
650+ // No properties defined - this will force JSON mode
651+ } ,
652+ data : {
653+ type : "array" as const ,
654+ // No items defined - this will force JSON mode
655+ } ,
656+ } ,
657+ } ,
658+ } ;
659+
660+ it ( "should prevent tool execution when JSON validation fails" , async ( ) => {
661+ const mockCallTool = jest . fn ( ) ;
662+ renderToolsTab ( {
663+ tools : [ toolWithJsonParams ] ,
664+ selectedTool : toolWithJsonParams ,
665+ callTool : mockCallTool ,
666+ } ) ;
667+
668+ // Find JSON editor textareas (there should be at least 1 for JSON parameters)
669+ const textareas = screen . getAllByRole ( "textbox" ) ;
670+ expect ( textareas . length ) . toBeGreaterThanOrEqual ( 1 ) ;
671+
672+ // Enter invalid JSON in the first textarea
673+ const configTextarea = textareas [ 0 ] ;
674+ fireEvent . change ( configTextarea , {
675+ target : { value : '{ "invalid": json }' }
676+ } ) ;
677+
678+ // Try to run the tool
679+ const runButton = screen . getByRole ( "button" , { name : / r u n t o o l / i } ) ;
680+ await act ( async ( ) => {
681+ fireEvent . click ( runButton ) ;
682+ } ) ;
683+
684+ // Tool should not have been called due to validation failure
685+ expect ( mockCallTool ) . not . toHaveBeenCalled ( ) ;
686+ } ) ;
687+
688+ it ( "should allow tool execution when JSON validation passes" , async ( ) => {
689+ const mockCallTool = jest . fn ( ) ;
690+ renderToolsTab ( {
691+ tools : [ toolWithJsonParams ] ,
692+ selectedTool : toolWithJsonParams ,
693+ callTool : mockCallTool ,
694+ } ) ;
695+
696+ // Find JSON editor textareas
697+ const textareas = screen . getAllByRole ( "textbox" ) ;
698+
699+ // Enter valid JSON in the first textarea
700+ fireEvent . change ( textareas [ 0 ] , {
701+ target : { value : '{ "config": { "setting": "value" }, "data": ["item1", "item2"] }' }
702+ } ) ;
703+
704+ // Wait for debounced updates
705+ await act ( async ( ) => {
706+ await new Promise ( resolve => setTimeout ( resolve , 350 ) ) ;
707+ } ) ;
708+
709+ // Try to run the tool
710+ const runButton = screen . getByRole ( "button" , { name : / r u n t o o l / i } ) ;
711+ await act ( async ( ) => {
712+ fireEvent . click ( runButton ) ;
713+ } ) ;
714+
715+ // Tool should have been called successfully
716+ expect ( mockCallTool ) . toHaveBeenCalled ( ) ;
717+ } ) ;
718+
719+ it ( "should handle mixed valid and invalid JSON parameters" , async ( ) => {
720+ const mockCallTool = jest . fn ( ) ;
721+ renderToolsTab ( {
722+ tools : [ toolWithJsonParams ] ,
723+ selectedTool : toolWithJsonParams ,
724+ callTool : mockCallTool ,
725+ } ) ;
726+
727+ const textareas = screen . getAllByRole ( "textbox" ) ;
728+
729+ // Enter invalid JSON that contains both valid and invalid parts
730+ fireEvent . change ( textareas [ 0 ] , {
731+ target : { value : '{ "config": { "setting": "value" }, "data": ["unclosed array" }' }
732+ } ) ;
733+
734+ // Try to run the tool
735+ const runButton = screen . getByRole ( "button" , { name : / r u n t o o l / i } ) ;
736+ await act ( async ( ) => {
737+ fireEvent . click ( runButton ) ;
738+ } ) ;
739+
740+ // Tool should not have been called due to validation failure
741+ expect ( mockCallTool ) . not . toHaveBeenCalled ( ) ;
742+ } ) ;
743+
744+ it ( "should work with tools that have no JSON parameters" , async ( ) => {
745+ const mockCallTool = jest . fn ( ) ;
746+ const simpleToolWithStringParam : Tool = {
747+ name : "simpleTool" ,
748+ description : "Tool with simple parameters" ,
749+ inputSchema : {
750+ type : "object" as const ,
751+ properties : {
752+ message : { type : "string" as const } ,
753+ count : { type : "number" as const } ,
754+ } ,
755+ } ,
756+ } ;
757+
758+ renderToolsTab ( {
759+ tools : [ simpleToolWithStringParam ] ,
760+ selectedTool : simpleToolWithStringParam ,
761+ callTool : mockCallTool ,
762+ } ) ;
763+
764+ // Fill in the simple parameters
765+ const messageInput = screen . getByRole ( "textbox" ) ;
766+ const countInput = screen . getByRole ( "spinbutton" ) ;
767+
768+ fireEvent . change ( messageInput , { target : { value : "test message" } } ) ;
769+ fireEvent . change ( countInput , { target : { value : "5" } } ) ;
770+
771+ // Run the tool
772+ const runButton = screen . getByRole ( "button" , { name : / r u n t o o l / i } ) ;
773+ await act ( async ( ) => {
774+ fireEvent . click ( runButton ) ;
775+ } ) ;
776+
777+ // Tool should have been called successfully (no JSON validation needed)
778+ expect ( mockCallTool ) . toHaveBeenCalledWith ( simpleToolWithStringParam . name , {
779+ message : "test message" ,
780+ count : 5 ,
781+ } ) ;
782+ } ) ;
783+
784+ it ( "should handle empty JSON parameters correctly" , async ( ) => {
785+ const mockCallTool = jest . fn ( ) ;
786+ renderToolsTab ( {
787+ tools : [ toolWithJsonParams ] ,
788+ selectedTool : toolWithJsonParams ,
789+ callTool : mockCallTool ,
790+ } ) ;
791+
792+ const textareas = screen . getAllByRole ( "textbox" ) ;
793+
794+ // Clear the textarea (empty JSON should be valid)
795+ fireEvent . change ( textareas [ 0 ] , { target : { value : '' } } ) ;
796+
797+ // Try to run the tool
798+ const runButton = screen . getByRole ( "button" , { name : / r u n t o o l / i } ) ;
799+ await act ( async ( ) => {
800+ fireEvent . click ( runButton ) ;
801+ } ) ;
802+
803+ // Tool should have been called (empty JSON is considered valid)
804+ expect ( mockCallTool ) . toHaveBeenCalled ( ) ;
805+ } ) ;
806+ } ) ;
640807} ) ;
0 commit comments