@@ -56,6 +56,172 @@ test.describe('7. Test Data Generation', () => {
5656 expectNoPageErrors ( pageErrors ) ;
5757 } ) ;
5858
59+ test ( 'invalid domain command text preserves the domain row type in the app editor' , async ( { page } ) => {
60+ const { appPage, pageErrors } = await openApp ( page ) ;
61+
62+ await appPage . testDataPanel . expand ( ) ;
63+ await appPage . testDataPanel . expectExpanded ( ) ;
64+
65+ await appPage . testDataPanel . setSchemaText ( 'Name\nperson.fullName' ) ;
66+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 1 ) ;
67+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'type' ) ) . toBe ( 'person.fullName' ) ;
68+ await expect
69+ . poll ( async ( ) =>
70+ appPage . testDataPanel . page
71+ . locator ( '#testDataSchemaRows .generator-schema-row' )
72+ . first ( )
73+ . locator ( 'select[data-field="sourceType"]' )
74+ . inputValue ( )
75+ )
76+ . toBe ( 'domain' ) ;
77+
78+ await appPage . testDataPanel . setSchemaText ( 'Name\nperson.fullNam' ) ;
79+
80+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'type' ) ) . toBe ( 'person.fullNam' ) ;
81+ await expect
82+ . poll ( async ( ) =>
83+ appPage . testDataPanel . page
84+ . locator ( '#testDataSchemaRows .generator-schema-row' )
85+ . first ( )
86+ . locator ( 'select[data-field="sourceType"]' )
87+ . inputValue ( )
88+ )
89+ . toBe ( 'domain' ) ;
90+
91+ await expect ( appPage . testDataPanel . page . locator ( '#testDataSchemaRows .generator-schema-row' ) . first ( ) ) . toHaveClass (
92+ / g e n e r a t o r - s c h e m a - r o w - i n v a l i d /
93+ ) ;
94+
95+ await appPage . testDataPanel . clickGenerate ( ) ;
96+ await expect
97+ . poll ( async ( ) => appPage . testDataPanel . getSchemaErrorText ( ) )
98+ . toContain ( 'Row 1: unknown domain command "person.fullNam".' ) ;
99+
100+ expectNoPageErrors ( pageErrors ) ;
101+ } ) ;
102+
103+ test ( 'domain rows with invalid params stay domain after text mode round-trip in the app editor' , async ( { page } ) => {
104+ const { appPage, pageErrors } = await openApp ( page ) ;
105+
106+ await appPage . testDataPanel . expand ( ) ;
107+ await appPage . testDataPanel . expectExpanded ( ) ;
108+
109+ await appPage . testDataPanel . addSchemaRow ( ) ;
110+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 1 ) ;
111+ await appPage . testDataPanel . setSchemaCell ( 0 , 'columnName' , 'Name' ) ;
112+ await appPage . testDataPanel . setSchemaTypeValue ( 0 , 'person.fullName' ) ;
113+ await appPage . testDataPanel . setSchemaCell ( 0 , 'params' , '(10)' ) ;
114+
115+ await appPage . testDataPanel . setSchemaTextMode ( true ) ;
116+ await appPage . testDataPanel . setSchemaTextMode ( false ) ;
117+
118+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'type' ) ) . toBe ( 'person.fullName' ) ;
119+ await expect
120+ . poll ( async ( ) =>
121+ appPage . testDataPanel . page
122+ . locator ( '#testDataSchemaRows .generator-schema-row' )
123+ . first ( )
124+ . locator ( 'select[data-field="sourceType"]' )
125+ . inputValue ( )
126+ )
127+ . toBe ( 'domain' ) ;
128+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'params' ) ) . toBe ( '(10)' ) ;
129+ await expect (
130+ appPage . testDataPanel . page
131+ . locator ( '#testDataSchemaRows .generator-schema-row' )
132+ . first ( )
133+ . locator ( '.generator-schema-row-validation' )
134+ ) . toContainText ( 'invalid domain params' ) ;
135+
136+ expectNoPageErrors ( pageErrors ) ;
137+ } ) ;
138+
139+ test ( 'debounced semantic validation appears after typing stops in the app editor' , async ( { page } ) => {
140+ const { appPage, pageErrors } = await openApp ( page ) ;
141+
142+ await appPage . testDataPanel . expand ( ) ;
143+ await appPage . testDataPanel . expectExpanded ( ) ;
144+
145+ await appPage . testDataPanel . addSchemaRow ( ) ;
146+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 1 ) ;
147+ await appPage . testDataPanel . setSchemaCell ( 0 , 'columnName' , 'Name' ) ;
148+ await appPage . testDataPanel . setSchemaTypeValue ( 0 , 'person.fullName' ) ;
149+
150+ const paramsInput = appPage . testDataPanel . page
151+ . locator ( '#testDataSchemaRows .generator-schema-row' )
152+ . first ( )
153+ . locator ( 'input[data-field="params"]' ) ;
154+ await paramsInput . fill ( '(10)' ) ;
155+
156+ await expect
157+ . poll (
158+ async ( ) =>
159+ await appPage . testDataPanel . page
160+ . locator ( '#testDataSchemaRows .generator-schema-row' )
161+ . first ( )
162+ . locator ( '.generator-schema-row-validation' )
163+ . textContent ( ) ,
164+ { timeout : 2500 }
165+ )
166+ . toContain ( 'invalid domain params' ) ;
167+
168+ expectNoPageErrors ( pageErrors ) ;
169+ } ) ;
170+
171+ test ( 'row validation clears when name editing blurs into the next field in the app editor' , async ( { page } ) => {
172+ const { appPage, pageErrors } = await openApp ( page ) ;
173+
174+ await appPage . testDataPanel . expand ( ) ;
175+ await appPage . testDataPanel . expectExpanded ( ) ;
176+
177+ await appPage . testDataPanel . addSchemaRow ( ) ;
178+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 1 ) ;
179+ const row = appPage . testDataPanel . page . locator ( '#testDataSchemaRows .generator-schema-row' ) . first ( ) ;
180+ await appPage . testDataPanel . setSchemaTypeValue ( 0 , 'literal' ) ;
181+ await appPage . testDataPanel . setSchemaCell ( 0 , 'value' , 'Active' ) ;
182+ await appPage . testDataPanel . clickGenerate ( ) ;
183+ await expect ( row . locator ( '.generator-schema-row-validation' ) ) . toContainText ( 'column name is required' ) ;
184+
185+ const nameInput = row . locator ( 'input[data-field="name"]' ) ;
186+ const valueInput = row . locator ( 'input[data-field="value"]' ) ;
187+ await nameInput . fill ( 'Status' ) ;
188+ await valueInput . click ( ) ;
189+
190+ await expect ( row . locator ( '.generator-schema-row-validation' ) ) . toHaveCount ( 0 ) ;
191+ expectNoPageErrors ( pageErrors ) ;
192+ } ) ;
193+
194+ test ( 'domain params without brackets are normalized and preserved in the app editor' , async ( { page } ) => {
195+ const { appPage, pageErrors } = await openApp ( page ) ;
196+
197+ await appPage . testDataPanel . expand ( ) ;
198+ await appPage . testDataPanel . expectExpanded ( ) ;
199+
200+ await appPage . testDataPanel . addSchemaRow ( ) ;
201+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 1 ) ;
202+ await appPage . testDataPanel . setSchemaCell ( 0 , 'columnName' , 'Phone' ) ;
203+ await appPage . testDataPanel . setSchemaTypeValue ( 0 , 'phone.number' ) ;
204+ await appPage . testDataPanel . setSchemaCell ( 0 , 'params' , 'style=13' ) ;
205+
206+ await appPage . testDataPanel . setSchemaTextMode ( true ) ;
207+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaText ( ) ) . toContain ( 'phone.number(style=13)' ) ;
208+ await appPage . testDataPanel . setSchemaTextMode ( false ) ;
209+
210+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'type' ) ) . toBe ( 'phone.number' ) ;
211+ await expect
212+ . poll ( async ( ) =>
213+ appPage . testDataPanel . page
214+ . locator ( '#testDataSchemaRows .generator-schema-row' )
215+ . first ( )
216+ . locator ( 'select[data-field="sourceType"]' )
217+ . inputValue ( )
218+ )
219+ . toBe ( 'domain' ) ;
220+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'params' ) ) . toBe ( '(style=13)' ) ;
221+
222+ expectNoPageErrors ( pageErrors ) ;
223+ } ) ;
224+
59225 test ( 'Schema grid edits after initial sync are reflected in the text schema' , async ( { page } ) => {
60226 const { appPage, pageErrors } = await openApp ( page ) ;
61227
@@ -78,6 +244,50 @@ test.describe('7. Test Data Generation', () => {
78244 expectNoPageErrors ( pageErrors ) ;
79245 } ) ;
80246
247+ test ( 'schema rows can be dragged to the top and bottom in the app editor' , async ( { page } ) => {
248+ const { appPage, pageErrors } = await openApp ( page ) ;
249+
250+ await appPage . testDataPanel . expand ( ) ;
251+ await appPage . testDataPanel . expectExpanded ( ) ;
252+
253+ await appPage . testDataPanel . setSchemaText ( 'A\nliteral(a)\n\nB\nliteral(b)\n\nC\nliteral(c)\n\nD\nliteral(d)' ) ;
254+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 4 ) ;
255+
256+ await appPage . testDataPanel . schemaEditor . dragRowToIndex ( 3 , 0 , { placement : 'before' } ) ;
257+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'columnName' ) ) . toBe ( 'D' ) ;
258+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 1 , 'columnName' ) ) . toBe ( 'A' ) ;
259+
260+ await appPage . testDataPanel . schemaEditor . dragRowToIndex ( 0 , 3 , { placement : 'after' } ) ;
261+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'columnName' ) ) . toBe ( 'A' ) ;
262+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 3 , 'columnName' ) ) . toBe ( 'D' ) ;
263+
264+ await expect
265+ . poll ( async ( ) => appPage . testDataPanel . getSchemaText ( ) )
266+ . toContain ( 'A\nliteral(a)\nB\nliteral(b)\nC\nliteral(c)\nD\nliteral(d)' ) ;
267+ expectNoPageErrors ( pageErrors ) ;
268+ } ) ;
269+
270+ test ( 'switching enum rows to domain datatype.enum preserves the enum params in the app editor' , async ( { page } ) => {
271+ const { appPage, pageErrors } = await openApp ( page ) ;
272+
273+ await appPage . testDataPanel . expand ( ) ;
274+ await appPage . testDataPanel . expectExpanded ( ) ;
275+
276+ await appPage . testDataPanel . addSchemaRow ( ) ;
277+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaRowCount ( ) ) . toBe ( 1 ) ;
278+ await appPage . testDataPanel . setSchemaCell ( 0 , 'columnName' , 'Status' ) ;
279+ await appPage . testDataPanel . setSchemaTypeValue ( 0 , 'enum' , { assertSchemaTextIncludesType : false } ) ;
280+ await appPage . testDataPanel . setSchemaCell ( 0 , 'value' , 'active,inactive,pending' ) ;
281+
282+ await appPage . testDataPanel . setSchemaTypeValue ( 0 , 'datatype.enum' , { assertSchemaTextIncludesType : false } ) ;
283+
284+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'type' ) ) . toBe ( 'datatype.enum' ) ;
285+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaCell ( 0 , 'value' ) ) . toBe ( 'active,inactive,pending' ) ;
286+ await expect . poll ( async ( ) => appPage . testDataPanel . getSchemaText ( ) ) . toContain ( 'enum(active,inactive,pending)' ) ;
287+
288+ expectNoPageErrors ( pageErrors ) ;
289+ } ) ;
290+
81291 test ( 'empty schema text can switch back to schema mode without locking' , async ( { page } ) => {
82292 const { appPage, pageErrors } = await openApp ( page ) ;
83293
0 commit comments