Skip to content
Draft
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 @@ -585,8 +585,124 @@ describe('VisualizationService / abstract fields', () => {
(c) => c instanceof FieldItem && c.field.name === 'Cat',
) as FieldItem;

const candidateChildren = VisualizationService.generateNonDocumentNodeDataChildren(freshAbstractNode);
expect(candidateChildren.map((c) => c.title)).toEqual(['catName']);
});
const candidateChildren = VisualizationService.generateNonDocumentNodeDataChildren(freshAbstractNode);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why updating before the describe?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. This is a structural change to the test file.

The original describe('VisualizationService / abstract fields') block contained a single existing test. Since I added a new test suite for the choice-selected abstract wrapper feature, I closed the original describe block and created a new describe block for the new tests.

So what looks like 'updating' is really just:

  1. Closing the existing describe block with proper indentation
  2. Adding a new describe block with the new tests

The original test logic is unchanged — just properly closed out.

expect(candidateChildren.map((c) => c.title)).toEqual(['catName']);
});
});

describe('choice-selected abstract wrapper rendering', () => {
let sourceDoc: XmlSchemaDocument;
let sourceDocNode: DocumentNodeData;
let targetDoc: XmlSchemaDocument;
let tree: MappingTree;

function createMockAbstractField(
candidates: { name: string; children?: { name: string }[] }[],
selectedMemberIndex?: number,
) {
const baseField = sourceDoc.fields[0];
const candidateFields = candidates.map((c) => ({
...baseField,
name: c.name,
displayName: c.name,
fields: (c.children ?? []).map((child) => ({
...baseField,
name: child.name,
displayName: child.name,
fields: [],
})),
}));
return {
...baseField,
name: 'AbstractElement',
displayName: 'AbstractElement',
wrapperKind: 'abstract' as const,
selectedMemberIndex,
fields: candidateFields,
} as unknown as typeof baseField;
}

beforeEach(() => {
sourceDoc = TestUtil.createSourceOrderDoc();
sourceDocNode = new DocumentNodeData(sourceDoc);
targetDoc = TestUtil.createTargetOrderDoc();
tree = new MappingTree(targetDoc.documentType, targetDoc.documentId, DocumentDefinitionType.XML_SCHEMA);
});

it('should create AbstractFieldNodeData when a choice selects an abstract wrapper member (source)', () => {
const abstractField = createMockAbstractField([{ name: 'Cat' }, { name: 'Dog' }]);
const baseField = sourceDoc.fields[0];
const choiceField = {
...baseField,
name: '__choice__',
displayName: '__choice__',
wrapperKind: 'choice' as const,
selectedMemberIndex: 0,
fields: [abstractField],
} as unknown as typeof baseField;
const parentField = {
...sourceDoc.fields[0],
fields: [choiceField],
};
const parentNode = new FieldNodeData(sourceDocNode, parentField as (typeof sourceDoc.fields)[0]);
const children = VisualizationService.generateNonDocumentNodeDataChildren(parentNode);

expect(children.length).toEqual(1);
expect(children[0]).toBeInstanceOf(AbstractFieldNodeData);
const abstractNode = children[0] as AbstractFieldNodeData;
expect(abstractNode.abstractField).toBe(abstractField);
expect(VisualizationUtilService.isAbstractField(abstractNode)).toBe(true);
});

it('should create TargetAbstractFieldNodeData when a choice selects an abstract wrapper member (target)', () => {
const abstractField = createMockAbstractField([{ name: 'Cat' }, { name: 'Dog' }]);
const baseField = targetDoc.fields[0];
const choiceField = {
...baseField,
name: '__choice__',
displayName: '__choice__',
wrapperKind: 'choice' as const,
selectedMemberIndex: 0,
fields: [abstractField],
} as unknown as typeof baseField;
const parentField = {
...targetDoc.fields[0],
fields: [choiceField],
};
const localTargetDocNode = new TargetDocumentNodeData(targetDoc, tree);
const parentNode = new TargetFieldNodeData(localTargetDocNode, parentField as (typeof targetDoc.fields)[0]);
const children = VisualizationService.generateNonDocumentNodeDataChildren(parentNode);

expect(children.length).toEqual(1);
expect(children[0]).toBeInstanceOf(TargetAbstractFieldNodeData);
const abstractNode = children[0] as TargetAbstractFieldNodeData;
expect(abstractNode.abstractField).toBe(abstractField);
expect(VisualizationUtilService.isAbstractField(abstractNode)).toBe(true);
});

it('should expand abstract candidates as children when choice-selected abstract node is expanded', () => {
const abstractField = createMockAbstractField([{ name: 'Cat' }, { name: 'Dog' }]);
const baseField = sourceDoc.fields[0];
const choiceField = {
...baseField,
name: '__choice__',
displayName: '__choice__',
wrapperKind: 'choice' as const,
selectedMemberIndex: 0,
fields: [abstractField],
} as unknown as typeof baseField;
const parentField = {
...sourceDoc.fields[0],
fields: [choiceField],
};
const parentNode = new FieldNodeData(sourceDocNode, parentField as (typeof sourceDoc.fields)[0]);
const children = VisualizationService.generateNonDocumentNodeDataChildren(parentNode);

const abstractNode = children[0] as AbstractFieldNodeData;
expect(VisualizationService.hasChildren(abstractNode)).toBe(true);
const abstractChildren = VisualizationService.generateNonDocumentNodeDataChildren(abstractNode);
const childNames = abstractChildren.map((c) => c.title);
expect(childNames).toContain('Cat');
expect(childNames).toContain('Dog');
});
});
16 changes: 12 additions & 4 deletions packages/ui/src/services/visualization/visualization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,25 @@ export class VisualizationService {
const selectedMember =
field.selectedMemberIndex === undefined ? undefined : field.fields?.[field.selectedMemberIndex];
const nodeField = selectedMember ?? field;

// When a choice wrapper selects a member that is itself an abstract wrapper,
// delegate to the ABSTRACT_WRAPPER spec so the node is created as
// AbstractFieldNodeData / TargetAbstractFieldNodeData rather than ChoiceFieldNodeData.
// This preserves the abstract badge and field-substitution menu.
const effectiveSpec =
spec === CHOICE_WRAPPER && selectedMember?.wrapperKind === 'abstract' ? ABSTRACT_WRAPPER : spec;

if (parent.isSource) {
const node = spec.createSourceNode(parent, nodeField);
if (selectedMember) spec.setWrapperRef(node, field);
const node = effectiveSpec.createSourceNode(parent, nodeField);
if (selectedMember) effectiveSpec.setWrapperRef(node, field);
return node;
}

const mappingsForMember =
selectedMember && mappings ? MappingService.filterMappingsForField(mappings, selectedMember) : [];
const mapping = mappingsForMember.find((m) => m instanceof FieldItem) as FieldItem;
const node = spec.createTargetNode(parent as TargetNodeData, nodeField, mapping);
if (selectedMember) spec.setWrapperRef(node, field);
const node = effectiveSpec.createTargetNode(parent as TargetNodeData, nodeField, mapping);
if (selectedMember) effectiveSpec.setWrapperRef(node, field);
return node;
}

Expand Down