diff --git a/src/main/java/de/ozgcloud/formcycle/formdata/FormStructureParser.java b/src/main/java/de/ozgcloud/formcycle/formdata/FormStructureParser.java index da525d6f08cf849bf3c33652cb3d6cd0b4a82472..7920ddda533aa93bc7659fc8a57825bf9064bc75 100644 --- a/src/main/java/de/ozgcloud/formcycle/formdata/FormStructureParser.java +++ b/src/main/java/de/ozgcloud/formcycle/formdata/FormStructureParser.java @@ -20,47 +20,47 @@ class FormStructureParser { private final Map<String, XItem> formularElementsSorted; private final Deque<FormNode> containerNodes = new ArrayDeque<>(); - private final List<FormNode> nodes = new ArrayList<>(); + private final List<FormNode> resultNodes = new ArrayList<>(); public List<FormNode> parse() { - nodes.clear(); + resultNodes.clear(); for (Map.Entry<String, XItem> entry : formularElementsSorted.entrySet()) { var xItem = entry.getValue(); var itemClass = ItemClass.fromString(xItem.getClassName()); if (isInputNode(itemClass)) { addItem(xItem); } else if (isNestedContainer(itemClass)) { - var formNode = buildNode(xItem); - getNodes(xItem).add(formNode); - containerNodes.push(formNode); + containerNodes.push(addItem(xItem)); } } - return nodes; + return resultNodes; } - void addItem(XItem xItem) { - getNodes(xItem).add(buildNode(xItem)); + FormNode addItem(XItem xItem) { + var formNode = buildNode(xItem); + getResultNodes(xItem).add(formNode); + return formNode; } FormNode buildNode(XItem xItem) { var formNode = FormNode.builder().itemId(xItem.getId()).name(xItem.getName()); - ItemClass.fromString(xItem.getClassName()).getTitleProperty().map(xItem::get).ifPresent(propertyValue -> formNode.title(propertyValue.getString())); + ItemClass.fromString(xItem.getClassName()).getTitleProperty().map(xItem::get) + .ifPresent(propertyValue -> formNode.title(propertyValue.getString())); return formNode.build(); } - List<FormNode> getNodes(XItem xItem) { - if (CONTAINER_NAME_ANTRAGSTELLER.equalsIgnoreCase(xItem.getName())) { - return nodes; + List<FormNode> getResultNodes(XItem xItem) { + if (CONTAINER_NAME_ANTRAGSTELLER.equalsIgnoreCase(xItem.getName()) || containerNodes.isEmpty()) { + return resultNodes; } - var parentId = xItem.getParentId(); - return containerNodes.isEmpty() - ? nodes - : getNextContainerNode(parentId).map(FormNode::getNestedElements).orElse(nodes); + return getNextContainerNode(xItem.getParentId()).map(FormNode::getNestedElements).orElse(resultNodes); } Optional<FormNode> getNextContainerNode(String parentId) { var nextContainerNode = containerNodes.peekFirst(); - Objects.requireNonNull(nextContainerNode); + if (Objects.isNull(nextContainerNode)) { + return Optional.empty(); + } while (nonParent(nextContainerNode, parentId)) { containerNodes.pollFirst(); if (containerNodes.isEmpty()) { diff --git a/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserITCase.java b/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..68e51fefe4226f28a4f8823191d2b43d58fcaa06 --- /dev/null +++ b/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserITCase.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.formcycle.formdata; + +import static de.ozgcloud.formcycle.formdata.ItemClass.*; +import static de.ozgcloud.formcycle.formdata.StructureMockFactory.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import de.xima.fc.form.common.items.XItem; + +class FormStructureParserITCase { + + private static final String FS_CONTAINER_ID = "xi-fs-n"; + private static final String NESTED_ITEM_ID1 = "nested1"; + private static final String NESTED_ITEM_ID2 = "nested2"; + private static final String NEXT_ITEM_ID = "new-item"; + + @Test + void shouldParseInputItem() { + var expected = FormNodeTestFactory.createBuilder().values(null).build(); + + var formNodes = new FormStructureParser(createItem(TEXT_FIELD)).parse(); + + assertThat(formNodes).hasSize(1).first().usingRecursiveComparison().isEqualTo(expected); + } + + @Test + void shouldParseContainerItem() { + var expected = FormNodeTestFactory.createBuilder().values(null).build(); + + var formNodes = new FormStructureParser(createItem(FIELD_SET)).parse(); + + assertThat(formNodes).hasSize(1).first().usingRecursiveComparison().isEqualTo(expected); + } + + @Test + void shouldNotParseTextItem() { + var structureParser = new FormStructureParser(createItem(SPAN)); + + var formNodes = structureParser.parse(); + + assertThat(formNodes).isEmpty(); + } + + @Test + void shouldParseAllInputItems() { + var formNodes = new FormStructureParser(StructureMockFactory.create()).parse(); + + assertThat(formNodes).hasSize(2); + } + + @Test + void shouldParseNestedFieldSetContainer() { + var expectedFsNode = FormNodeTestFactory.createBuilder().values(null).itemId(FS_CONTAINER_ID).build(); + var expectedTfNode = FormNodeTestFactory.createBuilder().values(null).itemId(NEXT_ITEM_ID).build(); + + var formNodes = new FormStructureParser(initNestedMap()).parse(); + + assertThat(formNodes).hasSize(2); + var containerNodes = formNodes.get(0).getNestedElements(); + assertThat(containerNodes).hasSize(2).first().usingRecursiveComparison().ignoringFields("nestedElements").isEqualTo(expectedFsNode); + assertThat(containerNodes.get(1)).usingRecursiveComparison().ignoringFields("nestedElements").isEqualTo(expectedTfNode); + } + + @Test + void shouldParseItemsInFieldSet() { + var expectedTfNode = FormNodeTestFactory.createBuilder().values(null).itemId(NESTED_ITEM_ID1).build(); + + var fieldSetNodes = new FormStructureParser(initNestedMap()).parse().get(0).getNestedElements().get(0).getNestedElements(); + + assertThat(fieldSetNodes).hasSize(1).first().usingRecursiveComparison().ignoringFields("nestedElements").isEqualTo(expectedTfNode); + } + + private Map<String, XItem> initNestedMap() { + var containerWithItem = createPageItem(); + containerWithItem.put(CONTAINER_ITEM_ID, getMock(CONTAINER, PAGE_ITEM_ID)); + containerWithItem.put(FS_CONTAINER_ID, getMock(FIELD_SET, FS_CONTAINER_ID, CONTAINER_ITEM_ID, ITEM_TITLE, ITEM_NAME)); + containerWithItem.put(NESTED_ITEM_ID1, getMock(TEXT_FIELD, NESTED_ITEM_ID1, FS_CONTAINER_ID, ITEM_TITLE, ITEM_NAME)); + containerWithItem.put(NESTED_ITEM_ID2, getMock(SPAN, NESTED_ITEM_ID2, FS_CONTAINER_ID, ITEM_TITLE, ITEM_NAME)); + containerWithItem.put(NEXT_ITEM_ID, getMock(TEXT_FIELD, NEXT_ITEM_ID, CONTAINER_ITEM_ID, ITEM_TITLE, ITEM_NAME)); + containerWithItem.put(ITEM_ID, getMock(CHECKBOX, PAGE_ITEM_ID)); + + return containerWithItem; + } +} diff --git a/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserTest.java b/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserTest.java index 3e4ba50584db125130aec4757839e300303a612f..88448798a4215d5ffa0087dc8b6a81bc830b5c35 100644 --- a/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserTest.java +++ b/src/test/java/de/ozgcloud/formcycle/formdata/FormStructureParserTest.java @@ -3,120 +3,125 @@ package de.ozgcloud.formcycle.formdata; import static de.ozgcloud.formcycle.formdata.ItemClass.*; import static de.ozgcloud.formcycle.formdata.StructureMockFactory.*; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.Map; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; +import org.mockito.Spy; import de.xima.fc.form.common.items.XItem; import lombok.SneakyThrows; class FormStructureParserTest { - private static final String NEXT_ITEM_ID = "new-item"; + @Spy + @InjectMocks + private FormStructureParser structureParser; @Nested - class TestItems { + class TestParse { @Test - void shouldParseInputItem() { - var expected = FormNodeTestFactory.createBuilder().values(null).build(); - var structureParser = createParser(TEXT_FIELD); + void shouldClearResultNodes() { + structureParser = new FormStructureParser(Collections.emptyMap()); + injectResultNodes(new ArrayList<>(List.of(FormNodeTestFactory.create()))); - var formNodes = structureParser.parse(); + var result = structureParser.parse(); - assertThat(formNodes).hasSize(1).first().usingRecursiveComparison().isEqualTo(expected); + assertThat(result).isEmpty(); } @Test - void shouldParseContainerItem() { - var expected = FormNodeTestFactory.createBuilder().values(null).build(); - var structureParser = createParser(FIELD_SET); + void shouldCallAddItemWhenInputNode() { + var items = createItem(TEXT_FIELD); + structureParser = spy(createParser(items)); - var formNodes = structureParser.parse(); + structureParser.parse(); - assertThat(formNodes).hasSize(1).first().usingRecursiveComparison().isEqualTo(expected); + verify(structureParser).addItem(items.get(ITEM_ID)); } @Test - void shouldNotParseTextItem() { - var structureParser = createParser(SPAN); + void shouldCallAddItemWhenContainerNode() { + var items = createItem(FIELD_SET); + structureParser = spy(createParser(items)); - var formNodes = structureParser.parse(); + structureParser.parse(); - assertThat(formNodes).isEmpty(); + verify(structureParser).addItem(items.get(ITEM_ID)); } - private FormStructureParser createParser(ItemClass itemClass) { - return new FormStructureParser(createItem(itemClass)); + @Test + void shouldReturnResultNodes() { + structureParser = new FormStructureParser(Collections.emptyMap()); + var expectedResultNode = new ArrayList<>(List.of(FormNodeTestFactory.create())); + injectResultNodes(expectedResultNode); + + var result = structureParser.parse(); + + assertThat(result).isSameAs(expectedResultNode); } + + private FormStructureParser createParser(Map<String, XItem> items) { + return new FormStructureParser(items); + } + } @Nested - class TestNestedItems { + class TestAddItem { - private static final String FS_CONTAINER_ID = "xi-fs-n"; - private static final String NESTED_ITEM_ID1 = "nested1"; - private static final String NESTED_ITEM_ID2 = "nested2"; + @Test + void shouldCallGetResultNodes() { + var xItem = getMock(TEXT_FIELD); - private FormStructureParser structureParser; + structureParser.addItem(xItem); - @BeforeEach - void setup() { - var containerWithItem = initNestedMap(); - structureParser = new FormStructureParser(containerWithItem); + verify(structureParser).getResultNodes(xItem); } @Test - void shouldParseAllInputItems() { - var structure = new FormStructureParser(StructureMockFactory.create()); + void shouldCallBuildNode() { + var xItem = getMock(TEXT_FIELD); + doReturn(new ArrayList<>()).when(structureParser).getResultNodes(any()); - var formNodes = structure.parse(); + structureParser.addItem(xItem); - assertThat(formNodes).hasSize(2); + verify(structureParser).buildNode(xItem); } @Test - void shouldParseNestedFieldSetContainer() { - var expectedFsNode = FormNodeTestFactory.createBuilder().values(null).itemId(FS_CONTAINER_ID).build(); - var expectedTfNode = FormNodeTestFactory.createBuilder().values(null).itemId(NEXT_ITEM_ID).build(); + void shouldAddNodeToResultNodes() { + var resultNodes = new ArrayList<FormNode>(); + doReturn(resultNodes).when(structureParser).getResultNodes(any()); + var formNode = FormNodeTestFactory.create(); + doReturn(formNode).when(structureParser).buildNode(any()); - var formNodes = structureParser.parse(); + structureParser.addItem(getMock(TEXT_FIELD)); - assertThat(formNodes).hasSize(2); - var containerNodes = formNodes.get(0).getNestedElements(); - assertThat(containerNodes).hasSize(2).first().usingRecursiveComparison().ignoringFields("nestedElements").isEqualTo(expectedFsNode); - assertThat(containerNodes.get(1)).usingRecursiveComparison().ignoringFields("nestedElements").isEqualTo(expectedTfNode); + assertThat(resultNodes).containsExactly(formNode); } @Test - void shouldParseItemsInFieldSet() { - var expectedTfNode = FormNodeTestFactory.createBuilder().values(null).itemId(NESTED_ITEM_ID1).build(); + void shouldReturnBuiltNode() { + var formNode = FormNodeTestFactory.create(); + doReturn(formNode).when(structureParser).buildNode(any()); - var fieldSetNodes = structureParser.parse().get(0).getNestedElements().get(0).getNestedElements(); + var result = structureParser.addItem(getMock(TEXT_FIELD)); - assertThat(fieldSetNodes).hasSize(1).first().usingRecursiveComparison().ignoringFields("nestedElements").isEqualTo(expectedTfNode); + assertThat(result).isSameAs(formNode); } - - private Map<String, XItem> initNestedMap() { - var containerWithItem = createPageItem(); - containerWithItem.put(CONTAINER_ITEM_ID, getMock(CONTAINER, PAGE_ITEM_ID)); - containerWithItem.put(FS_CONTAINER_ID, getMock(FIELD_SET, FS_CONTAINER_ID, CONTAINER_ITEM_ID, ITEM_TITLE, ITEM_NAME)); - containerWithItem.put(NESTED_ITEM_ID1, getMock(TEXT_FIELD, NESTED_ITEM_ID1, FS_CONTAINER_ID, ITEM_TITLE, ITEM_NAME)); - containerWithItem.put(NESTED_ITEM_ID2, getMock(SPAN, NESTED_ITEM_ID2, FS_CONTAINER_ID, ITEM_TITLE, ITEM_NAME)); - containerWithItem.put(NEXT_ITEM_ID, getMock(TEXT_FIELD, NEXT_ITEM_ID, CONTAINER_ITEM_ID, ITEM_TITLE, ITEM_NAME)); - containerWithItem.put(ITEM_ID, getMock(CHECKBOX, PAGE_ITEM_ID)); - - return containerWithItem; - } - } @Nested @@ -178,131 +183,118 @@ class FormStructureParserTest { } @Nested - class TestMethods { - - private static final String PARENT_ID = "parentId"; - - @InjectMocks - private FormStructureParser structureParser; + class TestGetResultNodes { - private Deque<FormNode> containerNodes; - private FormNode containerNode; + protected List<FormNode> expectedResultNodes = List.of(FormNodeTestFactory.create()); - @SneakyThrows @BeforeEach - void setup() { - containerNode = FormNodeTestFactory.create(); - containerNodes = new ArrayDeque<>(); - containerNodes.push(containerNode); - var stackField = structureParser.getClass().getDeclaredField("containerNodes"); - stackField.setAccessible(true); - stackField.set(structureParser, containerNodes); + void init() { + injectResultNodes(expectedResultNodes); } - @Nested - @DisplayName("GetNextContainerNode") - class TestNextContainerNode { - - @Test - @DisplayName("should return empty when container stack is empty") - void shouldReturnEmpty() { - injectNodesField(); + @Test + void shouldReturnRootNodesWhenNoContainerNodes() { + var nodes = structureParser.getResultNodes(StructureMockFactory.getMock(TEXT_FIELD)); - var nextContainerNode = structureParser.getNextContainerNode(PARENT_ID); + assertThat(nodes).isSameAs(expectedResultNodes); + } - assertThat(nextContainerNode).isEmpty(); - } + @Test + void shouldReturnRootWhenAntragsteller() { + var antragstellerXItem = getMock(CONTAINER); + when(antragstellerXItem.getName()).thenReturn(FormStructureParser.CONTAINER_NAME_ANTRAGSTELLER); + injectContainerNodes(new ArrayDeque<>(List.of(FormNodeTestFactory.createBuilder().itemId(CONTAINER_ITEM_ID).build()))); - @Test - void shouldReturnParent() { - var nextContainerNode = structureParser.getNextContainerNode(ITEM_ID); + var nodes = structureParser.getResultNodes(antragstellerXItem); - assertThat(nextContainerNode).isPresent().get().isEqualTo(containerNode); - } + assertThat(nodes).isSameAs(expectedResultNodes); + } - @Test - void shouldReturnNestedParent() { - var parentContainer = FormNodeTestFactory.createBuilder().itemId(NEXT_ITEM_ID).build(); - containerNodes.addLast(parentContainer); + @Test + void shouldReturnRootWhenNoParentNode() { + injectContainerNodes(); - var nextContainerNode = structureParser.getNextContainerNode(NEXT_ITEM_ID); + var nodes = structureParser.getResultNodes(getMock(CONTAINER)); - assertThat(nextContainerNode).isPresent().get().isEqualTo(parentContainer); - } + assertThat(nodes).isSameAs(expectedResultNodes); + } - @Test - @DisplayName("should return empty when no parent on stack") - void shouldReturnEmptyNoParents() { - var parentContainer = FormNodeTestFactory.createBuilder().itemId(NEXT_ITEM_ID).build(); - containerNodes.addLast(parentContainer); + @Test + void shouldCallGetNextContainerNode() { + var parentId = "parentId"; + var xItem = getMock(CONTAINER); + when(xItem.getParentId()).thenReturn(parentId); + injectContainerNodes(); - var nextContainerNode = structureParser.getNextContainerNode("id"); + structureParser.getResultNodes(xItem); - assertThat(nextContainerNode).isEmpty(); - } + verify(structureParser).getNextContainerNode(parentId); + } - @Test - @DisplayName("should clear stack when no parent found") - void shouldClearStack() { - structureParser.getNextContainerNode("id"); + @Test + void shouldReturnNestedNodes() { + injectContainerNodes(); + var formNode = FormNodeTestFactory.create(); + doReturn(Optional.of(formNode)).when(structureParser).getNextContainerNode(anyString()); + var expectedNestedCollection = formNode.getNestedElements(); - assertThat(getStackCollection()).isEmpty(); - } + var nodes = structureParser.getResultNodes(getMock(CONTAINER)); + assertThat(nodes).isSameAs(expectedNestedCollection); } + } - @Nested - @DisplayName("GetNodes") - class TestGetNodes { - - @Test - @DisplayName("should return result collection when container stack is empty") - void shouldReturnResultCollectionStackEmpty() { - getStackCollection().clear(); - var expectedNodes = injectNodesField(); + @Nested + class TestGetNextContainerNode { - var resultNodes = structureParser.getNodes(ITEM_ID); + @Test + void shouldReturnEmptyWhenNodeStackEmpty() { + var nextContainerNode = structureParser.getNextContainerNode(ITEM_ID); - assertThat(resultNodes).isEqualTo(expectedNodes); - } + assertThat(nextContainerNode).isEmpty(); + } - @Test - @DisplayName("should return result collection when no parent found") - void shouldReturnResultCollectionNoParent() { - var expectedNodes = injectNodesField(); + @Test + void shouldReturnEmpty() { + injectContainerNodes(new ArrayDeque<>(List.of(FormNodeTestFactory.create()))); - var resultNodes = structureParser.getNodes(PARENT_ID); + var nextContainerNode = structureParser.getNextContainerNode("parentId"); - assertThat(resultNodes).isEqualTo(expectedNodes); - } + assertThat(nextContainerNode).isEmpty(); + } - @Test - @DisplayName("should return container collection when parent found") - void shouldReturnResultContainerCollection() { - var expectedCollection = containerNode.getNestedElements(); + @Test + void shouldReturnParent() { + var parentId = "parentId"; + FormNode parentNode = FormNodeTestFactory.createBuilder().itemId(parentId).build(); + var parentNodes = injectContainerNodes(new ArrayDeque<>()); + parentNodes.push(parentNode); + parentNodes.push(FormNodeTestFactory.create()); - var resultNodes = structureParser.getNodes(ITEM_ID); + var nextContainerNode = structureParser.getNextContainerNode(parentId); - assertThat(resultNodes).isEqualTo(expectedCollection); - } + assertThat(nextContainerNode).contains(parentNode); } + } - @SneakyThrows - private List<FormNode> injectNodesField() { - List<FormNode> nodes = List.of(FormNodeTestFactory.create()); - var nodesField = structureParser.getClass().getDeclaredField("nodes"); - nodesField.setAccessible(true); - nodesField.set(structureParser, nodes); - - return nodes; - } + @SneakyThrows + private List<FormNode> injectResultNodes(List<FormNode> nodes) { + var containerNodesField = FormStructureParser.class.getDeclaredField("resultNodes"); + containerNodesField.setAccessible(true); + containerNodesField.set(structureParser, nodes); + return nodes; + } - @SneakyThrows - private Deque<FormNode> getStackCollection() { - var stackField = structureParser.getClass().getDeclaredField("containerNodes"); - stackField.setAccessible(true); - return (Deque<FormNode>) stackField.get(structureParser); - } + @SneakyThrows + private Deque<FormNode> injectContainerNodes() { + return injectContainerNodes(new ArrayDeque<>(List.of(FormNodeTestFactory.create()))); + } + @SneakyThrows + private Deque<FormNode> injectContainerNodes(Deque<FormNode> containerNodes) { + var containerNodesField = FormStructureParser.class.getDeclaredField("containerNodes"); + containerNodesField.setAccessible(true); + containerNodesField.set(structureParser, containerNodes); + return containerNodes; } } \ No newline at end of file