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
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ static Map<String, Object> buildModelJsonForTest(IntentModel model, String inten
return buildDocument(null, model, intentName, new IntentSettings.Branding()).modelJson;
}

/**
* Test seam: render the {@code .edm} XML (including the {@code mxGraphModel} diagram) without a
* repository - so a test can assert the XML twin, not just the {@code .model} JSON. Never use in
* production code.
*/
static String buildEdmXmlForTest(IntentModel model, String intentName) {
return renderEdmXml(buildDocument(null, model, intentName, new IntentSettings.Branding()));
}

/** The two views over one model tree: the {@code .model} JSON root and the XML extras. */
private static final class EdmDocument {
/** Root of the {@code .model} JSON: {@code {model: {entities, perspectives, navigations}}}. */
Expand Down Expand Up @@ -2076,20 +2085,25 @@ private static List<Map<String, Object>> propertiesOf(Map<String, Object> entity

/**
* The {@code <Entity>} cell value. {@code type="Entity"} is the constant cell-kind marker the
* editor keys on; the PRIMARY/DEPENDENT distinction is carried in {@code entityType} (omitted for
* PRIMARY), matching the editor's own serializer.
* editor keys on; the PRIMARY/DEPENDENT/SETTING/PROJECTION distinction is carried in
* {@code entityType} (omitted only for PRIMARY, the editor's default), matching the editor's own
* serializer.
*/
private static void appendEntityValue(StringBuilder sb, Map<String, Object> entity) {
sb.append(" <Entity");
appendAttribute(sb, "name", entity.get("name"));
// PRIMARY is the editor's default (omitted); DEPENDENT and SETTING are carried explicitly so
// the EDM editor restyles the cell and the template engine routes the entity correctly.
// PRIMARY is the editor's default (omitted); DEPENDENT, SETTING and PROJECTION are carried
// explicitly so the EDM editor restyles the cell and the template engine routes the entity
// correctly. Without the PROJECTION marker a cross-model reference (e.g. PaymentMethod) rendered
// as a plain PRIMARY entity box - indistinguishable from an owned entity, and the editor would
// treat it as manageable local state.
Object entityType = entity.get("type");
if ("DEPENDENT".equals(entityType) || "SETTING".equals(entityType)) {
if ("DEPENDENT".equals(entityType) || "SETTING".equals(entityType) || "PROJECTION".equals(entityType)) {
appendAttribute(sb, "entityType", entityType);
}
for (String key : new String[] {"dataName", "dataCount", "dataQuery", "title", "caption", "description", "tooltip", "menuKey",
"menuLabel", "layoutType", "perspectiveName", "importsCode", "generateDefaultRoles", "roleRead", "roleWrite"}) {
"menuLabel", "layoutType", "perspectiveName", "importsCode", "generateDefaultRoles", "roleRead", "roleWrite",
"projectionReferencedModel", "projectionReferencedEntity"}) {
if (entity.get(key) != null) {
appendAttribute(sb, key, entity.get(key));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ void customerEmitsCrossModelProjectionsAndForeignKeys() {
assertEquals("master-data", customer.get("perspectiveNavId"));
}

@Test
void crossModelProjectionCellIsMarkedProjectionInTheEdmDiagram() {
IntentModel parsed = IntentParser.parse(readResource("/billing/customers.intent"));
String edm = EdmIntentGenerator.buildEdmXmlForTest(parsed, "customers");

// The mxGraph diagram <Entity> cell for a cross-model reference must carry
// entityType="PROJECTION" (+ the reference attrs) so the EDM editor renders it as a projection,
// not as a plain owned PRIMARY entity box.
int cell = edm.indexOf("<Entity name=\"Country\"");
assertTrue(cell >= 0, "the Country projection cell must be present in the mxGraph diagram");
int end = edm.indexOf("/>", cell);
String countryCell = edm.substring(cell, end);
assertTrue(countryCell.contains("entityType=\"PROJECTION\""),
"the cross-model Country cell must be entityType=PROJECTION, was: " + countryCell);
assertTrue(countryCell.contains("projectionReferencedEntity=\"Country\""), "the projection cell must carry its referenced entity");

// A locally-owned entity stays a plain cell (no entityType=PROJECTION).
int owned = edm.indexOf("<Entity name=\"Customer\"");
String customerCell = edm.substring(owned, edm.indexOf("/>", owned));
assertTrue(!customerCell.contains("entityType=\"PROJECTION\""), "an owned entity must not be a projection cell");
}

@Test
void salesInvoiceModelsCrossModelNToMAndCalculatedNumber() {
Map<String, Object> model = buildFromResource("/billing/sales-invoices.intent", "sales-invoices");
Expand Down
Loading