@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.
Files changed (49) hide show
  1. package/api/authors/[username]/route.ts +5 -2
  2. package/api/authors/docs.md +135 -0
  3. package/api/authors/presets.ts +45 -0
  4. package/api/authors/route.ts +4 -1
  5. package/api/posts/public/docs.md +124 -0
  6. package/api/posts/public/presets.ts +65 -0
  7. package/api/posts/public/route.ts +4 -1
  8. package/config/app.config.ts +4 -5
  9. package/config/billing.config.ts +4 -7
  10. package/config/dashboard.config.ts +13 -0
  11. package/config/permissions.config.ts +11 -0
  12. package/entities/categories/api/docs.md +119 -0
  13. package/entities/categories/api/presets.ts +67 -0
  14. package/entities/posts/api/docs.md +174 -0
  15. package/entities/posts/api/presets.ts +137 -0
  16. package/lib/selectors.ts +2 -3
  17. package/nextsparkjs-theme-blog-0.1.0-beta.137.tgz +0 -0
  18. package/package.json +4 -3
  19. package/styles/globals.css +45 -0
  20. package/tests/cypress/e2e/README.md +170 -0
  21. package/tests/cypress/e2e/categories/categories-crud.cy.ts +322 -0
  22. package/tests/cypress/e2e/categories/categories-crud.md +73 -0
  23. package/tests/cypress/e2e/posts/posts-crud.cy.ts +460 -0
  24. package/tests/cypress/e2e/posts/posts-crud.md +115 -0
  25. package/tests/cypress/e2e/posts/posts-editor.cy.ts +290 -0
  26. package/tests/cypress/e2e/posts/posts-editor.md +139 -0
  27. package/tests/cypress/e2e/posts/posts-status-workflow.cy.ts +302 -0
  28. package/tests/cypress/e2e/posts/posts-status-workflow.md +83 -0
  29. package/tests/cypress/fixtures/blocks.json +9 -0
  30. package/tests/cypress/fixtures/entities.json +42 -0
  31. package/tests/cypress/src/FeaturedImageUpload.js +131 -0
  32. package/tests/cypress/src/PostEditor.js +386 -0
  33. package/tests/cypress/src/PostsList.js +350 -0
  34. package/tests/cypress/src/WysiwygEditor.js +373 -0
  35. package/tests/cypress/src/components/EntityForm.ts +378 -0
  36. package/tests/cypress/src/components/EntityList.ts +378 -0
  37. package/tests/cypress/src/components/PostEditorPOM.ts +447 -0
  38. package/tests/cypress/src/components/PostsPOM.ts +362 -0
  39. package/tests/cypress/src/components/index.ts +18 -0
  40. package/tests/cypress/src/index.js +33 -0
  41. package/tests/cypress/src/selectors.ts +49 -0
  42. package/tests/cypress/src/session-helpers.ts +151 -0
  43. package/tests/cypress/support/e2e.ts +90 -0
  44. package/tests/cypress.config.ts +154 -0
  45. package/tests/jest/__mocks__/jose.js +22 -0
  46. package/tests/jest/__mocks__/next-server.js +56 -0
  47. package/tests/jest/jest.config.cjs +131 -0
  48. package/tests/jest/setup.ts +170 -0
  49. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Generic Entity List POM
