@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,460 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Posts CRUD Tests - Blog Theme
|
|
5
|
+
*
|
|
6
|
+
* Tests for Posts entity CRUD operations.
|
|
7
|
+
* Uses custom PostsList and PostEditor POM classes.
|
|
8
|
+
*
|
|
9
|
+
* Theme Mode: single-user (isolated blogs, no team collaboration)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { PostsList } from '../../../src/PostsList.js'
|
|
13
|
+
import { PostEditor } from '../../../src/PostEditor.js'
|
|
14
|
+
import { loginAsBlogAuthor, BLOG_USERS } from '../../../src/session-helpers'
|
|
15
|
+
|
|
16
|
+
describe('Posts CRUD - Blog Author (Full Access)', () => {
|
|
17
|
+
const postsList = new PostsList()
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
loginAsBlogAuthor('MARCOS')
|
|
21
|
+
cy.visit('/dashboard/posts')
|
|
22
|
+
postsList.validateListVisible()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// =========================================================================
|
|
26
|
+
// CREATE - Author can create posts
|
|
27
|
+
// =========================================================================
|
|
28
|
+
describe('CREATE - Author can create posts', () => {
|
|
29
|
+
it('BLOG_POST_CREATE_001: should create draft post', () => {
|
|
30
|
+
const postTitle = `Draft Post ${Date.now()}`
|
|
31
|
+
const postEditor = new PostEditor('create')
|
|
32
|
+
|
|
33
|
+
// Click create button
|
|
34
|
+
postsList.clickCreate()
|
|
35
|
+
|
|
36
|
+
// Validate create page
|
|
37
|
+
postEditor.validatePageVisible()
|
|
38
|
+
postEditor.validateStatusBadge('new-draft')
|
|
39
|
+
|
|
40
|
+
// Fill post data
|
|
41
|
+
postEditor.fillTitle(postTitle)
|
|
42
|
+
postEditor.typeContent('This is a draft post content.')
|
|
43
|
+
|
|
44
|
+
// Save as draft
|
|
45
|
+
postEditor.saveDraft()
|
|
46
|
+
|
|
47
|
+
// Validate redirect to edit page
|
|
48
|
+
cy.url().should('match', /\/dashboard\/posts\/[a-z0-9-]+\/edit/)
|
|
49
|
+
|
|
50
|
+
// After redirect, we're now on edit page - use edit mode editor
|
|
51
|
+
const editEditor = new PostEditor('edit')
|
|
52
|
+
editEditor.validateStatusBadge('draft')
|
|
53
|
+
|
|
54
|
+
cy.log('✅ Author created draft post successfully')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('BLOG_POST_CREATE_002: should create and publish post immediately', () => {
|
|
58
|
+
const postTitle = `Published Post ${Date.now()}`
|
|
59
|
+
const postEditor = new PostEditor('create')
|
|
60
|
+
|
|
61
|
+
// Click create button
|
|
62
|
+
postsList.clickCreate()
|
|
63
|
+
|
|
64
|
+
// Fill post data
|
|
65
|
+
postEditor.fillTitle(postTitle)
|
|
66
|
+
postEditor.typeContent('This is a published post content.')
|
|
67
|
+
|
|
68
|
+
// Publish immediately
|
|
69
|
+
postEditor.publish()
|
|
70
|
+
|
|
71
|
+
// Validate redirect to edit page
|
|
72
|
+
cy.url().should('match', /\/dashboard\/posts\/[a-z0-9-]+\/edit/)
|
|
73
|
+
|
|
74
|
+
// After redirect, we're now on edit page - use edit mode editor
|
|
75
|
+
const editEditor = new PostEditor('edit')
|
|
76
|
+
|
|
77
|
+
// Go back to list using edit mode editor
|
|
78
|
+
editEditor.clickBack()
|
|
79
|
+
|
|
80
|
+
// Validate post appears with published status
|
|
81
|
+
cy.contains(postTitle).should('be.visible')
|
|
82
|
+
|
|
83
|
+
cy.log('✅ Author created and published post successfully')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('BLOG_POST_CREATE_003: should create post with all metadata', () => {
|
|
87
|
+
const postTitle = `Full Post ${Date.now()}`
|
|
88
|
+
const postSlug = `full-post-${Date.now()}`
|
|
89
|
+
const postExcerpt = 'This is a brief excerpt for the post.'
|
|
90
|
+
const postEditor = new PostEditor('create')
|
|
91
|
+
|
|
92
|
+
// Click create button
|
|
93
|
+
postsList.clickCreate()
|
|
94
|
+
|
|
95
|
+
// Fill all fields
|
|
96
|
+
postEditor.fillTitle(postTitle)
|
|
97
|
+
postEditor.typeContent('This is a complete post with all metadata filled.')
|
|
98
|
+
postEditor.fillSlug(postSlug)
|
|
99
|
+
postEditor.fillExcerpt(postExcerpt)
|
|
100
|
+
postEditor.toggleFeatured(true)
|
|
101
|
+
|
|
102
|
+
// Save
|
|
103
|
+
postEditor.saveDraft()
|
|
104
|
+
|
|
105
|
+
// Validate slug was saved
|
|
106
|
+
const editEditor = new PostEditor('edit')
|
|
107
|
+
editEditor.validateSlug(postSlug)
|
|
108
|
+
editEditor.validateFeaturedState(true)
|
|
109
|
+
|
|
110
|
+
cy.log('✅ Author created post with all metadata successfully')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('BLOG_POST_CREATE_004: should show validation error for empty title', () => {
|
|
114
|
+
const postEditor = new PostEditor('create')
|
|
115
|
+
|
|
116
|
+
// Click create button
|
|
117
|
+
postsList.clickCreate()
|
|
118
|
+
|
|
119
|
+
// Try to save without title
|
|
120
|
+
postEditor.typeContent('Content without a title')
|
|
121
|
+
postEditor.saveDraft()
|
|
122
|
+
|
|
123
|
+
// Validate error is shown
|
|
124
|
+
postEditor.validateError('title-required')
|
|
125
|
+
|
|
126
|
+
cy.log('✅ Validation error shown for empty title')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// =========================================================================
|
|
131
|
+
// READ - Author can view posts
|
|
132
|
+
// =========================================================================
|
|
133
|
+
describe('READ - Author can view posts', () => {
|
|
134
|
+
it('BLOG_POST_READ_001: should view posts list in table mode', () => {
|
|
135
|
+
// Set table view
|
|
136
|
+
postsList.setViewMode('table')
|
|
137
|
+
|
|
138
|
+
// Validate table view
|
|
139
|
+
postsList.validateViewMode('table')
|
|
140
|
+
|
|
141
|
+
cy.log('✅ Author can view posts in table mode')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('BLOG_POST_READ_002: should view posts list in grid mode', () => {
|
|
145
|
+
// Set grid view
|
|
146
|
+
postsList.setViewMode('grid')
|
|
147
|
+
|
|
148
|
+
// Validate grid view
|
|
149
|
+
postsList.validateViewMode('grid')
|
|
150
|
+
|
|
151
|
+
cy.log('✅ Author can view posts in grid mode')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('BLOG_POST_READ_003: should filter posts by status', () => {
|
|
155
|
+
// Filter by published
|
|
156
|
+
postsList.filterByStatus('published')
|
|
157
|
+
cy.wait(500) // Wait for filter to apply
|
|
158
|
+
|
|
159
|
+
// Filter by draft
|
|
160
|
+
postsList.filterByStatus('draft')
|
|
161
|
+
cy.wait(500)
|
|
162
|
+
|
|
163
|
+
// Show all
|
|
164
|
+
postsList.filterByStatus('all')
|
|
165
|
+
|
|
166
|
+
cy.log('✅ Author can filter posts by status')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('BLOG_POST_READ_004: should search posts by title', () => {
|
|
170
|
+
// Create a post with unique title first
|
|
171
|
+
const uniqueTitle = `Searchable Post ${Date.now()}`
|
|
172
|
+
const postEditor = new PostEditor('create')
|
|
173
|
+
|
|
174
|
+
postsList.clickCreate()
|
|
175
|
+
postEditor.fillTitle(uniqueTitle)
|
|
176
|
+
postEditor.typeContent('Content for search test')
|
|
177
|
+
postEditor.saveDraft()
|
|
178
|
+
postEditor.clickBack()
|
|
179
|
+
|
|
180
|
+
// Search for the post
|
|
181
|
+
postsList.search(uniqueTitle.substring(0, 10))
|
|
182
|
+
cy.wait(500) // Wait for search
|
|
183
|
+
|
|
184
|
+
// Validate search results
|
|
185
|
+
cy.contains(uniqueTitle).should('be.visible')
|
|
186
|
+
|
|
187
|
+
// Clear search
|
|
188
|
+
postsList.clearSearch()
|
|
189
|
+
|
|
190
|
+
cy.log('✅ Author can search posts')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('BLOG_POST_READ_005: should view post details via edit', () => {
|
|
194
|
+
// Check if there are posts to view
|
|
195
|
+
cy.get('body').then($body => {
|
|
196
|
+
const rowSelector = '[data-cy^="posts-row-"]'
|
|
197
|
+
|
|
198
|
+
if ($body.find(rowSelector).length > 0) {
|
|
199
|
+
// Get the first post ID and click edit
|
|
200
|
+
cy.get(rowSelector).first().invoke('attr', 'data-cy').then(dataCy => {
|
|
201
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
202
|
+
if (postId) {
|
|
203
|
+
postsList.clickEdit(postId)
|
|
204
|
+
|
|
205
|
+
// Validate edit page
|
|
206
|
+
const postEditor = new PostEditor('edit')
|
|
207
|
+
postEditor.validatePageVisible()
|
|
208
|
+
|
|
209
|
+
cy.log('✅ Author can view post details')
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
} else {
|
|
213
|
+
cy.log('⚠️ No posts available to view details')
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// =========================================================================
|
|
220
|
+
// UPDATE - Author can update posts
|
|
221
|
+
// =========================================================================
|
|
222
|
+
describe('UPDATE - Author can update posts', () => {
|
|
223
|
+
beforeEach(() => {
|
|
224
|
+
// Create a test post for update tests
|
|
225
|
+
const postEditor = new PostEditor('create')
|
|
226
|
+
const testTitle = `Update Test ${Date.now()}`
|
|
227
|
+
|
|
228
|
+
postsList.clickCreate()
|
|
229
|
+
postEditor.fillTitle(testTitle)
|
|
230
|
+
postEditor.typeContent('Original content')
|
|
231
|
+
postEditor.saveDraft()
|
|
232
|
+
postEditor.clickBack()
|
|
233
|
+
postsList.validateListVisible()
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('BLOG_POST_UPDATE_001: should edit post title and content', () => {
|
|
237
|
+
// Find and edit the first post
|
|
238
|
+
cy.get('[data-cy^="posts-row-"]').first().invoke('attr', 'data-cy').then(dataCy => {
|
|
239
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
240
|
+
if (postId) {
|
|
241
|
+
postsList.clickEdit(postId)
|
|
242
|
+
|
|
243
|
+
const postEditor = new PostEditor('edit')
|
|
244
|
+
postEditor.validatePageVisible()
|
|
245
|
+
|
|
246
|
+
// Update title
|
|
247
|
+
const updatedTitle = `Updated Title ${Date.now()}`
|
|
248
|
+
postEditor.fillTitle(updatedTitle)
|
|
249
|
+
|
|
250
|
+
// Save
|
|
251
|
+
postEditor.saveDraft()
|
|
252
|
+
postEditor.validateAutoSaved()
|
|
253
|
+
|
|
254
|
+
// Go back and verify
|
|
255
|
+
postEditor.clickBack()
|
|
256
|
+
cy.contains(updatedTitle).should('be.visible')
|
|
257
|
+
|
|
258
|
+
cy.log('✅ Author updated post title successfully')
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('BLOG_POST_UPDATE_002: should change post status from draft to published', () => {
|
|
264
|
+
// Find and edit the first draft post
|
|
265
|
+
postsList.filterByStatus('draft')
|
|
266
|
+
|
|
267
|
+
cy.get('body').then($body => {
|
|
268
|
+
const rowSelector = '[data-cy^="posts-row-"]'
|
|
269
|
+
|
|
270
|
+
if ($body.find(rowSelector).length > 0) {
|
|
271
|
+
cy.get(rowSelector).first().invoke('attr', 'data-cy').then(dataCy => {
|
|
272
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
273
|
+
if (postId) {
|
|
274
|
+
postsList.clickEdit(postId)
|
|
275
|
+
|
|
276
|
+
const postEditor = new PostEditor('edit')
|
|
277
|
+
postEditor.validatePageVisible()
|
|
278
|
+
|
|
279
|
+
// Publish the post
|
|
280
|
+
postEditor.publish()
|
|
281
|
+
|
|
282
|
+
// Wait for save operation to complete
|
|
283
|
+
cy.wait(1500)
|
|
284
|
+
|
|
285
|
+
// Reload page to verify status was persisted
|
|
286
|
+
cy.reload()
|
|
287
|
+
|
|
288
|
+
// Wait for page to load
|
|
289
|
+
cy.get('[data-cy="post-edit-container"]').should('be.visible')
|
|
290
|
+
|
|
291
|
+
// After reload, status should show Published and Publish button should be replaced with Unpublish
|
|
292
|
+
cy.get('[data-cy="post-edit-unpublish"]').should('be.visible')
|
|
293
|
+
|
|
294
|
+
cy.log('✅ Author changed post status to published')
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
} else {
|
|
298
|
+
cy.log('⚠️ No draft posts available to publish')
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('BLOG_POST_UPDATE_003: should toggle featured flag', () => {
|
|
304
|
+
cy.get('[data-cy^="posts-row-"]').first().invoke('attr', 'data-cy').then(dataCy => {
|
|
305
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
306
|
+
if (postId) {
|
|
307
|
+
postsList.clickEdit(postId)
|
|
308
|
+
|
|
309
|
+
const postEditor = new PostEditor('edit')
|
|
310
|
+
postEditor.validatePageVisible()
|
|
311
|
+
|
|
312
|
+
// Click on the featured toggle to change its state
|
|
313
|
+
cy.get('[data-cy="post-edit-featured-toggle"]').click({ force: true })
|
|
314
|
+
|
|
315
|
+
// Wait for state to update
|
|
316
|
+
cy.wait(300)
|
|
317
|
+
|
|
318
|
+
// Save changes
|
|
319
|
+
postEditor.saveDraft()
|
|
320
|
+
|
|
321
|
+
// Wait for save
|
|
322
|
+
cy.wait(500)
|
|
323
|
+
|
|
324
|
+
// Toggle again (back to original state)
|
|
325
|
+
cy.get('[data-cy="post-edit-featured-toggle"]').click({ force: true })
|
|
326
|
+
cy.wait(300)
|
|
327
|
+
postEditor.saveDraft()
|
|
328
|
+
|
|
329
|
+
cy.log('✅ Author toggled featured flag successfully')
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('BLOG_POST_UPDATE_004: should update URL slug', () => {
|
|
335
|
+
cy.get('[data-cy^="posts-row-"]').first().invoke('attr', 'data-cy').then(dataCy => {
|
|
336
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
337
|
+
if (postId) {
|
|
338
|
+
postsList.clickEdit(postId)
|
|
339
|
+
|
|
340
|
+
const postEditor = new PostEditor('edit')
|
|
341
|
+
postEditor.validatePageVisible()
|
|
342
|
+
|
|
343
|
+
// Update slug
|
|
344
|
+
const newSlug = `custom-slug-${Date.now()}`
|
|
345
|
+
postEditor.fillSlug(newSlug)
|
|
346
|
+
postEditor.saveDraft()
|
|
347
|
+
|
|
348
|
+
// Validate slug was saved
|
|
349
|
+
postEditor.validateSlug(newSlug)
|
|
350
|
+
|
|
351
|
+
cy.log('✅ Author updated URL slug successfully')
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
// =========================================================================
|
|
358
|
+
// DELETE - Author can delete posts
|
|
359
|
+
// =========================================================================
|
|
360
|
+
describe('DELETE - Author can delete posts', () => {
|
|
361
|
+
it('BLOG_POST_DELETE_001: should delete draft post', () => {
|
|
362
|
+
// Create a post to delete
|
|
363
|
+
const postTitle = `Delete Test ${Date.now()}`
|
|
364
|
+
const postEditor = new PostEditor('create')
|
|
365
|
+
|
|
366
|
+
postsList.clickCreate()
|
|
367
|
+
postEditor.fillTitle(postTitle)
|
|
368
|
+
postEditor.typeContent('Post to be deleted')
|
|
369
|
+
postEditor.saveDraft()
|
|
370
|
+
postEditor.clickBack()
|
|
371
|
+
|
|
372
|
+
// Wait for post to appear
|
|
373
|
+
cy.contains(postTitle).should('be.visible')
|
|
374
|
+
|
|
375
|
+
// Find the post row and get its ID
|
|
376
|
+
cy.contains('[data-cy^="posts-row-"]', postTitle)
|
|
377
|
+
.invoke('attr', 'data-cy')
|
|
378
|
+
.then(dataCy => {
|
|
379
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
380
|
+
if (postId) {
|
|
381
|
+
// Click delete
|
|
382
|
+
postsList.clickDelete(postId)
|
|
383
|
+
|
|
384
|
+
// Confirm delete
|
|
385
|
+
postsList.confirmDelete()
|
|
386
|
+
|
|
387
|
+
// Validate post is removed
|
|
388
|
+
cy.wait(500)
|
|
389
|
+
cy.contains(postTitle).should('not.exist')
|
|
390
|
+
|
|
391
|
+
cy.log('✅ Author deleted draft post successfully')
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('BLOG_POST_DELETE_002: should delete post from edit page', () => {
|
|
397
|
+
// Create a post to delete
|
|
398
|
+
const postTitle = `Delete From Edit ${Date.now()}`
|
|
399
|
+
const postEditor = new PostEditor('create')
|
|
400
|
+
|
|
401
|
+
postsList.clickCreate()
|
|
402
|
+
postEditor.fillTitle(postTitle)
|
|
403
|
+
postEditor.typeContent('Post to be deleted from edit page')
|
|
404
|
+
postEditor.saveDraft()
|
|
405
|
+
|
|
406
|
+
// Now we're on edit page
|
|
407
|
+
const editEditor = new PostEditor('edit')
|
|
408
|
+
editEditor.validatePageVisible()
|
|
409
|
+
|
|
410
|
+
// Click delete button
|
|
411
|
+
editEditor.clickDelete()
|
|
412
|
+
|
|
413
|
+
// Confirm deletion
|
|
414
|
+
editEditor.confirmDelete()
|
|
415
|
+
|
|
416
|
+
// Should redirect to posts list
|
|
417
|
+
cy.url().should('include', '/dashboard/posts')
|
|
418
|
+
cy.contains(postTitle).should('not.exist')
|
|
419
|
+
|
|
420
|
+
cy.log('✅ Author deleted post from edit page successfully')
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('BLOG_POST_DELETE_003: should cancel delete operation', () => {
|
|
424
|
+
// Create a post
|
|
425
|
+
const postTitle = `Cancel Delete ${Date.now()}`
|
|
426
|
+
const postEditor = new PostEditor('create')
|
|
427
|
+
|
|
428
|
+
postsList.clickCreate()
|
|
429
|
+
postEditor.fillTitle(postTitle)
|
|
430
|
+
postEditor.typeContent('Post that should not be deleted')
|
|
431
|
+
postEditor.saveDraft()
|
|
432
|
+
postEditor.clickBack()
|
|
433
|
+
|
|
434
|
+
// Wait for post to appear
|
|
435
|
+
cy.contains(postTitle).should('be.visible')
|
|
436
|
+
|
|
437
|
+
// Find the post and click delete
|
|
438
|
+
cy.contains('[data-cy^="posts-row-"]', postTitle)
|
|
439
|
+
.invoke('attr', 'data-cy')
|
|
440
|
+
.then(dataCy => {
|
|
441
|
+
const postId = dataCy?.replace('posts-row-', '')
|
|
442
|
+
if (postId) {
|
|
443
|
+
postsList.clickDelete(postId)
|
|
444
|
+
|
|
445
|
+
// Cancel delete
|
|
446
|
+
postsList.cancelDelete()
|
|
447
|
+
|
|
448
|
+
// Validate post still exists
|
|
449
|
+
cy.contains(postTitle).should('be.visible')
|
|
450
|
+
|
|
451
|
+
cy.log('✅ Author cancelled delete operation successfully')
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
after(() => {
|
|
458
|
+
cy.log('✅ Posts CRUD tests completed')
|
|
459
|
+
})
|
|
460
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Posts CRUD - E2E Tests
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Tests for Posts entity CRUD operations in the Blog theme.
|
|
6
|
+
|
|
7
|
+
**Test File:** `test/cypress/e2e/themes/blog/posts/posts-crud.cy.ts`
|
|
8
|
+
|
|
9
|
+
## Entity Characteristics
|
|
10
|
+
|
|
11
|
+
| Property | Value |
|
|
12
|
+
|----------|-------|
|
|
13
|
+
| **Entity** | Posts |
|
|
14
|
+
| **UI** | Custom (PostsList, PostEditor) |
|
|
15
|
+
| **Team Mode** | Single-user (isolated) |
|
|
16
|
+
| **Status** | Draft/Published workflow |
|
|
17
|
+
| **Features** | Featured toggle, WYSIWYG editor |
|
|
18
|
+
|
|
19
|
+
## Test Users
|
|
20
|
+
|
|
21
|
+
| Author | Email | Role |
|
|
22
|
+
|--------|-------|------|
|
|
23
|
+
| Marcos Tech | blog_author_marcos@nextspark.dev | Owner |
|
|
24
|
+
| Lucia Lifestyle | blog_author_lucia@nextspark.dev | Owner |
|
|
25
|
+
| Carlos Finance | blog_author_carlos@nextspark.dev | Owner |
|
|
26
|
+
|
|
27
|
+
**Password:** `Test1234`
|
|
28
|
+
|
|
29
|
+
## Test Coverage
|
|
30
|
+
|
|
31
|
+
### 1. CREATE (4 tests)
|
|
32
|
+
|
|
33
|
+
| ID | Test Case | Description | Status |
|
|
34
|
+
|----|-----------|-------------|--------|
|
|
35
|
+
| BLOG_POST_CREATE_001 | Create draft post | Create a new post as draft | ✅ Passing |
|
|
36
|
+
| BLOG_POST_CREATE_002 | Create and publish immediately | Create post and publish in one action | ✅ Passing |
|
|
37
|
+
| BLOG_POST_CREATE_003 | Create with all metadata | Create post with slug, excerpt, featured | ✅ Passing |
|
|
38
|
+
| BLOG_POST_CREATE_004 | Validation error empty title | Show error when title is empty | ✅ Passing |
|
|
39
|
+
|
|
40
|
+
### 2. READ (5 tests)
|
|
41
|
+
|
|
42
|
+
| ID | Test Case | Description | Status |
|
|
43
|
+
|----|-----------|-------------|--------|
|
|
44
|
+
| BLOG_POST_READ_001 | View posts in table mode | Display posts in table layout | ✅ Passing |
|
|
45
|
+
| BLOG_POST_READ_002 | View posts in grid mode | Display posts in grid layout | ✅ Passing |
|
|
46
|
+
| BLOG_POST_READ_003 | Filter by status | Filter posts by published/draft | ✅ Passing |
|
|
47
|
+
| BLOG_POST_READ_004 | Search posts | Search posts by title | ✅ Passing |
|
|
48
|
+
| BLOG_POST_READ_005 | View post details | Navigate to edit page | ✅ Passing |
|
|
49
|
+
|
|
50
|
+
### 3. UPDATE (4 tests)
|
|
51
|
+
|
|
52
|
+
| ID | Test Case | Description | Status |
|
|
53
|
+
|----|-----------|-------------|--------|
|
|
54
|
+
| BLOG_POST_UPDATE_001 | Edit title and content | Update post title and content | ✅ Passing |
|
|
55
|
+
| BLOG_POST_UPDATE_002 | Change status | Publish draft post | ✅ Passing |
|
|
56
|
+
| BLOG_POST_UPDATE_003 | Toggle featured flag | Mark post as featured | ✅ Passing |
|
|
57
|
+
| BLOG_POST_UPDATE_004 | Update URL slug | Change post slug | ✅ Passing |
|
|
58
|
+
|
|
59
|
+
### 4. DELETE (3 tests)
|
|
60
|
+
|
|
61
|
+
| ID | Test Case | Description | Status |
|
|
62
|
+
|----|-----------|-------------|--------|
|
|
63
|
+
| BLOG_POST_DELETE_001 | Delete draft post | Delete post from list actions | ✅ Passing |
|
|
64
|
+
| BLOG_POST_DELETE_002 | Delete from edit page | Delete post from editor | ✅ Passing |
|
|
65
|
+
| BLOG_POST_DELETE_003 | Cancel delete | Cancel delete operation | ✅ Passing |
|
|
66
|
+
|
|
67
|
+
## Summary
|
|
68
|
+
|
|
69
|
+
| Category | Total | Passing | Pending | Failing |
|
|
70
|
+
|----------|-------|---------|---------|---------|
|
|
71
|
+
| CREATE | 4 | 4 | 0 | 0 |
|
|
72
|
+
| READ | 5 | 5 | 0 | 0 |
|
|
73
|
+
| UPDATE | 4 | 4 | 0 | 0 |
|
|
74
|
+
| DELETE | 3 | 3 | 0 | 0 |
|
|
75
|
+
| **Total** | **16** | **16** | **0** | **0** |
|
|
76
|
+
|
|
77
|
+
## Data-cy Selectors Used
|
|
78
|
+
|
|
79
|
+
### PostsList
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
[data-cy="posts-list-container"]
|
|
83
|
+
[data-cy="posts-stat-{status}"]
|
|
84
|
+
[data-cy="posts-search-input"]
|
|
85
|
+
[data-cy="posts-view-{mode}"]
|
|
86
|
+
[data-cy="posts-row-{id}"]
|
|
87
|
+
[data-cy="posts-actions-{id}"]
|
|
88
|
+
[data-cy="posts-create-button"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### PostEditor
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
[data-cy="post-{mode}-container"]
|
|
95
|
+
[data-cy="post-{mode}-title"]
|
|
96
|
+
[data-cy="post-{mode}-content"]
|
|
97
|
+
[data-cy="post-{mode}-save"]
|
|
98
|
+
[data-cy="post-{mode}-publish"]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## POM Classes
|
|
102
|
+
|
|
103
|
+
- **PostsList:** `test/cypress/src/classes/themes/blog/PostsList.js`
|
|
104
|
+
- **PostEditor:** `test/cypress/src/classes/themes/blog/PostEditor.js`
|
|
105
|
+
- **WysiwygEditor:** `test/cypress/src/classes/themes/blog/WysiwygEditor.js`
|
|
106
|
+
|
|
107
|
+
## Running Tests
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx cypress run --spec "test/cypress/e2e/themes/blog/posts/posts-crud.cy.ts"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
**Last Updated:** 2025-12-04
|