Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions CLAUDE_BPMN.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,10 @@ d0968c65dd Fix property-panel popup lock-up: $hide/$show + prefixEvent + displa
- **Templates**
- `…/editor-bpm/editor-app/configuration/properties/duedate-popup.html` — `bs-select` → `<select>`
- `…/editor-bpm/views/popover/formfield-edit-popover.html` — `bs-tooltip` → native `title`
- **Tests** *(all new on this branch)*
- `tests/.../BpmnEditorLoadsIT.java` — smoke (canvas + palette + Start Events + theme variable + `$modal`/`$popover` injector contract)
- `tests/.../BpmnEditorIT.java` — full edit / rename / save / "Published"
- `tests/.../BpmnEditorPropertyPopupIT.java` — open Execution Listeners modal, assert `$hide` + ✕-size + close-via-Cancel + canvas still interactive
- **Tests** *(all new on this branch; later merged into one journey)*
- `tests/.../BpmnEditorIT.java` — ONE editor session asserting all three stations, in order: editor boot (canvas + palette + Start Events + theme variable + `$modal`/`$popover` injector contract), property-popup lifecycle (open Execution Listeners modal, assert `$hide` + ✕-size + close-via-Cancel + canvas still interactive), then full edit / rename / save / "Published". The original three classes (`BpmnEditorLoadsIT`, `BpmnEditorPropertyPopupIT`, `BpmnEditorIT`) each paid a full Dirigible boot + browser + ~30 s editor bring-up for the same freshly created `.bpmn` file, so they were consolidated.
- `tests/.../BpmnModelApiIT.java` — API-level

### Tests on this branch

```
BpmnEditorLoadsIT 25 s
BpmnEditorIT 59 s
BpmnEditorPropertyPopupIT 46 s
```

All three pass on `origin/fix-bpmn-editor`.

### Don't repeat these dead-ends

- Don't add `.when('/editor/', { redirectTo: '/editor' })`. Angular 1.8 normalises trailing slashes and the route loops forever (`$rootScope:infdig`).
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE_UI.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ angular.module('myApp', ['ngRoute', 'blimpKit', /* … */]);

Definitive source: `/webjars/blimpkit__blimpkit/dist/css/themes/blimpkit-light.css` (and `-dark.css`). Both files define the same names with theme-appropriate values, so `var(--foreground)` is correct for either theme.

