@nextsparkjs/theme-default 0.1.0-beta.49 → 0.1.0-beta.51
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/api/ai/chat/stream/route.ts +4 -1
- package/api/ai/orchestrator/route.ts +10 -3
- package/api/ai/single-agent/route.ts +10 -3
- package/api/ai/usage/route.ts +4 -1
- package/blocks/text-content/component.tsx +10 -8
- package/config/dashboard.config.ts +14 -0
- package/config/permissions.config.ts +11 -0
- package/messages/en/admin.json +12 -1
- package/messages/es/admin.json +12 -1
- package/migrations/093_pages_sample_data.sql +7 -7
- package/migrations/098_patterns_sample_data.sql +234 -0
- package/package.json +3 -3
- package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +127 -3
- package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +124 -0
- package/tests/cypress/e2e/ai/chat-api.cy.ts +50 -38
- package/tests/cypress/e2e/api/_core/security/security-headers.cy.ts +601 -0
- package/tests/cypress/e2e/patterns/patterns-in-pages.cy.ts +367 -0
- package/tests/cypress/fixtures/entities.json +9 -0
- package/tests/cypress/src/entities/PatternsPOM.ts +329 -0
- package/tests/cypress/src/entities/index.ts +2 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patterns in Pages - Functional E2E Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the integration of the patterns system with the page builder:
|
|
5
|
+
* - Patterns tab visibility and functionality
|
|
6
|
+
* - Pattern insertion into pages
|
|
7
|
+
* - Pattern reference rendering and interaction
|
|
8
|
+
* - Pattern nesting prevention (patterns tab hidden when editing patterns)
|
|
9
|
+
*
|
|
10
|
+
* Sample Patterns in DB (team-nextspark-001):
|
|
11
|
+
* - Newsletter CTA (slug: newsletter-cta, status: published)
|
|
12
|
+
* - Footer Links (slug: footer-links, status: published)
|
|
13
|
+
* - Hero Header (slug: hero-header, status: draft)
|
|
14
|
+
*
|
|
15
|
+
* Re-execution:
|
|
16
|
+
* pnpm tags @patterns # Run all patterns tests
|
|
17
|
+
* pnpm tags @TC_PAT_001 # Run specific test
|
|
18
|
+
* pnpm cy:run --spec "cypress/e2e/patterns/patterns-in-pages.cy.ts"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { PageBuilderPOM } from '../../src/features/PageBuilderPOM'
|
|
22
|
+
import { loginAsDefaultDeveloper } from '../../src/session-helpers'
|
|
23
|
+
|
|
24
|
+
const DEVELOPER_TEAM_ID = 'team-nextspark-001'
|
|
25
|
+
|
|
26
|
+
describe('Patterns in Pages - Functional Tests', {
|
|
27
|
+
tags: ['@patterns', '@block-editor', '@functional']
|
|
28
|
+
}, () => {
|
|
29
|
+
const pom = PageBuilderPOM.create()
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
loginAsDefaultDeveloper()
|
|
33
|
+
cy.window().then((win) => {
|
|
34
|
+
win.localStorage.setItem('activeTeamId', DEVELOPER_TEAM_ID)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
// TC_PAT_001: Can switch to Patterns tab in block picker
|
|
40
|
+
// ===========================================================================
|
|
41
|
+
describe('TC_PAT_001: Switch to Patterns Tab', { tags: '@TC_PAT_001' }, () => {
|
|
42
|
+
it('should display patterns tab when editing pages', { tags: '@TC_PAT_001_01' }, () => {
|
|
43
|
+
pom.visitCreate()
|
|
44
|
+
pom.waitForEditor()
|
|
45
|
+
|
|
46
|
+
// Patterns tab should be visible
|
|
47
|
+
cy.get('[data-cy="block-picker-tab-patterns"]')
|
|
48
|
+
.should('exist')
|
|
49
|
+
.and('be.visible')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should switch to patterns tab on click', { tags: '@TC_PAT_001_02' }, () => {
|
|
53
|
+
pom.visitCreate()
|
|
54
|
+
pom.waitForEditor()
|
|
55
|
+
|
|
56
|
+
// Click patterns tab
|
|
57
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
58
|
+
|
|
59
|
+
// Patterns search and list should appear
|
|
60
|
+
cy.get('[data-cy="block-picker-patterns-search"]').should('be.visible')
|
|
61
|
+
cy.get('[data-cy="block-picker-patterns-list"]').should('be.visible')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should switch back to blocks tab', { tags: '@TC_PAT_001_03' }, () => {
|
|
65
|
+
pom.visitCreate()
|
|
66
|
+
pom.waitForEditor()
|
|
67
|
+
|
|
68
|
+
// Switch to patterns
|
|
69
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
70
|
+
cy.get('[data-cy="block-picker-patterns-list"]').should('be.visible')
|
|
71
|
+
|
|
72
|
+
// Switch back to blocks
|
|
73
|
+
cy.get('[data-cy="block-picker-tab-blocks"]').click()
|
|
74
|
+
cy.get('[data-cy="block-picker-list"]').should('be.visible')
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// ===========================================================================
|
|
79
|
+
// TC_PAT_002: Patterns tab shows sample patterns
|
|
80
|
+
// ===========================================================================
|
|
81
|
+
describe('TC_PAT_002: Display Sample Patterns', { tags: '@TC_PAT_002' }, () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
pom.visitCreate()
|
|
84
|
+
pom.waitForEditor()
|
|
85
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
86
|
+
cy.wait(500) // Wait for patterns to load
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should display at least one published pattern', { tags: '@TC_PAT_002_01' }, () => {
|
|
90
|
+
// Check that at least one pattern card exists
|
|
91
|
+
cy.get('[data-cy^="block-picker-pattern-card-"]')
|
|
92
|
+
.should('have.length.at.least', 1)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should display pattern card with all elements', { tags: '@TC_PAT_002_02' }, () => {
|
|
96
|
+
// Get first pattern card
|
|
97
|
+
cy.get('[data-cy^="block-picker-pattern-card-"]').first().within(() => {
|
|
98
|
+
// Check for icon, title, description, insert button
|
|
99
|
+
cy.get('[data-cy^="block-picker-pattern-icon-"]').should('exist')
|
|
100
|
+
cy.get('[data-cy^="block-picker-pattern-title-"]').should('exist').and('not.be.empty')
|
|
101
|
+
cy.get('[data-cy^="block-picker-pattern-desc-"]').should('exist')
|
|
102
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').should('exist').and('be.visible')
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should filter patterns using search', { tags: '@TC_PAT_002_03' }, () => {
|
|
107
|
+
// Get initial count
|
|
108
|
+
cy.get('[data-cy^="block-picker-pattern-card-"]').then(($cards) => {
|
|
109
|
+
const initialCount = $cards.length
|
|
110
|
+
|
|
111
|
+
// Type search term
|
|
112
|
+
cy.get('[data-cy="block-picker-patterns-search"]').type('Newsletter')
|
|
113
|
+
cy.wait(300)
|
|
114
|
+
|
|
115
|
+
// Pattern list should be filtered
|
|
116
|
+
cy.get('[data-cy^="block-picker-pattern-card-"]').then(($filtered) => {
|
|
117
|
+
// Should have fewer or equal patterns after filtering
|
|
118
|
+
expect($filtered.length).to.be.at.most(initialCount)
|
|
119
|
+
|
|
120
|
+
// If newsletter pattern exists, it should be visible
|
|
121
|
+
if ($filtered.length > 0) {
|
|
122
|
+
cy.get('[data-cy^="block-picker-pattern-title-"]')
|
|
123
|
+
.first()
|
|
124
|
+
.should('contain.text', 'Newsletter')
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should clear search filter', { tags: '@TC_PAT_002_04' }, () => {
|
|
131
|
+
// Search for something
|
|
132
|
+
cy.get('[data-cy="block-picker-patterns-search"]').type('Footer')
|
|
133
|
+
cy.wait(300)
|
|
134
|
+
|
|
135
|
+
// Clear search
|
|
136
|
+
cy.get('[data-cy="block-picker-patterns-search"]').clear()
|
|
137
|
+
cy.wait(300)
|
|
138
|
+
|
|
139
|
+
// All published patterns should be visible again
|
|
140
|
+
cy.get('[data-cy^="block-picker-pattern-card-"]')
|
|
141
|
+
.should('have.length.at.least', 1)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// ===========================================================================
|
|
146
|
+
// TC_PAT_003: Can insert a pattern into a page
|
|
147
|
+
// ===========================================================================
|
|
148
|
+
describe('TC_PAT_003: Insert Pattern into Page', { tags: '@TC_PAT_003' }, () => {
|
|
149
|
+
it('should insert pattern when clicking insert button', { tags: '@TC_PAT_003_01' }, () => {
|
|
150
|
+
pom.visitCreate()
|
|
151
|
+
pom.waitForEditor()
|
|
152
|
+
pom.switchToLayoutMode()
|
|
153
|
+
|
|
154
|
+
// Switch to patterns tab
|
|
155
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
156
|
+
cy.wait(500)
|
|
157
|
+
|
|
158
|
+
// Click insert on first pattern
|
|
159
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').first().click()
|
|
160
|
+
|
|
161
|
+
// Pattern reference should appear in layout canvas
|
|
162
|
+
cy.wait(1000)
|
|
163
|
+
cy.get('[data-cy^="sortable-block-"]').should('have.length.at.least', 1)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should show pattern reference in sortable blocks list', { tags: '@TC_PAT_003_02' }, () => {
|
|
167
|
+
pom.visitCreate()
|
|
168
|
+
pom.waitForEditor()
|
|
169
|
+
pom.switchToLayoutMode()
|
|
170
|
+
|
|
171
|
+
// Insert pattern
|
|
172
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
173
|
+
cy.wait(500)
|
|
174
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').first().click()
|
|
175
|
+
cy.wait(1000)
|
|
176
|
+
|
|
177
|
+
// Should see pattern reference block in layout canvas
|
|
178
|
+
// Pattern reference should have a distinct appearance (badge, etc.)
|
|
179
|
+
cy.get(pom.editorSelectors.sortableBlockGeneric)
|
|
180
|
+
.should('have.length.at.least', 1)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should allow inserting multiple patterns', { tags: '@TC_PAT_003_03' }, () => {
|
|
184
|
+
pom.visitCreate()
|
|
185
|
+
pom.waitForEditor()
|
|
186
|
+
pom.switchToLayoutMode()
|
|
187
|
+
|
|
188
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
189
|
+
cy.wait(500)
|
|
190
|
+
|
|
191
|
+
// Get count of available patterns
|
|
192
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').then(($buttons) => {
|
|
193
|
+
const patternCount = Math.min($buttons.length, 2) // Insert max 2 patterns
|
|
194
|
+
|
|
195
|
+
// Insert first pattern
|
|
196
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').eq(0).click()
|
|
197
|
+
cy.wait(500)
|
|
198
|
+
|
|
199
|
+
if (patternCount > 1) {
|
|
200
|
+
// Insert second pattern if available
|
|
201
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').eq(1).click()
|
|
202
|
+
cy.wait(500)
|
|
203
|
+
|
|
204
|
+
// Should have 2 blocks
|
|
205
|
+
cy.get(pom.editorSelectors.sortableBlockGeneric)
|
|
206
|
+
.should('have.length.at.least', 2)
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// ===========================================================================
|
|
213
|
+
// TC_PAT_004: Pattern reference displays correctly in preview
|
|
214
|
+
// ===========================================================================
|
|
215
|
+
describe('TC_PAT_004: Pattern Reference in Preview', { tags: '@TC_PAT_004' }, () => {
|
|
216
|
+
beforeEach(() => {
|
|
217
|
+
pom.visitCreate()
|
|
218
|
+
pom.waitForEditor()
|
|
219
|
+
pom.switchToLayoutMode()
|
|
220
|
+
|
|
221
|
+
// Insert a pattern
|
|
222
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
223
|
+
cy.wait(500)
|
|
224
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').first().click()
|
|
225
|
+
cy.wait(1000)
|
|
226
|
+
|
|
227
|
+
// Switch to preview mode
|
|
228
|
+
pom.switchToPreviewMode()
|
|
229
|
+
cy.wait(500)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should render pattern reference container in preview', { tags: '@TC_PAT_004_01' }, () => {
|
|
233
|
+
// Pattern reference should be visible
|
|
234
|
+
cy.get('[data-cy^="pattern-reference-"]')
|
|
235
|
+
.should('exist')
|
|
236
|
+
.and('be.visible')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should display pattern reference badge', { tags: '@TC_PAT_004_02' }, () => {
|
|
240
|
+
// Badge should indicate this is a pattern reference
|
|
241
|
+
cy.get('[data-cy^="pattern-reference-badge-"]')
|
|
242
|
+
.should('exist')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should show pattern reference edit link', { tags: '@TC_PAT_004_03' }, () => {
|
|
246
|
+
// Edit link should allow navigating to pattern editor
|
|
247
|
+
cy.get('[data-cy^="pattern-reference-edit-link-"]')
|
|
248
|
+
.should('exist')
|
|
249
|
+
.and('be.visible')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('should show remove button for pattern reference', { tags: '@TC_PAT_004_04' }, () => {
|
|
253
|
+
cy.get('[data-cy^="pattern-reference-remove-"]')
|
|
254
|
+
.should('exist')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('should show locked state when pattern reference is selected', { tags: '@TC_PAT_004_05' }, () => {
|
|
258
|
+
// Click on pattern reference
|
|
259
|
+
cy.get('[data-cy^="pattern-reference-"]').first().click()
|
|
260
|
+
cy.wait(500)
|
|
261
|
+
|
|
262
|
+
// Locked indicator should appear
|
|
263
|
+
cy.get('[data-cy^="pattern-reference-locked-"]')
|
|
264
|
+
.should('exist')
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
// ===========================================================================
|
|
269
|
+
// TC_PAT_005: Can remove pattern reference from page
|
|
270
|
+
// ===========================================================================
|
|
271
|
+
describe('TC_PAT_005: Remove Pattern Reference', { tags: '@TC_PAT_005' }, () => {
|
|
272
|
+
it('should remove pattern reference when clicking remove button', { tags: '@TC_PAT_005_01' }, () => {
|
|
273
|
+
pom.visitCreate()
|
|
274
|
+
pom.waitForEditor()
|
|
275
|
+
pom.switchToLayoutMode()
|
|
276
|
+
|
|
277
|
+
// Insert pattern
|
|
278
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
279
|
+
cy.wait(500)
|
|
280
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').first().click()
|
|
281
|
+
cy.wait(1000)
|
|
282
|
+
|
|
283
|
+
// Get initial count
|
|
284
|
+
cy.get(pom.editorSelectors.sortableBlockGeneric).then(($blocks) => {
|
|
285
|
+
const initialCount = $blocks.length
|
|
286
|
+
|
|
287
|
+
// Switch to preview to access remove button
|
|
288
|
+
pom.switchToPreviewMode()
|
|
289
|
+
cy.wait(500)
|
|
290
|
+
|
|
291
|
+
// Click remove button
|
|
292
|
+
cy.get('[data-cy^="pattern-reference-remove-"]').first().click()
|
|
293
|
+
cy.wait(500)
|
|
294
|
+
|
|
295
|
+
// Switch back to layout mode to verify
|
|
296
|
+
pom.switchToLayoutMode()
|
|
297
|
+
cy.wait(500)
|
|
298
|
+
|
|
299
|
+
// Block count should decrease
|
|
300
|
+
cy.get(pom.editorSelectors.sortableBlockGeneric).should('have.length', initialCount - 1)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('should remove pattern reference from layout mode', { tags: '@TC_PAT_005_02' }, () => {
|
|
305
|
+
pom.visitCreate()
|
|
306
|
+
pom.waitForEditor()
|
|
307
|
+
pom.switchToLayoutMode()
|
|
308
|
+
|
|
309
|
+
// Insert pattern
|
|
310
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').click()
|
|
311
|
+
cy.wait(500)
|
|
312
|
+
cy.get('[data-cy^="block-picker-pattern-insert-"]').first().click()
|
|
313
|
+
cy.wait(1000)
|
|
314
|
+
|
|
315
|
+
// In layout mode, use standard block remove button
|
|
316
|
+
cy.get(pom.editorSelectors.sortableBlockGeneric).first()
|
|
317
|
+
.invoke('attr', 'data-cy')
|
|
318
|
+
.then((dataCy) => {
|
|
319
|
+
const blockId = dataCy?.replace('sortable-block-', '') || ''
|
|
320
|
+
cy.get(pom.editorSelectors.removeBlock(blockId)).click()
|
|
321
|
+
cy.wait(500)
|
|
322
|
+
|
|
323
|
+
// Block should be removed
|
|
324
|
+
cy.get(pom.editorSelectors.sortableBlockGeneric).should('have.length', 0)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
// ===========================================================================
|
|
330
|
+
// TC_PAT_006: Patterns tab is NOT shown when editing patterns entity
|
|
331
|
+
// ===========================================================================
|
|
332
|
+
describe('TC_PAT_006: Prevent Pattern Nesting', { tags: '@TC_PAT_006' }, () => {
|
|
333
|
+
it('should NOT show patterns tab when editing a pattern', { tags: '@TC_PAT_006_01' }, () => {
|
|
334
|
+
// Visit the patterns entity create page
|
|
335
|
+
cy.visit('/dashboard/patterns/create')
|
|
336
|
+
cy.wait(1000)
|
|
337
|
+
|
|
338
|
+
// Wait for editor to load
|
|
339
|
+
cy.get(pom.editorSelectors.container, { timeout: 15000 }).should('be.visible')
|
|
340
|
+
|
|
341
|
+
// Patterns tab should NOT exist (to prevent nesting patterns inside patterns)
|
|
342
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').should('not.exist')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('should only show blocks and config tabs when editing patterns', { tags: '@TC_PAT_006_02' }, () => {
|
|
346
|
+
cy.visit('/dashboard/patterns/create')
|
|
347
|
+
cy.wait(1000)
|
|
348
|
+
cy.get(pom.editorSelectors.container, { timeout: 15000 }).should('be.visible')
|
|
349
|
+
|
|
350
|
+
// Only Blocks and Config tabs should be visible
|
|
351
|
+
cy.get('[data-cy="block-picker-tab-blocks"]').should('exist').and('be.visible')
|
|
352
|
+
cy.get('[data-cy="block-picker-tab-config"]').should('exist').and('be.visible')
|
|
353
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').should('not.exist')
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('should show patterns tab when editing pages (control test)', { tags: '@TC_PAT_006_03' }, () => {
|
|
357
|
+
// Visit pages create (should show patterns tab)
|
|
358
|
+
pom.visitCreate()
|
|
359
|
+
pom.waitForEditor()
|
|
360
|
+
|
|
361
|
+
// All three tabs should be visible
|
|
362
|
+
cy.get('[data-cy="block-picker-tab-blocks"]').should('exist').and('be.visible')
|
|
363
|
+
cy.get('[data-cy="block-picker-tab-patterns"]').should('exist').and('be.visible')
|
|
364
|
+
cy.get('[data-cy="block-picker-tab-config"]').should('exist').and('be.visible')
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
})
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
"_warning": "AUTO-GENERATED by build-registry.mjs - DO NOT EDIT MANUALLY",
|
|
4
4
|
"_theme": "default",
|
|
5
5
|
"entities": {
|
|
6
|
+
"patterns": {
|
|
7
|
+
"slug": "patterns",
|
|
8
|
+
"singular": "pattern",
|
|
9
|
+
"plural": "Patterns",
|
|
10
|
+
"tableName": "patterns",
|
|
11
|
+
"fields": [],
|
|
12
|
+
"filters": [],
|
|
13
|
+
"source": "core"
|
|
14
|
+
},
|
|
6
15
|
"customers": {
|
|
7
16
|
"slug": "customers",
|
|
8
17
|
"singular": "customer",
|