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

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.52",
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.52",
21
+ "@nextsparkjs/testing": "0.1.0-beta.52"
22
22
  },
23
23
  "nextspark": {
24
24
  "type": "theme",
@@ -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'] }, () => {
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('should find page container', () => {
53
+ cy.get(patterns.selectors.page).should('exist')
54
+ })
55
+
56
+ it('should find page title', () => {
57
+ cy.get(patterns.selectors.title).should('exist')
58
+ })
59
+
60
+ it('should find add button', () => {
61
+ cy.get(patterns.selectors.addBtn).should('exist')
62
+ })
63
+
64
+ it('should find search container', () => {
65
+ cy.get(patterns.selectors.searchContainer).should('exist')
66
+ })
67
+
68
+ it('should find table container', () => {
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('should find row menu for first pattern', () => {
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('should find edit action in row menu', () => {
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('should find delete action in row menu', () => {
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('should find usages quick action in row menu', () => {
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('should find header container', () => {
143
+ cy.get(cySelector('blockEditor.header.container')).should('exist')
144
+ })
145
+
146
+ it('should find title input', () => {
147
+ cy.get(cySelector('blockEditor.header.titleInput')).should('exist')
148
+ })
149
+
150
+ it('should NOT have slug input (patterns-specific)', () => {
151
+ // KEY DIFFERENCE: Patterns do not have slugs
152
+ cy.get(cySelector('blockEditor.header.slugInput')).should('not.exist')
153
+ })
154
+
155
+ it('should find save button', () => {
156
+ cy.get(cySelector('blockEditor.header.saveButton')).should('exist')
157
+ })
158
+
159
+ it('should find view mode toggle', () => {
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('should find block picker container', () => {
176
+ cy.get(cySelector('blockEditor.blockPicker.container')).should('exist')
177
+ })
178
+
179
+ it('should find blocks tab', () => {
180
+ cy.get(cySelector('blockEditor.blockPicker.tabBlocks')).should('exist')
181
+ })
182
+
183
+ it('should NOT have patterns tab (patterns-specific)', () => {
184
+ // KEY DIFFERENCE: Patterns cannot contain other patterns (prevent recursion)
185
+ cy.get(cySelector('blockEditor.blockPicker.tabPatterns')).should('not.exist')
186
+ })
187
+
188
+ it('should find config tab', () => {
189
+ cy.get(cySelector('blockEditor.blockPicker.tabConfig')).should('exist')
190
+ })
191
+
192
+ it('should find search input', () => {
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('should navigate to pattern usages and find report container', () => {
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('should find back button on usages page', () => {
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('should find page title on usages page', () => {
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('should find edit button on usages page', () => {
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('should find stats container', () => {
284
+ cy.get(patterns.patternSelectors.usageStats.container).should('exist')
285
+ })
286
+
287
+ it('should find total usage card', () => {
288
+ cy.get(patterns.patternSelectors.usageStats.total).should('exist')
289
+ })
290
+
291
+ it('should find usage by type cards (if usages exist)', () => {
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('should find report container', () => {
327
+ cy.get(patterns.patternSelectors.usageReport.container).should('exist')
328
+ })
329
+
330
+ it('should find filter select', () => {
331
+ cy.get(patterns.patternSelectors.usageReport.filterSelect).should('exist')
332
+ })
333
+
334
+ it('should find pagination container', () => {
335
+ cy.get(patterns.patternSelectors.usageReport.pagination).should('exist')
336
+ })
337
+
338
+ it('should find prev page button', () => {
339
+ cy.get(patterns.patternSelectors.usageReport.prevPage).should('exist')
340
+ })
341
+
342
+ it('should find next page button', () => {
343
+ cy.get(patterns.patternSelectors.usageReport.nextPage).should('exist')
344
+ })
345
+
346
+ it('should find results info', () => {
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('should find usage table container (or empty state)', () => {
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('should find usage row (requires pattern with usages)', () => {
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('should find view link in usage row (requires pattern with usages)', () => {
388
+ // This test requires the pattern to have actual usages
389
+ cy.get('[data-cy^="pattern-usage-view-"]').first().should('exist')
390
+ })
391
+ })
392
+ })