@nextsparkjs/theme-productivity 0.1.0-beta.19 → 0.1.0-beta.20
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 +2 -2
- package/tests/cypress/e2e/ui/boards/boards-owner.cy.ts +349 -0
- package/tests/cypress/e2e/ui/cards/cards-modal.cy.ts +369 -0
- package/tests/cypress/e2e/ui/kanban/kanban-cards.cy.ts +280 -0
- package/tests/cypress/e2e/ui/kanban/kanban-columns.cy.ts +243 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +60 -0
- package/tests/cypress/src/components/BoardsPOM.ts +353 -0
- package/tests/cypress/src/components/CardsPOM.ts +383 -0
- package/tests/cypress/src/components/KanbanPOM.ts +399 -0
- package/tests/cypress/src/components/index.ts +9 -0
- package/tests/cypress/src/controllers/BoardsAPIController.js +302 -0
- package/tests/cypress/src/controllers/CardsAPIController.js +406 -0
- package/tests/cypress/src/controllers/ListsAPIController.js +299 -0
- package/tests/cypress/src/index.ts +25 -0
- package/tests/cypress/src/selectors.ts +50 -0
- package/tests/cypress/src/session-helpers.ts +105 -0
- package/tests/cypress/support/e2e.ts +89 -0
- package/tests/cypress.config.ts +165 -0
- package/tests/tsconfig.json +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/theme-productivity",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.20",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./config/theme.config.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"react": "^19.0.0",
|
|
13
13
|
"react-dom": "^19.0.0",
|
|
14
14
|
"zod": "^4.0.0",
|
|
15
|
-
"@nextsparkjs/core": "0.1.0-beta.
|
|
15
|
+
"@nextsparkjs/core": "0.1.0-beta.23"
|
|
16
16
|
},
|
|
17
17
|
"nextspark": {
|
|
18
18
|
"type": "theme",
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Boards CRUD - Owner Role (Full Access)
|
|
5
|
+
*
|
|
6
|
+
* Tests for the productivity theme boards using the Entity Testing Convention.
|
|
7
|
+
* All selectors follow the pattern: {slug}-{component}-{detail}
|
|
8
|
+
*
|
|
9
|
+
* @see test/cypress/src/classes/themes/productivity/components/BoardsPOM.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { BoardsPOM } from '../../../src/components/BoardsPOM'
|
|
13
|
+
import { loginAsProductivityOwner } from '../../../src/session-helpers'
|
|
14
|
+
|
|
15
|
+
describe('Boards CRUD - Owner Role (Full Access)', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
loginAsProductivityOwner()
|
|
18
|
+
BoardsPOM.visitList()
|
|
19
|
+
BoardsPOM.waitForListLoad()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('CREATE - Owner can create boards', () => {
|
|
23
|
+
it('OWNER_BOARDS_CREATE_001: should create new board with required fields', () => {
|
|
24
|
+
const timestamp = Date.now()
|
|
25
|
+
const name = `Test Board ${timestamp}`
|
|
26
|
+
const description = `Test description for board ${timestamp}`
|
|
27
|
+
|
|
28
|
+
// Click create button
|
|
29
|
+
BoardsPOM.clickCreate()
|
|
30
|
+
|
|
31
|
+
// Validate form is visible
|
|
32
|
+
BoardsPOM.waitForFormLoad()
|
|
33
|
+
BoardsPOM.validateFormVisible()
|
|
34
|
+
|
|
35
|
+
// Fill required board fields
|
|
36
|
+
BoardsPOM.fillBoardForm({
|
|
37
|
+
name,
|
|
38
|
+
description,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Submit form
|
|
42
|
+
BoardsPOM.submitForm()
|
|
43
|
+
|
|
44
|
+
// Should redirect to the new board's kanban view
|
|
45
|
+
cy.url().should('match', /\/dashboard\/boards\/[a-z0-9_-]+$/)
|
|
46
|
+
|
|
47
|
+
cy.log('✅ Owner created board successfully')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('OWNER_BOARDS_CREATE_002: should create board with color', () => {
|
|
51
|
+
const timestamp = Date.now()
|
|
52
|
+
const name = `Colored Board ${timestamp}`
|
|
53
|
+
|
|
54
|
+
// Click create button
|
|
55
|
+
BoardsPOM.clickCreate()
|
|
56
|
+
BoardsPOM.waitForFormLoad()
|
|
57
|
+
|
|
58
|
+
// Fill fields with color selection
|
|
59
|
+
BoardsPOM.fillBoardForm({
|
|
60
|
+
name,
|
|
61
|
+
color: 'purple',
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Submit form
|
|
65
|
+
BoardsPOM.submitForm()
|
|
66
|
+
|
|
67
|
+
// Should redirect to board
|
|
68
|
+
cy.url().should('match', /\/dashboard\/boards\/[a-z0-9_-]+$/)
|
|
69
|
+
|
|
70
|
+
cy.log('✅ Owner created board with color successfully')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('OWNER_BOARDS_CREATE_003: should validate required fields', () => {
|
|
74
|
+
// Click create button
|
|
75
|
+
BoardsPOM.clickCreate()
|
|
76
|
+
BoardsPOM.waitForFormLoad()
|
|
77
|
+
|
|
78
|
+
// Try to submit without filling required fields
|
|
79
|
+
BoardsPOM.submitForm()
|
|
80
|
+
|
|
81
|
+
// Form should still be visible (validation failed)
|
|
82
|
+
BoardsPOM.validateFormVisible()
|
|
83
|
+
|
|
84
|
+
cy.log('✅ Form validation working correctly')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('OWNER_BOARDS_CREATE_004: should cancel board creation', () => {
|
|
88
|
+
const timestamp = Date.now()
|
|
89
|
+
const name = `Cancelled Board ${timestamp}`
|
|
90
|
+
|
|
91
|
+
// Click create button
|
|
92
|
+
BoardsPOM.clickCreate()
|
|
93
|
+
BoardsPOM.waitForFormLoad()
|
|
94
|
+
|
|
95
|
+
// Fill some fields
|
|
96
|
+
BoardsPOM.fillName(name)
|
|
97
|
+
|
|
98
|
+
// Cancel form
|
|
99
|
+
BoardsPOM.cancelForm()
|
|
100
|
+
|
|
101
|
+
// Should return to list
|
|
102
|
+
BoardsPOM.assertOnListPage()
|
|
103
|
+
|
|
104
|
+
// Board should not exist
|
|
105
|
+
BoardsPOM.assertBoardNotInList(name)
|
|
106
|
+
|
|
107
|
+
cy.log('✅ Board creation cancelled successfully')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('READ - Owner can view boards', () => {
|
|
112
|
+
it('OWNER_BOARDS_READ_001: should view boards list page', () => {
|
|
113
|
+
// Validate list page is visible
|
|
114
|
+
BoardsPOM.validateListPageVisible()
|
|
115
|
+
|
|
116
|
+
cy.log('✅ Owner can view boards list')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('OWNER_BOARDS_READ_002: should see create button', () => {
|
|
120
|
+
// Create button should be visible for owner
|
|
121
|
+
cy.get(BoardsPOM.selectors.createBtn).should('be.visible')
|
|
122
|
+
|
|
123
|
+
cy.log('✅ Create button is visible for owner')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('OWNER_BOARDS_READ_003: should click on board to open it', () => {
|
|
127
|
+
// Check if there are boards
|
|
128
|
+
cy.get('body').then(($body) => {
|
|
129
|
+
const boardCards = $body.find('[data-cy^="boards-card-"]')
|
|
130
|
+
if (boardCards.length > 0) {
|
|
131
|
+
// Click on first board card
|
|
132
|
+
cy.get('[data-cy^="boards-card-"]').first().find('a').first().click()
|
|
133
|
+
|
|
134
|
+
// Should navigate to board view
|
|
135
|
+
cy.url().should('match', /\/dashboard\/boards\/[a-z0-9_-]+$/)
|
|
136
|
+
|
|
137
|
+
cy.log('✅ Owner can open board')
|
|
138
|
+
} else {
|
|
139
|
+
cy.log('⚠️ No boards available to click')
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('UPDATE - Owner can update boards', () => {
|
|
146
|
+
let testBoardId: string
|
|
147
|
+
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
// Create a board for update tests
|
|
150
|
+
const timestamp = Date.now()
|
|
151
|
+
const name = `Update Test ${timestamp}`
|
|
152
|
+
|
|
153
|
+
BoardsPOM.clickCreate()
|
|
154
|
+
BoardsPOM.waitForFormLoad()
|
|
155
|
+
BoardsPOM.fillName(name)
|
|
156
|
+
BoardsPOM.submitForm()
|
|
157
|
+
|
|
158
|
+
// Get board ID from URL
|
|
159
|
+
cy.url().then((url) => {
|
|
160
|
+
const match = url.match(/\/boards\/([a-z0-9_-]+)/)
|
|
161
|
+
if (match) {
|
|
162
|
+
testBoardId = match[1]
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('OWNER_BOARDS_UPDATE_001: should edit board name', () => {
|
|
168
|
+
const updatedName = `Updated Board ${Date.now()}`
|
|
169
|
+
|
|
170
|
+
// Visit edit page
|
|
171
|
+
cy.url().then((url) => {
|
|
172
|
+
const match = url.match(/\/boards\/([a-z0-9_-]+)/)
|
|
173
|
+
if (match) {
|
|
174
|
+
const boardId = match[1]
|
|
175
|
+
BoardsPOM.visitEdit(boardId)
|
|
176
|
+
BoardsPOM.waitForFormLoad()
|
|
177
|
+
|
|
178
|
+
// Update name
|
|
179
|
+
BoardsPOM.fillName(updatedName)
|
|
180
|
+
|
|
181
|
+
// Submit form
|
|
182
|
+
BoardsPOM.submitForm()
|
|
183
|
+
|
|
184
|
+
// Should redirect to board
|
|
185
|
+
cy.url().should('include', `/dashboard/boards/${boardId}`)
|
|
186
|
+
|
|
187
|
+
cy.log('✅ Owner updated board name successfully')
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('OWNER_BOARDS_UPDATE_002: should update board description', () => {
|
|
193
|
+
const updatedDescription = `Updated description ${Date.now()}`
|
|
194
|
+
|
|
195
|
+
cy.url().then((url) => {
|
|
196
|
+
const match = url.match(/\/boards\/([a-z0-9_-]+)/)
|
|
197
|
+
if (match) {
|
|
198
|
+
const boardId = match[1]
|
|
199
|
+
BoardsPOM.visitEdit(boardId)
|
|
200
|
+
BoardsPOM.waitForFormLoad()
|
|
201
|
+
|
|
202
|
+
// Update description
|
|
203
|
+
BoardsPOM.fillDescription(updatedDescription)
|
|
204
|
+
|
|
205
|
+
// Submit form
|
|
206
|
+
BoardsPOM.submitForm()
|
|
207
|
+
|
|
208
|
+
// Should redirect to board
|
|
209
|
+
cy.url().should('include', `/dashboard/boards/${boardId}`)
|
|
210
|
+
|
|
211
|
+
cy.log('✅ Owner updated board description successfully')
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('DELETE - Owner can delete boards', () => {
|
|
218
|
+
it('OWNER_BOARDS_DELETE_001: should delete board from edit page', () => {
|
|
219
|
+
// Create a board to delete
|
|
220
|
+
const timestamp = Date.now()
|
|
221
|
+
const name = `Delete Test ${timestamp}`
|
|
222
|
+
|
|
223
|
+
BoardsPOM.clickCreate()
|
|
224
|
+
BoardsPOM.waitForFormLoad()
|
|
225
|
+
BoardsPOM.fillName(name)
|
|
226
|
+
BoardsPOM.submitForm()
|
|
227
|
+
|
|
228
|
+
// Get board ID and go to edit page
|
|
229
|
+
cy.url().then((url) => {
|
|
230
|
+
const match = url.match(/\/boards\/([a-z0-9_-]+)/)
|
|
231
|
+
if (match) {
|
|
232
|
+
const boardId = match[1]
|
|
233
|
+
BoardsPOM.visitEdit(boardId)
|
|
234
|
+
BoardsPOM.waitForFormLoad()
|
|
235
|
+
|
|
236
|
+
// Confirm deletion
|
|
237
|
+
BoardsPOM.confirmDelete()
|
|
238
|
+
|
|
239
|
+
// Click delete button
|
|
240
|
+
BoardsPOM.deleteFromEdit()
|
|
241
|
+
|
|
242
|
+
// Should redirect to list
|
|
243
|
+
BoardsPOM.waitForListLoad()
|
|
244
|
+
BoardsPOM.assertOnListPage()
|
|
245
|
+
|
|
246
|
+
// Board should not exist
|
|
247
|
+
BoardsPOM.assertBoardNotInList(name)
|
|
248
|
+
|
|
249
|
+
cy.log('✅ Owner deleted board successfully')
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('OWNER_BOARDS_DELETE_002: should delete board from list page menu', () => {
|
|
255
|
+
// Create a board to delete
|
|
256
|
+
const timestamp = Date.now()
|
|
257
|
+
const name = `Delete Menu Test ${timestamp}`
|
|
258
|
+
|
|
259
|
+
BoardsPOM.clickCreate()
|
|
260
|
+
BoardsPOM.waitForFormLoad()
|
|
261
|
+
BoardsPOM.fillName(name)
|
|
262
|
+
BoardsPOM.submitForm()
|
|
263
|
+
|
|
264
|
+
// Go back to list
|
|
265
|
+
BoardsPOM.visitList()
|
|
266
|
+
BoardsPOM.waitForListLoad()
|
|
267
|
+
|
|
268
|
+
// Find the board and open its menu
|
|
269
|
+
cy.get('body').then(($body) => {
|
|
270
|
+
// Find the board card containing our name
|
|
271
|
+
cy.contains('[data-cy^="boards-card-"]', name).then(($card) => {
|
|
272
|
+
// Extract ID from data-cy attribute
|
|
273
|
+
const dataCy = $card.attr('data-cy')
|
|
274
|
+
if (dataCy) {
|
|
275
|
+
const boardId = dataCy.replace('boards-card-', '')
|
|
276
|
+
|
|
277
|
+
// Confirm deletion
|
|
278
|
+
BoardsPOM.confirmDelete()
|
|
279
|
+
|
|
280
|
+
// Click delete from menu
|
|
281
|
+
BoardsPOM.clickCardDelete(boardId)
|
|
282
|
+
|
|
283
|
+
// Board should no longer exist
|
|
284
|
+
cy.contains('[data-cy^="boards-card-"]', name).should('not.exist')
|
|
285
|
+
|
|
286
|
+
cy.log('✅ Owner deleted board from list menu')
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('INTEGRATION - Complete board lifecycle', () => {
|
|
294
|
+
it('OWNER_BOARDS_LIFECYCLE_001: should perform full CRUD lifecycle', () => {
|
|
295
|
+
const timestamp = Date.now()
|
|
296
|
+
const name = `Lifecycle Board ${timestamp}`
|
|
297
|
+
const description = `Lifecycle test description ${timestamp}`
|
|
298
|
+
const updatedName = `Updated Lifecycle ${timestamp}`
|
|
299
|
+
|
|
300
|
+
// CREATE
|
|
301
|
+
cy.log('🔄 Step 1: Create board')
|
|
302
|
+
BoardsPOM.clickCreate()
|
|
303
|
+
BoardsPOM.waitForFormLoad()
|
|
304
|
+
BoardsPOM.fillBoardForm({ name, description })
|
|
305
|
+
BoardsPOM.submitForm()
|
|
306
|
+
|
|
307
|
+
// Get board ID
|
|
308
|
+
cy.url().then((url) => {
|
|
309
|
+
const match = url.match(/\/boards\/([a-z0-9_-]+)/)
|
|
310
|
+
expect(match).to.not.be.null
|
|
311
|
+
|
|
312
|
+
if (match) {
|
|
313
|
+
const boardId = match[1]
|
|
314
|
+
|
|
315
|
+
// READ
|
|
316
|
+
cy.log('🔄 Step 2: Verify board was created')
|
|
317
|
+
BoardsPOM.visitList()
|
|
318
|
+
BoardsPOM.waitForListLoad()
|
|
319
|
+
BoardsPOM.assertBoardInList(name)
|
|
320
|
+
|
|
321
|
+
// UPDATE
|
|
322
|
+
cy.log('🔄 Step 3: Update board')
|
|
323
|
+
BoardsPOM.visitEdit(boardId)
|
|
324
|
+
BoardsPOM.waitForFormLoad()
|
|
325
|
+
BoardsPOM.fillName(updatedName)
|
|
326
|
+
BoardsPOM.submitForm()
|
|
327
|
+
|
|
328
|
+
// Verify update
|
|
329
|
+
BoardsPOM.visitList()
|
|
330
|
+
BoardsPOM.waitForListLoad()
|
|
331
|
+
BoardsPOM.assertBoardInList(updatedName)
|
|
332
|
+
|
|
333
|
+
// DELETE
|
|
334
|
+
cy.log('🔄 Step 4: Delete board')
|
|
335
|
+
BoardsPOM.visitEdit(boardId)
|
|
336
|
+
BoardsPOM.waitForFormLoad()
|
|
337
|
+
BoardsPOM.confirmDelete()
|
|
338
|
+
BoardsPOM.deleteFromEdit()
|
|
339
|
+
|
|
340
|
+
// Verify deletion
|
|
341
|
+
BoardsPOM.waitForListLoad()
|
|
342
|
+
BoardsPOM.assertBoardNotInList(updatedName)
|
|
343
|
+
|
|
344
|
+
cy.log('✅ Full board lifecycle completed successfully')
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
})
|