Skip to content

Commit

Permalink
[TEST] Add visual tests for edges targeting collapsed elements (#1867)
Browse files Browse the repository at this point in the history
Changes
  - add diagrams for expanded/collapsed pools and subprocesses
  - HTML page: manage collapse element query parameter
  - added tests: test the diagram rendering with and without collapsed elements

Note that collapsing elements is not officially supported and is managed with mxGraph custom code.
We demonstrate it in the examples, so the new tests ensure the sequence and message flows are rendered correctly. The new tests should avoid finding a problem in the examples repository after a new version is released.
  • Loading branch information
tbouffard authored Mar 16, 2022
1 parent 54154f4 commit d13de43
Show file tree
Hide file tree
Showing 12 changed files with 520 additions and 3 deletions.
29 changes: 29 additions & 0 deletions dev/ts/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './helper';

let bpmnVisualization: ThemedBpmnVisualization;
let loadOptions: LoadOptions = {};
let bpmnElementIdToCollapse: string;

export function updateLoadOptions(fitOptions: FitOptions): void {
log('Updating load options', fitOptions);
Expand All @@ -45,6 +46,7 @@ function loadBpmn(bpmn: string): void {
try {
bpmnVisualization.load(bpmn, loadOptions);
log('BPMN loaded with configuration', stringify(loadOptions));
collapseBpmnElement(bpmnElementIdToCollapse);
document.dispatchEvent(new CustomEvent('diagramLoaded'));
} catch (e) {
logErrorAndOpenAlert(e, `Cannot load the BPMN diagram: ${e.message}`);
Expand Down Expand Up @@ -81,6 +83,28 @@ export function removeAllOverlays(bpmnElementId: string): void {
return bpmnVisualization.bpmnElementsRegistry.removeAllOverlays(bpmnElementId);
}

// Not natively supported by bpmn-visualization for now but demonstrated in https://cdn.statically.io/gh/process-analytics/bpmn-visualization-examples/v0.22.0/examples/custom-behavior/select-elements-by-bpmn-kind/index.html
// We want to ensure that the edges terminal waypoints are correctly set to the enclosing parent (pool or subprocess), whatever the terminal waypoint computation is.
function collapseBpmnElement(bpmnElementId: string): void {
if (!bpmnElementIdToCollapse) {
return;
}
log('Updating model, bpmnElement to collapse:', bpmnElementId);
const model = bpmnVisualization.graph.getModel();
const cell = model.getCell(bpmnElementId);
if (!cell) {
log('Element not found in the model, do nothing');
} else {
model.beginUpdate();
try {
model.setCollapsed(cell, true);
} finally {
model.endUpdate();
}
log('Model updated');
}
}

// callback function for opening | dropping the file to be loaded
function readAndLoadFile(f: File): void {
const reader = new FileReader();
Expand Down Expand Up @@ -177,6 +201,10 @@ function configureStyleFromParameters(parameters: URLSearchParams): void {
}
}

function configureBpmnElementIdToCollapseFromParameters(parameters: URLSearchParams): void {
bpmnElementIdToCollapse = parameters.get('bpmn.element.id.collapsed');
}

export function startBpmnVisualization(config: BpmnVisualizationDemoConfiguration): void {
const log = logStartup;
const container = config.globalOptions.container;
Expand All @@ -194,6 +222,7 @@ export function startBpmnVisualization(config: BpmnVisualizationDemoConfiguratio
loadOptions.fit = getFitOptionsFromParameters(config, parameters);

configureStyleFromParameters(parameters);
configureBpmnElementIdToCollapseFromParameters(parameters);

log("Checking if an 'url to fetch BPMN content' is provided as query parameter");
const urlParameterValue = parameters.get('url');
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions test/e2e/bpmn.elements.collapsed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright 2022 Bonitasoft S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { MatchImageSnapshotOptions } from 'jest-image-snapshot';
import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config';
import { PageTester } from './helpers/visu/bpmn-page-utils';
import type { Page } from 'playwright';
import { getBpmnDiagramNames } from './helpers/test-utils';

// key: diagram name
// values: the ids of the elements to collapse. The elements are collapsed one by one, in dedicated tests
const elementsToCollapsePerDiagram = new Map<string, Array<string>>([
['pools', ['Participant_1', 'Participant_2', 'Participant_3', 'Participant_4']],
['subprocess', ['SubProcess_1']],
]);

class CollapsedElementImageSnapshotConfigurator extends ImageSnapshotConfigurator {
override getConfig(param: { fileName: string; collapsedElement: string }): MatchImageSnapshotOptions {
const config = super.getConfig(param);
config.customSnapshotIdentifier = `${param.fileName}-collapse-${param.collapsedElement}`;
return config;
}
}

const getElementsToCollapse = (bpmnDiagramName: string): Array<string> => {
const elementsToCollapse = elementsToCollapsePerDiagram.get(bpmnDiagramName);
// add 'none' to test the diagram rendering without collapsing any element. There is no element in diagrams with the 'none' id (in this case, the value is ignored and there is no collapsing).
return ['none', ...elementsToCollapse];
};

describe('Collapse BPMN elements', () => {
const diagramSubfolder = 'collapse-expand';
const imageSnapshotConfigurator = new CollapsedElementImageSnapshotConfigurator(
// firefox: max 0.2640822887892802%
// webkit: max 0.29993542878872237%
new MultiBrowserImageSnapshotThresholds({ chromium: 0, firefox: 0.27 / 100, webkit: 0.3 / 100 }),
diagramSubfolder,
);
const pageTester = new PageTester({ pageFileName: 'non-regression', expectedPageTitle: 'BPMN Visualization Non Regression', diagramSubfolder }, <Page>page);
const bpmnDiagramNames = getBpmnDiagramNames(diagramSubfolder);

describe.each(bpmnDiagramNames)(`%s`, (bpmnDiagramName: string) => {
it.each(getElementsToCollapse(bpmnDiagramName))(`collapse %s`, async (bpmnElementIdToCollapse: string) => {
await pageTester.gotoPageAndLoadBpmnDiagram(bpmnDiagramName, {
bpmnElementIdToCollapse: bpmnElementIdToCollapse,
});
const image = await page.screenshot({ fullPage: true });
const config = imageSnapshotConfigurator.getConfig({ fileName: bpmnDiagramName, collapsedElement: bpmnElementIdToCollapse });
expect(image).toMatchImageSnapshot(config);
});
});
});
15 changes: 12 additions & 3 deletions test/e2e/helpers/visu/bpmn-page-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface StyleOptions {
export interface PageOptions {
loadOptions?: LoadOptions;
styleOptions?: StyleOptions;
bpmnElementIdToCollapse?: string;
}

export interface Point {
Expand Down Expand Up @@ -111,7 +112,12 @@ export class PageTester {
}

async gotoPageAndLoadBpmnDiagram(bpmnDiagramName: string, pageOptions?: PageOptions): Promise<void> {
const url = this.computePageUrl(bpmnDiagramName, pageOptions?.loadOptions ?? { fit: { type: FitType.HorizontalVertical } }, pageOptions?.styleOptions);
const url = this.computePageUrl(
bpmnDiagramName,
pageOptions?.loadOptions ?? { fit: { type: FitType.HorizontalVertical } },
pageOptions?.styleOptions,
pageOptions?.bpmnElementIdToCollapse,
);
await this.doGotoPageAndLoadBpmnDiagram(url);
}

Expand All @@ -131,9 +137,9 @@ export class PageTester {
/**
* @param bpmnDiagramName the name of the BPMN file without extension
* @param loadOptions optional fit options
* @param styleOptions optional style options
* @param styleOptions? optional style options
*/
private computePageUrl(bpmnDiagramName: string, loadOptions: LoadOptions, styleOptions?: StyleOptions): string {
private computePageUrl(bpmnDiagramName: string, loadOptions: LoadOptions, styleOptions?: StyleOptions, bpmndElementIdToCollapse?: string | undefined): string {
let url = this.baseUrl;
url += `&url=./static/bpmn/${this.diagramSubfolder}/${bpmnDiagramName}.bpmn`;

Expand All @@ -147,6 +153,9 @@ export class PageTester {
(url += `&style.container.alternative.background.color=${styleOptions.bpmnContainer.useAlternativeBackgroundColor}`);
styleOptions?.theme && (url += `&style.theme=${styleOptions.theme}`);

// elements to collapse
bpmndElementIdToCollapse && (url += `&bpmn.element.id.collapsed=${bpmndElementIdToCollapse}`);

return url;
}

Expand Down
199 changes: 199 additions & 0 deletions test/fixtures/bpmn/collapse-expand/pools.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1" targetNamespace="http://example.com/schema/bpmn">
<bpmn:collaboration id="Collaboration_16qn7f7">
<bpmn:participant id="Participant_1" processRef="Process_1" />
<bpmn:participant id="Participant_2" processRef="Process_2" />
<bpmn:participant id="Participant_3" processRef="Process_3" />
<bpmn:participant id="Participant_4" processRef="Process_4" />
<bpmn:messageFlow id="Flow_1oodl1k" sourceRef="Activity_14jx6w8" targetRef="StartEvent_17g4nu8" />
<bpmn:messageFlow id="Flow_1xspsn1" sourceRef="Event_0r61dg1" targetRef="Activity_14jx6w8" />
<bpmn:messageFlow id="Flow_09jmgm4" sourceRef="Event_0z01vb5" targetRef="Event_0f92tj3" />
<bpmn:messageFlow id="Flow_10w9tdb" sourceRef="Activity_1ka7pcl" targetRef="Activity_0ltcxpv" />
</bpmn:collaboration>
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:sequenceFlow id="Flow_0jsdvpk" sourceRef="StartEvent_17g4nu8" targetRef="Activity_0puqzjv" />
<bpmn:sequenceFlow id="Flow_17ljr6c" sourceRef="Activity_0puqzjv" targetRef="Event_0r61dg1" />
<bpmn:startEvent id="StartEvent_17g4nu8">
<bpmn:outgoing>Flow_0jsdvpk</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_0jn3ex4" />
</bpmn:startEvent>
<bpmn:task id="Activity_0puqzjv">
<bpmn:incoming>Flow_0jsdvpk</bpmn:incoming>
<bpmn:outgoing>Flow_17ljr6c</bpmn:outgoing>
</bpmn:task>
<bpmn:endEvent id="Event_0r61dg1">
<bpmn:incoming>Flow_17ljr6c</bpmn:incoming>
<bpmn:messageEventDefinition id="MessageEventDefinition_0exqd0a" />
</bpmn:endEvent>
</bpmn:process>
<bpmn:process id="Process_2">
<bpmn:startEvent id="Event_0aa0ktp">
<bpmn:outgoing>Flow_1vroj9a</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Activity_14jx6w8">
<bpmn:incoming>Flow_1vroj9a</bpmn:incoming>
<bpmn:outgoing>Flow_0cx4m6y</bpmn:outgoing>
</bpmn:task>
<bpmn:intermediateThrowEvent id="Event_0z01vb5">
<bpmn:incoming>Flow_0cx4m6y</bpmn:incoming>
<bpmn:outgoing>Flow_1qzs7cq</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_1ofhuys" />
</bpmn:intermediateThrowEvent>
<bpmn:endEvent id="Event_1d8mi1m">
<bpmn:incoming>Flow_1qzs7cq</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1vroj9a" sourceRef="Event_0aa0ktp" targetRef="Activity_14jx6w8" />
<bpmn:sequenceFlow id="Flow_0cx4m6y" sourceRef="Activity_14jx6w8" targetRef="Event_0z01vb5" />
<bpmn:sequenceFlow id="Flow_1qzs7cq" sourceRef="Event_0z01vb5" targetRef="Event_1d8mi1m" />
</bpmn:process>
<bpmn:process id="Process_3">
<bpmn:startEvent id="Event_0f92tj3">
<bpmn:outgoing>Flow_0ipugn9</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_18c8g9f" />
</bpmn:startEvent>
<bpmn:task id="Activity_1ka7pcl">
<bpmn:incoming>Flow_0ipugn9</bpmn:incoming>
<bpmn:outgoing>Flow_1bw85cc</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_0ipugn9" sourceRef="Event_0f92tj3" targetRef="Activity_1ka7pcl" />
<bpmn:endEvent id="Event_09s4aci">
<bpmn:incoming>Flow_1bw85cc</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1bw85cc" sourceRef="Activity_1ka7pcl" targetRef="Event_09s4aci" />
</bpmn:process>
<bpmn:process id="Process_4">
<bpmn:startEvent id="Event_0v4aheg">
<bpmn:outgoing>Flow_0txp0nu</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Activity_0ltcxpv">
<bpmn:incoming>Flow_0txp0nu</bpmn:incoming>
<bpmn:outgoing>Flow_16r4h6t</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_0txp0nu" sourceRef="Event_0v4aheg" targetRef="Activity_0ltcxpv" />
<bpmn:endEvent id="Event_0expexd">
<bpmn:incoming>Flow_16r4h6t</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_16r4h6t" sourceRef="Activity_0ltcxpv" targetRef="Event_0expexd" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_16qn7f7">
<bpmndi:BPMNShape id="Participant_1_di" bpmnElement="Participant_1" isHorizontal="true">
<dc:Bounds x="-30" y="-250" width="500" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_17ljr6c_di" bpmnElement="Flow_17ljr6c">
<di:waypoint x="260" y="-140" />
<di:waypoint x="322" y="-140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jsdvpk_di" bpmnElement="Flow_0jsdvpk">
<di:waypoint x="68" y="-140" />
<di:waypoint x="160" y="-140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_0dr110a_di" bpmnElement="StartEvent_17g4nu8">
<dc:Bounds x="32" y="-158" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0puqzjv_di" bpmnElement="Activity_0puqzjv">
<dc:Bounds x="160" y="-180" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ewtrd7_di" bpmnElement="Event_0r61dg1">
<dc:Bounds x="322" y="-158" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_2_di" bpmnElement="Participant_2" isHorizontal="true">
<dc:Bounds x="-30" y="100" width="500" height="220" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1qzs7cq_di" bpmnElement="Flow_1qzs7cq">
<di:waypoint x="344" y="210" />
<di:waypoint x="398" y="210" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0cx4m6y_di" bpmnElement="Flow_0cx4m6y">
<di:waypoint x="256" y="210" />
<di:waypoint x="308" y="210" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vroj9a_di" bpmnElement="Flow_1vroj9a">
<di:waypoint x="58" y="210" />
<di:waypoint x="156" y="210" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_0aa0ktp_di" bpmnElement="Event_0aa0ktp">
<dc:Bounds x="22" y="192" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_14jx6w8_di" bpmnElement="Activity_14jx6w8">
<dc:Bounds x="156" y="170" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1osag5t_di" bpmnElement="Event_0z01vb5">
<dc:Bounds x="308" y="192" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1d8mi1m_di" bpmnElement="Event_1d8mi1m">
<dc:Bounds x="398" y="192" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_3_di" bpmnElement="Participant_3" isHorizontal="true">
<dc:Bounds x="530" y="100" width="410" height="220" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0ipugn9_di" bpmnElement="Flow_0ipugn9">
<di:waypoint x="638" y="210" />
<di:waypoint x="690" y="210" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1bw85cc_di" bpmnElement="Flow_1bw85cc">
<di:waypoint x="790" y="210" />
<di:waypoint x="842" y="210" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_04xwdv4_di" bpmnElement="Event_0f92tj3">
<dc:Bounds x="602" y="192" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1ka7pcl_di" bpmnElement="Activity_1ka7pcl">
<dc:Bounds x="690" y="170" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_09s4aci_di" bpmnElement="Event_09s4aci">
<dc:Bounds x="842" y="192" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_4_di" bpmnElement="Participant_4" isHorizontal="true">
<dc:Bounds x="530" y="-250" width="410" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0txp0nu_di" bpmnElement="Flow_0txp0nu">
<di:waypoint x="638" y="-140" />
<di:waypoint x="690" y="-140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_16r4h6t_di" bpmnElement="Flow_16r4h6t">
<di:waypoint x="790" y="-140" />
<di:waypoint x="842" y="-140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_0v4aheg_di" bpmnElement="Event_0v4aheg">
<dc:Bounds x="602" y="-158" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ltcxpv_di" bpmnElement="Activity_0ltcxpv">
<dc:Bounds x="690" y="-180" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0expexd_di" bpmnElement="Event_0expexd">
<dc:Bounds x="842" y="-158" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1oodl1k_di" bpmnElement="Flow_1oodl1k">
<di:waypoint x="180" y="170" />
<di:waypoint x="180" y="140" />
<di:waypoint x="130" y="140" />
<di:waypoint x="130" y="-50" />
<di:waypoint x="160" y="-50" />
<di:waypoint x="160" y="-90" />
<di:waypoint x="50" y="-90" />
<di:waypoint x="50" y="-122" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1xspsn1_di" bpmnElement="Flow_1xspsn1">
<di:waypoint x="340" y="-122" />
<di:waypoint x="340" y="-60" />
<di:waypoint x="250" y="-60" />
<di:waypoint x="250" y="-30" />
<di:waypoint x="310" y="-30" />
<di:waypoint x="310" y="49" />
<di:waypoint x="230" y="49" />
<di:waypoint x="230" y="170" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09jmgm4_di" bpmnElement="Flow_09jmgm4">
<di:waypoint x="326" y="228" />
<di:waypoint x="326" y="280" />
<di:waypoint x="620" y="280" />
<di:waypoint x="620" y="228" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_10w9tdb_di" bpmnElement="Flow_10w9tdb">
<di:waypoint x="740" y="170" />
<di:waypoint x="740" y="-100" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Loading

0 comments on commit d13de43

Please sign in to comment.