@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,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/lib/test/selector-factory'
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/lib/test/selector-factory'
177
+ export type { Replacements } from '@nextsparkjs/core/selectors'
179
178
  export { CORE_SELECTORS }
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-blog",
3
- "version": "0.1.0-beta.17",
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",
@@ -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