@nextsparkjs/theme-blog 0.1.0-beta.19 → 0.1.0-beta.24
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 +3 -3
- package/tests/cypress/e2e/README.md +170 -0
- package/tests/cypress/e2e/categories/categories-crud.cy.ts +322 -0
- package/tests/cypress/e2e/categories/categories-crud.md +73 -0
- package/tests/cypress/e2e/posts/posts-crud.cy.ts +460 -0
- package/tests/cypress/e2e/posts/posts-crud.md +115 -0
- package/tests/cypress/e2e/posts/posts-editor.cy.ts +290 -0
- package/tests/cypress/e2e/posts/posts-editor.md +139 -0
- package/tests/cypress/e2e/posts/posts-status-workflow.cy.ts +302 -0
- package/tests/cypress/e2e/posts/posts-status-workflow.md +83 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +42 -0
- package/tests/cypress/src/FeaturedImageUpload.js +131 -0
- package/tests/cypress/src/PostEditor.js +386 -0
- package/tests/cypress/src/PostsList.js +350 -0
- package/tests/cypress/src/WysiwygEditor.js +373 -0
- package/tests/cypress/src/components/EntityForm.ts +357 -0
- package/tests/cypress/src/components/EntityList.ts +360 -0
- package/tests/cypress/src/components/PostEditorPOM.ts +447 -0
- package/tests/cypress/src/components/PostsPOM.ts +362 -0
- package/tests/cypress/src/components/index.ts +18 -0
- package/tests/cypress/src/index.js +33 -0
- package/tests/cypress/src/selectors.ts +49 -0
- package/tests/cypress/src/session-helpers.ts +89 -0
- package/tests/cypress/support/e2e.ts +89 -0
- package/tests/cypress.config.ts +165 -0
- package/tests/jest/__mocks__/jose.js +22 -0
- package/tests/jest/__mocks__/next-server.js +56 -0
- package/tests/jest/jest.config.cjs +127 -0
- package/tests/jest/setup.ts +170 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostEditor - Blog Theme Post Create/Edit Page POM
|
|
3
|
+
*
|
|
4
|
+
* Handles post creation and editing with WYSIWYG editor.
|
|
5
|
+
* Supports both create and edit modes with dynamic selector prefixes.
|
|
6
|
+
*
|
|
7
|
+
* Test cases: BLOG_POST_CREATE_001-004, BLOG_POST_UPDATE_001-004
|
|
8
|
+
*/
|
|
9
|
+
export class PostEditor {
|
|
10
|
+
/**
|
|
11
|
+
* Create a PostEditor instance
|
|
12
|
+
* @param {string} mode - 'create' | 'edit'
|
|
13
|
+
*/
|
|
14
|
+
constructor(mode = 'create') {
|
|
15
|
+
this.mode = mode
|
|
16
|
+
this.prefix = mode === 'create' ? 'post-create' : 'post-edit'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get selectors based on current mode
|
|
21
|
+
*/
|
|
22
|
+
get selectors() {
|
|
23
|
+
const prefix = this.prefix
|
|
24
|
+
return {
|
|
25
|
+
// Container
|
|
26
|
+
container: `[data-cy="${prefix}-container"]`,
|
|
27
|
+
header: `[data-cy="${prefix}-header"]`,
|
|
28
|
+
|
|
29
|
+
// Navigation
|
|
30
|
+
back: `[data-cy="${prefix}-back"]`,
|
|
31
|
+
|
|
32
|
+
// Status
|
|
33
|
+
status: `[data-cy="${prefix}-status"]`,
|
|
34
|
+
autosaved: `[data-cy="${prefix}-autosaved"]`,
|
|
35
|
+
|
|
36
|
+
// Actions
|
|
37
|
+
settingsToggle: `[data-cy="${prefix}-settings-toggle"]`,
|
|
38
|
+
save: `[data-cy="${prefix}-save"]`,
|
|
39
|
+
publish: `[data-cy="${prefix}-publish"]`,
|
|
40
|
+
unpublish: `[data-cy="${prefix}-unpublish"]`, // Edit mode only
|
|
41
|
+
viewLive: `[data-cy="${prefix}-view-live"]`, // Edit mode only
|
|
42
|
+
|
|
43
|
+
// Error
|
|
44
|
+
error: `[data-cy="${prefix}-error"]`,
|
|
45
|
+
errorDismiss: `[data-cy="${prefix}-error-dismiss"]`,
|
|
46
|
+
|
|
47
|
+
// Editor
|
|
48
|
+
title: `[data-cy="${prefix}-title"]`,
|
|
49
|
+
content: `[data-cy="${prefix}-content"]`,
|
|
50
|
+
|
|
51
|
+
// Settings Panel
|
|
52
|
+
settings: `[data-cy="${prefix}-settings"]`,
|
|
53
|
+
statusSelect: `[data-cy="${prefix}-status-select"]`,
|
|
54
|
+
slug: `[data-cy="${prefix}-slug"]`,
|
|
55
|
+
excerpt: `[data-cy="${prefix}-excerpt"]`,
|
|
56
|
+
featuredImage: `[data-cy="${prefix}-featured-image"]`,
|
|
57
|
+
featuredToggle: `[data-cy="${prefix}-featured-toggle"]`,
|
|
58
|
+
|
|
59
|
+
// Delete Dialog (Edit mode only)
|
|
60
|
+
delete: `[data-cy="${prefix}-delete"]`,
|
|
61
|
+
deleteDialog: `[data-cy="${prefix}-delete-dialog"]`,
|
|
62
|
+
deleteConfirm: `[data-cy="${prefix}-delete-confirm"]`,
|
|
63
|
+
deleteCancel: `[data-cy="${prefix}-delete-cancel"]`,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate page is visible and loaded
|
|
69
|
+
*/
|
|
70
|
+
validatePageVisible() {
|
|
71
|
+
cy.get(this.selectors.container).should('be.visible')
|
|
72
|
+
cy.get(this.selectors.header).should('be.visible')
|
|
73
|
+
|
|
74
|
+
const urlPattern = this.mode === 'create'
|
|
75
|
+
? '/dashboard/posts/create'
|
|
76
|
+
: '/dashboard/posts/.+/edit'
|
|
77
|
+
cy.url().should('match', new RegExp(urlPattern))
|
|
78
|
+
|
|
79
|
+
return this
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Click back button to return to posts list
|
|
84
|
+
*/
|
|
85
|
+
clickBack() {
|
|
86
|
+
cy.get(this.selectors.back).click()
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Fill post title
|
|
92
|
+
* @param {string} title - Post title
|
|
93
|
+
*/
|
|
94
|
+
fillTitle(title) {
|
|
95
|
+
cy.get(this.selectors.title)
|
|
96
|
+
.clear()
|
|
97
|
+
.type(title)
|
|
98
|
+
return this
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get current title value
|
|
103
|
+
*/
|
|
104
|
+
getTitle() {
|
|
105
|
+
return cy.get(this.selectors.title).invoke('val')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Validate title value
|
|
110
|
+
* @param {string} expectedTitle - Expected title
|
|
111
|
+
*/
|
|
112
|
+
validateTitle(expectedTitle) {
|
|
113
|
+
cy.get(this.selectors.title).should('have.value', expectedTitle)
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Focus on content editor
|
|
119
|
+
*/
|
|
120
|
+
focusContent() {
|
|
121
|
+
cy.get(this.selectors.content).find('[data-cy="wysiwyg-content"]').focus()
|
|
122
|
+
return this
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Type content in the WYSIWYG editor
|
|
127
|
+
* @param {string} content - Content to type
|
|
128
|
+
*/
|
|
129
|
+
typeContent(content) {
|
|
130
|
+
cy.get(this.selectors.content)
|
|
131
|
+
.find('[data-cy="wysiwyg-content"]')
|
|
132
|
+
.focus()
|
|
133
|
+
.type(content)
|
|
134
|
+
return this
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Toggle settings panel visibility
|
|
139
|
+
*/
|
|
140
|
+
toggleSettings() {
|
|
141
|
+
cy.get(this.selectors.settingsToggle).click()
|
|
142
|
+
return this
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate settings panel is visible
|
|
147
|
+
*/
|
|
148
|
+
validateSettingsVisible() {
|
|
149
|
+
cy.get(this.selectors.settings).should('be.visible')
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validate settings panel is hidden
|
|
155
|
+
*/
|
|
156
|
+
validateSettingsHidden() {
|
|
157
|
+
cy.get(this.selectors.settings).should('not.exist')
|
|
158
|
+
return this
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Set post status via dropdown
|
|
163
|
+
* @param {string} status - 'draft' | 'published'
|
|
164
|
+
*/
|
|
165
|
+
setStatus(status) {
|
|
166
|
+
cy.get(this.selectors.statusSelect).click()
|
|
167
|
+
cy.get(`[data-value="${status}"]`).click()
|
|
168
|
+
return this
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Fill URL slug
|
|
173
|
+
* @param {string} slug - URL slug
|
|
174
|
+
*/
|
|
175
|
+
fillSlug(slug) {
|
|
176
|
+
cy.get(this.selectors.slug)
|
|
177
|
+
.clear()
|
|
178
|
+
.type(slug)
|
|
179
|
+
return this
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validate slug value
|
|
184
|
+
* @param {string} expectedSlug - Expected slug
|
|
185
|
+
*/
|
|
186
|
+
validateSlug(expectedSlug) {
|
|
187
|
+
cy.get(this.selectors.slug).should('have.value', expectedSlug)
|
|
188
|
+
return this
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Fill excerpt
|
|
193
|
+
* @param {string} excerpt - Post excerpt
|
|
194
|
+
*/
|
|
195
|
+
fillExcerpt(excerpt) {
|
|
196
|
+
cy.get(this.selectors.excerpt)
|
|
197
|
+
.clear()
|
|
198
|
+
.type(excerpt)
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Toggle featured post switch
|
|
204
|
+
* @param {boolean} enabled - Whether to enable featured
|
|
205
|
+
*/
|
|
206
|
+
toggleFeatured(enabled) {
|
|
207
|
+
const expectedState = enabled ? 'checked' : 'unchecked'
|
|
208
|
+
|
|
209
|
+
cy.get(this.selectors.featuredToggle).then($switch => {
|
|
210
|
+
const currentState = $switch.attr('data-state')
|
|
211
|
+
if (currentState !== expectedState) {
|
|
212
|
+
// Click the switch element directly (it's already a button)
|
|
213
|
+
cy.wrap($switch).click({ force: true })
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Wait for state update and verify
|
|
218
|
+
cy.get(this.selectors.featuredToggle, { timeout: 5000 })
|
|
219
|
+
.should('have.attr', 'data-state', expectedState)
|
|
220
|
+
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Validate featured toggle state
|
|
226
|
+
* @param {boolean} enabled - Expected state
|
|
227
|
+
*/
|
|
228
|
+
validateFeaturedState(enabled) {
|
|
229
|
+
const expectedState = enabled ? 'checked' : 'unchecked'
|
|
230
|
+
cy.get(this.selectors.featuredToggle, { timeout: 5000 })
|
|
231
|
+
.should('have.attr', 'data-state', expectedState)
|
|
232
|
+
return this
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Click save draft button
|
|
237
|
+
*/
|
|
238
|
+
saveDraft() {
|
|
239
|
+
cy.get(this.selectors.save).click()
|
|
240
|
+
return this
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Click publish button
|
|
245
|
+
*/
|
|
246
|
+
publish() {
|
|
247
|
+
cy.get(this.selectors.publish).click()
|
|
248
|
+
return this
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Click unpublish button (edit mode only)
|
|
253
|
+
*/
|
|
254
|
+
unpublish() {
|
|
255
|
+
if (this.mode !== 'edit') {
|
|
256
|
+
throw new Error('unpublish is only available in edit mode')
|
|
257
|
+
}
|
|
258
|
+
cy.get(this.selectors.unpublish).click()
|
|
259
|
+
return this
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Click view live button (edit mode only)
|
|
264
|
+
*/
|
|
265
|
+
clickViewLive() {
|
|
266
|
+
if (this.mode !== 'edit') {
|
|
267
|
+
throw new Error('viewLive is only available in edit mode')
|
|
268
|
+
}
|
|
269
|
+
cy.get(this.selectors.viewLive).click()
|
|
270
|
+
return this
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Click delete button to open dialog (edit mode only)
|
|
275
|
+
*/
|
|
276
|
+
clickDelete() {
|
|
277
|
+
if (this.mode !== 'edit') {
|
|
278
|
+
throw new Error('delete is only available in edit mode')
|
|
279
|
+
}
|
|
280
|
+
cy.get(this.selectors.delete).click()
|
|
281
|
+
return this
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Confirm deletion in dialog (edit mode only)
|
|
286
|
+
*/
|
|
287
|
+
confirmDelete() {
|
|
288
|
+
cy.get(this.selectors.deleteDialog).should('be.visible')
|
|
289
|
+
cy.get(this.selectors.deleteConfirm).click()
|
|
290
|
+
return this
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Cancel deletion in dialog (edit mode only)
|
|
295
|
+
*/
|
|
296
|
+
cancelDelete() {
|
|
297
|
+
cy.get(this.selectors.deleteDialog).should('be.visible')
|
|
298
|
+
cy.get(this.selectors.deleteCancel).click()
|
|
299
|
+
return this
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Validate delete dialog is visible
|
|
304
|
+
*/
|
|
305
|
+
validateDeleteDialogVisible() {
|
|
306
|
+
cy.get(this.selectors.deleteDialog).should('be.visible')
|
|
307
|
+
return this
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Validate auto-saved indicator is shown
|
|
312
|
+
*/
|
|
313
|
+
validateAutoSaved() {
|
|
314
|
+
cy.get(this.selectors.autosaved).should('be.visible')
|
|
315
|
+
return this
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Validate error message is shown
|
|
320
|
+
* @param {string} errorType - Error type code (e.g., 'title-required') for data-cy-error matching
|
|
321
|
+
*/
|
|
322
|
+
validateError(errorType) {
|
|
323
|
+
cy.get(this.selectors.error).should('be.visible')
|
|
324
|
+
if (errorType) {
|
|
325
|
+
cy.get(this.selectors.error).should('have.attr', 'data-cy-error', errorType)
|
|
326
|
+
}
|
|
327
|
+
return this
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Dismiss error message
|
|
332
|
+
*/
|
|
333
|
+
dismissError() {
|
|
334
|
+
cy.get(this.selectors.errorDismiss).click()
|
|
335
|
+
return this
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Validate error is not visible
|
|
340
|
+
*/
|
|
341
|
+
validateNoError() {
|
|
342
|
+
cy.get(this.selectors.error).should('not.exist')
|
|
343
|
+
return this
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Validate status badge shows correct status using data-cy-status attribute
|
|
348
|
+
* @param {string} status - Status code: 'draft' | 'published' | 'new-draft' | 'new-post'
|
|
349
|
+
*/
|
|
350
|
+
validateStatusBadge(status) {
|
|
351
|
+
cy.get(this.selectors.status).should('have.attr', 'data-cy-status', status)
|
|
352
|
+
return this
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Validate unsaved changes indicator is visible (edit mode only)
|
|
357
|
+
*/
|
|
358
|
+
validateUnsavedChanges() {
|
|
359
|
+
cy.get('[data-cy="post-unsaved-indicator"]').should('be.visible')
|
|
360
|
+
return this
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Validate unsaved changes indicator is not visible
|
|
365
|
+
*/
|
|
366
|
+
validateNoUnsavedChanges() {
|
|
367
|
+
cy.get('[data-cy="post-unsaved-indicator"]').should('not.exist')
|
|
368
|
+
return this
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Fill complete post form
|
|
373
|
+
* @param {object} post - Post data
|
|
374
|
+
*/
|
|
375
|
+
fillPost(post) {
|
|
376
|
+
if (post.title) this.fillTitle(post.title)
|
|
377
|
+
if (post.content) this.typeContent(post.content)
|
|
378
|
+
if (post.slug) this.fillSlug(post.slug)
|
|
379
|
+
if (post.excerpt) this.fillExcerpt(post.excerpt)
|
|
380
|
+
if (post.featured !== undefined) this.toggleFeatured(post.featured)
|
|
381
|
+
if (post.status) this.setStatus(post.status)
|
|
382
|
+
return this
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export default PostEditor
|