3. **A `--sap*` legacy variable** only when nothing above expresses the need. The shim still ships via `theme-blimpkit/css/sap-variables-{light,dark,auto}.css` for backwards compatibility (`BpmnEditorLoadsIT` asserts `--sapBackgroundColor` is non-empty inside the iframe), but most existing references are migration debt, not a pattern to copy. If you write `var(--sapXxx)`, leave a comment that explains which BlimpKit variable you tried first and why it didn't work.
3. **A `--sap*` legacy variable** only when nothing above expresses the need. The shim still ships via `theme-blimpkit/css/sap-variables-{light,dark,auto}.css` for backwards compatibility (`BpmnEditorIT`'s boot station asserts the theme variable is non-empty inside the iframe), but most existing references are migration debt, not a pattern to copy. If you write `var(--sapXxx)`, leave a comment that explains which BlimpKit variable you tried first and why it didn't work.

**Theme-detection class.** `@media (prefers-color-scheme: dark)` is the **OS preference**, not the Dirigible theme. To target the theme the user picked, write `setTheme()` to add `body.dirigible-theme-{light,dark,auto}` and gate your CSS on that class. Reserve `prefers-color-scheme` for the `dirigible-theme-auto` fallback.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,173 @@
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.Selenide;
import org.eclipse.dirigible.tests.base.UserInterfaceIntegrationTest;
import org.eclipse.dirigible.tests.framework.browser.HtmlAttribute;
import org.eclipse.dirigible.tests.framework.browser.HtmlElementType;
import org.eclipse.dirigible.tests.framework.ide.Workbench;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;

import java.time.Duration;

/**
* The complete BPMN visual modeler journey in ONE editor session: boot, property-panel popup
* lifecycle, and shape rename + save. These used to be three separate IT classes
* (BpmnEditorLoadsIT, BpmnEditorPropertyPopupIT, BpmnEditorIT), each paying its own Dirigible boot,
* browser and ~30s editor bring-up to look at the same freshly created {@code .bpmn} file - the
* three aspects are sequential stations of one user journey, so they are asserted in one pass.
*
* <p>
* Editor boot (former BpmnEditorLoadsIT): the Oryx-based editor loads without errors - the editor
* tab is present, the canvas and stencil palette render (proving {@code GET
* /services/bpm/stencil-sets} returned data and AngularJS processed it), the theme CSS variables
* resolve inside the editor iframe, and the $modal/$popover + Bootstrap-3 jQuery plugin stack that
* replaced angular-strap after the Angular 1.8.2 migration is complete. Guards against regressions
* where missing Angular module dependencies silently prevent the editor from bootstrapping.
*
* <p>
* Property-panel popup lifecycle (former BpmnEditorPropertyPopupIT): the wide property popups
* (execution listeners et al.) open as a BlimpKit {@code <bk-dialog>}
* ({@code section.fd-dialog--active}), the modal scope exposes the angular-strap-compatible
* {@code $hide()}, the header close button is a real interactable target, Cancel dismisses the
* dialog, and the canvas stays interactive afterwards. If {@code modal-service.js} misses any of
* that, the popup can never be closed - the dialog stays open and the editor grays out.
*
* <p>
* Rename + save (former BpmnEditorIT body): inline rename via double-click on the shape, rename via
* the property panel, then save through the Flowable toolbar action - asserting the status-bar
* "saved" message and the final "Published" message propagate across frames.
*/
public class BpmnEditorIT extends UserInterfaceIntegrationTest {

private static final String PROJECT = "BpmnEditorIT";
private static final String BPMN_FILE = "bpmn-new.bpmn";

@Test
void bpmnEditor_select_rename_and_save() {
void bpmnEditor_boots_popups_close_and_rename_save_publishes() {
Workbench workbench = ide.openWorkbench();
workbench.createNewProject(PROJECT);
workbench.createFileInProject(PROJECT, "Business Process Model");
workbench.openFile(BPMN_FILE);

assertEditorBoots();
assertPropertyPopupOpensAndCloses();
assertRenameAndSavePublishes();
}

private void assertEditorBoots() {
browser.assertElementExistByAttributePatternAndText(HtmlElementType.SPAN, HtmlAttribute.CLASS, "fd-icon-tab-bar__tag", BPMN_FILE);

// Editor loads: canvas and stencil palette are visible
browser.findElementInAllFrames(By.id("canvasSection"), Condition.visible);
browser.findElementInAllFrames(By.id("paletteSection"), Condition.visible);

// Expand "Activities" group and verify UserTask is initialized as draggable
Selenide.$(By.xpath("//span[contains(.,'Activities')]"))
// Expand "Start Events" group and verify StartNoneEvent item appears
Selenide.$(By.xpath("//span[contains(.,'Start Events')]"))
.click();
Selenide.$(By.id("UserTask"))
.shouldBe(Condition.visible, Duration.ofSeconds(5))
.shouldHave(Condition.cssClass("ui-draggable"));
browser.findElementInAllFrames(By.id("StartNoneEvent"), Condition.visible);

// The BPM editor's editor-app/theme/*.css references CSS variables that come from the
// active theme's *-{auto,light,dark}.css file. Assert the variable resolves to a non-empty
// value so we catch any regression where the theme stops shipping the legacy variables.
Selenide.$(By.id("canvasSection"))
.shouldBe(Condition.visible);
Object sapBgColor =
Selenide.executeJavaScript("return getComputedStyle(document.documentElement).getPropertyValue('--background').trim();");
Assertions.assertTrue(sapBgColor != null && !sapBgColor.toString()
.isEmpty(),
"--background is undefined inside the BPM editor iframe — theme variables not applied.");

// angular-strap was removed during the Angular 1.4.7 -> 1.8.2 migration. Its $modal and
// $popover services are now provided by scripts/services/{modal,popover}-service.js (each
// delegating to Bootstrap-3's jQuery .modal/.popover plugin). The property-panel popups —
// execution-listeners, task-listeners, event-listeners; each opens a modal containing a
// "Delegate Expression" input — depend on this whole chain. Verify both factories are in
// the injector AND the underlying Bootstrap-3 jQuery plugins are present, so an accidental
// drop of any of the four script tags is caught here.
Object stackOk = Selenide.executeJavaScript("var inj = angular.element(document.body).injector();"
+ "return inj.has('$modal') && typeof inj.get('$modal') === 'function'"
+ " && inj.has('$popover') && typeof inj.get('$popover') === 'function'" + " && typeof jQuery.fn.modal === 'function'"
+ " && typeof jQuery.fn.popover === 'function';");
Assertions.assertTrue(Boolean.TRUE.equals(stackOk),
"Modal/popover stack incomplete — $modal/$popover factories or Bootstrap-3 jQuery plugins missing inside the BPM editor iframe.");
}

// Click on the existing service task shape (from the BPMN template) to select it
private void assertPropertyPopupOpensAndCloses() {
// Select the service task that ships in the template — selecting populates the property panel.
Selenide.$(By.id("svg-my-service-task"))
.click();
Selenide.$(By.cssSelector("#propertySection .property-row"))
.shouldBe(Condition.visible, Duration.ofSeconds(10));

// Inline rename via double-click on the shape
// The "Execution listeners" property row is one of the wide popups (it lives inside
// editor-app/configuration/properties/execution-listeners-popup.html, opened by
// FlowableExecutionListenersCtrl via _internalCreateModal($modal, …)). Clicking its title
// switches the row to write mode and immediately opens the dialog.
Selenide.$(By.xpath(
"//*[@id='propertySection']//span[contains(@class,'title') and contains(normalize-space(.),'Execution listeners')]"))
.click();

// Dialog appeared: BlimpKit's bk-dialog renders <section class="fd-dialog"> and adds
// .fd-dialog--active while it's visible.
Selenide.$(By.cssSelector("section.fd-dialog.fd-dialog--active"))
.shouldBe(Condition.visible, Duration.ofSeconds(5));

// Modal scope must expose $hide() — without it the close()/cancel() handlers in
// properties-execution-listeners-controller.js (both call $scope.$hide()) silently throw
// and the dialog can never be dismissed. The dialog itself has an isolate scope (bk-dialog
// directive); the controller's scope sits on a child element with ng-controller, so we
// look up scope via the close button which is inside the controller's subtree.
Boolean hideExists = Selenide.executeJavaScript("var btn = document.querySelector("
+ " 'section.fd-dialog.fd-dialog--active .fd-dialog__header button.fd-button');" + "if (!btn) return false;"
+ "var scope = angular.element(btn).scope();" + "while (scope && typeof scope.$hide !== 'function') scope = scope.$parent;"
+ "return scope && typeof scope.$hide === 'function';");
Assertions.assertTrue(Boolean.TRUE.equals(hideExists),
"modal scope.$hide is missing — angular-strap-compatible $hide() helper was not added by modal-service.js.");

// The header ✕ button must be a real, non-zero-sized, interactable target. <bk-button>
// renders the icon as a child <i class="sap-icon sap-icon--decline">; the button itself
// is a native <button class="fd-button …"> carrying the click handler.
Object closeBtnSize = Selenide.executeJavaScript("var b = document.querySelector("
+ " 'section.fd-dialog.fd-dialog--active .fd-dialog__header button.fd-button');" + "if (!b) return null;"
+ "var r = b.getBoundingClientRect();" + "return JSON.stringify({ w: Math.round(r.width), h: Math.round(r.height) });");
Assertions.assertNotNull(closeBtnSize, "dialog header close (×) button is missing from the DOM.");
Assertions.assertFalse(closeBtnSize.toString()
.contains("\"w\":0")
|| closeBtnSize.toString()
.contains("\"h\":0"),
"dialog header close (×) button has zero size: " + closeBtnSize);

// Dismiss the dialog via the Cancel button in the footer — same path the user takes.
// bk-button[label="Cancel"] compiles to a <button class="fd-button ...">Cancel</button>
// wrapped in <div class="fd-bar__element">.
Selenide.$(By.xpath("//section[contains(@class,'fd-dialog--active')]" + "//footer//button[normalize-space(.)='Cancel']"))
.shouldBe(Condition.visible, Duration.ofSeconds(5))
.click();

// Dialog must be gone: no active fd-dialog left in the DOM. modal-service removes the
// compiled element ~300ms after flipping `modal.visible` to false so the fd-dialog--active
// class is gone first, then the element itself unmounts.
Selenide.$(By.cssSelector("section.fd-dialog.fd-dialog--active"))
.shouldNotBe(Condition.visible, Duration.ofSeconds(5));

// Canvas is interactive again: re-clicking the service task should re-populate the property
// panel (proves the iframe still accepts pointer events).
Selenide.$(By.id("svg-my-service-task"))
.click();
Selenide.$(By.cssSelector("#propertySection .property-row"))
.shouldBe(Condition.visible, Duration.ofSeconds(5));
}

private void assertRenameAndSavePublishes() {
// Expand "Activities" group and verify UserTask is initialized as draggable
Selenide.$(By.xpath("//span[contains(.,'Activities')]"))
.click();
Selenide.$(By.id("UserTask"))
.shouldBe(Condition.visible, Duration.ofSeconds(5))
.shouldHave(Condition.cssClass("ui-draggable"));

// Inline rename via double-click on the shape (already selected by the popup station)
Selenide.$(By.id("svg-my-service-task"))
.doubleClick();
Selenide.$(By.id("shapeTextInput"))
Expand Down

This file was deleted.

Loading
Loading