From b5384cd11ac699837dd53670b878bdf31391f0e4 Mon Sep 17 00:00:00 2001 From: "alexander.nesvizhsky" Date: Mon, 19 Jan 2026 16:24:34 +0100 Subject: [PATCH 1/2] feat: add validation for customization selectors on non-field elements and refactor selector validation logic --- .../SO301000/extensions/SO301000_FieldSelectors.html | 1 + acumate-plugin/src/test/suite/htmlValidation.test.ts | 10 ++++++++++ .../src/validation/htmlValidation/html-validation.ts | 8 +++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/acumate-plugin/src/test/fixtures/screens/SO/SO301000/extensions/SO301000_FieldSelectors.html b/acumate-plugin/src/test/fixtures/screens/SO/SO301000/extensions/SO301000_FieldSelectors.html index acda23e..4de751f 100644 --- a/acumate-plugin/src/test/fixtures/screens/SO/SO301000/extensions/SO301000_FieldSelectors.html +++ b/acumate-plugin/src/test/fixtures/screens/SO/SO301000/extensions/SO301000_FieldSelectors.html @@ -3,4 +3,5 @@ + diff --git a/acumate-plugin/src/test/suite/htmlValidation.test.ts b/acumate-plugin/src/test/suite/htmlValidation.test.ts index 8b5e888..ab24bb1 100644 --- a/acumate-plugin/src/test/suite/htmlValidation.test.ts +++ b/acumate-plugin/src/test/suite/htmlValidation.test.ts @@ -370,6 +370,16 @@ describe('HTML validation diagnostics', () => { ); }); + it('validates customization selectors on non-field elements', async () => { + const document = await vscode.workspace.openTextDocument(screenSelectorExtensionFixture); + await validateHtmlFile(document); + const diagnostics = AcuMateContext.HtmlValidator?.get(document.uri) ?? []; + assert.ok( + diagnostics.some(d => d.message.includes('after selector') && d.message.includes('tab-PutAway111')), + 'Expected diagnostic when qp-tab after selector targets missing element' + ); + }); + it('derives view metadata from selector targets when lacks a parent view', async () => { const document = await vscode.workspace.openTextDocument(screenSelectorExtensionFixture); await validateHtmlFile(document); diff --git a/acumate-plugin/src/validation/htmlValidation/html-validation.ts b/acumate-plugin/src/validation/htmlValidation/html-validation.ts index a57683c..f8ebdfd 100644 --- a/acumate-plugin/src/validation/htmlValidation/html-validation.ts +++ b/acumate-plugin/src/validation/htmlValidation/html-validation.ts @@ -183,6 +183,8 @@ function validateDom( : `The <${node.name}> element must define an id attribute.`; pushHtmlDiagnostic(diagnostics, suppression, range, message); } + + validateCustomizationSelectors(node); } if ( @@ -292,10 +294,6 @@ function validateDom( validateConfigBinding(node.attribs["config.bind"], node); } - if (node.type === "tag" && node.name === "field") { - validateFieldCustomizationSelectors(node); - } - if ( node.type === "tag" && (node.name === "field" || node.name === "qp-field") && @@ -387,7 +385,7 @@ function validateDom( } } - function validateFieldCustomizationSelectors(node: any) { + function validateCustomizationSelectors(node: any) { if (!baseScreenDocument) { return; } From 8cf192cb1c8a3a32d8de4115be8950e591a021f9 Mon Sep 17 00:00:00 2001 From: "alexander.nesvizhsky" Date: Mon, 19 Jan 2026 16:40:17 +0100 Subject: [PATCH 2/2] feat: implement merging of duplicate backend views and add corresponding tests --- acumate-plugin/src/backend-metadata-utils.ts | 30 +++++++++---- .../src/test/suite/backendMetadata.test.ts | 45 +++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 acumate-plugin/src/test/suite/backendMetadata.test.ts diff --git a/acumate-plugin/src/backend-metadata-utils.ts b/acumate-plugin/src/backend-metadata-utils.ts index 488bd7b..5a9920e 100644 --- a/acumate-plugin/src/backend-metadata-utils.ts +++ b/acumate-plugin/src/backend-metadata-utils.ts @@ -84,15 +84,20 @@ export function buildBackendViewMap(structure: GraphStructure | undefined): Map< continue; } - const metadata: BackendViewMetadata = { - viewName: view.name ?? key, - normalizedName: lookupKey, - view, - fields: buildBackendFieldMap(view), - }; + let metadata = views.get(lookupKey); + if (!metadata) { + metadata = { + viewName: view.name ?? key, + normalizedName: lookupKey, + view, + fields: buildBackendFieldMap(view), + }; + views.set(lookupKey, metadata); + } else { + mergeBackendFields(metadata.fields, view); + } - views.set(lookupKey, metadata); - if (normalizedKey && normalizedKey !== lookupKey && !views.has(normalizedKey)) { + if (normalizedKey && !views.has(normalizedKey)) { views.set(normalizedKey, metadata); } } @@ -132,3 +137,12 @@ export function buildBackendFieldMap(view: View | undefined): Map, sourceView: View) { + const incomingFields = buildBackendFieldMap(sourceView); + for (const [fieldKey, fieldMetadata] of incomingFields) { + if (!target.has(fieldKey)) { + target.set(fieldKey, fieldMetadata); + } + } +} diff --git a/acumate-plugin/src/test/suite/backendMetadata.test.ts b/acumate-plugin/src/test/suite/backendMetadata.test.ts new file mode 100644 index 0000000..a97dfdf --- /dev/null +++ b/acumate-plugin/src/test/suite/backendMetadata.test.ts @@ -0,0 +1,45 @@ +import * as assert from 'assert'; +import { describe, it } from 'mocha'; +import { buildBackendViewMap } from '../../backend-metadata-utils'; +import { GraphStructure } from '../../model/graph-structure'; +import { View } from '../../model/view'; + +describe('backend metadata utilities', () => { + it('merges duplicate backend views and preserves the first metadata instance', () => { + const baseTransactionsView: View = { + name: 'transactions', + cacheName: 'Purchase Receipt Line', + cacheType: 'POReceiptLine', + fields: { + ReceiptNbr: { name: 'ReceiptNbr' }, + }, + }; + + const secondaryTransactionsView: View = { + name: 'Transactions', + cacheName: 'Purchase Receipt Line (Alt)', + cacheType: 'POReceiptLine', + fields: { + ReceiptDate: { name: 'ReceiptDate' }, + }, + }; + + const structure: GraphStructure = { + name: 'PX.Objects.PO.POReceiptEntry', + views: { + transactions: baseTransactionsView, + transactionsPOLine: secondaryTransactionsView, + }, + }; + + const backendViewMap = buildBackendViewMap(structure); + const canonical = backendViewMap.get('transactions'); + assert.ok(canonical, 'Expected canonical transactions view metadata'); + assert.strictEqual(canonical.view, baseTransactionsView, 'First view definition should be preserved'); + assert.ok(canonical.fields.has('receiptnbr'), 'Original field should remain'); + assert.ok(canonical.fields.has('receiptdate'), 'Fields from duplicate view should be merged'); + + const aliasMetadata = backendViewMap.get('transactionspoline'); + assert.strictEqual(aliasMetadata, canonical, 'Alternate view keys should reference the canonical metadata'); + }); +});