@nextsparkjs/theme-default 0.1.0-beta.51 → 0.1.0-beta.54

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-default",
3
- "version": "0.1.0-beta.51",
3
+ "version": "0.1.0-beta.54",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
@@ -17,8 +17,8 @@
17
17
  "react-dom": "^19.0.0",
18
18
  "react-markdown": "^10.1.0",
19
19
  "zod": "^4.0.0",
20
- "@nextsparkjs/core": "0.1.0-beta.51",
21
- "@nextsparkjs/testing": "0.1.0-beta.51"
20
+ "@nextsparkjs/core": "0.1.0-beta.54",
21
+ "@nextsparkjs/testing": "0.1.0-beta.54"
22
22
  },
23
23
  "nextspark": {
24
24
  "type": "theme",
@@ -2,8 +2,8 @@
2
2
  feature: Dashboard Topnav UI Selectors Validation
3
3
  priority: high
4
4
  tags: [selectors, topnav, dashboard, ui-validation]
5
- grepTags: [ui-selectors, dashboard, topnav, SEL_TNAV_001, SEL_TNAV_002, SEL_TNAV_003, SEL_TNAV_004, SEL_TNAV_005, SEL_TNAV_006]
6
- coverage: 6
5
+ grepTags: [ui-selectors, dashboard, topnav, SEL_TNAV_001, SEL_TNAV_002, SEL_TNAV_003, SEL_TNAV_004, SEL_TNAV_005, SEL_TNAV_006, SEL_TNAV_007]
6
+ coverage: 7
7
7
  ---
8
8
 
9
9
  # Dashboard Topnav UI Selectors Validation
@@ -263,6 +263,53 @@ Then deberia encontrar el indicador de estado de carga del usuario
263
263
 
264
264
  ---
265
265
 
266
+ ## @test SEL_TNAV_007: Settings Menu
267
+
268
+ ### Metadata
269
+ - **Priority:** Medium
270
+ - **Type:** Selector Validation
271
+ - **Tags:** topnav, settings-menu, dropdown
272
+ - **Grep:** `@ui-selectors` `@dashboard` `@topnav` `@SEL_TNAV_007`
273
+ - **Status:** Active (3 passing, 0 skipped)
274
+
275
+ ```gherkin:en
276
+ Scenario: Settings menu opens and shows links
277
+
278
+ Given I am logged in as a developer user
279
+ And the settings menu is enabled in theme config
280
+ And I navigate to the dashboard
281
+ Then I should find the settings menu trigger
282
+ When I click on the settings menu trigger
283
+ Then I should see the settings menu content
284
+ And I should find settings menu links (e.g., patterns)
285
+ ```
286
+
287
+ ```gherkin:es
288
+ Scenario: Menu de configuracion se abre y muestra links
289
+
290
+ Given estoy logueado como usuario developer
291
+ And el menu de configuracion esta habilitado en la config del tema
292
+ And navego al dashboard
293
+ Then deberia encontrar el trigger del menu de configuracion
294
+ When hago click en el trigger del menu de configuracion
295
+ Then deberia ver el contenido del menu de configuracion
296
+ And deberia encontrar links del menu de configuracion (ej. patrones)
297
+ ```
298
+
299
+ ### Expected Results
300
+ | Test ID | Selector Path | POM Accessor | data-cy Value | Status |
301
+ |---------|---------------|--------------|---------------|--------|
302
+ | SEL_TNAV_007_01 | dashboard.topnav.settingsMenu.trigger | dashboard.selectors.topnavSettingsMenuTrigger | topnav-settings-menu-trigger | Implemented |
303
+ | SEL_TNAV_007_02 | dashboard.topnav.settingsMenu.content | dashboard.selectors.topnavSettingsMenuContent | topnav-settings-menu | Implemented |
304
+ | SEL_TNAV_007_03 | dashboard.topnav.settingsMenu.link(index) | dashboard.selectors.topnavSettingsMenuLink(0) | topnav-settings-link-0 | Implemented |
305
+
306
+ ### Notes
307
+ - Settings menu must be enabled in theme config (TOPBAR_CONFIG.settingsMenu)
308
+ - Links are dynamically generated from config
309
+ - Common links include: Patterns
310
+
311
+ ---
312
+
266
313
  ## Related Components
267
314
 
268
315
  | Component | File | Selectors |
@@ -21,6 +21,7 @@
21
21
  * - SEL_TNAV_004: User Menu (5 selectors)
22
22
  * - SEL_TNAV_005: Quick Create (3 selectors)
23
23
  * - SEL_TNAV_006: Loading State (1 skipped - transient state)
24
+ * - SEL_TNAV_007: Settings Menu (3 selectors)
24
25
  *
25
26
  * NOTE: Public navbar tests (logo, signin, signup) are in public.cy.ts
26
27
  */
@@ -149,8 +150,28 @@ describe('Dashboard Topnav Selectors Validation', { tags: ['@ui-selectors', '@da
149
150
  // NOTE: Only visible during auth loading - transient state
150
151
  // ============================================
151
152
  describe('SEL_TNAV_006: Loading State', { tags: '@SEL_TNAV_006' }, () => {
152
- it.skip('should find user loading state (only visible during auth loading)', () => {
153
+ it.skip('SEL_TNAV_006_01: should find user loading state (only visible during auth loading)', { tags: '@SEL_TNAV_006_01' }, () => {
153
154
  cy.get(dashboard.selectors.topnavUserLoading).should('exist')
154
155
  })
155
156
  })
157
+
158
+ // ============================================
159
+ // SEL_TNAV_007: SETTINGS MENU (3 selectors)
160
+ // NOTE: settingsMenu must be enabled in theme config
161
+ // ============================================
162
+ describe('SEL_TNAV_007: Settings Menu', { tags: '@SEL_TNAV_007' }, () => {
163
+ it('SEL_TNAV_007_01: should find settings menu trigger', { tags: '@SEL_TNAV_007_01' }, () => {
164
+ cy.get(dashboard.selectors.topnavSettingsMenuTrigger).should('exist')
165
+ })
166
+
167
+ it('SEL_TNAV_007_02: should find settings menu content when opened', { tags: '@SEL_TNAV_007_02' }, () => {
168
+ cy.get(dashboard.selectors.topnavSettingsMenuTrigger).click()
169
+ cy.get(dashboard.selectors.topnavSettingsMenuContent).should('be.visible')
170
+ })
171
+
172
+ it('SEL_TNAV_007_03: should find settings menu link when opened', { tags: '@SEL_TNAV_007_03' }, () => {
173
+ cy.get(dashboard.selectors.topnavSettingsMenuTrigger).click()
174
+ cy.get(dashboard.selectors.topnavSettingsMenuLink(0)).should('exist')
175
+ })
176
+ })
156
177
  })
@@ -0,0 +1,388 @@
1
+ ---
2
+ feature: Patterns UI Selectors Validation
3
+ priority: high
4
+ tags: [selectors, patterns, ui-validation]
5
+ grepTags: [ui-selectors, patterns, feat-patterns, SEL_PAT_001, SEL_PAT_002, SEL_PAT_003, SEL_PAT_004, SEL_PAT_005, SEL_PAT_006, SEL_PAT_007, SEL_PAT_008]
6
+ coverage: 8
7
+ ---
8
+
9
+ # Patterns UI Selectors Validation
10
+
11
+ > Validates that patterns-specific selectors exist in the DOM. This is a lightweight test that ONLY checks selector presence, not functionality. Validates KEY DIFFERENCES from standard entity editor: no slug input and no patterns tab in block picker.
12
+
13
+ **Login:** Uses Developer via `loginAsDefaultDeveloper()`.
14
+
15
+ **Dependencies:**
16
+ - Sample patterns must exist (from seed data)
17
+ - At least one pattern must have usages (for reports tests)
18
+
19
+ ## @test SEL_PAT_001: Patterns List Page Selectors
20
+
21
+ ### Metadata
22
+ - **Priority:** High
23
+ - **Type:** Selector Validation
24
+ - **Tags:** patterns, list, page
25
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_001`
26
+ - **Status:** Active (5 passing, 0 skipped)
27
+
28
+ ```gherkin:en
29
+ Scenario: Patterns list page has complete selector coverage
30
+
31
+ Given I am logged in as a developer user
32
+ And I navigate to the patterns list page
33
+ Then I should find the page container
34
+ And I should find the page title
35
+ And I should find the add button
36
+ And I should find the search container
37
+ And I should find the table container
38
+ ```
39
+
40
+ ```gherkin:es
41
+ Scenario: Pagina de lista de patrones tiene cobertura completa de selectores
42
+
43
+ Given estoy logueado como usuario developer
44
+ And navego a la pagina de lista de patrones
45
+ Then deberia encontrar el contenedor de pagina
46
+ And deberia encontrar el titulo de pagina
47
+ And deberia encontrar el boton de agregar
48
+ And deberia encontrar el contenedor de busqueda
49
+ And deberia encontrar el contenedor de tabla
50
+ ```
51
+
52
+ ### Expected Results
53
+ | Test ID | Selector Path | Status |
54
+ |---------|---------------|--------|
55
+ | SEL_PAT_001_01 | patterns.selectors.page | Implemented |
56
+ | SEL_PAT_001_02 | patterns.selectors.title | Implemented |
57
+ | SEL_PAT_001_03 | patterns.selectors.addBtn | Implemented |
58
+ | SEL_PAT_001_04 | patterns.selectors.searchContainer | Implemented |
59
+ | SEL_PAT_001_05 | patterns.selectors.table | Implemented |
60
+
61
+ ---
62
+
63
+ ## @test SEL_PAT_002: Patterns List Table Actions
64
+
65
+ ### Metadata
66
+ - **Priority:** High
67
+ - **Type:** Selector Validation
68
+ - **Tags:** patterns, list, actions, table
69
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_002`
70
+ - **Status:** Active (4 passing, 0 skipped)
71
+
72
+ ```gherkin:en
73
+ Scenario: Patterns list table has all row action selectors
74
+
75
+ Given I am logged in as a developer user
76
+ And I navigate to the patterns list page
77
+ And at least one pattern exists in the database
78
+ Then I should find the row menu for the first pattern
79
+ When I click the row menu
80
+ Then I should find the edit action
81
+ And I should find the delete action
82
+ And I should find the usages quick action
83
+ ```
84
+
85
+ ```gherkin:es
86
+ Scenario: Tabla de lista de patrones tiene todos los selectores de acciones de fila
87
+
88
+ Given estoy logueado como usuario developer
89
+ And navego a la pagina de lista de patrones
90
+ And al menos un patron existe en la base de datos
91
+ Then deberia encontrar el menu de fila del primer patron
92
+ When hago click en el menu de fila
93
+ Then deberia encontrar la accion de editar
94
+ And deberia encontrar la accion de eliminar
95
+ And deberia encontrar la accion rapida de usos
96
+ ```
97
+
98
+ ### Expected Results
99
+ | Test ID | Selector Path | Status |
100
+ |---------|---------------|--------|
101
+ | SEL_PAT_002_01 | patterns.selectors.rowMenu(id) | Implemented |
102
+ | SEL_PAT_002_02 | patterns.selectors.rowAction('edit', id) | Implemented |
103
+ | SEL_PAT_002_03 | patterns.selectors.rowAction('delete', id) | Implemented |
104
+ | SEL_PAT_002_04 | patterns.selectors.rowAction('usages', id) | Implemented |
105
+
106
+ ---
107
+
108
+ ## @test SEL_PAT_003: Patterns Editor Header - KEY DIFFERENCES
109
+
110
+ ### Metadata
111
+ - **Priority:** High
112
+ - **Type:** Selector Validation
113
+ - **Tags:** patterns, editor, header, differences
114
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_003`
115
+ - **Status:** Active (5 passing, 0 skipped)
116
+
117
+ ```gherkin:en
118
+ Scenario: Patterns editor header validates key differences from standard editor
119
+
120
+ Given I am logged in as a developer user
121
+ And I navigate to create a new pattern
122
+ And the editor is loaded
123
+ Then I should find the header container
124
+ And I should find the title input
125
+ And I should NOT find the slug input (patterns-specific)
126
+ And I should find the save button
127
+ And I should find the view mode toggle
128
+ ```
129
+
130
+ ```gherkin:es
131
+ Scenario: Header del editor de patrones valida diferencias clave del editor estandar
132
+
133
+ Given estoy logueado como usuario developer
134
+ And navego a crear un nuevo patron
135
+ And el editor esta cargado
136
+ Then deberia encontrar el contenedor del header
137
+ And deberia encontrar el input de titulo
138
+ And NO deberia encontrar el input de slug (especifico de patrones)
139
+ And deberia encontrar el boton de guardar
140
+ And deberia encontrar el toggle de modo de vista
141
+ ```
142
+
143
+ ### Expected Results
144
+ | Test ID | Selector Path | Assertion | Status |
145
+ |---------|---------------|-----------|--------|
146
+ | SEL_PAT_003_01 | blockEditor.header.container | should exist | Implemented |
147
+ | SEL_PAT_003_02 | blockEditor.header.titleInput | should exist | Implemented |
148
+ | SEL_PAT_003_03 | blockEditor.header.slugInput | should NOT exist | **KEY DIFFERENCE** |
149
+ | SEL_PAT_003_04 | blockEditor.header.saveButton | should exist | Implemented |
150
+ | SEL_PAT_003_05 | blockEditor.header.viewToggle | should exist | Implemented |
151
+
152
+ ### Notes
153
+ - **KEY DIFFERENCE:** Patterns do not have slugs, so the slug input should NOT exist.
154
+
155
+ ---
156
+
157
+ ## @test SEL_PAT_004: Patterns Editor Block Picker - KEY DIFFERENCES
158
+
159
+ ### Metadata
160
+ - **Priority:** High
161
+ - **Type:** Selector Validation
162
+ - **Tags:** patterns, editor, block-picker, differences
163
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_004`
164
+ - **Status:** Active (5 passing, 0 skipped)
165
+
166
+ ```gherkin:en
167
+ Scenario: Patterns block picker validates key differences from standard editor
168
+
169
+ Given I am logged in as a developer user
170
+ And I navigate to create a new pattern
171
+ And the editor is loaded
172
+ Then I should find the block picker container
173
+ And I should find the blocks tab
174
+ And I should NOT find the patterns tab (patterns-specific)
175
+ And I should find the config tab
176
+ And I should find the search input
177
+ ```
178
+
179
+ ```gherkin:es
180
+ Scenario: Block picker de patrones valida diferencias clave del editor estandar
181
+
182
+ Given estoy logueado como usuario developer
183
+ And navego a crear un nuevo patron
184
+ And el editor esta cargado
185
+ Then deberia encontrar el contenedor del block picker
186
+ And deberia encontrar la pestana de bloques
187
+ And NO deberia encontrar la pestana de patrones (especifico de patrones)
188
+ And deberia encontrar la pestana de configuracion
189
+ And deberia encontrar el input de busqueda
190
+ ```
191
+
192
+ ### Expected Results
193
+ | Test ID | Selector Path | Assertion | Status |
194
+ |---------|---------------|-----------|--------|
195
+ | SEL_PAT_004_01 | blockEditor.blockPicker.container | should exist | Implemented |
196
+ | SEL_PAT_004_02 | blockEditor.blockPicker.tabBlocks | should exist | Implemented |
197
+ | SEL_PAT_004_03 | blockEditor.blockPicker.tabPatterns | should NOT exist | **KEY DIFFERENCE** |
198
+ | SEL_PAT_004_04 | blockEditor.blockPicker.tabConfig | should exist | Implemented |
199
+ | SEL_PAT_004_05 | blockEditor.blockPicker.searchInput | should exist | Implemented |
200
+
201
+ ### Notes
202
+ - **KEY DIFFERENCE:** Patterns cannot contain other patterns (to prevent recursion), so the patterns tab should NOT exist.
203
+
204
+ ---
205
+
206
+ ## @test SEL_PAT_005: Patterns Reports Page
207
+
208
+ ### Metadata
209
+ - **Priority:** Medium
210
+ - **Type:** Selector Validation
211
+ - **Tags:** patterns, reports, usages
212
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_005`
213
+ - **Status:** Active (4 passing, 0 skipped)
214
+
215
+ ```gherkin:en
216
+ Scenario: Pattern usages report page has complete structure
217
+
218
+ Given I am logged in as a developer user
219
+ And I navigate to the patterns list page
220
+ And I click the usages action on the first pattern
221
+ Then I should find the report container
222
+ And I should find the back button
223
+ And I should find the page title
224
+ And I should find the edit button
225
+ ```
226
+
227
+ ```gherkin:es
228
+ Scenario: Pagina de reporte de usos de patrones tiene estructura completa
229
+
230
+ Given estoy logueado como usuario developer
231
+ And navego a la pagina de lista de patrones
232
+ And hago click en la accion de usos del primer patron
233
+ Then deberia encontrar el contenedor del reporte
234
+ And deberia encontrar el boton de volver
235
+ And deberia encontrar el titulo de pagina
236
+ And deberia encontrar el boton de editar
237
+ ```
238
+
239
+ ### Expected Results
240
+ | Test ID | Selector Path | Status |
241
+ |---------|---------------|--------|
242
+ | SEL_PAT_005_01 | patterns.patternSelectors.usageReport.container | Implemented |
243
+ | SEL_PAT_005_02 | patterns.selectors.backButton | Implemented |
244
+ | SEL_PAT_005_03 | patterns.selectors.headerTitle | Implemented |
245
+ | SEL_PAT_005_04 | patterns.selectors.editButton | Implemented |
246
+
247
+ ---
248
+
249
+ ## @test SEL_PAT_006: Pattern Usage Stats
250
+
251
+ ### Metadata
252
+ - **Priority:** Medium
253
+ - **Type:** Selector Validation
254
+ - **Tags:** patterns, reports, stats
255
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_006`
256
+ - **Status:** Active (3 passing, 0 skipped)
257
+
258
+ ```gherkin:en
259
+ Scenario: Pattern usage stats have all required selectors
260
+
261
+ Given I am logged in as a developer user
262
+ And I navigate to a pattern's usages page
263
+ Then I should find the stats container
264
+ And I should find the total usage card
265
+ And I should find usage by type cards (if usages exist)
266
+ ```
267
+
268
+ ```gherkin:es
269
+ Scenario: Estadisticas de uso de patron tienen todos los selectores requeridos
270
+
271
+ Given estoy logueado como usuario developer
272
+ And navego a la pagina de usos de un patron
273
+ Then deberia encontrar el contenedor de estadisticas
274
+ And deberia encontrar la tarjeta de uso total
275
+ And deberia encontrar las tarjetas de uso por tipo (si existen usos)
276
+ ```
277
+
278
+ ### Expected Results
279
+ | Test ID | Selector Path | Status |
280
+ |---------|---------------|--------|
281
+ | SEL_PAT_006_01 | patterns.patternSelectors.usageStats.container | Implemented |
282
+ | SEL_PAT_006_02 | patterns.patternSelectors.usageStats.total | Implemented |
283
+ | SEL_PAT_006_03 | patterns.patternSelectors.usageStats.byType(type) | Conditional |
284
+
285
+ ---
286
+
287
+ ## @test SEL_PAT_007: Pattern Usage Report Controls
288
+
289
+ ### Metadata
290
+ - **Priority:** Medium
291
+ - **Type:** Selector Validation
292
+ - **Tags:** patterns, reports, controls, pagination
293
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_007`
294
+ - **Status:** Active (6 passing, 0 skipped)
295
+
296
+ ```gherkin:en
297
+ Scenario: Pattern usage report controls have all required selectors
298
+
299
+ Given I am logged in as a developer user
300
+ And I navigate to a pattern's usages page
301
+ Then I should find the report container
302
+ And I should find the filter select
303
+ And I should find the pagination container
304
+ And I should find the prev page button
305
+ And I should find the next page button
306
+ And I should find the results info
307
+ ```
308
+
309
+ ```gherkin:es
310
+ Scenario: Controles de reporte de uso de patron tienen todos los selectores requeridos
311
+
312
+ Given estoy logueado como usuario developer
313
+ And navego a la pagina de usos de un patron
314
+ Then deberia encontrar el contenedor del reporte
315
+ And deberia encontrar el select de filtro
316
+ And deberia encontrar el contenedor de paginacion
317
+ And deberia encontrar el boton de pagina anterior
318
+ And deberia encontrar el boton de pagina siguiente
319
+ And deberia encontrar la info de resultados
320
+ ```
321
+
322
+ ### Expected Results
323
+ | Test ID | Selector Path | Status |
324
+ |---------|---------------|--------|
325
+ | SEL_PAT_007_01 | patterns.patternSelectors.usageReport.container | Implemented |
326
+ | SEL_PAT_007_02 | patterns.patternSelectors.usageReport.filterSelect | Implemented |
327
+ | SEL_PAT_007_03 | patterns.patternSelectors.usageReport.pagination | Implemented |
328
+ | SEL_PAT_007_04 | patterns.patternSelectors.usageReport.prevPage | Implemented |
329
+ | SEL_PAT_007_05 | patterns.patternSelectors.usageReport.nextPage | Implemented |
330
+ | SEL_PAT_007_06 | patterns.patternSelectors.usageReport.resultsInfo | Implemented |
331
+
332
+ ---
333
+
334
+ ## @test SEL_PAT_008: Pattern Usage Table
335
+
336
+ ### Metadata
337
+ - **Priority:** Medium
338
+ - **Type:** Selector Validation
339
+ - **Tags:** patterns, reports, table
340
+ - **Grep:** `@ui-selectors` `@patterns` `@feat-patterns` `@SEL_PAT_008`
341
+ - **Status:** Partial (1 passing, 2 skipped)
342
+
343
+ ```gherkin:en
344
+ Scenario: Pattern usage table has all required selectors
345
+
346
+ Given I am logged in as a developer user
347
+ And I navigate to a pattern's usages page
348
+ Then I should find the usage table container (or empty state)
349
+ And I should find usage rows (if pattern has usages)
350
+ And I should find view links in usage rows (if pattern has usages)
351
+ ```
352
+
353
+ ```gherkin:es
354
+ Scenario: Tabla de usos de patron tiene todos los selectores requeridos
355
+
356
+ Given estoy logueado como usuario developer
357
+ And navego a la pagina de usos de un patron
358
+ Then deberia encontrar el contenedor de tabla de usos (o estado vacio)
359
+ And deberia encontrar filas de uso (si el patron tiene usos)
360
+ And deberia encontrar links de ver en filas de uso (si el patron tiene usos)
361
+ ```
362
+
363
+ ### Expected Results
364
+ | Test ID | Selector Path | Status |
365
+ |---------|---------------|--------|
366
+ | SEL_PAT_008_01 | patterns.patternSelectors.usageTable.container/empty | Implemented |
367
+ | SEL_PAT_008_02 | [data-cy^="pattern-usage-row-"] | **Skipped** |
368
+ | SEL_PAT_008_03 | [data-cy^="pattern-usage-view-"] | **Skipped** |
369
+
370
+ ### Notes
371
+ - Tests SEL_PAT_008_02 and SEL_PAT_008_03 are skipped because they require the pattern to have actual usages.
372
+
373
+ ---
374
+
375
+ ## Related Components
376
+
377
+ | Component | File | Key Selectors |
378
+ |-----------|------|---------------|
379
+ | PatternsListPage | `packages/core/src/app/[locale]/(auth)/dashboard/patterns/page.tsx` | page, title, addBtn, table |
380
+ | PatternEditor | `packages/core/src/components/page-builder/PageBuilder.tsx` | header.*, blockPicker.* |
381
+ | PatternUsagesPage | `packages/core/src/app/[locale]/(auth)/dashboard/patterns/[id]/usages/page.tsx` | usageReport.*, usageStats.* |
382
+
383
+ ## Related POMs
384
+
385
+ | POM | File | Usage |
386
+ |-----|------|-------|
387
+ | PatternsPOM | `themes/default/tests/cypress/src/entities/PatternsPOM.ts` | List operations, entity selectors |
388
+ | PageBuilderPOM | `themes/default/tests/cypress/src/features/PageBuilderPOM.ts` | Editor selectors |
@@ -0,0 +1,392 @@
1
+ /**
2
+ * UI Selectors Validation: Patterns
3
+ *
4
+ * This test validates that patterns-specific selectors exist in the DOM.
5
+ * This is a lightweight test that ONLY checks selector presence, not functionality.
6
+ *
7
+ * Purpose:
8
+ * - Validate PatternsPOM and PageBuilderPOM selectors work correctly
9
+ * - Ensure all patterns-specific selectors are implemented
10
+ * - Validate KEY DIFFERENCES from standard entity editor:
11
+ * - NO slug input (patterns don't have slugs)
12
+ * - NO patterns tab in block picker (to prevent recursive patterns)
13
+ * - Run as Phase 12 sub-gate before functional tests
14
+ *
15
+ * Scope:
16
+ * - Navigate to patterns pages (requires login)
17
+ * - Assert elements exist in DOM (no form submissions)
18
+ * - Fast execution (< 30 seconds per describe block)
19
+ *
20
+ * Test IDs:
21
+ * - SEL_PAT_001: Patterns List Page Selectors (5 selectors)
22
+ * - SEL_PAT_002: Patterns List Table Actions (4 selectors)
23
+ * - SEL_PAT_003: Patterns Editor Header - KEY DIFFERENCES (5 selectors)
24
+ * - SEL_PAT_004: Patterns Editor Block Picker - KEY DIFFERENCES (5 selectors)
25
+ * - SEL_PAT_005: Patterns Reports Page (4 selectors)
26
+ * - SEL_PAT_006: Pattern Usage Stats (3 selectors)
27
+ * - SEL_PAT_007: Pattern Usage Report Controls (5 selectors)
28
+ * - SEL_PAT_008: Pattern Usage Table (3 selectors)
29
+ *
30
+ * DEPENDENCIES:
31
+ * - Sample patterns must exist (from seed data)
32
+ * - At least one pattern must have usages (for reports tests)
33
+ */
34
+
35
+ import { PatternsPOM } from '../../../src/entities/PatternsPOM'
36
+ import { cySelector } from '../../../src/selectors'
37
+ import { loginAsDefaultDeveloper } from '../../../src/session-helpers'
38
+
39
+ describe('Patterns Selectors Validation', { tags: ['@ui-selectors', '@patterns', '@feat-patterns'] }, () => {
40
+ const patterns = PatternsPOM.create()
41
+
42
+ // ============================================
43
+ // SEL_PAT_001: PATTERNS LIST PAGE SELECTORS (5 selectors)
44
+ // ============================================
45
+ describe('SEL_PAT_001: Patterns List Page Selectors', { tags: '@SEL_PAT_001' }, () => {
46
+ beforeEach(() => {
47
+ loginAsDefaultDeveloper()
48
+ patterns.visitList()
49
+ patterns.waitForList()
50
+ })
51
+
52
+ it('SEL_PAT_001_01: should find page container', { tags: '@SEL_PAT_001_01' }, () => {
53
+ cy.get(patterns.selectors.page).should('exist')
54
+ })
55
+
56
+ it('SEL_PAT_001_02: should find page title', { tags: '@SEL_PAT_001_02' }, () => {
57
+ cy.get(patterns.selectors.title).should('exist')
58
+ })
59
+
60
+ it('SEL_PAT_001_03: should find add button', { tags: '@SEL_PAT_001_03' }, () => {
61
+ cy.get(patterns.selectors.addButton).should('exist')
62
+ })
63
+
64
+ it('SEL_PAT_001_04: should find search container', { tags: '@SEL_PAT_001_04' }, () => {
65
+ cy.get(patterns.selectors.searchContainer).should('exist')
66
+ })
67
+
68
+ it('SEL_PAT_001_05: should find table container', { tags: '@SEL_PAT_001_05' }, () => {
69
+ cy.get(patterns.selectors.table).should('exist')
70
+ })
71
+ })
72
+
73
+ // ============================================
74
+ // SEL_PAT_002: PATTERNS LIST TABLE ACTIONS (4 selectors)
75
+ // REQUIRES: At least one pattern in the database
76
+ // ============================================
77
+ describe('SEL_PAT_002: Patterns List Table Actions', { tags: '@SEL_PAT_002' }, () => {
78
+ beforeEach(() => {
79
+ loginAsDefaultDeveloper()
80
+ patterns.visitList()
81
+ patterns.waitForList()
82
+ })
83
+
84
+ it('SEL_PAT_002_01: should find row menu for first pattern', { tags: '@SEL_PAT_002_01' }, () => {
85
+ // Get first pattern row and find its menu
86
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
87
+ const dataCy = $row.attr('data-cy')
88
+ const patternId = dataCy?.replace('patterns-row-', '')
89
+ if (patternId) {
90
+ cy.get(patterns.selectors.rowMenu(patternId)).should('exist')
91
+ }
92
+ })
93
+ })
94
+
95
+ it('SEL_PAT_002_02: should find edit action in row menu', { tags: '@SEL_PAT_002_02' }, () => {
96
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
97
+ const dataCy = $row.attr('data-cy')
98
+ const patternId = dataCy?.replace('patterns-row-', '')
99
+ if (patternId) {
100
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
101
+ cy.get(patterns.selectors.rowAction('edit', patternId)).should('exist')
102
+ }
103
+ })
104
+ })
105
+
106
+ it('SEL_PAT_002_03: should find delete action in row menu', { tags: '@SEL_PAT_002_03' }, () => {
107
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
108
+ const dataCy = $row.attr('data-cy')
109
+ const patternId = dataCy?.replace('patterns-row-', '')
110
+ if (patternId) {
111
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
112
+ cy.get(patterns.selectors.rowAction('delete', patternId)).should('exist')
113
+ }
114
+ })
115
+ })
116
+
117
+ it('SEL_PAT_002_04: should find usages quick action in row menu', { tags: '@SEL_PAT_002_04' }, () => {
118
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
119
+ const dataCy = $row.attr('data-cy')
120
+ const patternId = dataCy?.replace('patterns-row-', '')
121
+ if (patternId) {
122
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
123
+ cy.get(patterns.selectors.rowAction('usages', patternId)).should('exist')
124
+ }
125
+ })
126
+ })
127
+ })
128
+
129
+ // ============================================
130
+ // SEL_PAT_003: PATTERNS EDITOR HEADER - KEY DIFFERENCES (5 selectors)
131
+ // NOTE: Patterns editor does NOT have slug input (unlike pages/posts)
132
+ // ============================================
133
+ describe('SEL_PAT_003: Patterns Editor Header', { tags: '@SEL_PAT_003' }, () => {
134
+ beforeEach(() => {
135
+ loginAsDefaultDeveloper()
136
+ // Navigate to create a new pattern
137
+ cy.visit('/dashboard/patterns/create', { timeout: 60000 })
138
+ // Wait for editor to load using block editor selector
139
+ cy.get(cySelector('blockEditor.header.container'), { timeout: 15000 }).should('be.visible')
140
+ })
141
+
142
+ it('SEL_PAT_003_01: should find header container', { tags: '@SEL_PAT_003_01' }, () => {
143
+ cy.get(cySelector('blockEditor.header.container')).should('exist')
144
+ })
145
+
146
+ it('SEL_PAT_003_02: should find title input', { tags: '@SEL_PAT_003_02' }, () => {
147
+ cy.get(cySelector('blockEditor.header.titleInput')).should('exist')
148
+ })
149
+
150
+ it('SEL_PAT_003_03: should NOT have slug input (patterns-specific)', { tags: '@SEL_PAT_003_03' }, () => {
151
+ // KEY DIFFERENCE: Patterns do not have slugs
152
+ cy.get(cySelector('blockEditor.header.slugInput')).should('not.exist')
153
+ })
154
+
155
+ it('SEL_PAT_003_04: should find save button', { tags: '@SEL_PAT_003_04' }, () => {
156
+ cy.get(cySelector('blockEditor.header.saveButton')).should('exist')
157
+ })
158
+
159
+ it('SEL_PAT_003_05: should find view mode toggle', { tags: '@SEL_PAT_003_05' }, () => {
160
+ cy.get(cySelector('blockEditor.header.viewToggle')).should('exist')
161
+ })
162
+ })
163
+
164
+ // ============================================
165
+ // SEL_PAT_004: PATTERNS EDITOR BLOCK PICKER - KEY DIFFERENCES (5 selectors)
166
+ // NOTE: Patterns block picker does NOT have patterns tab (to prevent recursion)
167
+ // ============================================
168
+ describe('SEL_PAT_004: Patterns Editor Block Picker', { tags: '@SEL_PAT_004' }, () => {
169
+ beforeEach(() => {
170
+ loginAsDefaultDeveloper()
171
+ cy.visit('/dashboard/patterns/create', { timeout: 60000 })
172
+ cy.get(cySelector('blockEditor.header.container'), { timeout: 15000 }).should('be.visible')
173
+ })
174
+
175
+ it('SEL_PAT_004_01: should find block picker container', { tags: '@SEL_PAT_004_01' }, () => {
176
+ cy.get(cySelector('blockEditor.blockPicker.container')).should('exist')
177
+ })
178
+
179
+ it('SEL_PAT_004_02: should find blocks tab', { tags: '@SEL_PAT_004_02' }, () => {
180
+ cy.get(cySelector('blockEditor.blockPicker.tabBlocks')).should('exist')
181
+ })
182
+
183
+ it('SEL_PAT_004_03: should NOT have patterns tab (patterns-specific)', { tags: '@SEL_PAT_004_03' }, () => {
184
+ // KEY DIFFERENCE: Patterns cannot contain other patterns (prevent recursion)
185
+ cy.get(cySelector('blockEditor.blockPicker.tabPatterns')).should('not.exist')
186
+ })
187
+
188
+ it('SEL_PAT_004_04: should find config tab', { tags: '@SEL_PAT_004_04' }, () => {
189
+ cy.get(cySelector('blockEditor.blockPicker.tabConfig')).should('exist')
190
+ })
191
+
192
+ it('SEL_PAT_004_05: should find search input', { tags: '@SEL_PAT_004_05' }, () => {
193
+ cy.get(cySelector('blockEditor.blockPicker.searchInput')).should('exist')
194
+ })
195
+ })
196
+
197
+ // ============================================
198
+ // SEL_PAT_005: PATTERNS REPORTS PAGE (4 selectors)
199
+ // REQUIRES: A pattern with usages for meaningful tests
200
+ // ============================================
201
+ describe('SEL_PAT_005: Patterns Reports Page', { tags: '@SEL_PAT_005' }, () => {
202
+ beforeEach(() => {
203
+ loginAsDefaultDeveloper()
204
+ patterns.visitList()
205
+ patterns.waitForList()
206
+ })
207
+
208
+ it('SEL_PAT_005_01: should navigate to pattern usages and find report container', { tags: '@SEL_PAT_005_01' }, () => {
209
+ // Click usages action on first pattern
210
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
211
+ const dataCy = $row.attr('data-cy')
212
+ const patternId = dataCy?.replace('patterns-row-', '')
213
+ if (patternId) {
214
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
215
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
216
+ // Wait for page to load and find report container
217
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
218
+ }
219
+ })
220
+ })
221
+
222
+ it('SEL_PAT_005_02: should find back button on usages page', { tags: '@SEL_PAT_005_02' }, () => {
223
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
224
+ const dataCy = $row.attr('data-cy')
225
+ const patternId = dataCy?.replace('patterns-row-', '')
226
+ if (patternId) {
227
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
228
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
229
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
230
+ // Back button should exist on the page
231
+ cy.get(patterns.selectors.backButton).should('exist')
232
+ }
233
+ })
234
+ })
235
+
236
+ it('SEL_PAT_005_03: should find page title on usages page', { tags: '@SEL_PAT_005_03' }, () => {
237
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
238
+ const dataCy = $row.attr('data-cy')
239
+ const patternId = dataCy?.replace('patterns-row-', '')
240
+ if (patternId) {
241
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
242
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
243
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
244
+ cy.get(patterns.selectors.headerTitle).should('exist')
245
+ }
246
+ })
247
+ })
248
+
249
+ it('SEL_PAT_005_04: should find edit button on usages page', { tags: '@SEL_PAT_005_04' }, () => {
250
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
251
+ const dataCy = $row.attr('data-cy')
252
+ const patternId = dataCy?.replace('patterns-row-', '')
253
+ if (patternId) {
254
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
255
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
256
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
257
+ cy.get(patterns.selectors.editButton).should('exist')
258
+ }
259
+ })
260
+ })
261
+ })
262
+
263
+ // ============================================
264
+ // SEL_PAT_006: PATTERN USAGE STATS (3 selectors)
265
+ // ============================================
266
+ describe('SEL_PAT_006: Pattern Usage Stats', { tags: '@SEL_PAT_006' }, () => {
267
+ beforeEach(() => {
268
+ loginAsDefaultDeveloper()
269
+ patterns.visitList()
270
+ patterns.waitForList()
271
+ // Navigate to first pattern's usages
272
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
273
+ const dataCy = $row.attr('data-cy')
274
+ const patternId = dataCy?.replace('patterns-row-', '')
275
+ if (patternId) {
276
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
277
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
278
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
279
+ }
280
+ })
281
+ })
282
+
283
+ it('SEL_PAT_006_01: should find stats container', { tags: '@SEL_PAT_006_01' }, () => {
284
+ cy.get(patterns.patternSelectors.usageStats.container).should('exist')
285
+ })
286
+
287
+ it('SEL_PAT_006_02: should find total usage card', { tags: '@SEL_PAT_006_02' }, () => {
288
+ cy.get(patterns.patternSelectors.usageStats.total).should('exist')
289
+ })
290
+
291
+ it('SEL_PAT_006_03: should find usage by type cards (if usages exist)', { tags: '@SEL_PAT_006_03' }, () => {
292
+ // This might not exist if pattern has no usages, so check for either stats or loading
293
+ cy.get('body').then(($body) => {
294
+ if ($body.find(patterns.patternSelectors.usageStats.byType('pages')).length > 0) {
295
+ cy.get(patterns.patternSelectors.usageStats.byType('pages')).should('exist')
296
+ } else if ($body.find(patterns.patternSelectors.usageStats.byType('posts')).length > 0) {
297
+ cy.get(patterns.patternSelectors.usageStats.byType('posts')).should('exist')
298
+ } else {
299
+ // No usage by type cards, which is fine if pattern has no usages
300
+ cy.log('No usage by type cards found - pattern may have no usages')
301
+ }
302
+ })
303
+ })
304
+ })
305
+
306
+ // ============================================
307
+ // SEL_PAT_007: PATTERN USAGE REPORT CONTROLS (5 selectors)
308
+ // ============================================
309
+ describe('SEL_PAT_007: Pattern Usage Report Controls', { tags: '@SEL_PAT_007' }, () => {
310
+ beforeEach(() => {
311
+ loginAsDefaultDeveloper()
312
+ patterns.visitList()
313
+ patterns.waitForList()
314
+ // Navigate to first pattern's usages
315
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
316
+ const dataCy = $row.attr('data-cy')
317
+ const patternId = dataCy?.replace('patterns-row-', '')
318
+ if (patternId) {
319
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
320
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
321
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
322
+ }
323
+ })
324
+ })
325
+
326
+ it('SEL_PAT_007_01: should find report container', { tags: '@SEL_PAT_007_01' }, () => {
327
+ cy.get(patterns.patternSelectors.usageReport.container).should('exist')
328
+ })
329
+
330
+ it('SEL_PAT_007_02: should find filter select', { tags: '@SEL_PAT_007_02' }, () => {
331
+ cy.get(patterns.patternSelectors.usageReport.filterSelect).should('exist')
332
+ })
333
+
334
+ it('SEL_PAT_007_03: should find pagination container', { tags: '@SEL_PAT_007_03' }, () => {
335
+ cy.get(patterns.patternSelectors.usageReport.pagination).should('exist')
336
+ })
337
+
338
+ it('SEL_PAT_007_04: should find prev page button', { tags: '@SEL_PAT_007_04' }, () => {
339
+ cy.get(patterns.patternSelectors.usageReport.prevPage).should('exist')
340
+ })
341
+
342
+ it('SEL_PAT_007_05: should find next page button', { tags: '@SEL_PAT_007_05' }, () => {
343
+ cy.get(patterns.patternSelectors.usageReport.nextPage).should('exist')
344
+ })
345
+
346
+ it('SEL_PAT_007_06: should find results info', { tags: '@SEL_PAT_007_06' }, () => {
347
+ cy.get(patterns.patternSelectors.usageReport.resultsInfo).should('exist')
348
+ })
349
+ })
350
+
351
+ // ============================================
352
+ // SEL_PAT_008: PATTERN USAGE TABLE (3 selectors)
353
+ // ============================================
354
+ describe('SEL_PAT_008: Pattern Usage Table', { tags: '@SEL_PAT_008' }, () => {
355
+ beforeEach(() => {
356
+ loginAsDefaultDeveloper()
357
+ patterns.visitList()
358
+ patterns.waitForList()
359
+ // Navigate to first pattern's usages
360
+ cy.get('[data-cy^="patterns-row-"]').first().then(($row) => {
361
+ const dataCy = $row.attr('data-cy')
362
+ const patternId = dataCy?.replace('patterns-row-', '')
363
+ if (patternId) {
364
+ cy.get(patterns.selectors.rowMenu(patternId)).click()
365
+ cy.get(patterns.selectors.rowAction('usages', patternId)).click()
366
+ cy.get(patterns.patternSelectors.usageReport.container, { timeout: 15000 }).should('exist')
367
+ }
368
+ })
369
+ })
370
+
371
+ it('SEL_PAT_008_01: should find usage table container (or empty state)', { tags: '@SEL_PAT_008_01' }, () => {
372
+ cy.get('body').then(($body) => {
373
+ // Either the table exists or the empty state
374
+ if ($body.find(patterns.patternSelectors.usageTable.container).length > 0) {
375
+ cy.get(patterns.patternSelectors.usageTable.container).should('exist')
376
+ } else {
377
+ cy.get(patterns.patternSelectors.usageTable.empty).should('exist')
378
+ }
379
+ })
380
+ })
381
+
382
+ it.skip('SEL_PAT_008_02: should find usage row (requires pattern with usages)', { tags: '@SEL_PAT_008_02' }, () => {
383
+ // This test requires the pattern to have actual usages
384
+ cy.get('[data-cy^="pattern-usage-row-"]').first().should('exist')
385
+ })
386
+
387
+ it.skip('SEL_PAT_008_03: should find view link in usage row (requires pattern with usages)', { tags: '@SEL_PAT_008_03' }, () => {
388
+ // This test requires the pattern to have actual usages
389
+ cy.get('[data-cy^="pattern-usage-view-"]').first().should('exist')
390
+ })
391
+ })
392
+ })
@@ -66,6 +66,11 @@ export class DashboardPOM extends BasePOM {
66
66
  topnavUserLoading: cySelector('dashboard.topnav.userLoading'),
67
67
  topnavSignin: cySelector('dashboard.topnav.signin'),
68
68
  topnavSignup: cySelector('dashboard.topnav.signup'),
69
+ // Topnav settings menu
70
+ topnavSettingsMenuTrigger: cySelector('dashboard.topnav.settingsMenu.trigger'),
71
+ topnavSettingsMenuContent: cySelector('dashboard.topnav.settingsMenu.content'),
72
+ topnavSettingsMenuItem: (index: number) => cySelector('dashboard.topnav.settingsMenu.item', { index }),
73
+ topnavSettingsMenuLink: (index: number) => cySelector('dashboard.topnav.settingsMenu.link', { index }),
69
74
  // Topnav mobile menu
70
75
  topnavMobileMenuToggle: cySelector('dashboard.topnav.mobileMenu.toggle'),
71
76
  topnavMobileMenuContainer: cySelector('dashboard.topnav.mobileMenu.container'),