11import { describe , expect , it } from "vitest" ;
2- import { Flow } from "@code0-tech/sagittarius-graphql-types" ;
2+ import { Flow , FunctionDefinition } from "@code0-tech/sagittarius-graphql-types" ;
33import { getSignatureSchema , getTypeSchema } from "../../src" ;
44import { DATA_TYPES , FUNCTION_SIGNATURES } from "../data" ;
55
@@ -688,4 +688,141 @@ describe("Schema", () => {
688688 ) . toBe ( true ) ;
689689 } ) ;
690690
691+ it ( 'offers a nullable TEXT return as a ReferenceValue suggestion for a plain TEXT parameter' , ( ) => {
692+ // Node 1: custom::text::nullable(): TEXT | null — no parameters, returns a
693+ // nullable string.
694+ // Node 2: std::text::split(value: TEXT, delimiter: TEXT): LIST<TEXT>
695+ //
696+ // The `value` parameter is declared as plain TEXT (string). Node 1's return
697+ // type is string | null. Strict assignability would reject string | null → string,
698+ // but the editor must still offer node 1 as a ReferenceValue suggestion here:
699+ // the nullish part of a reference's type should be ignored for suggestion scoping.
700+ const NULLABLE_TEXT_FN : FunctionDefinition = {
701+ id : "gid://sagittarius/FunctionDefinition/9001" ,
702+ identifier : "custom::text::nullable" ,
703+ signature : "(): TEXT | null" ,
704+ } ;
705+
706+ const flow : Flow = {
707+ id : "gid://sagittarius/Flow/1" ,
708+ startingNodeId : "gid://sagittarius/NodeFunction/1" ,
709+ signature : "(): void" ,
710+ nodes : {
711+ nodes : [
712+ {
713+ id : "gid://sagittarius/NodeFunction/1" ,
714+ functionDefinition : { identifier : "custom::text::nullable" } ,
715+ nextNodeId : "gid://sagittarius/NodeFunction/2" ,
716+ parameters : {
717+ nodes : [ ] ,
718+ } ,
719+ } ,
720+ {
721+ id : "gid://sagittarius/NodeFunction/2" ,
722+ functionDefinition : { identifier : "std::text::split" } ,
723+ parameters : {
724+ nodes : [
725+ { value : null } ,
726+ { value : { __typename : "LiteralValue" , value : "," } } ,
727+ ] ,
728+ } ,
729+ } ,
730+ ] ,
731+ } ,
732+ } ;
733+
734+ const [ valueSchema ] = getSignatureSchema (
735+ flow ,
736+ DATA_TYPES ,
737+ [ ...FUNCTION_SIGNATURES , NULLABLE_TEXT_FN ] ,
738+ "gid://sagittarius/NodeFunction/2" ,
739+ ) ;
740+
741+ // value: TEXT → free-form text input.
742+ expect ( valueSchema . schema . input ) . toBe ( "text" ) ;
743+
744+ // Node 1 returns TEXT | null; stripping the nullish part leaves TEXT, which is
745+ // assignable to the TEXT parameter. Its return value is a direct reference
746+ // (no property path).
747+ const suggestions = ( valueSchema . schema . suggestions ?? [ ] ) as any [ ] ;
748+ expect (
749+ suggestions . some (
750+ ( s ) =>
751+ s . __typename === "ReferenceValue" &&
752+ s . nodeFunctionId === "gid://sagittarius/NodeFunction/1" &&
753+ ! s . referencePath ,
754+ ) ,
755+ ) . toBe ( true ) ;
756+ } ) ;
757+
758+ it ( 'offers a nullable object property as a ReferenceValue path suggestion for a plain TEXT parameter' , ( ) => {
759+ // Node 1: custom::text::nullable_object(): {text?: TEXT | null} — no parameters,
760+ // returns an object whose `text` property is optional and nullable.
761+ // Node 2: std::text::split(value: TEXT, delimiter: TEXT): LIST<TEXT>
762+ //
763+ // The `value` parameter is declared as plain TEXT (string). Node 1's `text`
764+ // property has type string | null | undefined. Strict assignability would
765+ // reject it, but the editor must still offer node 1's `text` property as a
766+ // ReferenceValue suggestion with referencePath [{path: "text"}]: the nullish
767+ // part of a reference's type should be ignored for suggestion scoping.
768+ const NULLABLE_OBJECT_FN : FunctionDefinition = {
769+ id : "gid://sagittarius/FunctionDefinition/9002" ,
770+ identifier : "custom::text::nullable_object" ,
771+ signature : "(): {text?: TEXT | null}" ,
772+ } ;
773+
774+ const flow : Flow = {
775+ id : "gid://sagittarius/Flow/1" ,
776+ startingNodeId : "gid://sagittarius/NodeFunction/1" ,
777+ signature : "(): void" ,
778+ nodes : {
779+ nodes : [
780+ {
781+ id : "gid://sagittarius/NodeFunction/1" ,
782+ functionDefinition : { identifier : "custom::text::nullable_object" } ,
783+ nextNodeId : "gid://sagittarius/NodeFunction/2" ,
784+ parameters : {
785+ nodes : [ ] ,
786+ } ,
787+ } ,
788+ {
789+ id : "gid://sagittarius/NodeFunction/2" ,
790+ functionDefinition : { identifier : "std::text::split" } ,
791+ parameters : {
792+ nodes : [
793+ { value : null } ,
794+ { value : { __typename : "LiteralValue" , value : "," } } ,
795+ ] ,
796+ } ,
797+ } ,
798+ ] ,
799+ } ,
800+ } ;
801+
802+ const [ valueSchema ] = getSignatureSchema (
803+ flow ,
804+ DATA_TYPES ,
805+ [ ...FUNCTION_SIGNATURES , NULLABLE_OBJECT_FN ] ,
806+ "gid://sagittarius/NodeFunction/2" ,
807+ ) ;
808+
809+ // value: TEXT → free-form text input.
810+ expect ( valueSchema . schema . input ) . toBe ( "text" ) ;
811+
812+ // Node 1's `text` property is TEXT | null | undefined; stripping the nullish
813+ // part leaves TEXT, which is assignable to the TEXT parameter → node 1 must
814+ // be offered with referencePath [{path: "text"}].
815+ const suggestions = ( valueSchema . schema . suggestions ?? [ ] ) as any [ ] ;
816+ expect (
817+ suggestions . some (
818+ ( s ) =>
819+ s . __typename === "ReferenceValue" &&
820+ s . nodeFunctionId === "gid://sagittarius/NodeFunction/1" &&
821+ Array . isArray ( s . referencePath ) &&
822+ s . referencePath . length === 1 &&
823+ s . referencePath [ 0 ] . path === "text" ,
824+ ) ,
825+ ) . toBe ( true ) ;
826+ } ) ;
827+
691828} )
0 commit comments