Skip to content

Commit c80fc0f

Browse files
TASK-1828886-3: adding tests for EmbeddedData tables
1 parent 868b44e commit c80fc0f

File tree

19 files changed

+6562
-36
lines changed

19 files changed

+6562
-36
lines changed

samples/android-cmp-app/src/androidInstrumentedTest/kotlin/com/pega/constellation/sdk/kmp/samples/androidcmpapp/test/cases/EmbeddedDataTest.kt

Lines changed: 209 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import androidx.compose.ui.test.SemanticsMatcher
66
import androidx.compose.ui.test.SemanticsNodeInteractionCollection
77
import androidx.compose.ui.test.filter
88
import androidx.compose.ui.test.hasAnyAncestor
9+
import androidx.compose.ui.test.hasClickAction
910
import androidx.compose.ui.test.hasContentDescription
11+
import androidx.compose.ui.test.hasSetTextAction
1012
import androidx.compose.ui.test.hasTestTag
1113
import androidx.compose.ui.test.hasText
1214
import androidx.compose.ui.test.onFirst
@@ -16,7 +18,10 @@ import androidx.compose.ui.test.onSiblings
1618
import androidx.compose.ui.test.performClick
1719
import androidx.compose.ui.test.performScrollTo
1820
import androidx.compose.ui.test.performTextInput
21+
import androidx.compose.ui.test.performTextReplacement
1922
import androidx.compose.ui.test.runComposeUiTest
23+
import androidx.compose.ui.test.waitUntilAtLeastOneExists
24+
import androidx.compose.ui.test.waitUntilDoesNotExist
2025
import androidx.compose.ui.test.waitUntilExactlyOneExists
2126
import androidx.compose.ui.test.waitUntilNodeCount
2227
import com.pega.constellation.sdk.kmp.samples.androidcmpapp.test.ComposeTest
@@ -31,13 +36,13 @@ class EmbeddedDataTest : ComposeTest(PegaVersion.v24_2_2) {
3136

3237
@Test
3338
fun test_embedded_data_repeating_view() = runComposeUiTest {
34-
setupApp("O40M3A-MarekCo-Work-EmbeddedDataTest")
39+
setupApp("O40M3A-MarekCo-Work-EmbeddedDataTest-RepeatingViewEditable")
3540

3641
// create case
3742
onNodeWithText("New Service").performClick()
3843

3944
// verify form title and instruction
40-
waitForNode("ED repeating view editable (", substring = true)
45+
waitForNode("ED repeating view editable", substring = true)
4146
waitForNode("ED repeating view editable & readonly instruction")
4247

4348
// verify repeating views presence
@@ -116,15 +121,196 @@ class EmbeddedDataTest : ComposeTest(PegaVersion.v24_2_2) {
116121

117122
// 2nd step
118123
onNodeWithText("Next").performClick()
119-
waitForNode("ED repeating view readonly (", substring = true)
124+
waitForNode("ED repeating view readonly", substring = true)
120125

121126
// verify row 1 on second step
122127
onAllNodes(hasAnyAncestor(hasTestTag("field_group_template_[Cars repeating view readonly]"))).let {
123128
verifyEmbeddedDataRecord(it, expectedDate = "2025-12-16", isEditable = false)
124129
}
125130
}
126131

127-
fun ComposeUiTest.verifyEmbeddedDataRecord(
132+
@Test
133+
fun test_embedded_data_table_simple_table() = runComposeUiTest {
134+
setupApp("O40M3A-MarekCo-Work-EmbeddedDataTest-EditableTable")
135+
136+
val columnValues = mutableMapOf(
137+
"brand" to "Ford",
138+
"model" to "Focus",
139+
"Price" to "123456",
140+
"IsFirstOwner" to "Yes",
141+
"interior" to "comfort",
142+
"Insurance" to "gold",
143+
"client meeting date" to "2026-01-08",
144+
"Client meeting time" to "12:00 AM",
145+
"Transaction date time" to "2026-01-08 12:00 AM",
146+
"Notes" to "This is a note"
147+
)
148+
val edContext = "caseInfo.content.EmbeddedDataListOfRecords"
149+
// create case
150+
onNodeWithText("New Service").performClick()
151+
152+
// Step 1 - editable table
153+
// verify form title
154+
waitForNode("ED table editable", substring = true)
155+
// verify table title
156+
waitForNode("Cars editable table")
157+
// verify columns
158+
columnValues.keys.forEach { waitForNodes(it.uppercase(), count = 2) } // despite there is only one table with column names, test sees two of them
159+
// verify add and delete records
160+
onNodeWithText("+ Add cars").performClick()
161+
waitUntilAtLeastOneExists(hasContentDescription("Delete item 1"))
162+
waitUntilAtLeastOneExists(hasContentDescription("Reorder item 1"))
163+
onAllNodes(hasContentDescription("Delete item 1")).onFirst().performClick()
164+
waitUntilDoesNotExist(hasContentDescription("Delete item 1"))
165+
waitUntilDoesNotExist(hasContentDescription("Reorder item 1"))
166+
// verify adding record with data
167+
onNodeWithText("+ Add cars").performClick()
168+
performTextInput("$edContext[0].Brand", "Ford")
169+
performTextInput("$edContext[0].Model", "Focus")
170+
performTextInput("$edContext[0].Price", "123456")
171+
performClick("$edContext[0].IsFirstOwner")
172+
performClick("$edContext[0].Interior")
173+
onNodeWithText("comfort").performClick()
174+
performClick("$edContext[0].Insurance")
175+
onNodeWithText("gold").performClick()
176+
performClick("$edContext[0].ClientMeetingDate")
177+
onNodeWithText("Today, ", substring = true).performClick()
178+
onNodeWithText("OK").performClick()
179+
performClick("$edContext[0].ClientMeetingTime")
180+
onNodeWithText("OK").performClick()
181+
performClick("$edContext[0].TransactionDateTime")
182+
onNodeWithText("Today, ", substring = true).performClick()
183+
onNodeWithText("OK").performClick()
184+
onNodeWithText("OK").performClick()
185+
performTextInput("$edContext[0].Notes", "This is a note")
186+
187+
// Step 2 - editable table with popup
188+
waitForNode("Next")
189+
onNodeWithText("Next").performClick()
190+
// verify form title
191+
waitForNode("ED table editable popup", substring = true)
192+
// verify table title
193+
waitForNode("Cars editable table with popup")
194+
// verify columns
195+
columnValues.keys.forEach { waitForNodes(it.uppercase(), count = 2) } // despite there is only one table with column names, test sees two of them
196+
// verify table data
197+
columnValues.values.forEach {
198+
waitForNodes(it, count = 2)
199+
}
200+
// verify reorder icon exists
201+
waitUntilAtLeastOneExists(hasContentDescription("Reorder item 1"))
202+
// verify edit record popup
203+
onAllNodes(hasContentDescription("Edit item 1")).onFirst().performScrollTo().performClick()
204+
waitForNode("Edit Record")
205+
206+
onAllNodes(hasAnyAncestor(hasTestTag("ModalViewContainer"))).let { nodes ->
207+
columnValues.forEach {
208+
nodes.findFirstWithText(it.key).assertExists()
209+
if (it.key != "IsFirstOwner") { // not able to check checkbox state
210+
nodes.findFirstWithText(it.value).assertExists()
211+
}
212+
}
213+
nodes.findFirstWithText("model").performTextReplacement("Fiesta")
214+
nodes.findFirstWithText("Submit").performClick()
215+
}
216+
// verify updated record in table
217+
waitForNodes("Fiesta", count = 2)
218+
219+
// adding new record via popup
220+
onNodeWithText("+ Add cars").performScrollTo().performClick()
221+
waitForNode("Add Record")
222+
onAllNodes(hasAnyAncestor(hasTestTag("ModalViewContainer"))).let { nodes ->
223+
nodes.findFirstWithText("Submit").performClick()
224+
waitUntilAtLeastOneExists(nodes, hasText("brand: Cannot be blank"), timeoutMillis = 5000L)
225+
nodes.findFirstWithText("brand").performTextReplacement("Opel")
226+
nodes.findFirstWithText("model").performTextReplacement("Astra")
227+
nodes.findFirstWithText("Submit").performScrollTo().performClick()
228+
}
229+
// verify new record in table
230+
waitForNodes("Opel", count = 2)
231+
waitForNodes("Astra", count = 2)
232+
233+
// Step 3 - readonly simple table
234+
onNodeWithText("Next").performClick()
235+
// verify form title
236+
waitForNode("ED simple table readonly", substring = true)
237+
// verify table title
238+
waitForNode("Cars readonly simple table")
239+
verifyReadonlyTable(columnValues)
240+
241+
// Step 4 - readonly table
242+
onNodeWithText("Next").performClick()
243+
// verify form title
244+
waitForNode("ED table readonly", substring = true)
245+
// verify table title
246+
waitForNode("Cars readonly table")
247+
verifyReadonlyTable(columnValues)
248+
}
249+
250+
@Test
251+
fun test_embedded_data_add_edit_remove_conditions() = runComposeUiTest {
252+
// Step 1 - Editable table
253+
setupApp("O40M3A-MarekCo-Work-EmbeddedDataTest-Conditions")
254+
// create case
255+
onNodeWithText("New Service").performClick()
256+
// verify form title
257+
waitForNode("ED table editable conditions", substring = true)
258+
// verify table title
259+
waitForNode("Cars editable table")
260+
261+
waitForNode("+ Add cars")
262+
onNodeWithText("+ Add cars").performClick()
263+
// verify add/edit/remove/reorder
264+
waitForNode("+ Add cars")
265+
val edContext = "caseInfo.content.EmbeddedDataListOfRecords"
266+
performTextInput("$edContext[0].Brand", "Ford")
267+
waitUntilAtLeastOneExists(hasContentDescription("Delete item 1"))
268+
waitUntilAtLeastOneExists(hasContentDescription("Reorder item 1"))
269+
270+
onNodeWithText("disable add/edit/remove/reorder").onSiblings().onFirst().performClick()
271+
272+
waitUntilDoesNotExist(hasText("+ Add cars"))
273+
waitUntilDoesNotExist(hasSetTextAction())
274+
waitUntilDoesNotExist(hasContentDescription("Delete item 1"))
275+
waitUntilDoesNotExist(hasContentDescription("Reorder item 1"))
276+
277+
onNodeWithText("disable add/edit/remove/reorder").onSiblings().onFirst().performClick()
278+
279+
// Step 2 - Editable popup table
280+
onNodeWithText("Next").performClick()
281+
// verify form title
282+
waitForNode("ED table editable popup conditions", substring = true)
283+
// verify table title
284+
waitForNode("Cars editable table with popup", substring = true)
285+
// verify add/edit/remove/reorder
286+
waitForNode("+ Add cars")
287+
waitUntilAtLeastOneExists(hasContentDescription("Edit item 1"))
288+
waitUntilAtLeastOneExists(hasContentDescription("Delete item 1"))
289+
waitUntilAtLeastOneExists(hasContentDescription("Reorder item 1"))
290+
291+
onNodeWithText("disable add/edit/remove/reorder").onSiblings().onFirst().performClick()
292+
293+
waitUntilDoesNotExist(hasText("+ Add cars"))
294+
waitUntilDoesNotExist(hasContentDescription("Edit item 1"))
295+
waitUntilDoesNotExist(hasContentDescription("Delete item 1"))
296+
waitUntilDoesNotExist(hasContentDescription("Reorder item 1"))
297+
}
298+
299+
private fun ComposeUiTest.performTextInput(testTag: String, inputText: String) {
300+
waitUntilAtLeastOneExists(hasTestTag(testTag))
301+
onAllNodes(hasAnyAncestor(hasTestTag(testTag)))
302+
.filter(hasSetTextAction())
303+
.onFirst().performTextInput(inputText)
304+
}
305+
306+
private fun ComposeUiTest.performClick(testTag: String) {
307+
waitUntilAtLeastOneExists(hasTestTag(testTag))
308+
onAllNodes(hasAnyAncestor(hasTestTag(testTag)))
309+
.filter(hasClickAction())
310+
.onFirst().performScrollTo().performClick()
311+
}
312+
313+
private fun ComposeUiTest.verifyEmbeddedDataRecord(
128314
nodes: SemanticsNodeInteractionCollection,
129315
expectedDate: String,
130316
isEditable: Boolean
@@ -146,10 +332,10 @@ class EmbeddedDataTest : ComposeTest(PegaVersion.v24_2_2) {
146332
}
147333
}
148334

149-
fun SemanticsNodeInteractionCollection.findFirstWithText(text: String) =
335+
private fun SemanticsNodeInteractionCollection.findFirstWithText(text: String) =
150336
this.filter(hasText(text)).onFirst()
151337

152-
fun ComposeUiTest.waitUntilAtLeastOneExists(
338+
private fun ComposeUiTest.waitUntilAtLeastOneExists(
153339
nodes: SemanticsNodeInteractionCollection,
154340
matcher: SemanticsMatcher,
155341
timeoutMillis: Long = 5000L
@@ -158,4 +344,21 @@ class EmbeddedDataTest : ComposeTest(PegaVersion.v24_2_2) {
158344
nodes.filter(matcher).fetchSemanticsNodes().size == 1
159345
}
160346
}
347+
348+
private fun ComposeUiTest.verifyReadonlyTable(columnValues: MutableMap<String, String>) {
349+
// verify columns
350+
columnValues.keys.forEach { waitForNodes(it.uppercase(), count = 2) }
351+
// verify table data
352+
columnValues["model"] = "Fiesta" // updated model name
353+
columnValues.values.forEach {
354+
waitForNodes(it, count = 2)
355+
}
356+
waitForNodes("Opel", count = 2)
357+
waitForNodes("Astra", count = 2)
358+
// verify absence of add/edit/delete actions
359+
waitUntilDoesNotExist(hasText("+ Add cars"))
360+
waitUntilDoesNotExist(hasContentDescription("Edit item 1"))
361+
waitUntilDoesNotExist(hasContentDescription("Delete item 1"))
362+
waitUntilDoesNotExist(hasContentDescription("Reorder item 1"))
363+
}
161364
}

scripts/dxcomponents/components/containers/templates/simple-table-manual.component.js

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ export class SimpleTableManualComponent extends BaseComponent {
9191
presets,
9292
allowActions,
9393
allowTableEdit,
94-
allowRowDelete,
95-
allowRowEdit,
94+
allowRowDelete: allowRowDeleteExpression,
95+
allowRowEdit: allowRowEditExpression,
9696
label: labelProp,
9797
propertyLabel,
9898
editMode,
@@ -111,7 +111,10 @@ export class SimpleTableManualComponent extends BaseComponent {
111111
this.props.label = this.pConn.getInheritedProps().label ?? labelProp ?? propertyLabel;
112112
this.targetClassLabel = targetClassLabel;
113113
this.props.addButtonLabel = targetClassLabel ? `+ Add ${targetClassLabel}` : "+ Add";
114-
this.referenceList = referenceList;
114+
this.referenceList = referenceList.map((element) => {
115+
element.allowEdit = conditions.allowEditRow && evaluateAllowRowAction(allowRowEditExpression, element)
116+
return element;
117+
});
115118
this.contextClass = this.#getContextClass(configProps);
116119

117120
this.pConn.setReferenceList(getReferenceList(this.pConn));
@@ -141,8 +144,18 @@ export class SimpleTableManualComponent extends BaseComponent {
141144
this.props.columnLabels = this.#getColumnLabels(fieldDefs, resolvedFields);
142145

143146
if ((!this.#listsEqual(this.prevReferenceList, this.referenceList))) {
144-
this.#buildRows(rawFields, editableMode, conditions.allowDeleteRow, allowRowDelete, conditions.allowEditRow, allowRowEdit);
147+
this.#buildRows(rawFields);
145148
}
149+
this.props.rows = this.editableRows.map((row, rowIndex) => {
150+
const allowDelete = conditions.allowDeleteRow && evaluateAllowRowAction(allowRowDeleteExpression, this.referenceList[rowIndex])
151+
const showEditButton = editableMode && this.allowEditingInModal && this.referenceList[rowIndex].allowEdit
152+
const showDeleteButton = editableMode && allowDelete
153+
return {
154+
cellComponentIds: row.cells.map((cell) => cell.component.compId),
155+
showEditButton: showEditButton,
156+
showDeleteButton: showDeleteButton
157+
}
158+
});
146159
this.prevReferenceList = this.referenceList;
147160
this.componentsManager.onComponentPropsUpdate(this)
148161
}
@@ -200,13 +213,10 @@ export class SimpleTableManualComponent extends BaseComponent {
200213
}
201214
}
202215

203-
#buildRows(rawFields, editableMode, allowDelete, allowRowDeleteExpression, allowEdit, allowRowEditExpression) {
216+
#buildRows(rawFields) {
204217
const context = this.pConn.getContextName();
205218
const newEditableRows = [];
206219
this.referenceList.forEach((element, rowIndex) => {
207-
const showDeleteButton = editableMode && allowDelete && evaluateAllowRowAction(allowRowDeleteExpression, element);
208-
const showEditButton = editableMode && allowEdit && evaluateAllowRowAction(allowRowEditExpression, element) && this.allowEditingInModal;
209-
210220
const editableRow = this.editableRows[rowIndex];
211221
const newEditableCells = [];
212222
rawFields?.forEach((item, cellIndex) => {
@@ -216,7 +226,7 @@ export class SimpleTableManualComponent extends BaseComponent {
216226
config: {
217227
...item.config,
218228
label: '',
219-
displayMode: this.readOnlyMode || this.allowEditingInModal ? 'DISPLAY_ONLY' : undefined
229+
displayMode: this.readOnlyMode || this.allowEditingInModal || !element.allowEdit ? 'DISPLAY_ONLY' : undefined
220230
}
221231
};
222232
const referenceListData = getReferenceList(this.pConn);
@@ -238,20 +248,9 @@ export class SimpleTableManualComponent extends BaseComponent {
238248
newEditableCells.push({ component: newComponent })
239249
}
240250
});
241-
newEditableRows.push({
242-
cells: newEditableCells,
243-
showEditButton: showEditButton,
244-
showDeleteButton: showDeleteButton
245-
});
251+
newEditableRows.push({ cells: newEditableCells});
246252
});
247253
this.editableRows = newEditableRows;
248-
this.props.rows = newEditableRows.map((row) => {
249-
return {
250-
cellComponentIds: row.cells.map((cell) => cell.component.compId),
251-
showEditButton: row.showEditButton,
252-
showDeleteButton: row.showDeleteButton
253-
}
254-
});
255254
}
256255

257256
#addSimpleTableRow() {

0 commit comments

Comments
 (0)