@@ -468,6 +468,118 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="orm" {
468468
469469 });
470470
471+ describe ( " serializeJson shape — programmatic discriminability for consumers" , function (){
472+
473+ it ( " literal-form metadata.default serialises as a bare string in JSON" , function (){
474+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
475+ var json = serializeJson ( getMetadata ( inst ) );
476+ var parsed = deserializeJson ( json );
477+ var meta = findProperty ( parsed .properties , " literalDef" );
478+ expect ( meta .default ).toBe ( " hello" );
479+ expect ( isSimpleValue ( meta .default ) ).toBeTrue (
480+ " literal-form must round-trip through JSON as a simple string"
481+ );
482+ });
483+
484+ it ( " expression-form metadata.default serialises as a struct with key 'expression' in JSON" , function (){
485+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
486+ var json = serializeJson ( getMetadata ( inst ) );
487+ var parsed = deserializeJson ( json );
488+ var meta = findProperty ( parsed .properties , " expressionDef" );
489+ expect ( isStruct ( meta .default ) ).toBeTrue (
490+ " expression-form must round-trip through JSON as a struct (programmatically discriminable from a literal default)"
491+ );
492+ expect ( structKeyExists ( meta .default , " expression" ) ).toBeTrue (
493+ " the JSON struct must use the lowercase key 'expression'"
494+ );
495+ expect ( meta .default .expression ).toBe ( " repeatString( 'x', 5 )" );
496+ });
497+
498+ it ( " JSON for #now () # expression-form is a struct with the source CFML, not an evaluated DateTime" , function (){
499+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
500+ var json = serializeJson ( getMetadata ( inst ) );
501+ var parsed = deserializeJson ( json );
502+ var meta = findProperty ( parsed .properties , " nowDef" );
503+ expect ( isStruct ( meta .default ) ).toBeTrue ();
504+ expect ( meta .default .expression ).toBe ( " now()" ,
505+ " JSON for #now () # must carry the source string, not a frozen evaluated DateTime"
506+ );
507+ });
508+
509+ it ( " JSON for struct-literal expression-form preserves the source verbatim with embedded quotes escaped" , function (){
510+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
511+ var json = serializeJson ( getMetadata ( inst ) );
512+ var parsed = deserializeJson ( json );
513+ var meta = findProperty ( parsed .properties , " nowStructDef" );
514+ expect ( isStruct ( meta .default ) ).toBeTrue ();
515+ // The CFC declares default='#{"ts":now(),"n":1}#' so source is {"ts":now(),"n":1}
516+ expect ( meta .default .expression ).toInclude ( " now()" );
517+ expect ( meta .default .expression ).toInclude ( " ts" );
518+ expect ( meta .default .expression ).toInclude ( " n" );
519+ });
520+
521+ });
522+
523+ describe ( " Castable behaviour — wrapper coerces to source string in CFML coercion paths" , function (){
524+
525+ it ( " string concat coerces metadata.default to the source CFML" , function (){
526+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
527+ var meta = findProperty ( getMetadata ( inst ).properties , " expressionDef" );
528+ var concat = meta .default & " " ;
529+ expect ( concat ).toBe ( " repeatString( 'x', 5 )" ,
530+ " string-concat with empty string must coerce wrapper to source via toString()"
531+ );
532+ });
533+
534+ it ( " len() of the wrapper returns the source string length" , function (){
535+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
536+ var meta = findProperty ( getMetadata ( inst ).properties , " nowDef" );
537+ expect ( len ( meta .default ) ).toBe ( len ( " now()" ) );
538+ });
539+
540+ it ( " ucase()/lcase() coerce the wrapper through string casting" , function (){
541+ var inst = new LDEV6303 .ExpressionDefaultsCfc ();
542+ var meta = findProperty ( getMetadata ( inst ).properties , " expressionDef" );
543+ expect ( ucase ( meta .default ) ).toBe ( " REPEATSTRING( 'X', 5 )" );
544+ });
545+
546+ });
547+
548+ describe ( " external serialisation — ExpressionDefault survives objectSave/objectLoad rehydration" , function (){
549+
550+ it ( " objectSave + objectLoad round-trips an instance and metadata.default for expression-form is still an Expression wrapper" , function (){
551+ var src = new LDEV6303 .ExpressionDefaultsCfc ();
552+ var bin = objectSave ( src );
553+ var dup = objectLoad ( bin );
554+
555+ // instance scope values must round-trip (variables-scope per-instance evaluation)
556+ expect ( dup .getExpressionDef () ).toBe ( " xxxxx" ,
557+ " variables-scope evaluated value must survive objectSave/objectLoad"
558+ );
559+
560+ // metadata.default for expression-form must still be the wrapper after rehydration
561+ var meta = findProperty ( getMetadata ( dup ).properties , " expressionDef" );
562+ expect ( structKeyExists ( meta , " default" ) ).toBeTrue ();
563+ expect ( isObject ( meta .default ) ).toBeTrue (
564+ " rehydrated metadata.default for expression-form must still be an Expression wrapper, not flattened to a string"
565+ );
566+ expect ( " " & meta .default ).toBe ( " repeatString( 'x', 5 )" ,
567+ " rehydrated wrapper must stringify to the same source CFML"
568+ );
569+ });
570+
571+ it ( " objectSave + objectLoad preserves literal-form metadata.default as a simple string" , function (){
572+ var src = new LDEV6303 .ExpressionDefaultsCfc ();
573+ var bin = objectSave ( src );
574+ var dup = objectLoad ( bin );
575+
576+ var meta = findProperty ( getMetadata ( dup ).properties , " literalDef" );
577+ expect ( meta .default ).toBe ( " hello" );
578+ expect ( isSimpleValue ( meta .default ) ).toBeTrue ();
579+ });
580+
581+ });
582+
471583 });
472584 }
473585
0 commit comments