@nextsparkjs/theme-productivity 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/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/boards/api/docs.md +131 -0
- package/entities/boards/api/presets.ts +113 -0
- package/entities/cards/api/docs.md +162 -0
- package/entities/cards/api/presets.ts +172 -0
- package/entities/lists/api/docs.md +143 -0
- package/entities/lists/api/presets.ts +81 -0
- package/lib/selectors.ts +2 -3
- package/nextsparkjs-theme-productivity-0.1.0-beta.137.tgz +0 -0
- package/package.json +4 -3
- package/styles/globals.css +37 -1
- 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 +176 -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,143 @@
|
|
|
1
|
+
# Lists API
|
|
2
|
+
|
|
3
|
+
Manage columns within boards (To Do, In Progress, Done, etc.).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Lists API allows you to create, read, update, and delete list records. Lists are the columns within a board that organize cards in a Kanban workflow.
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
|
|
11
|
+
All endpoints require authentication via:
|
|
12
|
+
- **Session cookie** (for browser-based requests)
|
|
13
|
+
- **API Key** header (for server-to-server requests)
|
|
14
|
+
|
|
15
|
+
## Endpoints
|
|
16
|
+
|
|
17
|
+
### List Lists
|
|
18
|
+
`GET /api/v1/lists`
|
|
19
|
+
|
|
20
|
+
Returns a paginated list of lists.
|
|
21
|
+
|
|
22
|
+
**Query Parameters:**
|
|
23
|
+
- `limit` (number, optional): Maximum records to return. Default: 20
|
|
24
|
+
- `offset` (number, optional): Number of records to skip. Default: 0
|
|
25
|
+
- `boardId` (string, optional): Filter by parent board
|
|
26
|
+
- `search` (string, optional): Search by name
|
|
27
|
+
- `sortBy` (string, optional): Field to sort by. Default: position
|
|
28
|
+
- `sortOrder` (string, optional): Sort direction (asc, desc)
|
|
29
|
+
|
|
30
|
+
**Example Response:**
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"data": [
|
|
34
|
+
{
|
|
35
|
+
"id": "list_abc123",
|
|
36
|
+
"name": "To Do",
|
|
37
|
+
"position": 1,
|
|
38
|
+
"boardId": "board_xyz789",
|
|
39
|
+
"createdAt": "2024-01-15T10:30:00Z",
|
|
40
|
+
"updatedAt": "2024-01-15T10:30:00Z"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "list_def456",
|
|
44
|
+
"name": "In Progress",
|
|
45
|
+
"position": 2,
|
|
46
|
+
"boardId": "board_xyz789",
|
|
47
|
+
"createdAt": "2024-01-15T10:30:00Z",
|
|
48
|
+
"updatedAt": "2024-01-15T10:30:00Z"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "list_ghi789",
|
|
52
|
+
"name": "Done",
|
|
53
|
+
"position": 3,
|
|
54
|
+
"boardId": "board_xyz789",
|
|
55
|
+
"createdAt": "2024-01-15T10:30:00Z",
|
|
56
|
+
"updatedAt": "2024-01-15T10:30:00Z"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"pagination": {
|
|
60
|
+
"total": 3,
|
|
61
|
+
"limit": 20,
|
|
62
|
+
"offset": 0
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Get Single List
|
|
68
|
+
`GET /api/v1/lists/[id]`
|
|
69
|
+
|
|
70
|
+
Returns a single list by ID.
|
|
71
|
+
|
|
72
|
+
### Create List
|
|
73
|
+
`POST /api/v1/lists`
|
|
74
|
+
|
|
75
|
+
Create a new list.
|
|
76
|
+
|
|
77
|
+
**Request Body:**
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"name": "Review",
|
|
81
|
+
"boardId": "board_xyz789",
|
|
82
|
+
"position": 4
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Update List
|
|
87
|
+
`PATCH /api/v1/lists/[id]`
|
|
88
|
+
|
|
89
|
+
Update an existing list. Supports partial updates.
|
|
90
|
+
|
|
91
|
+
### Reorder Lists
|
|
92
|
+
`PATCH /api/v1/lists/[id]`
|
|
93
|
+
|
|
94
|
+
Update list position for drag & drop reordering.
|
|
95
|
+
|
|
96
|
+
**Request Body:**
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"position": 2
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Delete List
|
|
104
|
+
`DELETE /api/v1/lists/[id]`
|
|
105
|
+
|
|
106
|
+
Delete a list. This will also delete all cards within the list.
|
|
107
|
+
|
|
108
|
+
## Fields
|
|
109
|
+
|
|
110
|
+
| Field | Type | Required | Default | Description |
|
|
111
|
+
|-------|------|----------|---------|-------------|
|
|
112
|
+
| name | text | Yes | - | List name |
|
|
113
|
+
| position | number | No | 0 | Display order within board |
|
|
114
|
+
| boardId | reference | Yes | - | Parent board ID |
|
|
115
|
+
| createdAt | datetime | Auto | - | Creation timestamp |
|
|
116
|
+
| updatedAt | datetime | Auto | - | Last update timestamp |
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
- **Searchable**: name
|
|
121
|
+
- **Sortable**: name, position, createdAt
|
|
122
|
+
- **Drag & Drop**: Update position for reordering
|
|
123
|
+
- **Cascade Delete**: Deleting a list removes all its cards
|
|
124
|
+
|
|
125
|
+
## Permissions
|
|
126
|
+
|
|
127
|
+
- **Create/Update/Delete**: Owner, Admin, Member
|
|
128
|
+
- **Delete**: Owner, Admin
|
|
129
|
+
|
|
130
|
+
## Error Responses
|
|
131
|
+
|
|
132
|
+
| Status | Description |
|
|
133
|
+
|--------|-------------|
|
|
134
|
+
| 400 | Bad Request - Invalid parameters |
|
|
135
|
+
| 401 | Unauthorized - Missing or invalid auth |
|
|
136
|
+
| 403 | Forbidden - Insufficient permissions |
|
|
137
|
+
| 404 | Not Found - List or Board doesn't exist |
|
|
138
|
+
| 422 | Validation Error - Invalid data |
|
|
139
|
+
|
|
140
|
+
## Related APIs
|
|
141
|
+
|
|
142
|
+
- **[Boards](/api/v1/boards)** - Parent boards
|
|
143
|
+
- **[Cards](/api/v1/cards)** - Cards within lists
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Lists Entity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
6
|
+
|
|
7
|
+
export default defineApiEndpoint({
|
|
8
|
+
summary: 'Manage kanban lists (columns) within boards',
|
|
9
|
+
presets: [
|
|
10
|
+
{
|
|
11
|
+
id: 'list-all',
|
|
12
|
+
title: 'List All Lists',
|
|
13
|
+
description: 'Get all lists with pagination',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
params: {
|
|
16
|
+
limit: 50
|
|
17
|
+
},
|
|
18
|
+
tags: ['read', 'list']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'list-by-board',
|
|
22
|
+
title: 'List by Board',
|
|
23
|
+
description: 'Get all lists for a specific board',
|
|
24
|
+
method: 'GET',
|
|
25
|
+
params: {
|
|
26
|
+
boardId: '{{boardId}}',
|
|
27
|
+
sortBy: 'position',
|
|
28
|
+
sortOrder: 'asc'
|
|
29
|
+
},
|
|
30
|
+
tags: ['read', 'filter']
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'create-list',
|
|
34
|
+
title: 'Create List',
|
|
35
|
+
description: 'Create a new list in a board',
|
|
36
|
+
method: 'POST',
|
|
37
|
+
payload: {
|
|
38
|
+
name: 'New List',
|
|
39
|
+
boardId: '{{boardId}}',
|
|
40
|
+
position: 0
|
|
41
|
+
},
|
|
42
|
+
tags: ['write', 'create']
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'rename-list',
|
|
46
|
+
title: 'Rename List',
|
|
47
|
+
description: 'Rename an existing list',
|
|
48
|
+
method: 'PATCH',
|
|
49
|
+
pathParams: {
|
|
50
|
+
id: '{{id}}'
|
|
51
|
+
},
|
|
52
|
+
payload: {
|
|
53
|
+
name: '{{name}}'
|
|
54
|
+
},
|
|
55
|
+
tags: ['write', 'update']
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'reorder-list',
|
|
59
|
+
title: 'Reorder List',
|
|
60
|
+
description: 'Update list position within board',
|
|
61
|
+
method: 'PATCH',
|
|
62
|
+
pathParams: {
|
|
63
|
+
id: '{{id}}'
|
|
64
|
+
},
|
|
65
|
+
payload: {
|
|
66
|
+
position: '{{position}}'
|
|
67
|
+
},
|
|
68
|
+
tags: ['write', 'update']
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'delete-list',
|
|
72
|
+
title: 'Delete List',
|
|
73
|
+
description: 'Delete a list and all its cards',
|
|
74
|
+
method: 'DELETE',
|
|
75
|
+
pathParams: {
|
|
76
|
+
id: '{{id}}'
|
|
77
|
+
},
|
|
78
|
+
tags: ['write', 'delete']
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
})
|
package/lib/selectors.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* - Cypress tests (via tests/cypress/src/selectors.ts)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { createSelectorHelpers } from '@nextsparkjs/core/
|
|
13
|
-
import { CORE_SELECTORS } from '@nextsparkjs/core/lib/test/core-selectors'
|
|
12
|
+
import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/core/selectors'
|
|
14
13
|
|
|
15
14
|
// =============================================================================
|
|
16
15
|
// BLOCK SELECTORS
|
|
@@ -202,5 +201,5 @@ export type ThemeSelectorsType = typeof THEME_SELECTORS
|
|
|
202
201
|
export type BlockSelectorsType = typeof BLOCK_SELECTORS
|
|
203
202
|
export type EntitySelectorsType = typeof ENTITY_SELECTORS
|
|
204
203
|
export type KanbanSelectorsType = typeof KANBAN_SELECTORS
|
|
205
|
-
export type { Replacements } from '@nextsparkjs/core/
|
|
204
|
+
export type { Replacements } from '@nextsparkjs/core/selectors'
|
|
206
205
|
export { CORE_SELECTORS }
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/theme-productivity",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.170",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./config/theme.config.ts",
|
|
7
7
|
"requiredPlugins": [],
|
|
8
8
|
"dependencies": {},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
+
"@nextsparkjs/core": "*",
|
|
11
|
+
"@nextsparkjs/testing": "*",
|
|
10
12
|
"lucide-react": "^0.539.0",
|
|
11
13
|
"next": "^15.0.0",
|
|
12
14
|
"react": "^19.0.0",
|
|
13
15
|
"react-dom": "^19.0.0",
|
|
14
|
-
"zod": "^4.0.0"
|
|
15
|
-
"@nextsparkjs/core": "0.1.0-beta.17"
|
|
16
|
+
"zod": "^4.0.0"
|
|
16
17
|
},
|
|
17
18
|
"nextspark": {
|
|
18
19
|
"type": "theme",
|
package/styles/globals.css
CHANGED
|
@@ -3,10 +3,40 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Clean, modern design focused on productivity.
|
|
5
5
|
* Blue-tinted palette with clear visual hierarchy.
|
|
6
|
+
*
|
|
7
|
+
* ÚNICA FUENTE DE VERDAD para estilos del theme.
|
|
8
|
+
* Editar este archivo para customizar el design system.
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
/* =============================================
|
|
12
|
+
IMPORTS
|
|
13
|
+
============================================= */
|
|
14
|
+
@import "tailwindcss";
|
|
15
|
+
|
|
16
|
+
/* =============================================
|
|
17
|
+
TAILWIND v4 DARK MODE CONFIGURATION
|
|
18
|
+
|
|
19
|
+
Por defecto Tailwind v4 usa @media (prefers-color-scheme: dark)
|
|
20
|
+
para las variantes dark:. Esto causa problemas porque:
|
|
21
|
+
- next-themes usa la clase .dark en el HTML
|
|
22
|
+
- El sistema operativo puede tener dark mode aunque la app use light
|
|
23
|
+
|
|
24
|
+
Esta configuración hace que Tailwind use el selector de clase .dark
|
|
25
|
+
en lugar de la media query, sincronizándose con next-themes.
|
|
26
|
+
============================================= */
|
|
27
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
28
|
+
|
|
29
|
+
@plugin "@tailwindcss/container-queries";
|
|
30
|
+
@import "@nextsparkjs/core/styles/ui.css";
|
|
31
|
+
@import "@nextsparkjs/core/styles/utilities.css";
|
|
32
|
+
@import "@nextsparkjs/core/styles/docs.css";
|
|
33
|
+
@source "../../../**/*.{js,ts,jsx,tsx}";
|
|
34
|
+
/* Core package: monorepo (short path) + npm projects (long path) */
|
|
35
|
+
@source "../node_modules/@nextsparkjs/core/dist/**/*.js";
|
|
36
|
+
@source "../../../../node_modules/@nextsparkjs/core/dist/**/*.js";
|
|
37
|
+
|
|
8
38
|
/* ============================================================================
|
|
9
|
-
|
|
39
|
+
PRODUCTIVITY THEME - LIGHT MODE
|
|
10
40
|
============================================================================ */
|
|
11
41
|
|
|
12
42
|
:root {
|
|
@@ -249,7 +279,13 @@
|
|
|
249
279
|
Base Styles
|
|
250
280
|
============================================================================ */
|
|
251
281
|
|
|
282
|
+
* {
|
|
283
|
+
border-color: var(--border);
|
|
284
|
+
}
|
|
285
|
+
|
|
252
286
|
body {
|
|
287
|
+
background-color: var(--background);
|
|
288
|
+
color: var(--foreground);
|
|
253
289
|
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
|
254
290
|
}
|
|
255
291
|
|
|
@@ -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
|
+
})
|