@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,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Categories Entity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
6
|
+
|
|
7
|
+
export default defineApiEndpoint({
|
|
8
|
+
summary: 'Manage blog categories for organizing posts',
|
|
9
|
+
presets: [
|
|
10
|
+
{
|
|
11
|
+
id: 'list-all',
|
|
12
|
+
title: 'List All Categories',
|
|
13
|
+
description: 'Get all categories with pagination',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
params: {
|
|
16
|
+
limit: 50
|
|
17
|
+
},
|
|
18
|
+
tags: ['read', 'list']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'search-by-name',
|
|
22
|
+
title: 'Search by Name',
|
|
23
|
+
description: 'Search categories by name',
|
|
24
|
+
method: 'GET',
|
|
25
|
+
params: {
|
|
26
|
+
search: '{{name}}'
|
|
27
|
+
},
|
|
28
|
+
tags: ['read', 'search']
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'create-category',
|
|
32
|
+
title: 'Create Category',
|
|
33
|
+
description: 'Create a new category',
|
|
34
|
+
method: 'POST',
|
|
35
|
+
payload: {
|
|
36
|
+
name: 'New Category',
|
|
37
|
+
slug: 'new-category',
|
|
38
|
+
description: 'Category description'
|
|
39
|
+
},
|
|
40
|
+
tags: ['write', 'create']
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'update-category',
|
|
44
|
+
title: 'Update Category',
|
|
45
|
+
description: 'Update an existing category',
|
|
46
|
+
method: 'PATCH',
|
|
47
|
+
pathParams: {
|
|
48
|
+
id: '{{id}}'
|
|
49
|
+
},
|
|
50
|
+
payload: {
|
|
51
|
+
name: '{{name}}',
|
|
52
|
+
description: '{{description}}'
|
|
53
|
+
},
|
|
54
|
+
tags: ['write', 'update']
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'delete-category',
|
|
58
|
+
title: 'Delete Category',
|
|
59
|
+
description: 'Delete a category record',
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
pathParams: {
|
|
62
|
+
id: '{{id}}'
|
|
63
|
+
},
|
|
64
|
+
tags: ['write', 'delete']
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Posts API
|
|
2
|
+
|
|
3
|
+
Manage blog posts with content builder, featured images, categories, and publication workflow.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Posts API allows you to create, read, update, and delete blog posts. Posts support a flexible content builder system, category organization, and a draft-to-published 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
|
+
**Note:** For public access to published posts, use the [Public Posts API](/api/v1/theme/blog/posts/public) instead.
|
|
16
|
+
|
|
17
|
+
## Endpoints
|
|
18
|
+
|
|
19
|
+
### List Posts
|
|
20
|
+
`GET /api/v1/posts`
|
|
21
|
+
|
|
22
|
+
Returns a paginated list of posts.
|
|
23
|
+
|
|
24
|
+
**Query Parameters:**
|
|
25
|
+
- `limit` (number, optional): Maximum records to return. Default: 20
|
|
26
|
+
- `offset` (number, optional): Number of records to skip. Default: 0
|
|
27
|
+
- `status` (string, optional): Filter by status (draft, published)
|
|
28
|
+
- `featured` (boolean, optional): Filter by featured flag
|
|
29
|
+
- `search` (string, optional): Search by title, excerpt, content
|
|
30
|
+
- `sortBy` (string, optional): Field to sort by
|
|
31
|
+
- `sortOrder` (string, optional): Sort direction (asc, desc)
|
|
32
|
+
|
|
33
|
+
**Example Response:**
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"data": [
|
|
37
|
+
{
|
|
38
|
+
"id": "post_abc123",
|
|
39
|
+
"title": "Getting Started with Next.js",
|
|
40
|
+
"slug": "getting-started-with-nextjs",
|
|
41
|
+
"excerpt": "A comprehensive guide to building modern web apps",
|
|
42
|
+
"content": "[{\"type\":\"text\",\"content\":\"...\"}]",
|
|
43
|
+
"featuredImage": "https://example.com/image.jpg",
|
|
44
|
+
"featured": true,
|
|
45
|
+
"status": "published",
|
|
46
|
+
"publishedAt": "2024-01-15T10:30:00Z",
|
|
47
|
+
"createdAt": "2024-01-10T08:00:00Z",
|
|
48
|
+
"updatedAt": "2024-01-15T10:30:00Z"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"pagination": {
|
|
52
|
+
"total": 25,
|
|
53
|
+
"limit": 20,
|
|
54
|
+
"offset": 0
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Get Single Post
|
|
60
|
+
`GET /api/v1/posts/[id]`
|
|
61
|
+
|
|
62
|
+
Returns a single post by ID.
|
|
63
|
+
|
|
64
|
+
### Create Post
|
|
65
|
+
`POST /api/v1/posts`
|
|
66
|
+
|
|
67
|
+
Create a new blog post.
|
|
68
|
+
|
|
69
|
+
**Request Body:**
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"title": "My New Post",
|
|
73
|
+
"slug": "my-new-post",
|
|
74
|
+
"excerpt": "A brief description of the post",
|
|
75
|
+
"content": "[{\"type\":\"text\",\"content\":\"Post content here\"}]",
|
|
76
|
+
"featuredImage": "https://example.com/image.jpg",
|
|
77
|
+
"featured": false,
|
|
78
|
+
"status": "draft"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Update Post
|
|
83
|
+
`PATCH /api/v1/posts/[id]`
|
|
84
|
+
|
|
85
|
+
Update an existing post. Supports partial updates.
|
|
86
|
+
|
|
87
|
+
### Publish Post
|
|
88
|
+
`PATCH /api/v1/posts/[id]`
|
|
89
|
+
|
|
90
|
+
Publish a draft post by setting status and publishedAt.
|
|
91
|
+
|
|
92
|
+
**Request Body:**
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"status": "published",
|
|
96
|
+
"publishedAt": "2024-01-15T10:30:00Z"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Delete Post
|
|
101
|
+
`DELETE /api/v1/posts/[id]`
|
|
102
|
+
|
|
103
|
+
Delete a post record.
|
|
104
|
+
|
|
105
|
+
## Fields
|
|
106
|
+
|
|
107
|
+
| Field | Type | Required | Description |
|
|
108
|
+
|-------|------|----------|-------------|
|
|
109
|
+
| title | text | Yes | Post title |
|
|
110
|
+
| slug | text | Yes | URL-friendly slug (auto-generated if not provided) |
|
|
111
|
+
| excerpt | text | No | Brief description for previews |
|
|
112
|
+
| content | json | No | Page builder content blocks |
|
|
113
|
+
| featuredImage | url | No | Featured image URL |
|
|
114
|
+
| featured | boolean | No | Whether post is featured. Default: false |
|
|
115
|
+
| status | select | No | Status: draft, published. Default: draft |
|
|
116
|
+
| publishedAt | datetime | No | Publication date/time |
|
|
117
|
+
| createdAt | datetime | Auto | Creation timestamp |
|
|
118
|
+
| updatedAt | datetime | Auto | Last update timestamp |
|
|
119
|
+
|
|
120
|
+
## Content Builder
|
|
121
|
+
|
|
122
|
+
Posts use a flexible content builder system. The `content` field stores an array of content blocks in JSON format:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
[
|
|
126
|
+
{
|
|
127
|
+
"type": "text",
|
|
128
|
+
"content": "Paragraph content here..."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"type": "heading",
|
|
132
|
+
"level": 2,
|
|
133
|
+
"content": "Section Title"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"type": "image",
|
|
137
|
+
"src": "https://example.com/image.jpg",
|
|
138
|
+
"alt": "Image description"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Categories
|
|
144
|
+
|
|
145
|
+
Posts can be organized into categories using a many-to-many relationship. Categories are managed via the `/api/v1/categories` endpoint and linked to posts through a pivot table.
|
|
146
|
+
|
|
147
|
+
To filter posts by category, use the Public Posts API with the `category` query parameter.
|
|
148
|
+
|
|
149
|
+
## Features
|
|
150
|
+
|
|
151
|
+
- **Searchable**: title, excerpt, content
|
|
152
|
+
- **Sortable**: Most fields
|
|
153
|
+
- **Public Access**: Published posts available via public API
|
|
154
|
+
- **Metadata**: Supported
|
|
155
|
+
|
|
156
|
+
## Permissions
|
|
157
|
+
|
|
158
|
+
- **Create/Update/Delete**: Owner only (single-user blog mode)
|
|
159
|
+
|
|
160
|
+
## Error Responses
|
|
161
|
+
|
|
162
|
+
| Status | Description |
|
|
163
|
+
|--------|-------------|
|
|
164
|
+
| 400 | Bad Request - Invalid parameters |
|
|
165
|
+
| 401 | Unauthorized - Missing or invalid auth |
|
|
166
|
+
| 403 | Forbidden - Insufficient permissions |
|
|
167
|
+
| 404 | Not Found - Post doesn't exist |
|
|
168
|
+
| 422 | Validation Error - Invalid data |
|
|
169
|
+
|
|
170
|
+
## Related APIs
|
|
171
|
+
|
|
172
|
+
- **[Categories](/api/v1/categories)** - Post categorization
|
|
173
|
+
- **[Public Posts](/api/v1/theme/blog/posts/public)** - Public feed (no auth required)
|
|
174
|
+
- **[Authors](/api/v1/theme/blog/authors)** - Author profiles
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Posts Entity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
6
|
+
|
|
7
|
+
export default defineApiEndpoint({
|
|
8
|
+
summary: 'Manage blog posts with content builder and publication workflow',
|
|
9
|
+
presets: [
|
|
10
|
+
{
|
|
11
|
+
id: 'list-all',
|
|
12
|
+
title: 'List All Posts',
|
|
13
|
+
description: 'Get all posts with pagination',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
params: {
|
|
16
|
+
limit: 20
|
|
17
|
+
},
|
|
18
|
+
tags: ['read', 'list']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'list-published',
|
|
22
|
+
title: 'List Published Posts',
|
|
23
|
+
description: 'Get all published posts',
|
|
24
|
+
method: 'GET',
|
|
25
|
+
params: {
|
|
26
|
+
status: 'published'
|
|
27
|
+
},
|
|
28
|
+
tags: ['read', 'filter']
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'list-drafts',
|
|
32
|
+
title: 'List Draft Posts',
|
|
33
|
+
description: 'Get all draft posts',
|
|
34
|
+
method: 'GET',
|
|
35
|
+
params: {
|
|
36
|
+
status: 'draft'
|
|
37
|
+
},
|
|
38
|
+
tags: ['read', 'filter']
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'list-featured',
|
|
42
|
+
title: 'List Featured Posts',
|
|
43
|
+
description: 'Get all featured posts',
|
|
44
|
+
method: 'GET',
|
|
45
|
+
params: {
|
|
46
|
+
featured: 'true'
|
|
47
|
+
},
|
|
48
|
+
tags: ['read', 'filter']
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'search-by-title',
|
|
52
|
+
title: 'Search by Title',
|
|
53
|
+
description: 'Search posts by title',
|
|
54
|
+
method: 'GET',
|
|
55
|
+
params: {
|
|
56
|
+
search: '{{title}}'
|
|
57
|
+
},
|
|
58
|
+
tags: ['read', 'search']
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'create-draft',
|
|
62
|
+
title: 'Create Draft Post',
|
|
63
|
+
description: 'Create a new draft post',
|
|
64
|
+
method: 'POST',
|
|
65
|
+
payload: {
|
|
66
|
+
title: 'New Post Title',
|
|
67
|
+
slug: 'new-post-title',
|
|
68
|
+
excerpt: 'Brief description of the post',
|
|
69
|
+
status: 'draft'
|
|
70
|
+
},
|
|
71
|
+
tags: ['write', 'create']
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'publish-post',
|
|
75
|
+
title: 'Publish Post',
|
|
76
|
+
description: 'Publish a draft post',
|
|
77
|
+
method: 'PATCH',
|
|
78
|
+
pathParams: {
|
|
79
|
+
id: '{{id}}'
|
|
80
|
+
},
|
|
81
|
+
payload: {
|
|
82
|
+
status: 'published',
|
|
83
|
+
publishedAt: '{{publishedAt}}'
|
|
84
|
+
},
|
|
85
|
+
tags: ['write', 'update']
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'unpublish-post',
|
|
89
|
+
title: 'Unpublish Post',
|
|
90
|
+
description: 'Revert a post to draft',
|
|
91
|
+
method: 'PATCH',
|
|
92
|
+
pathParams: {
|
|
93
|
+
id: '{{id}}'
|
|
94
|
+
},
|
|
95
|
+
payload: {
|
|
96
|
+
status: 'draft'
|
|
97
|
+
},
|
|
98
|
+
tags: ['write', 'update']
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'set-featured',
|
|
102
|
+
title: 'Set as Featured',
|
|
103
|
+
description: 'Mark a post as featured',
|
|
104
|
+
method: 'PATCH',
|
|
105
|
+
pathParams: {
|
|
106
|
+
id: '{{id}}'
|
|
107
|
+
},
|
|
108
|
+
payload: {
|
|
109
|
+
featured: true
|
|
110
|
+
},
|
|
111
|
+
tags: ['write', 'update']
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'update-content',
|
|
115
|
+
title: 'Update Content',
|
|
116
|
+
description: 'Update post content blocks',
|
|
117
|
+
method: 'PATCH',
|
|
118
|
+
pathParams: {
|
|
119
|
+
id: '{{id}}'
|
|
120
|
+
},
|
|
121
|
+
payload: {
|
|
122
|
+
content: '{{content}}'
|
|
123
|
+
},
|
|
124
|
+
tags: ['write', 'update']
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'delete-post',
|
|
128
|
+
title: 'Delete Post',
|
|
129
|
+
description: 'Delete a post record',
|
|
130
|
+
method: 'DELETE',
|
|
131
|
+
pathParams: {
|
|
132
|
+
id: '{{id}}'
|
|
133
|
+
},
|
|
134
|
+
tags: ['write', 'delete']
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
})
|
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
|
|
@@ -175,5 +174,5 @@ export const entitySelectors = helpers.entitySelectors
|
|
|
175
174
|
export type ThemeSelectorsType = typeof THEME_SELECTORS
|
|
176
175
|
export type BlockSelectorsType = typeof BLOCK_SELECTORS
|
|
177
176
|
export type EntitySelectorsType = typeof ENTITY_SELECTORS
|
|
178
|
-
export type { Replacements } from '@nextsparkjs/core/
|
|
177
|
+
export type { Replacements } from '@nextsparkjs/core/selectors'
|
|
179
178
|
export { CORE_SELECTORS }
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/theme-blog",
|
|
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
|
"next-intl": "^4.0.0",
|
|
13
15
|
"react": "^19.0.0",
|
|
14
16
|
"react-dom": "^19.0.0",
|
|
15
|
-
"zod": "^4.0.0"
|
|
16
|
-
"@nextsparkjs/core": "0.1.0-beta.17"
|
|
17
|
+
"zod": "^4.0.0"
|
|
17
18
|
},
|
|
18
19
|
"nextspark": {
|
|
19
20
|
"type": "theme",
|
package/styles/globals.css
CHANGED
|
@@ -3,8 +3,41 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Warm amber/orange palette with editorial typography.
|
|
5
5
|
* Fonts: Oxanium (sans), Merriweather (serif), Fira Code (mono)
|
|
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
|
+
|
|
38
|
+
/* =============================================
|
|
39
|
+
BLOG THEME - LIGHT MODE
|
|
40
|
+
============================================= */
|
|
8
41
|
:root {
|
|
9
42
|
--background: oklch(0.9885 0.0057 84.5659);
|
|
10
43
|
--foreground: oklch(0.3660 0.0251 49.6085);
|
|
@@ -152,6 +185,18 @@
|
|
|
152
185
|
--shadow-2xl: var(--shadow-2xl);
|
|
153
186
|
}
|
|
154
187
|
|
|
188
|
+
/* =============================================
|
|
189
|
+
BASE STYLES
|
|
190
|
+
============================================= */
|
|
191
|
+
* {
|
|
192
|
+
border-color: var(--border);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
body {
|
|
196
|
+
background-color: var(--background);
|
|
197
|
+
color: var(--foreground);
|
|
198
|
+
}
|
|
199
|
+
|
|
155
200
|
/* ============================================================================
|
|
156
201
|
TYPOGRAPHY
|
|
157
202
|
============================================================================ */
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Blog Theme - E2E Testing Suite
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Complete E2E testing infrastructure for the Blog theme. The blog theme operates in **single-user mode** where each user owns their own isolated blog without team collaboration.
|
|
6
|
+
|
|
7
|
+
## Test Status: ✅ All Tests Passing (52/52)
|
|
8
|
+
|
|
9
|
+
| Test File | Tests | Status |
|
|
10
|
+
|-----------|-------|--------|
|
|
11
|
+
| `categories/categories-crud.cy.ts` | 9 | ✅ Passing |
|
|
12
|
+
| `posts/posts-crud.cy.ts` | 16 | ✅ Passing |
|
|
13
|
+
| `posts/posts-editor.cy.ts` | 19 | ✅ Passing |
|
|
14
|
+
| `posts/posts-status-workflow.cy.ts` | 8 | ✅ Passing |
|
|
15
|
+
| **Total** | **52** | **100%** |
|
|
16
|
+
|
|
17
|
+
## Test Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
test/cypress/e2e/themes/blog/
|
|
21
|
+
├── posts/
|
|
22
|
+
│ ├── posts-crud.cy.ts # Full CRUD operations (16 tests)
|
|
23
|
+
│ ├── posts-status-workflow.cy.ts # Publish/Unpublish workflow (8 tests)
|
|
24
|
+
│ └── posts-editor.cy.ts # WYSIWYG editor tests (19 tests)
|
|
25
|
+
├── categories/
|
|
26
|
+
│ └── categories-crud.cy.ts # Standard CRUD (9 tests)
|
|
27
|
+
└── README.md # This file
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Test Users
|
|
31
|
+
|
|
32
|
+
All blog theme tests use isolated blog author accounts:
|
|
33
|
+
|
|
34
|
+
| Author | Email | Description |
|
|
35
|
+
|--------|-------|-------------|
|
|
36
|
+
| **Marcos** (Primary) | `blog_author_marcos@nextspark.dev` | Main test author |
|
|
37
|
+
| Lucia | `blog_author_lucia@nextspark.dev` | Lifestyle blog author |
|
|
38
|
+
| Carlos | `blog_author_carlos@nextspark.dev` | Finance blog author |
|
|
39
|
+
|
|
40
|
+
**Password for all users:** `Test1234`
|
|
41
|
+
|
|
42
|
+
## POM Classes
|
|
43
|
+
|
|
44
|
+
Blog theme POM classes are located at `test/cypress/src/classes/themes/blog/`:
|
|
45
|
+
|
|
46
|
+
| Class | Description |
|
|
47
|
+
|-------|-------------|
|
|
48
|
+
| `PostsList.js` | Posts list page interactions (filters, views, actions) |
|
|
49
|
+
| `PostEditor.js` | Create/Edit post pages (supports both modes) |
|
|
50
|
+
| `WysiwygEditor.js` | Rich text editor component |
|
|
51
|
+
| `FeaturedImageUpload.js` | Image upload component |
|
|
52
|
+
| `session-helpers.ts` | Blog-specific login helpers |
|
|
53
|
+
|
|
54
|
+
## Session Helpers
|
|
55
|
+
|
|
56
|
+
Blog theme has its own session helpers for isolated login:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { loginAsBlogAuthor, BLOG_USERS } from '../../../src/session-helpers'
|
|
60
|
+
|
|
61
|
+
// Login as default author (Marcos)
|
|
62
|
+
loginAsBlogAuthor('MARCOS')
|
|
63
|
+
|
|
64
|
+
// Login as specific author
|
|
65
|
+
loginAsBlogAuthor('LUCIA')
|
|
66
|
+
loginAsBlogAuthor('CARLOS')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Running Tests
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Run all blog theme tests
|
|
73
|
+
npx cypress run --spec "test/cypress/e2e/themes/blog/**/*.cy.ts"
|
|
74
|
+
|
|
75
|
+
# Run only posts tests
|
|
76
|
+
npx cypress run --spec "test/cypress/e2e/themes/blog/posts/**/*.cy.ts"
|
|
77
|
+
|
|
78
|
+
# Run only categories tests
|
|
79
|
+
npx cypress run --spec "test/cypress/e2e/themes/blog/categories/**/*.cy.ts"
|
|
80
|
+
|
|
81
|
+
# Run with blog theme active
|
|
82
|
+
NEXT_PUBLIC_ACTIVE_THEME=blog npx cypress run --spec "test/cypress/e2e/themes/blog/**/*.cy.ts"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Data-cy Selectors
|
|
86
|
+
|
|
87
|
+
### Posts List Page
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
[data-cy="posts-list-container"]
|
|
91
|
+
[data-cy="posts-stat-all|published|draft|scheduled"]
|
|
92
|
+
[data-cy="posts-search-input"]
|
|
93
|
+
[data-cy="posts-sort-select"]
|
|
94
|
+
[data-cy="posts-view-table|grid"]
|
|
95
|
+
[data-cy="posts-row-{id}"]
|
|
96
|
+
[data-cy="posts-title-{id}"]
|
|
97
|
+
[data-cy="posts-status-{id}"]
|
|
98
|
+
[data-cy="posts-actions-{id}"]
|
|
99
|
+
[data-cy="posts-edit-{id}"]
|
|
100
|
+
[data-cy="posts-publish-{id}"]
|
|
101
|
+
[data-cy="posts-delete-{id}"]
|
|
102
|
+
[data-cy="posts-create-button"]
|
|
103
|
+
[data-cy="posts-delete-dialog"]
|
|
104
|
+
[data-cy="posts-delete-confirm|cancel"]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Post Editor (Create/Edit)
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
[data-cy="post-{mode}-container"] # mode = create|edit
|
|
111
|
+
[data-cy="post-{mode}-header"]
|
|
112
|
+
[data-cy="post-{mode}-back"]
|
|
113
|
+
[data-cy="post-{mode}-status"]
|
|
114
|
+
[data-cy="post-{mode}-autosaved"]
|
|
115
|
+
[data-cy="post-{mode}-save"]
|
|
116
|
+
[data-cy="post-{mode}-publish"]
|
|
117
|
+
[data-cy="post-{mode}-unpublish"] # edit only
|
|
118
|
+
[data-cy="post-{mode}-title"]
|
|
119
|
+
[data-cy="post-{mode}-content"]
|
|
120
|
+
[data-cy="post-{mode}-settings"]
|
|
121
|
+
[data-cy="post-{mode}-status-select"]
|
|
122
|
+
[data-cy="post-{mode}-slug"]
|
|
123
|
+
[data-cy="post-{mode}-excerpt"]
|
|
124
|
+
[data-cy="post-{mode}-featured-image"]
|
|
125
|
+
[data-cy="post-{mode}-featured-toggle"]
|
|
126
|
+
[data-cy="post-{mode}-delete"] # edit only
|
|
127
|
+
[data-cy="post-{mode}-delete-dialog"]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### WYSIWYG Editor
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
[data-cy="wysiwyg-container"]
|
|
134
|
+
[data-cy="wysiwyg-toolbar"]
|
|
135
|
+
[data-cy="wysiwyg-content"]
|
|
136
|
+
[data-cy="wysiwyg-preview"]
|
|
137
|
+
[data-cy="wysiwyg-preview-toggle"]
|
|
138
|
+
[data-cy="wysiwyg-{command}"] # undo, redo, bold, italic, etc.
|
|
139
|
+
[data-cy="wysiwyg-formatBlock-{tag}"] # h1, h2, h3, blockquote, pre
|
|
140
|
+
[data-cy="wysiwyg-wordcount"]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Featured Image Upload
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
[data-cy="featured-image-container"]
|
|
147
|
+
[data-cy="featured-image-dropzone"]
|
|
148
|
+
[data-cy="featured-image-input"]
|
|
149
|
+
[data-cy="featured-image-preview"]
|
|
150
|
+
[data-cy="featured-image-remove"]
|
|
151
|
+
[data-cy="featured-image-loading"]
|
|
152
|
+
[data-cy="featured-image-error"]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Dependencies
|
|
156
|
+
|
|
157
|
+
- Generic POM classes from `test/cypress/src/classes/components/entities/`
|
|
158
|
+
- DevKeyring for authentication: `test/cypress/src/classes/components/auth/DevKeyring.js`
|
|
159
|
+
- Core session helpers (NOT modified): `test/cypress/src/helpers/session-helpers.ts`
|
|
160
|
+
|
|
161
|
+
## Notes
|
|
162
|
+
|
|
163
|
+
- Blog theme uses **custom UI** for posts (PostsList, PostEditor) unlike generic EntityList/EntityForm
|
|
164
|
+
- Categories use the **generic EntityList/EntityForm** pattern
|
|
165
|
+
- All tests use **cy.session()** for cached authentication (3-5x faster)
|
|
166
|
+
- Tests are designed to be **self-contained** and create their own test data
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
**Last Updated:** 2025-12-04
|