@nextsparkjs/theme-default 0.1.0-beta.44 → 0.1.0-beta.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/components/ai-chat/ChatPanel.tsx +7 -7
  2. package/components/ai-chat/Message.tsx +2 -2
  3. package/components/ai-chat/MessageInput.tsx +3 -3
  4. package/components/ai-chat/MessageList.tsx +3 -3
  5. package/components/ai-chat/TypingIndicator.tsx +2 -2
  6. package/entities/customers/api/docs.md +107 -0
  7. package/entities/customers/api/presets.ts +80 -0
  8. package/entities/pages/api/docs.md +114 -0
  9. package/entities/pages/api/presets.ts +72 -0
  10. package/entities/posts/api/docs.md +120 -0
  11. package/entities/posts/api/presets.ts +74 -0
  12. package/entities/tasks/api/docs.md +126 -0
  13. package/entities/tasks/api/presets.ts +84 -0
  14. package/lib/selectors.ts +2 -2
  15. package/messages/de/admin.json +45 -0
  16. package/messages/en/admin.json +45 -0
  17. package/messages/es/admin.json +45 -0
  18. package/messages/fr/admin.json +45 -0
  19. package/messages/it/admin.json +45 -0
  20. package/messages/pt/admin.json +45 -0
  21. package/package.json +3 -3
  22. package/styles/globals.css +24 -0
  23. package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +491 -0
  24. package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +475 -0
  25. package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
  26. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +14 -14
  27. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +3 -3
  28. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +38 -73
  29. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +21 -42
  30. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +117 -38
  31. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +35 -12
  32. package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +50 -59
  33. package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +15 -23
  34. package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +395 -155
  35. package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +795 -174
  36. package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
  37. package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
  38. package/tests/cypress/src/core/BlockEditorBasePOM.ts +269 -99
  39. package/tests/cypress/src/core/DashboardEntityPOM.ts +1 -1
  40. package/tests/cypress/src/features/DashboardPOM.ts +49 -28
  41. package/tests/cypress/src/features/PageBuilderPOM.ts +20 -0
  42. package/tests/cypress/src/features/SettingsPOM.ts +511 -166
  43. package/tests/cypress/src/features/SuperadminPOM.ts +679 -159
  44. package/tests/cypress/src/features/index.ts +10 -10
  45. package/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  46. package/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  47. package/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  48. package/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
