@@ -6,7 +6,9 @@ import androidx.compose.ui.test.SemanticsMatcher
66import androidx.compose.ui.test.SemanticsNodeInteractionCollection
77import androidx.compose.ui.test.filter
88import androidx.compose.ui.test.hasAnyAncestor
9+ import androidx.compose.ui.test.hasClickAction
910import androidx.compose.ui.test.hasContentDescription
11+ import androidx.compose.ui.test.hasSetTextAction
1012import androidx.compose.ui.test.hasTestTag
1113import androidx.compose.ui.test.hasText
1214import androidx.compose.ui.test.onFirst
@@ -16,7 +18,10 @@ import androidx.compose.ui.test.onSiblings
1618import androidx.compose.ui.test.performClick
1719import androidx.compose.ui.test.performScrollTo
1820import androidx.compose.ui.test.performTextInput
21+ import androidx.compose.ui.test.performTextReplacement
1922import androidx.compose.ui.test.runComposeUiTest
23+ import androidx.compose.ui.test.waitUntilAtLeastOneExists
24+ import androidx.compose.ui.test.waitUntilDoesNotExist
2025import androidx.compose.ui.test.waitUntilExactlyOneExists
2126import androidx.compose.ui.test.waitUntilNodeCount
2227import 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}
0 commit comments