@@ -960,3 +960,176 @@ describe('defineStack - Navigation Cross-Reference Validation', () => {
960960 expect ( ( ) => defineStack ( config ) ) . not . toThrow ( ) ;
961961 } ) ;
962962} ) ;
963+
964+ // ============================================================================
965+ // Example-Level Strict Validation — mirrors examples/app-todo & examples/app-crm
966+ // ============================================================================
967+
968+ describe ( 'defineStack - Example-Level Strict Validation' , ( ) => {
969+ it ( 'should validate a Todo-style app config (strict mode)' , ( ) => {
970+ const todoConfig = {
971+ manifest : {
972+ id : 'com.example.todo' ,
973+ namespace : 'todo' ,
974+ version : '2.0.0' ,
975+ type : 'app' as const ,
976+ name : 'Todo Manager' ,
977+ description : 'A comprehensive Todo app' ,
978+ } ,
979+ objects : [
980+ {
981+ name : 'task' ,
982+ label : 'Task' ,
983+ fields : {
984+ subject : { type : 'text' , label : 'Subject' , required : true } ,
985+ status : { type : 'select' , label : 'Status' , options : [
986+ { value : 'not_started' , label : 'Not Started' } ,
987+ { value : 'in_progress' , label : 'In Progress' } ,
988+ { value : 'completed' , label : 'Completed' } ,
989+ ] } ,
990+ priority : { type : 'select' , label : 'Priority' , options : [
991+ { value : 'low' , label : 'Low' } ,
992+ { value : 'normal' , label : 'Normal' } ,
993+ { value : 'high' , label : 'High' } ,
994+ ] } ,
995+ category : { type : 'text' , label : 'Category' } ,
996+ due_date : { type : 'date' , label : 'Due Date' } ,
997+ } ,
998+ } ,
999+ ] ,
1000+ data : [
1001+ {
1002+ object : 'task' ,
1003+ mode : 'upsert' as const ,
1004+ externalId : 'subject' ,
1005+ records : [
1006+ { subject : 'Learn ObjectStack' , status : 'completed' , priority : 'high' , category : 'Work' } ,
1007+ { subject : 'Build a cool app' , status : 'in_progress' , priority : 'normal' , category : 'Work' } ,
1008+ ] ,
1009+ } ,
1010+ ] ,
1011+ dashboards : [
1012+ {
1013+ name : 'task_overview' ,
1014+ label : 'Task Overview' ,
1015+ widgets : [
1016+ { title : 'Total Tasks' , type : 'metric' , object : 'task' , aggregate : 'count' , layout : { x : 0 , y : 0 , w : 3 , h : 2 } } ,
1017+ { title : 'By Status' , type : 'pie' , object : 'task' , categoryField : 'status' , aggregate : 'count' , layout : { x : 3 , y : 0 , w : 6 , h : 4 } } ,
1018+ ] ,
1019+ } ,
1020+ ] ,
1021+ apps : [
1022+ {
1023+ name : 'todo_app' ,
1024+ label : 'Todo Manager' ,
1025+ navigation : [
1026+ { id : 'nav_tasks' , type : 'object' as const , label : 'Tasks' , objectName : 'task' } ,
1027+ { id : 'nav_dashboard' , type : 'dashboard' as const , label : 'Overview' , dashboardName : 'task_overview' } ,
1028+ ] ,
1029+ } ,
1030+ ] ,
1031+ } ;
1032+ expect ( ( ) => defineStack ( todoConfig , { strict : true } ) ) . not . toThrow ( ) ;
1033+ } ) ;
1034+
1035+ it ( 'should validate a CRM-style app config with seed data and reports (strict mode)' , ( ) => {
1036+ const crmConfig = {
1037+ manifest : {
1038+ id : 'com.example.crm' ,
1039+ namespace : 'crm' ,
1040+ version : '1.0.0' ,
1041+ type : 'app' as const ,
1042+ name : 'Sales CRM' ,
1043+ description : 'Complete sales management solution' ,
1044+ } ,
1045+ objects : [
1046+ {
1047+ name : 'account' ,
1048+ label : 'Account' ,
1049+ fields : {
1050+ name : { type : 'text' , label : 'Name' , required : true } ,
1051+ industry : { type : 'text' , label : 'Industry' } ,
1052+ annual_revenue : { type : 'number' , label : 'Annual Revenue' } ,
1053+ } ,
1054+ } ,
1055+ {
1056+ name : 'opportunity' ,
1057+ label : 'Opportunity' ,
1058+ fields : {
1059+ name : { type : 'text' , label : 'Name' , required : true } ,
1060+ amount : { type : 'currency' , label : 'Amount' } ,
1061+ stage : { type : 'select' , label : 'Stage' , options : [
1062+ { value : 'prospecting' , label : 'Prospecting' } ,
1063+ { value : 'negotiation' , label : 'Negotiation' } ,
1064+ { value : 'closed_won' , label : 'Closed Won' } ,
1065+ ] } ,
1066+ } ,
1067+ } ,
1068+ ] ,
1069+ data : [
1070+ {
1071+ object : 'account' ,
1072+ mode : 'upsert' as const ,
1073+ externalId : 'name' ,
1074+ records : [
1075+ { name : 'Acme Corp' , industry : 'technology' , annual_revenue : 5000000 } ,
1076+ ] ,
1077+ } ,
1078+ ] ,
1079+ reports : [
1080+ {
1081+ name : 'pipeline_report' ,
1082+ label : 'Pipeline Report' ,
1083+ objectName : 'opportunity' ,
1084+ type : 'summary' as const ,
1085+ columns : [
1086+ { field : 'name' } ,
1087+ { field : 'amount' , aggregate : 'sum' as const } ,
1088+ ] ,
1089+ groupingsDown : [ { field : 'stage' } ] ,
1090+ } ,
1091+ ] ,
1092+ dashboards : [
1093+ {
1094+ name : 'sales_overview' ,
1095+ label : 'Sales Overview' ,
1096+ widgets : [
1097+ { title : 'Pipeline Value' , type : 'metric' , object : 'opportunity' , valueField : 'amount' , aggregate : 'sum' , layout : { x : 0 , y : 0 , w : 4 , h : 2 } } ,
1098+ ] ,
1099+ } ,
1100+ ] ,
1101+ apps : [
1102+ {
1103+ name : 'sales_crm' ,
1104+ label : 'Sales CRM' ,
1105+ icon : 'briefcase' ,
1106+ navigation : [
1107+ { id : 'nav_accounts' , type : 'object' as const , label : 'Accounts' , objectName : 'account' } ,
1108+ { id : 'nav_opportunities' , type : 'object' as const , label : 'Opportunities' , objectName : 'opportunity' } ,
1109+ { id : 'nav_dashboard' , type : 'dashboard' as const , label : 'Sales Overview' , dashboardName : 'sales_overview' } ,
1110+ { id : 'nav_report' , type : 'report' as const , label : 'Pipeline' , reportName : 'pipeline_report' } ,
1111+ ] ,
1112+ } ,
1113+ ] ,
1114+ } ;
1115+ expect ( ( ) => defineStack ( crmConfig , { strict : true } ) ) . not . toThrow ( ) ;
1116+ } ) ;
1117+
1118+ it ( 'should reject CRM config with seed data referencing non-existent object' , ( ) => {
1119+ const badConfig = {
1120+ manifest : {
1121+ id : 'com.example.crm' ,
1122+ name : 'crm' ,
1123+ version : '1.0.0' ,
1124+ type : 'app' as const ,
1125+ } ,
1126+ objects : [
1127+ { name : 'account' , fields : { name : { type : 'text' } } } ,
1128+ ] ,
1129+ data : [
1130+ { object : 'contact' , records : [ { name : 'John' } ] } ,
1131+ ] ,
1132+ } ;
1133+ expect ( ( ) => defineStack ( badConfig , { strict : true } ) ) . toThrow ( 'contact' ) ;
1134+ } ) ;
1135+ } ) ;
0 commit comments