@@ -56,102 +56,199 @@ export abstract class BlockEditorBasePOM extends BasePOM {
56
56
 
57
57
  get editorSelectors() {
58
58
  return {
59
- // Editor main
60
- container: cySelector('blockEditor.container'),
61
- titleInput: cySelector('blockEditor.titleInput'),
62
- slugInput: cySelector('blockEditor.slugInput'),
63
- saveButton: cySelector('blockEditor.saveButton'),
64
- statusBadge: cySelector('blockEditor.statusBadge'),
65
- leftSidebarToggle: cySelector('blockEditor.leftSidebarToggle'),
66
- viewModeToggle: cySelector('blockEditor.viewModeToggle'),
67
-
68
- // Block Picker
59
+ // =========================================================================
60
+ // HEADER - Top bar with title, slug, toggles, and actions
61
+ // =========================================================================
62
+ container: cySelector('blockEditor.header.container'),
63
+ backButton: cySelector('blockEditor.header.backButton'),
64
+ titleInput: cySelector('blockEditor.header.titleInput'),
65
+ slugInput: cySelector('blockEditor.header.slugInput'),
66
+ externalLink: cySelector('blockEditor.header.externalLink'),
67
+ viewModeToggle: cySelector('blockEditor.header.viewToggle'),
68
+ viewEditor: cySelector('blockEditor.header.viewEditor'),
69
+ viewPreview: cySelector('blockEditor.header.viewPreview'),
70
+ saveButton: cySelector('blockEditor.header.saveButton'),
71
+ publishButton: cySelector('blockEditor.header.publishButton'),
72
+ settingsButton: cySelector('blockEditor.header.settingsButton'),
73
+ // Status selector (in header)
74
+ statusSelector: cySelector('blockEditor.header.statusSelector'),
75
+ statusOption: (value: string) =>
76
+ cySelector('blockEditor.header.statusOption', { value }),
77
+ statusDot: cySelector('blockEditor.header.statusDot'),
78
+ statusLabel: cySelector('blockEditor.header.statusLabel'),
79
+ // Locale selector (in header)
80
+ localeSelector: cySelector('blockEditor.header.localeSelector'),
81
+ localeOption: (locale: string) =>
82
+ cySelector('blockEditor.header.localeOption', { locale }),
83
+
84
+ // =========================================================================
85
+ // BLOCK PICKER - Left column "Bloques" tab
86
+ // =========================================================================
69
87
  blockPicker: cySelector('blockEditor.blockPicker.container'),
88
+ // Tabs
89
+ tabBlocks: cySelector('blockEditor.blockPicker.tabBlocks'),
90
+ tabConfig: cySelector('blockEditor.blockPicker.tabConfig'),
91
+ tabIndicator: cySelector('blockEditor.blockPicker.tabIndicator'),
92
+ // Search
93
+ searchWrapper: cySelector('blockEditor.blockPicker.searchWrapper'),
94
+ searchIcon: cySelector('blockEditor.blockPicker.searchIcon'),
70
95
  blockSearch: cySelector('blockEditor.blockPicker.searchInput'),
71
- categoryAll: cySelector('blockEditor.blockPicker.categoryAll'),
96
+ blockSearchClear: cySelector('blockEditor.blockPicker.searchClear'),
97
+ // Categories
98
+ categoryChips: cySelector('blockEditor.blockPicker.categoryChips'),
72
99
  category: (category: string) =>
73
- cySelector('blockEditor.blockPicker.category', { category }),
74
- blockItem: (slug: string) => cySelector('blockEditor.blockPicker.blockItem', { slug }),
75
- addBlock: (slug: string) => cySelector('blockEditor.blockPicker.addBlock', { slug }),
76
-
77
- // Block Canvas
78
- blockCanvas: cySelector('blockEditor.blockCanvas.container'),
79
- blockCanvasEmpty: cySelector('blockEditor.blockCanvas.empty'),
80
-
81
- // Preview Canvas
100
+ cySelector('blockEditor.blockPicker.categoryChip', { category }),
101
+ // Block cards
102
+ blockItem: (slug: string) => cySelector('blockEditor.blockPicker.blockCard', { slug }),
103
+ blockIcon: (slug: string) => cySelector('blockEditor.blockPicker.blockIcon', { slug }),
104
+ blockName: (slug: string) => cySelector('blockEditor.blockPicker.blockName', { slug }),
105
+ addBlock: (slug: string) => cySelector('blockEditor.blockPicker.addButton', { slug }),
106
+ blockPickerEmpty: cySelector('blockEditor.blockPicker.empty'),
107
+
108
+ // =========================================================================
109
+ // ENTITY FIELDS PANEL - Left column "Configuración" tab
110
+ // =========================================================================
111
+ entityFieldsPanel: cySelector('blockEditor.entityFieldsPanel.container'),
112
+ entityField: (name: string) =>
113
+ cySelector('blockEditor.entityFieldsPanel.field', { name }),
114
+ entityCategoryList: cySelector('blockEditor.entityFieldsPanel.categoryList'),
115
+ entityCategory: (slug: string) =>
116
+ cySelector('blockEditor.entityFieldsPanel.categoryItem', { slug }),
117
+ entityCategoryCheckbox: (slug: string) =>
118
+ cySelector('blockEditor.entityFieldsPanel.categoryCheckbox', { slug }),
119
+
120
+ // =========================================================================
121
+ // LAYOUT CANVAS - Center column - Layout mode (draggable cards)
122
+ // =========================================================================
123
+ layoutCanvas: cySelector('blockEditor.layoutCanvas.container'),
124
+ layoutCanvasEmpty: cySelector('blockEditor.layoutCanvas.empty'),
125
+ layoutDropZone: cySelector('blockEditor.layoutCanvas.dropZone'),
126
+ // Sortable block cards
127
+ sortableBlock: (id: string) =>
128
+ cySelector('blockEditor.layoutCanvas.sortableBlock.container', { id }),
129
+ sortableBlockCard: (id: string) =>
130
+ cySelector('blockEditor.layoutCanvas.sortableBlock.card', { id }),
131
+ dragHandle: (id: string) =>
132
+ cySelector('blockEditor.layoutCanvas.sortableBlock.dragHandle', { id }),
133
+ sortableBlockName: (id: string) =>
134
+ cySelector('blockEditor.layoutCanvas.sortableBlock.name', { id }),
135
+ duplicateBlock: (id: string) =>
136
+ cySelector('blockEditor.layoutCanvas.sortableBlock.duplicateBtn', { id }),
137
+ removeBlock: (id: string) =>
138
+ cySelector('blockEditor.layoutCanvas.sortableBlock.removeBtn', { id }),
139
+ blockError: (id: string) =>
140
+ cySelector('blockEditor.layoutCanvas.sortableBlock.error', { id }),
141
+ // Generic selector for counting all sortable blocks
142
+ sortableBlockGeneric: '[data-cy^="sortable-block-"]',
143
+
144
+ // =========================================================================
145
+ // PREVIEW CANVAS - Center column - Preview mode (real blocks)
146
+ // =========================================================================
82
147
  previewCanvas: cySelector('blockEditor.previewCanvas.container'),
148
+ previewWrapper: cySelector('blockEditor.previewCanvas.wrapper'),
83
149
  previewCanvasEmpty: cySelector('blockEditor.previewCanvas.empty'),
84
150
  previewBlock: (id: string) => cySelector('blockEditor.previewCanvas.block', { id }),
85
- previewMoveUp: (id: string) => cySelector('blockEditor.previewCanvas.moveUp', { id }),
86
- previewMoveDown: (id: string) => cySelector('blockEditor.previewCanvas.moveDown', { id }),
87
-
88
- // Sortable Block
89
- sortableBlock: (id: string) => cySelector('blockEditor.sortableBlock.container', { id }),
90
- dragHandle: (id: string) => cySelector('blockEditor.sortableBlock.dragHandle', { id }),
91
- duplicateBlock: (id: string) => cySelector('blockEditor.sortableBlock.duplicate', { id }),
92
- removeBlock: (id: string) => cySelector('blockEditor.sortableBlock.remove', { id }),
93
- blockError: (id: string) => cySelector('blockEditor.sortableBlock.error', { id }),
94
-
95
- // Settings Panel
96
- settingsPanel: cySelector('blockEditor.settingsPanel.container'),
97
- settingsPanelEmpty: cySelector('blockEditor.settingsPanel.empty'),
98
- settingsPanelError: cySelector('blockEditor.settingsPanel.error'),
99
- resetProps: cySelector('blockEditor.settingsPanel.resetProps'),
100
- removeBlockSettings: cySelector('blockEditor.settingsPanel.removeBlock'),
101
- tabContent: cySelector('blockEditor.settingsPanel.tabContent'),
102
- tabDesign: cySelector('blockEditor.settingsPanel.tabDesign'),
103
- tabAdvanced: cySelector('blockEditor.settingsPanel.tabAdvanced'),
104
-
105
- // Page Settings
106
- pageSettings: cySelector('blockEditor.pageSettings.container'),
107
- seoTrigger: cySelector('blockEditor.pageSettings.seoTrigger'),
108
- metaTitle: cySelector('blockEditor.pageSettings.metaTitle'),
109
- metaDescription: cySelector('blockEditor.pageSettings.metaDescription'),
110
- metaKeywords: cySelector('blockEditor.pageSettings.metaKeywords'),
111
- ogImage: cySelector('blockEditor.pageSettings.ogImage'),
112
- customFieldsTrigger: cySelector('blockEditor.pageSettings.customFieldsTrigger'),
151
+ previewBlockWrapper: (id: string) =>
152
+ cySelector('blockEditor.previewCanvas.blockWrapper', { id }),
153
+ previewBlockSelected: (id: string) =>
154
+ cySelector('blockEditor.previewCanvas.blockSelected', { id }),
155
+ // Generic selector for counting all preview blocks
156
+ previewBlockGeneric: '[data-cy^="preview-block-"]',
157
+ // Move buttons (legacy selectors for backward compatibility)
158
+ moveUpBtn: (id: string) => `[data-cy="preview-block-${id}-move-up"]`,
159
+ moveDownBtn: (id: string) => `[data-cy="preview-block-${id}-move-down"]`,
160
+ // Floating toolbar
161
+ floatingToolbar: (id: string) =>
162
+ cySelector('blockEditor.previewCanvas.floatingToolbar.container', { id }),
163
+ floatingToolbarDrag: (id: string) =>
164
+ cySelector('blockEditor.previewCanvas.floatingToolbar.dragHandle', { id }),
165
+ floatingToolbarName: (id: string) =>
166
+ cySelector('blockEditor.previewCanvas.floatingToolbar.blockName', { id }),
167
+ floatingToolbarDuplicate: (id: string) =>
168
+ cySelector('blockEditor.previewCanvas.floatingToolbar.duplicateBtn', { id }),
169
+ floatingToolbarDelete: (id: string) =>
170
+ cySelector('blockEditor.previewCanvas.floatingToolbar.deleteBtn', { id }),
171
+
172
+ // =========================================================================
173
+ // ENTITY META PANEL - SEO and custom fields
174
+ // =========================================================================
175
+ entityMetaPanel: cySelector('blockEditor.entityMetaPanel.container'),
176
+ // SEO section
177
+ seoTrigger: cySelector('blockEditor.entityMetaPanel.seoSection.trigger'),
178
+ seoContent: cySelector('blockEditor.entityMetaPanel.seoSection.content'),
179
+ metaTitle: cySelector('blockEditor.entityMetaPanel.seoSection.metaTitle'),
180
+ metaDescription: cySelector('blockEditor.entityMetaPanel.seoSection.metaDescription'),
181
+ metaKeywords: cySelector('blockEditor.entityMetaPanel.seoSection.metaKeywords'),
182
+ ogImage: cySelector('blockEditor.entityMetaPanel.seoSection.ogImage'),
183
+ // Custom fields section
184
+ customFieldsTrigger: cySelector('blockEditor.entityMetaPanel.customFields.trigger'),
185
+ customFieldsContent: cySelector('blockEditor.entityMetaPanel.customFields.content'),
113
186
  customFieldKey: (index: number) =>
114
- cySelector('blockEditor.pageSettings.customFieldKey', { index }),
187
+ cySelector('blockEditor.entityMetaPanel.customFields.fieldKey', { index }),
115
188
  customFieldValue: (index: number) =>
116
- cySelector('blockEditor.pageSettings.customFieldValue', { index }),
189
+ cySelector('blockEditor.entityMetaPanel.customFields.fieldValue', { index }),
117
190
  customFieldRemove: (index: number) =>
118
- cySelector('blockEditor.pageSettings.customFieldRemove', { index }),
119
- addCustomField: cySelector('blockEditor.pageSettings.addCustomField'),
120
-
121
- // Status Selector
122
- statusSelector: cySelector('blockEditor.statusSelector.trigger'),
123
- statusOption: (value: string) =>
124
- cySelector('blockEditor.statusSelector.option', { value }),
125
-
126
- // Dynamic Form
127
- dynamicForm: cySelector('blockEditor.dynamicForm.container'),
191
+ cySelector('blockEditor.entityMetaPanel.customFields.fieldRemove', { index }),
192
+ addCustomField: cySelector('blockEditor.entityMetaPanel.customFields.addButton'),
193
+
194
+ // =========================================================================
195
+ // BLOCK PROPERTIES PANEL - Right column
196
+ // =========================================================================
197
+ blockPropertiesPanel: cySelector('blockEditor.blockPropertiesPanel.container'),
198
+ blockPropertiesHeader: cySelector('blockEditor.blockPropertiesPanel.header'),
199
+ blockPropertiesClose: cySelector('blockEditor.blockPropertiesPanel.closeBtn'),
200
+ blockPropertiesIcon: cySelector('blockEditor.blockPropertiesPanel.blockIcon'),
201
+ blockPropertiesName: cySelector('blockEditor.blockPropertiesPanel.blockName'),
202
+ blockPropertiesTabs: cySelector('blockEditor.blockPropertiesPanel.tabs'),
203
+ tabContent: cySelector('blockEditor.blockPropertiesPanel.tabContent'),
204
+ tabDesign: cySelector('blockEditor.blockPropertiesPanel.tabDesign'),
205
+ tabAdvanced: cySelector('blockEditor.blockPropertiesPanel.tabAdvanced'),
206
+ blockPropertiesEmpty: cySelector('blockEditor.blockPropertiesPanel.empty'),
207
+ blockPropertiesError: cySelector('blockEditor.blockPropertiesPanel.error'),
208
+ // Dynamic form
209
+ dynamicForm: cySelector('blockEditor.blockPropertiesPanel.form.container'),
128
210
  dynamicField: (name: string) =>
129
- cySelector('blockEditor.dynamicForm.field', { name }),
211
+ cySelector('blockEditor.blockPropertiesPanel.form.field', { name }),
212
+ /** @deprecated Use dynamicField instead */
213
+ fieldInput: (name: string) =>
214
+ cySelector('blockEditor.blockPropertiesPanel.form.field', { name }),
130
215
  fieldGroup: (id: string) =>
131
- cySelector('blockEditor.dynamicForm.fieldGroup', { id }),
132
- arrayGroup: (name: string) =>
133
- cySelector('blockEditor.dynamicForm.arrayGroup', { name }),
134
-
135
- // Array Field
216
+ cySelector('blockEditor.blockPropertiesPanel.form.fieldGroup', { id }),
217
+ // Reset props button (legacy selector)
218
+ resetPropsBtn: '[data-cy="reset-block-props"]',
219
+ // Array field
136
220
  arrayFieldContainer: (name: string) =>
137
- cySelector('blockEditor.arrayField.container', { name }),
138
- arrayFieldItem: (name: string, index: number, field: string) =>
139
- cySelector('blockEditor.arrayField.item', { name, index, field }),
221
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.container', { name }),
222
+ arrayFieldItem: (name: string, index: number) =>
223
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.item', { name, index }),
224
+ arrayFieldItemField: (name: string, index: number, field: string) =>
225
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.itemField', { name, index, field }),
140
226
  arrayFieldMoveUp: (name: string, index: number) =>
141
- cySelector('blockEditor.arrayField.moveUp', { name, index }),
227
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.itemMoveUp', { name, index }),
142
228
  arrayFieldMoveDown: (name: string, index: number) =>
143
- cySelector('blockEditor.arrayField.moveDown', { name, index }),
229
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.itemMoveDown', { name, index }),
144
230
  arrayFieldRemove: (name: string, index: number) =>
145
- cySelector('blockEditor.arrayField.remove', { name, index }),
231
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.itemRemove', { name, index }),
146
232
  arrayFieldAdd: (name: string) =>
147
- cySelector('blockEditor.arrayField.add', { name }),
148
-
149
- // Entity Fields Sidebar
150
- entityFieldsSidebar: cySelector('blockEditor.entityFieldsSidebar.container'),
151
- entityField: (name: string) =>
152
- cySelector('blockEditor.entityFieldsSidebar.field', { name }),
153
- entityCategory: (slug: string) =>
154
- cySelector('blockEditor.entityFieldsSidebar.category', { slug })
233
+ cySelector('blockEditor.blockPropertiesPanel.form.arrayField.addButton', { name }),
234
+
235
+ // =========================================================================
236
+ // LEGACY ALIASES (for backward compatibility - will be removed)
237
+ // =========================================================================
238
+ /** @deprecated Use layoutCanvas instead */
239
+ blockCanvas: cySelector('blockEditor.layoutCanvas.container'),
240
+ /** @deprecated Use layoutCanvasEmpty instead */
241
+ blockCanvasEmpty: cySelector('blockEditor.layoutCanvas.empty'),
242
+ /** @deprecated Use blockPropertiesPanel instead */
243
+ settingsPanel: cySelector('blockEditor.blockPropertiesPanel.container'),
244
+ /** @deprecated Use blockPropertiesEmpty instead */
245
+ settingsPanelEmpty: cySelector('blockEditor.blockPropertiesPanel.empty'),
246
+ /** @deprecated Use blockPropertiesError instead */
247
+ settingsPanelError: cySelector('blockEditor.blockPropertiesPanel.error'),
248
+ /** @deprecated Use entityMetaPanel instead */
249
+ pageSettings: cySelector('blockEditor.entityMetaPanel.container'),
250
+ /** @deprecated Use entityFieldsPanel instead */
251
+ entityFieldsSidebar: cySelector('blockEditor.entityFieldsPanel.container'),
155
252
  }
156
253
  }
157
254
 
@@ -183,6 +280,13 @@ export abstract class BlockEditorBasePOM extends BasePOM {
183
280
  return this
184
281
  }
185
282
 
283
+ /**
284
+ * Alias for waitForEditor (legacy compatibility)
285
+ */
286
+ waitForEditorLoad() {
287
+ return this.waitForEditor()
288
+ }
289
+
186
290
  waitForSave() {
187
291
  this.api.waitForUpdate()
188
292
  return this
@@ -236,18 +340,51 @@ export abstract class BlockEditorBasePOM extends BasePOM {
236
340
  }
237
341
 
238
342
  /**
239
- * Toggle left sidebar
343
+ * Toggle view mode (edit/preview)
240
344
  */
241
- toggleLeftSidebar() {
242
- cy.get(this.editorSelectors.leftSidebarToggle).click()
345
+ toggleViewMode() {
346
+ cy.get(this.editorSelectors.viewModeToggle).click()
243
347
  return this
244
348
  }
245
349
 
246
350
  /**
247
- * Toggle view mode (edit/preview)
351
+ * Switch to Layout/Editor mode
248
352
  */
249
- toggleViewMode() {
250
- cy.get(this.editorSelectors.viewModeToggle).click()
353
+ switchToLayoutMode() {
354
+ cy.get(this.editorSelectors.viewEditor).click()
355
+ // Wait for layout canvas OR empty state to render (depends on whether blocks exist)
356
+ cy.get('body').then(($body) => {
357
+ // Either the canvas with blocks or the empty state should exist
358
+ if ($body.find(this.editorSelectors.layoutCanvas).length > 0) {
359
+ cy.get(this.editorSelectors.layoutCanvas, { timeout: 5000 }).should('exist')
360
+ } else {
361
+ cy.get(this.editorSelectors.layoutCanvasEmpty, { timeout: 5000 }).should('exist')
362
+ }
363
+ })
364
+ return this
365
+ }
366
+
367
+ /**
368
+ * Switch to Preview mode
369
+ */
370
+ switchToPreviewMode() {
371
+ cy.get(this.editorSelectors.viewPreview).click()
372
+ return this
373
+ }
374
+
375
+ /**
376
+ * Select Blocks tab in left sidebar
377
+ */
378
+ selectBlocksTab() {
379
+ cy.get(this.editorSelectors.tabBlocks).click()
380
+ return this
381
+ }
382
+
383
+ /**
384
+ * Select Config/Fields tab in left sidebar
385
+ */
386
+ selectConfigTab() {
387
+ cy.get(this.editorSelectors.tabConfig).click()
251
388
  return this
252
389
  }
253
390
 
@@ -283,7 +420,7 @@ export abstract class BlockEditorBasePOM extends BasePOM {
283
420
  * Select "All" category
284
421
  */
285
422
  selectAllCategories() {
286
- cy.get(this.editorSelectors.categoryAll).click()
423
+ cy.get(this.editorSelectors.category('all')).click()
287
424
  return this
288
425
  }
289
426
 
@@ -335,7 +472,7 @@ export abstract class BlockEditorBasePOM extends BasePOM {
335
472
  * Move block up in preview
336
473
  */
337
474
  moveBlockUp(blockId: string) {
338
- cy.get(this.editorSelectors.previewMoveUp(blockId)).click()
475
+ cy.get(this.editorSelectors.moveUpBtn(blockId)).click()
339
476
  return this
340
477
  }
341
478
 
@@ -343,7 +480,7 @@ export abstract class BlockEditorBasePOM extends BasePOM {
343
480
  * Move block down in preview
344
481
  */
345
482
  moveBlockDown(blockId: string) {
346
- cy.get(this.editorSelectors.previewMoveDown(blockId)).click()
483
+ cy.get(this.editorSelectors.moveDownBtn(blockId)).click()
347
484
  return this
348
485
  }
349
486
 
@@ -379,24 +516,23 @@ export abstract class BlockEditorBasePOM extends BasePOM {
379
516
  * Reset block props to defaults
380
517
  */
381
518
  resetBlockProps() {
382
- cy.get(this.editorSelectors.resetProps).click()
519
+ cy.get(this.editorSelectors.resetPropsBtn).click()
383
520
  return this
384
521
  }
385
522
 
386
523
  /**
387
- * Remove block from settings panel
524
+ * Fill a dynamic form field
388
525
  */
389
- removeBlockFromSettings() {
390
- cy.get(this.editorSelectors.removeBlockSettings).click()
526
+ fillDynamicField(name: string, value: string) {
527
+ cy.get(this.editorSelectors.dynamicField(name)).find('input, textarea').clear().type(value)
391
528
  return this
392
529
  }
393
530
 
394
531
  /**
395
- * Fill a dynamic form field
532
+ * Alias for fillDynamicField (legacy compatibility)
396
533
  */
397
- fillDynamicField(name: string, value: string) {
398
- cy.get(this.editorSelectors.dynamicField(name)).find('input, textarea').clear().type(value)
399
- return this
534
+ fillField(name: string, value: string) {
535
+ return this.fillDynamicField(name, value)
400
536
  }
401
537
 
402
538
  // ============================================
@@ -549,10 +685,10 @@ export abstract class BlockEditorBasePOM extends BasePOM {
549
685
  }
550
686
 
551
687
  /**
552
- * Assert status badge shows specific status
688
+ * Assert status label shows specific status
553
689
  */
554
690
  assertStatus(status: string) {
555
- cy.get(this.editorSelectors.statusBadge).should('contain.text', status)
691
+ cy.get(this.editorSelectors.statusLabel).should('contain.text', status)
556
692
  return this
557
693
  }
558
694
 
@@ -571,6 +707,40 @@ export abstract class BlockEditorBasePOM extends BasePOM {
571
707
  cy.get(this.editorSelectors.settingsPanelEmpty).should('be.visible')
572
708
  return this
573
709
  }
710
+
711
+ /**
712
+ * Assert block count in canvas
713
+ */
714
+ assertBlockCount(count: number) {
715
+ cy.get('[data-cy^="sortable-block-"]').should('have.length', count)
716
+ return this
717
+ }
718
+
719
+ /**
720
+ * Assert preview block count
721
+ */
722
+ assertPreviewBlockCount(count: number) {
723
+ cy.get('[data-cy^="preview-block-"]').should('have.length', count)
724
+ return this
725
+ }
726
+
727
+ /**
728
+ * Assert block is visible in picker
729
+ */
730
+ assertBlockInPicker(blockSlug: string) {
731
+ cy.get(this.editorSelectors.blockItem(blockSlug))
732
+ .scrollIntoView()
733
+ .should('be.visible')
734
+ return this
735
+ }
736
+
737
+ /**
738
+ * Assert block picker is visible
739
+ */
740
+ assertBlockPickerVisible() {
741
+ cy.get(this.editorSelectors.blockPicker).should('be.visible')
742
+ return this
743
+ }
574
744
  }
575
745
 
576
746
  export default BlockEditorBasePOM
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { DashboardEntityPOMCore, type EntityConfig } from '@nextsparkjs/testing/pom'
25
- import { type Replacements } from '@nextsparkjs/testing/selectors'
25
+ import { type Replacements } from '@nextsparkjs/core/selectors'
26
26
  import { cySelector } from '../selectors'
27
27
 
28
28
  export abstract class DashboardEntityPOM extends DashboardEntityPOMCore {
@@ -34,71 +34,92 @@ export class DashboardPOM extends BasePOM {
34
34
 
35
35
  get selectors() {
36
36
  return {
37
+ // Main container
38
+ container: cySelector('dashboard.container'),
39
+
37
40
  // Navigation
38
- navMain: cySelector('dashboard.navigation.main'),
41
+ navContainer: cySelector('dashboard.navigation.container'),
39
42
  navDashboard: cySelector('dashboard.navigation.dashboardLink'),
40
43
  navEntity: (slug: string) => cySelector('dashboard.navigation.entityLink', { slug }),
41
44
  navSection: (id: string) => cySelector('dashboard.navigation.section', { id }),
42
45
  navSectionLabel: (id: string) => cySelector('dashboard.navigation.sectionLabel', { id }),
43
46
  navSectionItem: (sectionId: string, itemId: string) => cySelector('dashboard.navigation.sectionItem', { sectionId, itemId }),
44
47
 
45
- // Shell
46
- shellContainer: cySelector('dashboard.shell.container'),
47
- sidebarToggle: cySelector('dashboard.shell.sidebarToggle'),
48
- quickCreateButton: cySelector('dashboard.shell.quickCreateButton'),
49
- quickCreateDropdown: cySelector('dashboard.shell.quickCreateDropdown'),
50
- quickCreateLink: (slug: string) => cySelector('dashboard.shell.quickCreateLink', { slug }),
51
-
52
48
  // Topnav
49
+ topnavContainer: cySelector('dashboard.topnav.container'),
53
50
  topnavSidebarToggle: cySelector('dashboard.topnav.sidebarToggle'),
54
- topnavHeader: cySelector('dashboard.topnav.header'),
55
51
  topnavLogo: cySelector('dashboard.topnav.logo'),
56
- topnavSearchSection: cySelector('dashboard.topnav.searchSection'),
52
+ topnavSearchContainer: cySelector('dashboard.topnav.search.container'),
57
53
  topnavActions: cySelector('dashboard.topnav.actions'),
58
- topnavNotifications: cySelector('dashboard.topnav.notifications'),
54
+ topnavNotificationsTrigger: cySelector('dashboard.topnav.notifications.trigger'),
55
+ topnavQuickCreateTrigger: cySelector('dashboard.topnav.quickCreate.trigger'),
56
+ topnavQuickCreateContent: cySelector('dashboard.topnav.quickCreate.content'),
57
+ topnavQuickCreateLink: (slug: string) => cySelector('dashboard.topnav.quickCreate.link', { slug }),
58
+ topnavUserMenuTrigger: cySelector('dashboard.topnav.userMenu.trigger'),
59
+ topnavUserMenuContent: cySelector('dashboard.topnav.userMenu.content'),
60
+ topnavUserMenuItem: (icon: string) => cySelector('dashboard.topnav.userMenu.item', { icon }),
61
+ topnavUserMenuAction: (action: string) => cySelector('dashboard.topnav.userMenu.action', { action }),
59
62
  topnavHelp: cySelector('dashboard.topnav.help'),
60
63
  topnavThemeToggle: cySelector('dashboard.topnav.themeToggle'),
61
64
  topnavSuperadmin: cySelector('dashboard.topnav.superadmin'),
62
65
  topnavDevtools: cySelector('dashboard.topnav.devtools'),
63
- topnavUserMenuTrigger: cySelector('dashboard.topnav.userMenuTrigger'),
64
- topnavUserMenu: cySelector('dashboard.topnav.userMenu'),
65
- topnavMenuItem: (icon: string) => cySelector('dashboard.topnav.menuItem', { icon }),
66
- topnavMenuAction: (action: string) => cySelector('dashboard.topnav.menuAction', { action }),
67
66
  topnavUserLoading: cySelector('dashboard.topnav.userLoading'),
68
67
  topnavSignin: cySelector('dashboard.topnav.signin'),
69
68
  topnavSignup: cySelector('dashboard.topnav.signup'),
69
+ // Topnav mobile menu
70
+ topnavMobileMenuToggle: cySelector('dashboard.topnav.mobileMenu.toggle'),
71
+ topnavMobileMenuContainer: cySelector('dashboard.topnav.mobileMenu.container'),
72
+ topnavMobileMenuActions: cySelector('dashboard.topnav.mobileMenu.actions'),
73
+ topnavMobileMenuUserInfo: cySelector('dashboard.topnav.mobileMenu.userInfo'),
74
+ topnavMobileMenuLinkProfile: cySelector('dashboard.topnav.mobileMenu.linkProfile'),
75
+ topnavMobileMenuLinkSettings: cySelector('dashboard.topnav.mobileMenu.linkSettings'),
76
+ topnavMobileMenuLinkBilling: cySelector('dashboard.topnav.mobileMenu.linkBilling'),
77
+ topnavMobileMenuSignout: cySelector('dashboard.topnav.mobileMenu.signout'),
78
+ topnavMobileMenuSuperadmin: cySelector('dashboard.topnav.mobileMenu.superadmin'),
79
+ topnavMobileMenuDevtools: cySelector('dashboard.topnav.mobileMenu.devtools'),
70
80
 
71
81
  // Sidebar
72
- sidebarMain: cySelector('dashboard.sidebar.main'),
82
+ sidebarContainer: cySelector('dashboard.sidebar.container'),
73
83
  sidebarHeader: cySelector('dashboard.sidebar.header'),
84
+ sidebarLogo: cySelector('dashboard.sidebar.logo'),
74
85
  sidebarContent: cySelector('dashboard.sidebar.content'),
75
86
  sidebarFooter: cySelector('dashboard.sidebar.footer'),
76
87
 
77
88
  // Mobile Topbar
78
- mobileTopbarHeader: cySelector('dashboard.mobile.topbar.header'),
89
+ mobileTopbarContainer: cySelector('dashboard.mobile.topbar.container'),
79
90
  mobileTopbarUserProfile: cySelector('dashboard.mobile.topbar.userProfile'),
80
91
  mobileTopbarNotifications: cySelector('dashboard.mobile.topbar.notifications'),
81
92
  mobileTopbarThemeToggle: cySelector('dashboard.mobile.topbar.themeToggle'),
82
93
 
83
94
  // Mobile Bottom Nav
84
- mobileBottomNav: cySelector('dashboard.mobile.bottomNav.nav'),
95
+ mobileBottomNavContainer: cySelector('dashboard.mobile.bottomNav.container'),
85
96
  mobileBottomNavItem: (id: string) => cySelector('dashboard.mobile.bottomNav.item', { id }),
86
97
 
87
98
  // Mobile More Sheet
88
- mobileMoreSheetContent: cySelector('dashboard.mobile.moreSheet.content'),
99
+ mobileMoreSheetContainer: cySelector('dashboard.mobile.moreSheet.container'),
89
100
  mobileMoreSheetItem: (id: string) => cySelector('dashboard.mobile.moreSheet.item', { id }),
90
- mobileMoreSheetAdmin: cySelector('dashboard.mobile.moreSheet.adminLink'),
101
+ mobileMoreSheetSuperadminLink: cySelector('dashboard.mobile.moreSheet.superadminLink'),
91
102
  mobileMoreSheetTeamSwitcher: cySelector('dashboard.mobile.moreSheet.teamSwitcher'),
92
- mobileMoreSheetSignout: cySelector('dashboard.mobile.moreSheet.signoutButton'),
103
+ mobileMoreSheetSignoutButton: cySelector('dashboard.mobile.moreSheet.signoutButton'),
93
104
 
94
- // Mobile Quick Create
95
- mobileQuickCreateContent: cySelector('dashboard.mobile.quickCreateSheet.content'),
96
- mobileQuickCreateItem: (slug: string) => cySelector('dashboard.mobile.quickCreateSheet.item', { slug }),
105
+ // Mobile Quick Create Sheet
106
+ mobileQuickCreateSheetContainer: cySelector('dashboard.mobile.quickCreateSheet.container'),
107
+ mobileQuickCreateSheetItem: (slug: string) => cySelector('dashboard.mobile.quickCreateSheet.item', { slug }),
97
108
 
98
109
  // Entity table (for verifying entity pages)
99
110
  entityPage: (slug: string) => cySelector('entities.page.container', { slug }),
100
111
  entityTable: (slug: string) => cySelector('entities.table.container', { slug }),
101
112
  entityAddButton: (slug: string) => cySelector('entities.table.addButton', { slug }),
113
+
114
+ // Legacy aliases (for backward compatibility during transition)
115
+ /** @deprecated Use navContainer instead */
116
+ navMain: cySelector('dashboard.navigation.container'),
117
+ /** @deprecated Use topnavQuickCreateTrigger instead */
118
+ quickCreateButton: cySelector('dashboard.topnav.quickCreate.trigger'),
119
+ /** @deprecated Use topnavQuickCreateContent instead */
120
+ quickCreateDropdown: cySelector('dashboard.topnav.quickCreate.content'),
121
+ /** @deprecated Use topnavQuickCreateLink instead */
122
+ quickCreateLink: (slug: string) => cySelector('dashboard.topnav.quickCreate.link', { slug }),
102
123
  }
103
124
  }
104
125
 
@@ -146,7 +167,7 @@ export class DashboardPOM extends BasePOM {
146
167
  * Open quick create dropdown
147
168
  */
148
169
  openQuickCreate() {
149
- cy.get(this.selectors.quickCreateButton).click()
170
+ cy.get(this.selectors.topnavQuickCreateTrigger).click()
150
171
  return this
151
172
  }
152
173
 
@@ -155,7 +176,7 @@ export class DashboardPOM extends BasePOM {
155
176
  */
156
177
  quickCreate(slug: string) {
157
178
  this.openQuickCreate()
158
- cy.get(this.selectors.quickCreateLink(slug)).click()
179
+ cy.get(this.selectors.topnavQuickCreateLink(slug)).click()
159
180
  return this
160
181
  }
161
182
 
@@ -215,7 +236,7 @@ export class DashboardPOM extends BasePOM {
215
236
  * Assert quick create button is visible
216
237
  */
217
238
  assertQuickCreateVisible() {
218
- cy.get(this.selectors.quickCreateButton).should('be.visible')
239
+ cy.get(this.selectors.topnavQuickCreateTrigger).should('be.visible')
219
240
  return this
220
241
  }
221
242
 
@@ -228,7 +249,7 @@ export class DashboardPOM extends BasePOM {
228
249
  */
229
250
  waitForDashboard() {
230
251
  cy.url().should('include', '/dashboard')
231
- cy.get(this.selectors.navMain, { timeout: 15000 }).should('be.visible')
252
+ cy.get(this.selectors.navContainer, { timeout: 15000 }).should('be.visible')
232
253
  return this
233
254
  }
234
255