@@ -4,10 +4,13 @@ import {
44 ETaskPerformerType ,
55 ITemplateResponse ,
66 ITemplate ,
7+ EExtraFieldType ,
8+ IExtraField ,
79} from '../../types/template' ;
810import { ESubscriptionPlan } from '../../types/account' ;
911import { TUserListItem , EUserStatus } from '../../types/user' ;
10- import { getNormalizedTemplate , mapTemplateRequest , getEmptyKickoff } from '../template' ;
12+ import { getNormalizedTemplate , mapTemplateRequest , getEmptyKickoff , cleanTemplateReferences } from '../template' ;
13+ import { EConditionAction , EConditionOperators , EConditionLogicOperations , TConditionRule } from '../../components/TemplateEdit/TaskForm/Conditions/types' ;
1114
1215const createMockUser = ( overrides : Partial < TUserListItem > = { } ) : TUserListItem => ( {
1316 id : 1 ,
@@ -367,5 +370,171 @@ describe('template utilities', () => {
367370
368371 expect ( result . publicSuccessUrl ) . toBeNull ( ) ;
369372 } ) ;
373+ describe ( 'cleanTemplateReferences' , ( ) => {
374+ it ( 'removes invalid field references from template and task text fields but preserves system vars' , ( ) => {
375+ const template = createMockTemplate ( {
376+ wfNameTemplate : 'Name: {{valid-field}} and {{invalid-field}} and {{template-name}}' ,
377+ kickoff : {
378+ ...getEmptyKickoff ( ) ,
379+ fields : [ { apiName : 'valid-field' , type : EExtraFieldType . Text , name : 'Valid Field' , order : 1 } as unknown as IExtraField ] ,
380+ } ,
381+ tasks : [
382+ {
383+ ...createMockTemplate ( ) . tasks [ 0 ] ,
384+ name : 'Task {{invalid-field}} and {{valid-field}}' ,
385+ description : 'Desc {{workflow-starter}} and {{invalid-field}}' ,
386+ number : 1 ,
387+ fields : [ { apiName : 'valid-task-field' , type : EExtraFieldType . Text , name : 'Valid Task Field' , order : 1 } as unknown as IExtraField ] ,
388+ } ,
389+ {
390+ ...createMockTemplate ( ) . tasks [ 0 ] ,
391+ name : 'Task 2 {{valid-field}} and {{valid-task-field}} and {{broken}}' ,
392+ description : 'Desc' ,
393+ number : 2 ,
394+ } ,
395+ ] ,
396+ } ) ;
397+
398+ const cleaned = cleanTemplateReferences ( template ) ;
399+
400+ // wfNameTemplate: {{template-name}} is a WF_NAME system var, preserved; {{invalid-field}} removed
401+ expect ( cleaned . wfNameTemplate ) . toBe ( 'Name: {{valid-field}} and and {{template-name}}' ) ;
402+ expect ( cleaned . tasks [ 0 ] . name ) . toBe ( 'Task and {{valid-field}}' ) ;
403+ // {{workflow-starter}} is a TASK system var, preserved; {{invalid-field}} removed
404+ expect ( cleaned . tasks [ 0 ] . description ) . toBe ( 'Desc {{workflow-starter}} and ' ) ;
405+ // Task 2 variables should successfully retain valid fields from kickoff and task 1
406+ expect ( cleaned . tasks [ 1 ] . name ) . toBe ( 'Task 2 {{valid-field}} and {{valid-task-field}} and ' ) ;
407+ } ) ;
408+
409+ it ( 'removes invalid field references from conditions' , ( ) => {
410+ const template = createMockTemplate ( {
411+ tasks : [
412+ {
413+ ...createMockTemplate ( ) . tasks [ 0 ] ,
414+ conditions : [
415+ {
416+ apiName : 'cond-1' ,
417+ order : 1 ,
418+ action : EConditionAction . StartTask ,
419+ rules : [
420+ { field : 'valid-field' , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '1' } as unknown as TConditionRule ,
421+ { field : 'invalid-field' , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '2' } as unknown as TConditionRule ,
422+ ] ,
423+ } ,
424+ ] ,
425+ } ,
426+ ] ,
427+ kickoff : {
428+ ...getEmptyKickoff ( ) ,
429+ fields : [ { apiName : 'valid-field' , type : EExtraFieldType . Text , name : 'Valid' , order : 1 } as unknown as IExtraField ] ,
430+ } ,
431+ } ) ;
432+
433+ const cleaned = cleanTemplateReferences ( template ) ;
434+ const rules = cleaned . tasks [ 0 ] . conditions [ 0 ] . rules ;
435+ expect ( rules ) . toHaveLength ( 1 ) ;
436+ expect ( rules [ 0 ] . field ) . toBe ( 'valid-field' ) ;
437+ } ) ;
438+
439+ it ( 'allows task name to become empty if all variables are invalid' , ( ) => {
440+ const template = createMockTemplate ( {
441+ tasks : [
442+ {
443+ ...createMockTemplate ( ) . tasks [ 0 ] ,
444+ number : 2 ,
445+ name : '{{invalid-field}}' ,
446+ } ,
447+ ] ,
448+ } ) ;
449+ const cleaned = cleanTemplateReferences ( template ) ;
450+ expect ( cleaned . tasks [ 0 ] . name ) . toBe ( '' ) ;
451+ } ) ;
452+
453+ it ( 'safely handles empty rawPerformers array or invalid field performers and preserves valid members' , ( ) => {
454+ const template = createMockTemplate ( {
455+ kickoff : {
456+ ...getEmptyKickoff ( ) ,
457+ fields : [ { apiName : 'valid-user-field' , type : EExtraFieldType . User , name : 'Valid User' , order : 1 } as unknown as IExtraField ] ,
458+ } ,
459+ tasks : [
460+ {
461+ ...createMockTemplate ( ) . tasks [ 0 ] ,
462+ rawPerformers : [
463+ { type : ETaskPerformerType . OutputUser , sourceId : 'invalid-field' , label : 'Broken' , apiName : '1' } ,
464+ { type : ETaskPerformerType . OutputUser , sourceId : 'valid-user-field' , label : 'Valid User' , apiName : '2' } ,
465+ { type : ETaskPerformerType . User , sourceId : '1' , label : 'Regular User' , apiName : '3' } ,
466+ ] ,
467+ } ,
468+ ] ,
469+ } ) ;
470+ const cleaned = cleanTemplateReferences ( template ) ;
471+ expect ( cleaned . tasks [ 0 ] . rawPerformers ) . toHaveLength ( 2 ) ;
472+ expect ( cleaned . tasks [ 0 ] . rawPerformers . map ( ( p ) => p . apiName ) ) . toEqual ( [ '2' , '3' ] ) ;
473+ } ) ;
474+
475+ it ( 'removes invalid field references from rawDueDate' , ( ) => {
476+ const template = createMockTemplate ( {
477+ kickoff : {
478+ ...getEmptyKickoff ( ) ,
479+ fields : [ { apiName : 'valid-date-field' , type : EExtraFieldType . Date , name : 'Valid Date' , order : 1 } as unknown as IExtraField ] ,
480+ } ,
481+ tasks : [
482+ {
483+ ...createMockTemplate ( ) . tasks [ 0 ] ,
484+ number : 1 ,
485+ rawDueDate : {
486+ apiName : 'due-1' , duration : null , durationMonths : null , rulePreposition : 'after' , ruleTarget : 'field' , sourceId : 'valid-date-field' ,
487+ } ,
488+ } ,
489+ {
490+ ...createMockTemplate ( ) . tasks [ 0 ] ,
491+ number : 2 ,
492+ rawDueDate : {
493+ apiName : 'due-2' , duration : null , durationMonths : null , rulePreposition : 'after' , ruleTarget : 'field' , sourceId : 'invalid-field' ,
494+ } ,
495+ } ,
496+ ]
497+ } ) ;
498+ const cleaned = cleanTemplateReferences ( template ) ;
499+ expect ( cleaned . tasks [ 0 ] . rawDueDate ?. sourceId ) . toBe ( 'valid-date-field' ) ;
500+ expect ( cleaned . tasks [ 0 ] . rawDueDate ?. ruleTarget ) . toBe ( 'field' ) ;
501+ expect ( cleaned . tasks [ 1 ] . rawDueDate ?. duration ) . toBeNull ( ) ;
502+ expect ( cleaned . tasks [ 1 ] . rawDueDate ?. durationMonths ) . toBeNull ( ) ;
503+ expect ( cleaned . tasks [ 1 ] . rawDueDate ?. sourceId ) . toBeNull ( ) ;
504+ expect ( cleaned . tasks [ 1 ] . rawDueDate ?. ruleTarget ) . toBe ( 'task started' ) ;
505+ } ) ;
506+ it ( 'preserves Start After condition rules and handles empty fields' , ( ) => {
507+ const template = createMockTemplate ( {
508+ tasks : [
509+ {
510+ ...createMockTemplate ( ) . tasks [ 0 ] ,
511+ conditions : [
512+ {
513+ apiName : 'cond-1' ,
514+ order : 1 ,
515+ action : EConditionAction . StartTask ,
516+ rules : [
517+ { field : 'task-123' , fieldType : 'task' , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '1' } as unknown as TConditionRule ,
518+ { field : '' , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '2' } as unknown as TConditionRule ,
519+ { field : undefined , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '3' } as unknown as TConditionRule ,
520+ { field : null , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '4' } as unknown as TConditionRule ,
521+ { field : 'invalid-field' , fieldType : 'field' , operator : EConditionOperators . Equal , logicOperation : EConditionLogicOperations . And , predicateApiName : '5' } as unknown as TConditionRule ,
522+ ] ,
523+ } ,
524+ ] ,
525+ } ,
526+ ] ,
527+ } ) ;
528+
529+ const cleaned = cleanTemplateReferences ( template ) ;
530+ const rules = cleaned . tasks [ 0 ] . conditions [ 0 ] . rules ;
531+
532+ expect ( rules ) . toHaveLength ( 4 ) ;
533+ expect ( rules [ 0 ] . field ) . toBe ( 'task-123' ) ;
534+ expect ( rules [ 1 ] . field ) . toBe ( '' ) ;
535+ expect ( rules [ 2 ] . field ) . toBeUndefined ( ) ;
536+ expect ( rules [ 3 ] . field ) . toBeNull ( ) ;
537+ } ) ;
370538 } ) ;
371539} ) ;
540+ } ) ;
0 commit comments