@nextsparkjs/theme-blog 0.1.0-beta.17 → 0.1.0-beta.170
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/authors/[username]/route.ts +5 -2
- package/api/authors/docs.md +135 -0
- package/api/authors/presets.ts +45 -0
- package/api/authors/route.ts +4 -1
- package/api/posts/public/docs.md +124 -0
- package/api/posts/public/presets.ts +65 -0
- package/api/posts/public/route.ts +4 -1
- package/config/app.config.ts +4 -5
- package/config/billing.config.ts +4 -7
- package/config/dashboard.config.ts +13 -0
- package/config/permissions.config.ts +11 -0
- package/entities/categories/api/docs.md +119 -0
- package/entities/categories/api/presets.ts +67 -0
- package/entities/posts/api/docs.md +174 -0
- package/entities/posts/api/presets.ts +137 -0
- package/lib/selectors.ts +2 -3
- package/nextsparkjs-theme-blog-0.1.0-beta.137.tgz +0 -0
- package/package.json +4 -3
- package/styles/globals.css +45 -0
- 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 +378 -0
- package/tests/cypress/src/components/EntityList.ts +378 -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 +151 -0
- package/tests/cypress/support/e2e.ts +90 -0
- package/tests/cypress.config.ts +154 -0
- package/tests/jest/__mocks__/jose.js +22 -0
- package/tests/jest/__mocks__/next-server.js +56 -0
- package/tests/jest/jest.config.cjs +131 -0
- package/tests/jest/setup.ts +170 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Categories CRUD Tests - Blog Theme
|
|
5
|
+
*
|
|
6
|
+
* Tests for Categories entity CRUD operations.
|
|
7
|
+
* Uses generic EntityList and EntityForm POM classes.
|
|
8
|
+
*
|
|
9
|
+
* Theme Mode: single-user (isolated blogs, no team collaboration)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { EntityList } from '../../src/components/EntityList'
|
|
13
|
+
import { EntityForm } from '../../src/components/EntityForm'
|
|
14
|
+
import { loginAsBlogAuthor } from '../../src/session-helpers'
|
|
15
|
+
|
|
16
|
+
describe('Categories CRUD - Blog Author (Full Access)', () => {
|
|
17
|
+
const categoryList = new EntityList('categories')
|
|
18
|
+
const categoryForm = new EntityForm('categories')
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
loginAsBlogAuthor('MARCOS')
|
|
22
|
+
cy.visit('/dashboard/categories')
|
|
23
|
+
categoryList.validateListVisible()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// =========================================================================
|
|
27
|
+
// CREATE - Author can create categories
|
|
28
|
+
// =========================================================================
|
|
29
|
+
describe('CREATE - Author can create categories', () => {
|
|
30
|
+
it('BLOG_CAT_CREATE_001: should create new category successfully', () => {
|
|
31
|
+
const timestamp = Date.now()
|
|
32
|
+
const categoryName = `Category ${timestamp}`
|
|
33
|
+
const categorySlug = `category-${timestamp}`
|
|
34
|
+
|
|
35
|
+
// Click create button
|
|
36
|
+
categoryList.clickAdd()
|
|
37
|
+
|
|
38
|
+
// Validate form is visible
|
|
39
|
+
categoryForm.validateFormVisible()
|
|
40
|
+
|
|
41
|
+
// Fill required fields (name and slug are both required)
|
|
42
|
+
categoryForm.fillField('name', categoryName)
|
|
43
|
+
categoryForm.fillField('slug', categorySlug)
|
|
44
|
+
|
|
45
|
+
// Submit form
|
|
46
|
+
categoryForm.submit()
|
|
47
|
+
|
|
48
|
+
// Validate redirect to list
|
|
49
|
+
cy.url().should('include', '/dashboard/categories')
|
|
50
|
+
|
|
51
|
+
// Validate category appears in list
|
|
52
|
+
cy.contains(categoryName).should('be.visible')
|
|
53
|
+
|
|
54
|
+
cy.log('✅ Author created category successfully')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('BLOG_CAT_CREATE_002: should create category with description', () => {
|
|
58
|
+
const timestamp = Date.now()
|
|
59
|
+
const categoryName = `Full Category ${timestamp}`
|
|
60
|
+
const categorySlug = `full-category-${timestamp}`
|
|
61
|
+
const categoryDescription = 'This is a category with a description'
|
|
62
|
+
|
|
63
|
+
// Click create button
|
|
64
|
+
categoryList.clickAdd()
|
|
65
|
+
|
|
66
|
+
// Fill all fields (name and slug are required)
|
|
67
|
+
categoryForm.fillField('name', categoryName)
|
|
68
|
+
categoryForm.fillField('slug', categorySlug)
|
|
69
|
+
categoryForm.fillField('description', categoryDescription)
|
|
70
|
+
|
|
71
|
+
// Submit form
|
|
72
|
+
categoryForm.submit()
|
|
73
|
+
|
|
74
|
+
// Validate category appears in list
|
|
75
|
+
cy.url().should('include', '/dashboard/categories')
|
|
76
|
+
cy.contains(categoryName).should('be.visible')
|
|
77
|
+
|
|
78
|
+
cy.log('✅ Author created category with description successfully')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('BLOG_CAT_CREATE_003: should show validation error for empty name', () => {
|
|
82
|
+
// Click create button
|
|
83
|
+
categoryList.clickAdd()
|
|
84
|
+
|
|
85
|
+
// Try to submit without filling name
|
|
86
|
+
categoryForm.submit()
|
|
87
|
+
|
|
88
|
+
// Validate error is shown (form should have validation)
|
|
89
|
+
cy.get('body').then($body => {
|
|
90
|
+
// Check for validation message or form stays on same page
|
|
91
|
+
const isOnFormPage = $body.find('[data-cy="categories-form"]').length > 0 ||
|
|
92
|
+
$body.find('[data-cy="entity-form"]').length > 0
|
|
93
|
+
|
|
94
|
+
if (isOnFormPage) {
|
|
95
|
+
cy.log('✅ Form validation prevented submission')
|
|
96
|
+
} else {
|
|
97
|
+
// Check for error message
|
|
98
|
+
cy.get('[role="alert"], .text-destructive, [data-cy*="error"]')
|
|
99
|
+
.should('be.visible')
|
|
100
|
+
cy.log('✅ Validation error shown for empty name')
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// =========================================================================
|
|
107
|
+
// READ - Author can view categories
|
|
108
|
+
// =========================================================================
|
|
109
|
+
describe('READ - Author can view categories', () => {
|
|
110
|
+
it('BLOG_CAT_READ_001: should view categories list', () => {
|
|
111
|
+
// Validate list is visible
|
|
112
|
+
categoryList.validateListVisible()
|
|
113
|
+
|
|
114
|
+
cy.log('✅ Author can view categories list')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('BLOG_CAT_READ_002: should search categories', () => {
|
|
118
|
+
// Create a category with unique name first
|
|
119
|
+
const timestamp = Date.now()
|
|
120
|
+
const uniqueName = `Searchable Cat ${timestamp}`
|
|
121
|
+
const uniqueSlug = `searchable-cat-${timestamp}`
|
|
122
|
+
|
|
123
|
+
categoryList.clickAdd()
|
|
124
|
+
categoryForm.fillField('name', uniqueName)
|
|
125
|
+
categoryForm.fillField('slug', uniqueSlug)
|
|
126
|
+
categoryForm.submit()
|
|
127
|
+
|
|
128
|
+
// Go back to list
|
|
129
|
+
cy.visit('/dashboard/categories')
|
|
130
|
+
categoryList.validateListVisible()
|
|
131
|
+
|
|
132
|
+
// Search for the category
|
|
133
|
+
categoryList.search(uniqueName.substring(0, 10))
|
|
134
|
+
cy.wait(500) // Wait for search
|
|
135
|
+
|
|
136
|
+
// Validate search results
|
|
137
|
+
cy.contains(uniqueName).should('be.visible')
|
|
138
|
+
|
|
139
|
+
// Clear search
|
|
140
|
+
categoryList.clearSearch()
|
|
141
|
+
|
|
142
|
+
cy.log('✅ Author can search categories')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// =========================================================================
|
|
147
|
+
// UPDATE - Author can update categories
|
|
148
|
+
// =========================================================================
|
|
149
|
+
describe('UPDATE - Author can update categories', () => {
|
|
150
|
+
beforeEach(() => {
|
|
151
|
+
// Create a test category for update tests
|
|
152
|
+
const timestamp = Date.now()
|
|
153
|
+
const testName = `Update Cat ${timestamp}`
|
|
154
|
+
const testSlug = `update-cat-${timestamp}`
|
|
155
|
+
|
|
156
|
+
categoryList.clickAdd()
|
|
157
|
+
categoryForm.fillField('name', testName)
|
|
158
|
+
categoryForm.fillField('slug', testSlug)
|
|
159
|
+
categoryForm.submit()
|
|
160
|
+
|
|
161
|
+
cy.visit('/dashboard/categories')
|
|
162
|
+
categoryList.validateListVisible()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('BLOG_CAT_UPDATE_001: should edit category name', () => {
|
|
166
|
+
// Find and edit the first category
|
|
167
|
+
cy.get('body').then($body => {
|
|
168
|
+
const editSelector = '[data-cy*="edit"]'
|
|
169
|
+
|
|
170
|
+
if ($body.find(editSelector).length > 0) {
|
|
171
|
+
cy.get(editSelector).first().click()
|
|
172
|
+
|
|
173
|
+
// Validate form is visible
|
|
174
|
+
categoryForm.validateFormVisible()
|
|
175
|
+
|
|
176
|
+
// Update name
|
|
177
|
+
const updatedName = `Updated Category ${Date.now()}`
|
|
178
|
+
categoryForm.fillField('name', updatedName)
|
|
179
|
+
|
|
180
|
+
// Submit form
|
|
181
|
+
categoryForm.submit()
|
|
182
|
+
|
|
183
|
+
// Validate update
|
|
184
|
+
cy.url().should('include', '/dashboard/categories')
|
|
185
|
+
cy.contains(updatedName).should('be.visible')
|
|
186
|
+
|
|
187
|
+
cy.log('✅ Author updated category name successfully')
|
|
188
|
+
} else {
|
|
189
|
+
cy.log('⚠️ No categories available to edit')
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('BLOG_CAT_UPDATE_002: should update category description', () => {
|
|
195
|
+
cy.get('body').then($body => {
|
|
196
|
+
const editSelector = '[data-cy*="edit"]'
|
|
197
|
+
|
|
198
|
+
if ($body.find(editSelector).length > 0) {
|
|
199
|
+
cy.get(editSelector).first().click()
|
|
200
|
+
|
|
201
|
+
categoryForm.validateFormVisible()
|
|
202
|
+
|
|
203
|
+
// Update description
|
|
204
|
+
const updatedDescription = `Updated description ${Date.now()}`
|
|
205
|
+
categoryForm.fillField('description', updatedDescription)
|
|
206
|
+
|
|
207
|
+
// Submit form
|
|
208
|
+
categoryForm.submit()
|
|
209
|
+
|
|
210
|
+
cy.url().should('include', '/dashboard/categories')
|
|
211
|
+
|
|
212
|
+
cy.log('✅ Author updated category description successfully')
|
|
213
|
+
} else {
|
|
214
|
+
cy.log('⚠️ No categories available to edit')
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// =========================================================================
|
|
221
|
+
// DELETE - Author can delete categories
|
|
222
|
+
// =========================================================================
|
|
223
|
+
describe('DELETE - Author can delete categories', () => {
|
|
224
|
+
it('BLOG_CAT_DELETE_001: should delete category successfully', () => {
|
|
225
|
+
// Create a category to delete
|
|
226
|
+
const timestamp = Date.now()
|
|
227
|
+
const categoryName = `Delete Cat ${timestamp}`
|
|
228
|
+
const categorySlug = `delete-cat-${timestamp}`
|
|
229
|
+
|
|
230
|
+
categoryList.clickAdd()
|
|
231
|
+
categoryForm.fillField('name', categoryName)
|
|
232
|
+
categoryForm.fillField('slug', categorySlug)
|
|
233
|
+
categoryForm.submit()
|
|
234
|
+
|
|
235
|
+
// Wait for category to appear in list
|
|
236
|
+
cy.visit('/dashboard/categories')
|
|
237
|
+
cy.contains(categoryName).should('be.visible')
|
|
238
|
+
|
|
239
|
+
// Find and delete
|
|
240
|
+
cy.get('body').then($body => {
|
|
241
|
+
const deleteSelector = '[data-cy*="delete"]'
|
|
242
|
+
|
|
243
|
+
if ($body.find(deleteSelector).length > 0) {
|
|
244
|
+
cy.get(deleteSelector).first().click()
|
|
245
|
+
|
|
246
|
+
// Confirm deletion if modal appears
|
|
247
|
+
cy.get('body').then($body2 => {
|
|
248
|
+
if ($body2.find('[data-cy="confirm-delete"]').length > 0) {
|
|
249
|
+
cy.get('[data-cy="confirm-delete"]').click()
|
|
250
|
+
} else if ($body2.find('[data-cy*="delete-confirm"]').length > 0) {
|
|
251
|
+
cy.get('[data-cy*="delete-confirm"]').click()
|
|
252
|
+
} else if ($body2.find('[role="dialog"] button[data-cy="confirm-delete"]').length > 0) {
|
|
253
|
+
// Click the delete button in the modal (not the trigger)
|
|
254
|
+
cy.get('[role="dialog"] button[data-cy="confirm-delete"]').click()
|
|
255
|
+
} else if ($body2.find('[role="dialog"]').length > 0) {
|
|
256
|
+
// Fallback: click any button that looks like confirm delete in the dialog
|
|
257
|
+
cy.get('[role="dialog"] button').last().click()
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Validate deletion
|
|
262
|
+
cy.wait(500)
|
|
263
|
+
cy.contains(categoryName).should('not.exist')
|
|
264
|
+
|
|
265
|
+
cy.log('✅ Author deleted category successfully')
|
|
266
|
+
} else {
|
|
267
|
+
cy.log('⚠️ Delete button not found')
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('BLOG_CAT_DELETE_002: should cancel delete operation', () => {
|
|
273
|
+
// Create a category
|
|
274
|
+
const timestamp = Date.now()
|
|
275
|
+
const categoryName = `Cancel Delete Cat ${timestamp}`
|
|
276
|
+
const categorySlug = `cancel-delete-cat-${timestamp}`
|
|
277
|
+
|
|
278
|
+
categoryList.clickAdd()
|
|
279
|
+
categoryForm.fillField('name', categoryName)
|
|
280
|
+
categoryForm.fillField('slug', categorySlug)
|
|
281
|
+
categoryForm.submit()
|
|
282
|
+
|
|
283
|
+
// Wait for category to appear
|
|
284
|
+
cy.visit('/dashboard/categories')
|
|
285
|
+
cy.contains(categoryName).should('be.visible')
|
|
286
|
+
|
|
287
|
+
// Find and try to delete, then cancel
|
|
288
|
+
cy.get('body').then($body => {
|
|
289
|
+
const deleteSelector = '[data-cy*="delete"]'
|
|
290
|
+
|
|
291
|
+
if ($body.find(deleteSelector).length > 0) {
|
|
292
|
+
cy.get(deleteSelector).first().click()
|
|
293
|
+
|
|
294
|
+
// Cancel deletion if modal appears
|
|
295
|
+
cy.get('body').then($body2 => {
|
|
296
|
+
if ($body2.find('[data-cy="cancel-delete"]').length > 0) {
|
|
297
|
+
cy.get('[data-cy="cancel-delete"]').click()
|
|
298
|
+
} else if ($body2.find('[data-cy*="delete-cancel"]').length > 0) {
|
|
299
|
+
cy.get('[data-cy*="delete-cancel"]').click()
|
|
300
|
+
} else if ($body2.find('[role="dialog"] button[data-cy="cancel-delete"]').length > 0) {
|
|
301
|
+
cy.get('[role="dialog"] button[data-cy="cancel-delete"]').click()
|
|
302
|
+
} else if ($body2.find('[role="dialog"]').length > 0) {
|
|
303
|
+
// Fallback: click the first button (usually cancel) in the dialog
|
|
304
|
+
cy.get('[role="dialog"] button').first().click()
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// Validate category still exists
|
|
309
|
+
cy.contains(categoryName).should('be.visible')
|
|
310
|
+
|
|
311
|
+
cy.log('✅ Author cancelled delete operation successfully')
|
|
312
|
+
} else {
|
|
313
|
+
cy.log('⚠️ Delete button not found')
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
after(() => {
|
|
320
|
+
cy.log('✅ Categories CRUD tests completed')
|
|
321
|
+
})
|
|
322
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Categories CRUD - E2E Tests
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Tests for Categories entity CRUD operations in the Blog theme.
|
|
6
|
+
|
|
7
|
+
**Test File:** `test/cypress/e2e/themes/blog/categories/categories-crud.cy.ts`
|
|
8
|
+
|
|
9
|
+
## Entity Characteristics
|
|
10
|
+
|
|
11
|
+
| Property | Value |
|
|
12
|
+
|----------|-------|
|
|
13
|
+
| **Entity** | Categories |
|
|
14
|
+
| **UI** | Generic (EntityList, EntityForm) |
|
|
15
|
+
| **Team Mode** | Single-user (isolated) |
|
|
16
|
+
| **Fields** | name, slug, description |
|
|
17
|
+
|
|
18
|
+
## Test Coverage
|
|
19
|
+
|
|
20
|
+
### 1. CREATE (3 tests)
|
|
21
|
+
|
|
22
|
+
| ID | Test Case | Description | Status |
|
|
23
|
+
|----|-----------|-------------|--------|
|
|
24
|
+
| BLOG_CAT_CREATE_001 | Create category | Create with name and slug | ✅ Passing |
|
|
25
|
+
| BLOG_CAT_CREATE_002 | Create with description | Create with all fields | ✅ Passing |
|
|
26
|
+
| BLOG_CAT_CREATE_003 | Validation empty name | Show error for empty name | ✅ Passing |
|
|
27
|
+
|
|
28
|
+
### 2. READ (2 tests)
|
|
29
|
+
|
|
30
|
+
| ID | Test Case | Description | Status |
|
|
31
|
+
|----|-----------|-------------|--------|
|
|
32
|
+
| BLOG_CAT_READ_001 | View categories list | Display list of categories | ✅ Passing |
|
|
33
|
+
| BLOG_CAT_READ_002 | Search categories | Search by name | ✅ Passing |
|
|
34
|
+
|
|
35
|
+
### 3. UPDATE (2 tests)
|
|
36
|
+
|
|
37
|
+
| ID | Test Case | Description | Status |
|
|
38
|
+
|----|-----------|-------------|--------|
|
|
39
|
+
| BLOG_CAT_UPDATE_001 | Edit name | Update category name | ✅ Passing |
|
|
40
|
+
| BLOG_CAT_UPDATE_002 | Edit description | Update description | ✅ Passing |
|
|
41
|
+
|
|
42
|
+
### 4. DELETE (2 tests)
|
|
43
|
+
|
|
44
|
+
| ID | Test Case | Description | Status |
|
|
45
|
+
|----|-----------|-------------|--------|
|
|
46
|
+
| BLOG_CAT_DELETE_001 | Delete category | Confirm and delete | ✅ Passing |
|
|
47
|
+
| BLOG_CAT_DELETE_002 | Cancel delete | Cancel delete operation | ✅ Passing |
|
|
48
|
+
|
|
49
|
+
## Summary
|
|
50
|
+
|
|
51
|
+
| Category | Total | Passing | Pending | Failing |
|
|
52
|
+
|----------|-------|---------|---------|---------|
|
|
53
|
+
| CREATE | 3 | 3 | 0 | 0 |
|
|
54
|
+
| READ | 2 | 2 | 0 | 0 |
|
|
55
|
+
| UPDATE | 2 | 2 | 0 | 0 |
|
|
56
|
+
| DELETE | 2 | 2 | 0 | 0 |
|
|
57
|
+
| **Total** | **9** | **9** | **0** | **0** |
|
|
58
|
+
|
|
59
|
+
## POM Classes
|
|
60
|
+
|
|
61
|
+
Uses generic entity classes:
|
|
62
|
+
- **EntityList:** `test/cypress/src/classes/components/entities/EntityList.js`
|
|
63
|
+
- **EntityForm:** `test/cypress/src/classes/components/entities/EntityForm.js`
|
|
64
|
+
|
|
65
|
+
## Running Tests
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx cypress run --spec "test/cypress/e2e/themes/blog/categories/categories-crud.cy.ts"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
**Last Updated:** 2025-12-04
|