3
+ *
4
+ * Page Object Model for entity list pages in Blog theme.
5
+ * Uses standardized data-cy selectors from entities.json.
6
+ *
7
+ * Convention: {slug}-{component}-{detail}
8
+ * Examples: posts-table, categories-create-btn, posts-row-{id}
9
+ *
10
+ * Usage:
11
+ * const postsList = EntityList.for('posts')
12
+ * const categoriesList = EntityList.for('categories')
13
+ */
14
+
15
+ // Import entity configs from theme
16
+ import entitiesConfig from '../../fixtures/entities.json'
17
+
18
+ export interface EntityConfig {
19
+ slug: string
20
+ singular: string
21
+ plural: string
22
+ tableName: string
23
+ fields: string[]
24
+ sections: string[]
25
+ filters: string[]
26
+ }
27
+
28
+ export class EntityList {
29
+ protected config: EntityConfig
30
+ protected slug: string
31
+
32
+ /**
33
+ * Create a new EntityList POM instance from entity config
34
+ */
35
+ constructor(entityKey: string) {
36
+ const config = entitiesConfig.entities[entityKey as keyof typeof entitiesConfig.entities]
37
+ if (!config) {
38
+ throw new Error(`Unknown entity: ${entityKey}. Available: ${Object.keys(entitiesConfig.entities).join(', ')}`)
39
+ }
40
+ this.config = config as EntityConfig
41
+ this.slug = config.slug
42
+ }
43
+
44
+ // ============================================
45
+ // STATIC FACTORY METHOD
46
+ // ============================================
47
+
48
+ /**
49
+ * Create an EntityList from entity key
50
+ */
51
+ static for(entityKey: string): EntityList {
52
+ return new EntityList(entityKey)
53
+ }
54
+
55
+ // ============================================
56
+ // DYNAMIC SELECTORS (from entities.json convention)
57
+ // ============================================
58
+
59
+ /**
60
+ * Get selectors for this entity following the standard convention
61
+ */
62
+ get selectors() {
63
+ const slug = this.slug
64
+ return {
65
+ // Page elements
66
+ page: `[data-cy="${slug}-page"]`,
67
+ pageTitle: '[data-cy="page-title"]',
68
+
69
+ // Table
70
+ table: `[data-cy="${slug}-table"]`,
71
+
72
+ // Create button
73
+ createButton: `[data-cy="${slug}-create-btn"]`,
74
+
75
+ // Search
76
+ search: `[data-cy="${slug}-search"]`,
77
+ searchInput: `[data-cy="${slug}-search-input"]`,
78
+
79
+ // Filters
80
+ filter: (fieldName: string) => `[data-cy="${slug}-filter-${fieldName}"]`,
81
+ filterTrigger: (fieldName: string) => `[data-cy="${slug}-filter-${fieldName}-trigger"]`,
82
+ filterOption: (fieldName: string, value: string) => `[data-cy="${slug}-filter-${fieldName}-option-${value}"]`,
83
+
84
+ // Rows
85
+ row: (id: string) => `[data-cy="${slug}-row-${id}"]`,
86
+ rowGeneric: `[data-cy^="${slug}-row-"]`,
87
+
88
+ // Cards (for grid view)
89
+ card: (id: string) => `[data-cy="${slug}-card-${id}"]`,
90
+ cardGeneric: `[data-cy^="${slug}-card-"]`,
91
+
92
+ // Actions
93
+ actionEdit: (id: string) => `[data-cy="${slug}-action-edit-${id}"]`,
94
+ actionDelete: (id: string) => `[data-cy="${slug}-action-delete-${id}"]`,
95
+ actionView: (id: string) => `[data-cy="${slug}-action-view-${id}"]`,
96
+ actionsDropdown: (id: string) => `[data-cy="${slug}-actions-${id}"]`,
97
+ actionsTrigger: (id: string) => `[data-cy="${slug}-actions-trigger-${id}"]`,
98
+
99
+ // Pagination
100
+ pagination: `[data-cy="${slug}-pagination"]`,
101
+ paginationPrev: `[data-cy="${slug}-pagination-prev"]`,
102
+ paginationNext: `[data-cy="${slug}-pagination-next"]`,
103
+
104
+ // Bulk actions
105
+ bulkActions: `[data-cy="${slug}-bulk-actions"]`,
106
+
107
+ // Empty state
108
+ emptyState: `[data-cy="${slug}-empty"]`,
109
+
110
+ // Dialogs
111
+ confirmDelete: `[data-cy="${slug}-confirm-delete"]`,
112
+ confirmDeleteBtn: `[data-cy="${slug}-confirm-delete-btn"]`,
113
+ cancelDeleteBtn: `[data-cy="${slug}-cancel-delete-btn"]`,
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Get the entity config
119
+ */
120
+ get entityConfig(): EntityConfig {
121
+ return this.config
122
+ }
123
+
124
+ // ============================================
125
+ // VALIDATION METHODS
126
+ // ============================================
127
+
128
+ /**
129
+ * Validate the list page is visible
130
+ */
131
+ validatePageVisible() {
132
+ cy.get(this.selectors.page).should('be.visible')
133
+ return this
134
+ }
135
+
136
+ /**
137
+ * Validate the table is visible
138
+ */
139
+ validateTableVisible() {
140
+ cy.get(this.selectors.table).should('be.visible')
141
+ return this
142
+ }
143
+
144
+ /**
145
+ * Validate the page title text
146
+ */
147
+ validatePageTitle(expectedTitle: string) {
148
+ cy.get(this.selectors.pageTitle).should('contain.text', expectedTitle)
149
+ return this
150
+ }
151
+
152
+ /**
153
+ * Validate the create button is visible
154
+ */
155
+ validateCreateButtonVisible() {
156
+ cy.get(this.selectors.createButton).should('be.visible')
157
+ return this
158
+ }
159
+
160
+ /**
161
+ * Validate the table has rows
162
+ */
163
+ validateTableHasRows() {
164
+ cy.get(this.selectors.table).find('tbody tr').should('have.length.at.least', 1)
165
+ return this
166
+ }
167
+
168
+ /**
169
+ * Validate the table is empty
170
+ */
171
+ validateTableEmpty() {
172
+ cy.get(this.selectors.table).find('tbody tr').should('have.length', 0)
173
+ return this
174
+ }
175
+
176
+ // ============================================
177
+ // INTERACTION METHODS
178
+ // ============================================
179
+
180
+ /**
181
+ * Click the create button
182
+ */
183
+ clickCreate() {
184
+ cy.get(this.selectors.createButton).click()
185
+ return this
186
+ }
187
+
188
+ /**
189
+ * Search for a term
190
+ */
191
+ search(term: string) {
192
+ cy.get(this.selectors.searchInput).clear().type(term)
193
+ return this
194
+ }
195
+
196
+ /**
197
+ * Clear the search input
198
+ */
199
+ clearSearch() {
200
+ cy.get(this.selectors.searchInput).clear()
201
+ return this
202
+ }
203
+
204
+ /**
205
+ * Select a filter option
206
+ */
207
+ selectFilter(fieldName: string, value: string) {
208
+ cy.get(this.selectors.filterTrigger(fieldName)).click()
209
+ cy.get(this.selectors.filterOption(fieldName, value)).click()
210
+ return this
211
+ }
212
+
213
+ /**
214
+ * Click on a table row by index (0-based)
215
+ */
216
+ clickRowByIndex(index: number) {
217
+ cy.get(this.selectors.table).find('tbody tr').eq(index).click()
218
+ return this
219
+ }
220
+
221
+ /**
222
+ * Click on a table row by text content
223
+ */
224
+ clickRowByText(text: string) {
225
+ cy.get(this.selectors.table).find('tbody tr').contains(text).click()
226
+ return this
227
+ }
228
+
229
+ /**
230
+ * Click on a specific row by ID
231
+ */
232
+ clickRowById(id: string) {
233
+ cy.get(this.selectors.row(id)).click()
234
+ return this
235
+ }
236
+
237
+ /**
238
+ * Click edit action for a row
239
+ */
240
+ clickEditAction(id: string) {
241
+ cy.get(this.selectors.actionEdit(id)).click()
242
+ return this
243
+ }
244
+
245
+ /**
246
+ * Click delete action for a row
247
+ */
248
+ clickDeleteAction(id: string) {
249
+ cy.get(this.selectors.actionDelete(id)).click()
250
+ return this
251
+ }
252
+
253
+ /**
254
+ * Open actions dropdown for a row
255
+ */
256
+ openActionsDropdown(id: string) {
257
+ cy.get(this.selectors.actionsTrigger(id)).click()
258
+ return this
259
+ }
260
+
261
+ // ============================================
262
+ // PAGINATION METHODS
263
+ // ============================================
264
+
265
+ /**
266
+ * Go to next page
267
+ */
268
+ nextPage() {
269
+ cy.get(this.selectors.paginationNext).click()
270
+ return this
271
+ }
272
+
273
+ /**
274
+ * Go to previous page
275
+ */
276
+ previousPage() {
277
+ cy.get(this.selectors.paginationPrev).click()
278
+ return this
279
+ }
280
+
281
+ // ============================================
282
+ // BULK ACTIONS METHODS
283
+ // ============================================
284
+
285
+ /**
286
+ * Select all rows using the header checkbox
287
+ */
288
+ selectAll() {
289
+ cy.get(this.selectors.table).find('thead input[type="checkbox"]').check()
290
+ return this
291
+ }
292
+
293
+ /**
294
+ * Deselect all rows
295
+ */
296
+ deselectAll() {
297
+ cy.get(this.selectors.table).find('thead input[type="checkbox"]').uncheck()
298
+ return this
299
+ }
300
+
301
+ /**
302
+ * Select a row by index
303
+ */
304
+ selectRowByIndex(index: number) {
305
+ cy.get(this.selectors.table).find('tbody tr').eq(index).find('input[type="checkbox"]').check()
306
+ return this
307
+ }
308
+
309
+ /**
310
+ * Validate bulk actions panel is visible
311
+ */
312
+ validateBulkActionsVisible() {
313
+ cy.get(this.selectors.bulkActions).should('be.visible')
314
+ return this
315
+ }
316
+
317
+ // ============================================
318
+ // DELETE CONFIRMATION
319
+ // ============================================
320
+
321
+ /**
322
+ * Confirm deletion in dialog
323
+ */
324
+ confirmDelete() {
325
+ cy.get(this.selectors.confirmDeleteBtn).click()
326
+ return this
327
+ }
328
+
329
+ /**
330
+ * Cancel deletion in dialog
331
+ */
332
+ cancelDelete() {
333
+ cy.get(this.selectors.cancelDeleteBtn).click()
334
+ return this
335
+ }
336
+
337
+ // ============================================
338
+ // ALIASES FOR COMPATIBILITY
339
+ // ============================================
340
+
341
+ /**
342
+ * Alias for validateTableVisible
343
+ */
344
+ validateListVisible() {
345
+ return this.validateTableVisible()
346
+ }
347
+
348
+ /**
349
+ * Alias for clickCreate
350
+ */
351
+ clickAdd() {
352
+ return this.clickCreate()
353
+ }
354
+
355
+ // ============================================
356
+ // WAIT METHODS
357
+ // ============================================
358
+
359
+ /**
360
+ * Wait for the table to load
361
+ */
362
+ waitForTableLoad() {
363
+ cy.get(this.selectors.table).should('exist')
364
+ cy.get(this.selectors.page).find('[data-loading]').should('not.exist')
365
+ return this
366
+ }
367
+
368
+ /**
369
+ * Wait for page load
370
+ */
371
+ waitForPageLoad() {
372
+ cy.url().should('include', `/dashboard/${this.slug}`)
373
+ cy.get(this.selectors.page).should('be.visible')
374
+ return this
375
+ }
376
+ }
377
+
378
+ export default EntityList