diff --git a/index.js b/index.js
index 2002a4f..d087391 100644
--- a/index.js
+++ b/index.js
@@ -4,6 +4,7 @@ module.exports = {
rules: {
'processmaker/custom-validation': 'error',
'processmaker/gateway-direction': 'error',
+ 'processmaker/event-based-gateway': 'error',
'processmaker/call-activity-child-process': 'error',
'processmaker/call-activity-sequence-flow': 'error',
'processmaker/id-required': 'error',
diff --git a/rule-tester.js b/rule-tester.js
new file mode 100644
index 0000000..0957922
--- /dev/null
+++ b/rule-tester.js
@@ -0,0 +1,90 @@
+/* global it, describe, beforeEach */
+
+const assert = require('assert');
+
+const Linter = require('bpmnlint/lib/linter');
+
+function createResolver(rule) {
+ return {
+ resolveRule: () => Promise.resolve(rule)
+ };
+}
+
+function expectEqual(a, b) {
+ assert.deepStrictEqual(a, b);
+}
+
+
+function verify(ruleName, rule, testCases) {
+ const linterConfig = {
+ rules: { [ruleName]: 2 }
+ };
+
+ describe(`rules/${ruleName}`, function() {
+
+ let linter;
+
+ beforeEach(function() {
+ linter = new Linter({
+ resolver: createResolver(rule)
+ });
+ });
+
+
+ describe('should lint valid', function() {
+
+ testCases.valid.forEach(({ moddleElement }, idx) => (
+
+ it(`test case #${idx + 1}`, function() {
+ return (
+ Promise.resolve(moddleElement)
+ .then(moddleRoot => {
+ return linter.lint(moddleRoot.root, linterConfig);
+ })
+ .then(lintResults => {
+ expectEqual(lintResults, {});
+ })
+ );
+ })
+
+ ));
+
+ });
+
+
+ describe('should lint invalid', function() {
+
+ testCases.invalid.forEach(({ moddleElement, report }, idx) => (
+
+ it(`test case #${idx}`, function() {
+
+ const expectedResult = report instanceof Array ? report : [{
+ ...report,
+ category: 'error'
+ }];
+
+ return (
+ Promise.resolve(moddleElement)
+ .then(moddleRoot => {
+ return linter.lint(moddleRoot.root, linterConfig);
+ })
+ .then(lintResults => {
+ expectEqual(lintResults, {
+ [ruleName]: expectedResult
+ });
+ })
+ );
+ })
+
+ ));
+
+ });
+
+ });
+
+}
+
+
+module.exports = {
+ verify
+};
diff --git a/rules/event-based-gateway.js b/rules/event-based-gateway.js
new file mode 100644
index 0000000..01b9b7b
--- /dev/null
+++ b/rules/event-based-gateway.js
@@ -0,0 +1,44 @@
+const { isAny } = require('bpmnlint-utils');
+
+/**
+ * Rule that validates gateways according to the following rules:
+ *
+ * - An Event Based Gateway can only be connected to events.
+ */
+module.exports = function() {
+ let gateway = null;
+
+ function outgoingFlowsAreValid(gateway) {
+ const outgoing = gateway.get('outgoing');
+ return outgoing.filter(sequenceFlow => {
+ const target = sequenceFlow.get('targetRef');
+ return !isAny(target, ['bpmn:IntermediateCatchEvent', 'bpmn:EndEvent']);
+ }).length === 0;
+ }
+
+ function check(gateway, reporter) {
+ if (!isAny(gateway, ['bpmn:EventBasedGateway'])) {
+ return;
+ }
+
+ const outgoing = gateway.get('outgoing');
+ const valid = outgoing.filter(sequenceFlow => {
+ const target = sequenceFlow.get('targetRef');
+ const isValidType = isAny(target, ['bpmn:IntermediateCatchEvent', 'bpmn:EndEvent']);
+ const onlyOneIncoming = target.get('incoming').length === 1;
+ if (!isValidType) {
+ reporter.report(target.id, 'Event Gateways target elements must be Catch Events');
+ }
+ if (!onlyOneIncoming) {
+ reporter.report(target.id, 'Event Gateway target elements must not have additional incoming Sequence Flows');
+ }
+ return !isValidType || !onlyOneIncoming;
+ }).length === 0;
+
+ if (!valid) {
+ reporter.report(gateway.id, 'Event Gateways target elements must be valid Catch Events');
+ }
+ }
+
+ return { check };
+};
diff --git a/test-diagrams/event-based-gateway.invalid.bpmn b/test-diagrams/event-based-gateway.invalid.bpmn
new file mode 100644
index 0000000..c1b85d7
--- /dev/null
+++ b/test-diagrams/event-based-gateway.invalid.bpmn
@@ -0,0 +1,133 @@
+
+
+
+
+ node_5
+
+
+ node_5
+ node_9
+ node_11
+ node_26
+
+
+
+ node_9
+ node_10
+ node_18
+
+ PT1H
+
+
+
+
+ node_18
+
+
+
+
+ node_23
+
+
+
+ node_30
+
+
+ node_11
+ node_23
+
+
+
+
+
+ node_26
+ node_30
+
+
+
+
+
+ node_14
+ node_10
+
+
+
+ node_14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-diagrams/event-based-gateway.valid.bpmn b/test-diagrams/event-based-gateway.valid.bpmn
new file mode 100644
index 0000000..80e6ba5
--- /dev/null
+++ b/test-diagrams/event-based-gateway.valid.bpmn
@@ -0,0 +1,107 @@
+
+
+
+
+ node_5
+
+
+ node_5
+ node_9
+ node_11
+ node_26
+
+
+
+ node_9
+ node_18
+
+ PT1H
+
+
+
+
+ node_11
+ node_23
+
+
+
+
+ node_18
+
+
+
+
+ node_23
+
+
+
+
+ node_26
+ node_30
+
+
+
+
+ node_30
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test.js b/test.js
index 932e796..729353a 100644
--- a/test.js
+++ b/test.js
@@ -1,11 +1,12 @@
const readModdle = require('bpmnlint/lib/testers/helper').readModdle;
-const RuleTester = require('bpmnlint/lib/testers/rule-tester');
+const RuleTester = require('./rule-tester');
const gatewayDirectionRule = require('./rules/gateway-direction');
const callActivityChildProcessRule = require('./rules/call-activity-child-process');
const callActivitySequenceFlowRule = require('./rules/call-activity-sequence-flow');
const idRequiredRule = require('./rules/id-required');
const signalRefRequiredRule = require('./rules/signal-ref-required');
+const eventBasedGatewayRule = require('./rules/event-based-gateway');
RuleTester.verify('gateway-direction', gatewayDirectionRule, {
valid: [
@@ -165,3 +166,38 @@ RuleTester.verify('signal-ref-required', signalRefRequiredRule, {
},
]
});
+
+RuleTester.verify('event-based-gateway', eventBasedGatewayRule, {
+ valid: [
+ {
+ moddleElement: readModdle('./test-diagrams/event-based-gateway.valid.bpmn')
+ },
+ ],
+ invalid: [
+ {
+ moddleElement: readModdle('./test-diagrams/event-based-gateway.invalid.bpmn'),
+ report: [
+ {
+ "category": "error",
+ "id": "node_6",
+ "message": "Event Gateway target elements must not have additional incoming Sequence Flows"
+ },
+ {
+ "category": "error",
+ "id": "node_2",
+ "message": "Event Gateways target elements must be Catch Events"
+ },
+ {
+ "category": "error",
+ "id": "node_4",
+ "message": "Event Gateways target elements must be Catch Events"
+ },
+ {
+ "category": "error",
+ "id": "node_3",
+ "message": "Event Gateways target elements must be valid Catch Events"
+ }
+ ]
+ },
+ ]
+});