@@ -13,6 +13,8 @@ import {
13
13
isSubNodeType ,
14
14
applyDeclarativeNodeOptionParameters ,
15
15
getParameterIssues ,
16
+ isTriggerNode ,
17
+ isExecutable ,
16
18
} from '@/NodeHelpers' ;
17
19
import type { Workflow } from '@/Workflow' ;
18
20
@@ -4248,4 +4250,266 @@ describe('NodeHelpers', () => {
4248
4250
} ) ;
4249
4251
} ) ;
4250
4252
} ) ;
4253
+
4254
+ describe ( 'isTriggerNode' , ( ) => {
4255
+ const tests : Array < {
4256
+ description : string ;
4257
+ input : INodeTypeDescription ;
4258
+ expected : boolean ;
4259
+ } > = [
4260
+ {
4261
+ description : 'Should return true for node with trigger in group' ,
4262
+ input : {
4263
+ name : 'TriggerNode' ,
4264
+ displayName : 'Trigger Node' ,
4265
+ group : [ 'trigger' ] ,
4266
+ description : 'Trigger node description' ,
4267
+ version : 1 ,
4268
+ defaults : { } ,
4269
+ inputs : [ ] ,
4270
+ outputs : [ NodeConnectionType . Main ] ,
4271
+ properties : [ ] ,
4272
+ } ,
4273
+ expected : true ,
4274
+ } ,
4275
+ {
4276
+ description : 'Should return true for node with multiple groups including trigger' ,
4277
+ input : {
4278
+ name : 'MultiGroupTriggerNode' ,
4279
+ displayName : 'Multi-Group Trigger Node' ,
4280
+ group : [ 'trigger' , 'input' ] ,
4281
+ description : 'Multi-group trigger node description' ,
4282
+ version : 1 ,
4283
+ defaults : { } ,
4284
+ inputs : [ ] ,
4285
+ outputs : [ NodeConnectionType . Main ] ,
4286
+ properties : [ ] ,
4287
+ } ,
4288
+ expected : true ,
4289
+ } ,
4290
+ {
4291
+ description : 'Should return false for node without trigger in group' ,
4292
+ input : {
4293
+ name : 'RegularNode' ,
4294
+ displayName : 'Regular Node' ,
4295
+ group : [ 'input' ] ,
4296
+ description : 'Regular node description' ,
4297
+ version : 1 ,
4298
+ defaults : { } ,
4299
+ inputs : [ NodeConnectionType . Main ] ,
4300
+ outputs : [ NodeConnectionType . Main ] ,
4301
+ properties : [ ] ,
4302
+ } ,
4303
+ expected : false ,
4304
+ } ,
4305
+ {
4306
+ description : 'Should return false for node with empty group array' ,
4307
+ input : {
4308
+ name : 'EmptyGroupNode' ,
4309
+ displayName : 'Empty Group Node' ,
4310
+ group : [ ] ,
4311
+ description : 'Empty group node description' ,
4312
+ version : 1 ,
4313
+ defaults : { } ,
4314
+ inputs : [ NodeConnectionType . Main ] ,
4315
+ outputs : [ NodeConnectionType . Main ] ,
4316
+ properties : [ ] ,
4317
+ } ,
4318
+ expected : false ,
4319
+ } ,
4320
+ {
4321
+ description :
4322
+ 'Should return false when trigger is called Trigger, but does not have a trigger group' ,
4323
+ input : {
4324
+ name : 'AlmostTriggerNode' ,
4325
+ displayName : 'Almost Trigger Node' ,
4326
+ group : [ 'transform' ] ,
4327
+ description : 'Almost trigger node description' ,
4328
+ version : 1 ,
4329
+ defaults : { } ,
4330
+ inputs : [ NodeConnectionType . Main ] ,
4331
+ outputs : [ NodeConnectionType . Main ] ,
4332
+ properties : [ ] ,
4333
+ } ,
4334
+ expected : false ,
4335
+ } ,
4336
+ ] ;
4337
+
4338
+ for ( const testData of tests ) {
4339
+ test ( testData . description , ( ) => {
4340
+ const result = isTriggerNode ( testData . input ) ;
4341
+ expect ( result ) . toEqual ( testData . expected ) ;
4342
+ } ) ;
4343
+ }
4344
+ } ) ;
4345
+
4346
+ describe ( 'isExecutable' , ( ) => {
4347
+ const workflowMock = {
4348
+ expression : {
4349
+ getSimpleParameterValue : jest . fn ( ) . mockReturnValue ( [ NodeConnectionType . Main ] ) ,
4350
+ } ,
4351
+ } as unknown as Workflow ;
4352
+
4353
+ const tests : Array < {
4354
+ description : string ;
4355
+ node : INode ;
4356
+ nodeTypeData : INodeTypeDescription ;
4357
+ expected : boolean ;
4358
+ mockReturnValue ?: NodeConnectionType [ ] ;
4359
+ } > = [
4360
+ {
4361
+ description : 'Should return true for trigger node' ,
4362
+ node : {
4363
+ id : 'triggerNodeId' ,
4364
+ name : 'TriggerNode' ,
4365
+ position : [ 0 , 0 ] ,
4366
+ type : 'n8n-nodes-base.TriggerNode' ,
4367
+ typeVersion : 1 ,
4368
+ parameters : { } ,
4369
+ } ,
4370
+ nodeTypeData : {
4371
+ name : 'TriggerNode' ,
4372
+ displayName : 'Trigger Node' ,
4373
+ group : [ 'trigger' ] ,
4374
+ description : 'Trigger node description' ,
4375
+ version : 1 ,
4376
+ defaults : { } ,
4377
+ inputs : [ ] ,
4378
+ outputs : [ NodeConnectionType . Main ] ,
4379
+ properties : [ ] ,
4380
+ } ,
4381
+ expected : true ,
4382
+ } ,
4383
+ {
4384
+ description : 'Should return true for node with Main output' ,
4385
+ node : {
4386
+ id : 'mainOutputNodeId' ,
4387
+ name : 'MainOutputNode' ,
4388
+ position : [ 0 , 0 ] ,
4389
+ type : 'n8n-nodes-base.MainOutputNode' ,
4390
+ typeVersion : 1 ,
4391
+ parameters : { } ,
4392
+ } ,
4393
+ nodeTypeData : {
4394
+ name : 'MainOutputNode' ,
4395
+ displayName : 'Main Output Node' ,
4396
+ group : [ 'transform' ] ,
4397
+ description : 'Node with Main output' ,
4398
+ version : 1 ,
4399
+ defaults : { } ,
4400
+ inputs : [ NodeConnectionType . Main ] ,
4401
+ outputs : [ NodeConnectionType . Main ] ,
4402
+ properties : [ ] ,
4403
+ } ,
4404
+ expected : true ,
4405
+ } ,
4406
+ {
4407
+ description : 'Should return false for node without Main output and not a trigger' ,
4408
+ node : {
4409
+ id : 'nonExecutableNodeId' ,
4410
+ name : 'NonExecutableNode' ,
4411
+ position : [ 0 , 0 ] ,
4412
+ type : 'n8n-nodes-base.NonExecutableNode' ,
4413
+ typeVersion : 1 ,
4414
+ parameters : { } ,
4415
+ } ,
4416
+ nodeTypeData : {
4417
+ name : 'NonExecutableNode' ,
4418
+ displayName : 'Non-Executable Node' ,
4419
+ group : [ 'output' ] ,
4420
+ description : 'Node without Main output and not a trigger' ,
4421
+ version : 1 ,
4422
+ defaults : { } ,
4423
+ inputs : [ NodeConnectionType . Main ] ,
4424
+ outputs : [ NodeConnectionType . AiAgent ] ,
4425
+ properties : [ ] ,
4426
+ } ,
4427
+ expected : false ,
4428
+ } ,
4429
+ {
4430
+ description : 'Should return true for node with mixed outputs including Main' ,
4431
+ node : {
4432
+ id : 'mixedOutputNodeId' ,
4433
+ name : 'MixedOutputNode' ,
4434
+ position : [ 0 , 0 ] ,
4435
+ type : 'n8n-nodes-base.MixedOutputNode' ,
4436
+ typeVersion : 1 ,
4437
+ parameters : { } ,
4438
+ } ,
4439
+ nodeTypeData : {
4440
+ name : 'MixedOutputNode' ,
4441
+ displayName : 'Mixed Output Node' ,
4442
+ group : [ 'transform' ] ,
4443
+ description : 'Node with multiple output types including Main' ,
4444
+ version : 1 ,
4445
+ defaults : { } ,
4446
+ inputs : [ NodeConnectionType . Main ] ,
4447
+ outputs : [ NodeConnectionType . Main , NodeConnectionType . AiAgent ] ,
4448
+ properties : [ ] ,
4449
+ } ,
4450
+ expected : true ,
4451
+ } ,
4452
+ {
4453
+ description : 'Should return false for node with only AiTool output and not a trigger' ,
4454
+ node : {
4455
+ id : 'aiToolOutputNodeId' ,
4456
+ name : 'AiToolOutputNode' ,
4457
+ position : [ 0 , 0 ] ,
4458
+ type : 'n8n-nodes-base.AiToolOutputNode' ,
4459
+ typeVersion : 1 ,
4460
+ parameters : { } ,
4461
+ } ,
4462
+ nodeTypeData : {
4463
+ name : 'AiToolOutputNode' ,
4464
+ displayName : 'AI Tool Output Node' ,
4465
+ group : [ 'output' ] ,
4466
+ description : 'Node with only AiTool output and not a trigger' ,
4467
+ version : 1 ,
4468
+ defaults : { } ,
4469
+ inputs : [ ] ,
4470
+ outputs : [ NodeConnectionType . AiTool ] , // Only AiTool output, no Main
4471
+ properties : [ ] ,
4472
+ } ,
4473
+ expected : false ,
4474
+ } ,
4475
+ {
4476
+ description : 'Should return false for node with dynamic outputs set to AiTool only' ,
4477
+ node : {
4478
+ id : 'dynamicAiToolNodeId' ,
4479
+ name : 'DynamicAiToolNode' ,
4480
+ position : [ 0 , 0 ] ,
4481
+ type : 'n8n-nodes-base.DynamicAiToolNode' ,
4482
+ typeVersion : 1 ,
4483
+ parameters : { } ,
4484
+ } ,
4485
+ nodeTypeData : {
4486
+ name : 'DynamicAiToolNode' ,
4487
+ displayName : 'Dynamic AiTool Node' ,
4488
+ group : [ 'output' ] ,
4489
+ description : 'Node with dynamic outputs that resolve to only AiTool' ,
4490
+ version : 1 ,
4491
+ defaults : { } ,
4492
+ inputs : [ NodeConnectionType . Main ] ,
4493
+ outputs : '={{["ai_tool"]}}' , // Dynamic expression that resolves to AiTool only
4494
+ properties : [ ] ,
4495
+ } ,
4496
+ expected : false ,
4497
+ mockReturnValue : [ NodeConnectionType . AiTool ] ,
4498
+ } ,
4499
+ ] ;
4500
+
4501
+ for ( const testData of tests ) {
4502
+ test ( testData . description , ( ) => {
4503
+ // If this test has a custom mock return value, configure it
4504
+ if ( testData . mockReturnValue ) {
4505
+ ( workflowMock . expression . getSimpleParameterValue as jest . Mock ) . mockReturnValueOnce (
4506
+ testData . mockReturnValue ,
4507
+ ) ;
4508
+ }
4509
+
4510
+ const result = isExecutable ( workflowMock , testData . node , testData . nodeTypeData ) ;
4511
+ expect ( result ) . toEqual ( testData . expected ) ;
4512
+ } ) ;
4513
+ }
4514
+ } ) ;
4251
4515
} ) ;
0 commit comments