@nextsparkjs/theme-default 0.1.0-beta.20 → 0.1.0-beta.21
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 +1 -1
- package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
- package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
- package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
- package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
- package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
- package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
- package/tests/cypress/e2e/_docs/README.md +215 -0
- package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
- package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
- package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
- package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
- package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
- package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
- package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
- package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
- package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
- package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
- package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
- package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
- package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
- package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
- package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
- package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
- package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
- package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
- package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
- package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
- package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
- package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
- package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
- package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
- package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
- package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
- package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
- package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
- package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
- package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
- package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
- package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
- package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
- package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
- package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
- package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
- package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
- package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
- package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
- package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
- package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
- package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
- package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
- package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
- package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
- package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
- package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
- package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
- package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
- package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
- package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
- package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
- package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
- package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
- package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
- package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
- package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
- package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
- package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
- package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
- package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
- package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
- package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
- package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
- package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
- package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
- package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
- package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
- package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
- package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
- package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
- package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
- package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
- package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
- package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
- package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
- package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
- package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
- package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
- package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
- package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
- package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
- package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
- package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
- package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
- package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
- package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
- package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
- package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
- package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
- package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
- package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
- package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
- package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
- package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
- package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
- package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
- package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
- package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
- package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
- package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
- package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
- package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
- package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
- package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
- package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
- package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
- package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
- package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
- package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
- package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
- package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
- package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
- package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
- package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
- package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
- package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
- package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
- package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
- package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
- package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
- package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
- package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
- package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
- package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
- package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
- package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
- package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
- package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
- package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
- package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
- package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
- package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
- package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
- package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
- package/tests/cypress/fixtures/blocks.json +218 -0
- package/tests/cypress/fixtures/entities.json +78 -0
- package/tests/cypress/fixtures/page-builder.json +21 -0
- package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
- package/tests/cypress/src/components/CustomersPOM.ts +439 -0
- package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
- package/tests/cypress/src/components/EntityForm.ts +375 -0
- package/tests/cypress/src/components/EntityList.ts +389 -0
- package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
- package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
- package/tests/cypress/src/components/PostsListPOM.ts +223 -0
- package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
- package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
- package/tests/cypress/src/components/TasksPOM.ts +272 -0
- package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
- package/tests/cypress/src/components/index.ts +21 -0
- package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
- package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
- package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
- package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
- package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
- package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
- package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
- package/tests/cypress/src/controllers/index.js +25 -0
- package/tests/cypress/src/core/AuthPOM.ts +450 -0
- package/tests/cypress/src/core/BasePOM.ts +86 -0
- package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
- package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
- package/tests/cypress/src/core/index.ts +14 -0
- package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
- package/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/tests/cypress/src/entities/PostsPOM.ts +137 -0
- package/tests/cypress/src/entities/TasksPOM.ts +176 -0
- package/tests/cypress/src/entities/index.ts +14 -0
- package/tests/cypress/src/features/BillingPOM.ts +385 -0
- package/tests/cypress/src/features/DashboardPOM.ts +245 -0
- package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
- package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
- package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
- package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
- package/tests/cypress/src/features/SettingsPOM.ts +362 -0
- package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
- package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
- package/tests/cypress/src/features/index.ts +28 -0
- package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
- package/tests/cypress/src/index.ts +101 -0
- package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
- package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
- package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
- package/tests/cypress/src/pages/dashboard/index.js +6 -0
- package/tests/cypress/src/pages/index.js +5 -0
- package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
- package/tests/cypress/src/pages/public/LandingPage.js +69 -0
- package/tests/cypress/src/pages/public/PricingPage.js +33 -0
- package/tests/cypress/src/pages/public/index.js +6 -0
- package/tests/cypress/src/selectors.ts +46 -0
- package/tests/cypress/src/session-helpers.ts +500 -0
- package/tests/cypress/support/doc-commands.ts +260 -0
- package/tests/cypress.config.ts +150 -0
- package/tests/jest/components/post-header.test.tsx +377 -0
- package/tests/jest/config/role-config.test.ts +529 -0
- package/tests/jest/jest.config.ts +81 -0
- package/tests/jest/langchain/COVERAGE.md +372 -0
- package/tests/jest/langchain/guardrails.test.ts +465 -0
- package/tests/jest/langchain/streaming.test.ts +367 -0
- package/tests/jest/langchain/token-tracker.test.ts +455 -0
- package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
- package/tests/jest/langchain/tracer.test.ts +823 -0
- package/tests/jest/user-roles/role-helpers.test.ts +432 -0
- package/tests/jest/validation/categories.test.ts +429 -0
- package/tests/jest/validation/posts.test.ts +546 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post Editor POM
|
|
3
|
+
*
|
|
4
|
+
* Page Object Model for the Post Editor (create/edit posts with blocks).
|
|
5
|
+
* Reuses Page Builder patterns for block management.
|
|
6
|
+
*
|
|
7
|
+
* Convention: post-{component}-{element}
|
|
8
|
+
* Based on selectors documented in tests.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ApiInterceptor } from '../helpers/ApiInterceptor'
|
|
12
|
+
|
|
13
|
+
export interface PostFormData {
|
|
14
|
+
title: string
|
|
15
|
+
slug: string
|
|
16
|
+
excerpt?: string
|
|
17
|
+
featuredImage?: string
|
|
18
|
+
categoryIds?: string[]
|
|
19
|
+
published?: boolean
|
|
20
|
+
locale?: 'en' | 'es'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BlockData {
|
|
24
|
+
slug: string
|
|
25
|
+
props?: Record<string, unknown>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PostEditorPOM {
|
|
29
|
+
// ============================================
|
|
30
|
+
// STATIC CONFIG
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
static get slug() {
|
|
34
|
+
return 'posts'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================
|
|
38
|
+
// API INTERCEPTOR (for deterministic waits)
|
|
39
|
+
// ============================================
|
|
40
|
+
|
|
41
|
+
private static _api: ApiInterceptor | null = null
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the API interceptor instance for posts
|
|
45
|
+
* Lazy-initialized on first access
|
|
46
|
+
*/
|
|
47
|
+
static get api(): ApiInterceptor {
|
|
48
|
+
if (!this._api) {
|
|
49
|
+
this._api = new ApiInterceptor(this.slug)
|
|
50
|
+
}
|
|
51
|
+
return this._api
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Setup API intercepts for CRUD operations
|
|
56
|
+
* Call this in beforeEach BEFORE navigation
|
|
57
|
+
*/
|
|
58
|
+
static setupApiIntercepts(): typeof PostEditorPOM {
|
|
59
|
+
this.api.setupCrudIntercepts()
|
|
60
|
+
return this
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================
|
|
64
|
+
// SELECTORS - Post Editor
|
|
65
|
+
// ============================================
|
|
66
|
+
|
|
67
|
+
static get editorSelectors() {
|
|
68
|
+
return {
|
|
69
|
+
// Editor container
|
|
70
|
+
editor: '[data-cy="post-editor"]',
|
|
71
|
+
|
|
72
|
+
// Post form fields
|
|
73
|
+
titleInput: '[data-cy="post-title-input"]',
|
|
74
|
+
slugInput: '[data-cy="post-slug-input"]',
|
|
75
|
+
excerptInput: '[data-cy="post-excerpt-input"]',
|
|
76
|
+
featuredImage: '[data-cy="post-featured-image"]',
|
|
77
|
+
categoriesSelect: '[data-cy="post-categories-select"]',
|
|
78
|
+
publishToggle: '[data-cy="post-publish-toggle"]',
|
|
79
|
+
saveButton: '[data-cy="post-save-button"]',
|
|
80
|
+
publishButton: '[data-cy="post-publish-button"]',
|
|
81
|
+
|
|
82
|
+
// Block editor (reuses page builder components)
|
|
83
|
+
blockSelector: '[data-cy="post-block-selector"]',
|
|
84
|
+
blockEditor: '[data-cy="post-block-editor"]',
|
|
85
|
+
|
|
86
|
+
// From PageBuilderPOM patterns
|
|
87
|
+
blockPicker: '[data-cy="block-picker"]',
|
|
88
|
+
blockSearchInput: '[data-cy="block-search-input"]',
|
|
89
|
+
blockItem: (slug: string) => `[data-cy="block-item-${slug}"]`,
|
|
90
|
+
addBlockBtn: (slug: string) => `[data-cy="add-block-${slug}"]`,
|
|
91
|
+
|
|
92
|
+
blockCanvas: '[data-cy="block-canvas"]',
|
|
93
|
+
sortableBlock: (id: string) => `[data-cy="sortable-block-${id}"]`,
|
|
94
|
+
removeBlock: (id: string) => `[data-cy="remove-block-${id}"]`,
|
|
95
|
+
sortableBlockGeneric: '[data-cy^="sortable-block-"]',
|
|
96
|
+
|
|
97
|
+
// Settings panel
|
|
98
|
+
settingsPanel: '[data-cy="block-settings-panel"]',
|
|
99
|
+
fieldInput: (name: string) => `[data-cy="field-${name}"]`,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================
|
|
104
|
+
// NAVIGATION
|
|
105
|
+
// ============================================
|
|
106
|
+
|
|
107
|
+
static visitCreate() {
|
|
108
|
+
cy.visit('/dashboard/posts/create')
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static visitEdit(id: string) {
|
|
113
|
+
cy.visit(`/dashboard/posts/${id}/edit`)
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================
|
|
118
|
+
// API-AWARE NAVIGATION
|
|
119
|
+
// ============================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Visit create page with API intercepts
|
|
123
|
+
*/
|
|
124
|
+
static visitCreateWithApiWait(): typeof PostEditorPOM {
|
|
125
|
+
this.setupApiIntercepts()
|
|
126
|
+
this.visitCreate()
|
|
127
|
+
return this
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Visit edit page with API intercepts
|
|
132
|
+
*/
|
|
133
|
+
static visitEditWithApiWait(id: string): typeof PostEditorPOM {
|
|
134
|
+
this.setupApiIntercepts()
|
|
135
|
+
this.visitEdit(id)
|
|
136
|
+
return this
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================
|
|
140
|
+
// WAIT METHODS
|
|
141
|
+
// ============================================
|
|
142
|
+
|
|
143
|
+
static waitForEditorLoad() {
|
|
144
|
+
cy.get(this.editorSelectors.editor, { timeout: 15000 }).should('be.visible')
|
|
145
|
+
return this
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static waitForBlockPickerLoad() {
|
|
149
|
+
cy.get(this.editorSelectors.blockPicker, { timeout: 10000 }).should('be.visible')
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================
|
|
154
|
+
// POST FORM INTERACTIONS
|
|
155
|
+
// ============================================
|
|
156
|
+
|
|
157
|
+
static setTitle(title: string) {
|
|
158
|
+
cy.get(this.editorSelectors.titleInput).clear().type(title)
|
|
159
|
+
return this
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static setSlug(slug: string) {
|
|
163
|
+
cy.get(this.editorSelectors.slugInput).clear().type(slug)
|
|
164
|
+
return this
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static setExcerpt(excerpt: string) {
|
|
168
|
+
cy.get(this.editorSelectors.excerptInput).clear().type(excerpt)
|
|
169
|
+
return this
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static setFeaturedImage(url: string) {
|
|
173
|
+
cy.get(this.editorSelectors.featuredImage).clear().type(url)
|
|
174
|
+
return this
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static selectCategories(categoryNames: string[]) {
|
|
178
|
+
// Click on categories select to open
|
|
179
|
+
cy.get(this.editorSelectors.categoriesSelect).click()
|
|
180
|
+
|
|
181
|
+
// Select each category
|
|
182
|
+
categoryNames.forEach((name) => {
|
|
183
|
+
cy.contains('[role="option"]', name).click()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// Click outside to close
|
|
187
|
+
cy.get(this.editorSelectors.editor).click()
|
|
188
|
+
return this
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static togglePublish() {
|
|
192
|
+
cy.get(this.editorSelectors.publishToggle).click()
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static savePost() {
|
|
197
|
+
cy.get(this.editorSelectors.saveButton).click()
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static publishPost() {
|
|
202
|
+
cy.get(this.editorSelectors.publishButton).click()
|
|
203
|
+
return this
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================
|
|
207
|
+
// API-AWARE SAVE OPERATIONS
|
|
208
|
+
// ============================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Save post and wait for API response
|
|
212
|
+
*/
|
|
213
|
+
static savePostWithApiWait(): typeof PostEditorPOM {
|
|
214
|
+
this.savePost()
|
|
215
|
+
this.api.waitForCreate()
|
|
216
|
+
return this
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Update post and wait for API response
|
|
221
|
+
*/
|
|
222
|
+
static updatePostWithApiWait(): typeof PostEditorPOM {
|
|
223
|
+
this.savePost()
|
|
224
|
+
this.api.waitForUpdate()
|
|
225
|
+
return this
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================
|
|
229
|
+
// BLOCK EDITOR INTERACTIONS
|
|
230
|
+
// ============================================
|
|
231
|
+
|
|
232
|
+
static searchBlocks(term: string) {
|
|
233
|
+
cy.get(this.editorSelectors.blockSearchInput).clear().type(term)
|
|
234
|
+
return this
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
static addBlock(slug: string) {
|
|
238
|
+
cy.get(this.editorSelectors.addBlockBtn(slug)).click()
|
|
239
|
+
return this
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static selectBlock(blockId: string) {
|
|
243
|
+
cy.get(this.editorSelectors.sortableBlock(blockId)).click()
|
|
244
|
+
return this
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static removeBlock(blockId: string) {
|
|
248
|
+
cy.get(this.editorSelectors.removeBlock(blockId)).click()
|
|
249
|
+
return this
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static fillBlockField(fieldName: string, value: string) {
|
|
253
|
+
cy.get(this.editorSelectors.fieldInput(fieldName)).find('input, textarea').first().clear().type(value)
|
|
254
|
+
return this
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================
|
|
258
|
+
// COMPLETE WORKFLOWS
|
|
259
|
+
// ============================================
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create a new post with specified data and blocks
|
|
263
|
+
*/
|
|
264
|
+
static createPost(data: PostFormData, blocks: BlockData[] = []) {
|
|
265
|
+
this.visitCreate()
|
|
266
|
+
this.waitForEditorLoad()
|
|
267
|
+
|
|
268
|
+
// Set post fields
|
|
269
|
+
this.setTitle(data.title)
|
|
270
|
+
this.setSlug(data.slug)
|
|
271
|
+
|
|
272
|
+
if (data.excerpt) {
|
|
273
|
+
this.setExcerpt(data.excerpt)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (data.featuredImage) {
|
|
277
|
+
this.setFeaturedImage(data.featuredImage)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (data.categoryIds && data.categoryIds.length > 0) {
|
|
281
|
+
this.selectCategories(data.categoryIds)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Add blocks
|
|
285
|
+
blocks.forEach((block) => {
|
|
286
|
+
this.addBlock(block.slug)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Publish or save
|
|
290
|
+
if (data.published) {
|
|
291
|
+
this.publishPost()
|
|
292
|
+
} else {
|
|
293
|
+
this.savePost()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Create post with API wait for deterministic testing
|
|
301
|
+
*/
|
|
302
|
+
static createPostWithApiWait(data: PostFormData, blocks: BlockData[] = []): typeof PostEditorPOM {
|
|
303
|
+
this.setupApiIntercepts()
|
|
304
|
+
this.visitCreate()
|
|
305
|
+
this.waitForEditorLoad()
|
|
306
|
+
|
|
307
|
+
this.setTitle(data.title)
|
|
308
|
+
this.setSlug(data.slug)
|
|
309
|
+
|
|
310
|
+
if (data.excerpt) this.setExcerpt(data.excerpt)
|
|
311
|
+
if (data.featuredImage) this.setFeaturedImage(data.featuredImage)
|
|
312
|
+
|
|
313
|
+
blocks.forEach((block) => this.addBlock(block.slug))
|
|
314
|
+
|
|
315
|
+
this.savePost()
|
|
316
|
+
this.api.waitForCreate()
|
|
317
|
+
return this
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================
|
|
321
|
+
// ASSERTIONS
|
|
322
|
+
// ============================================
|
|
323
|
+
|
|
324
|
+
static assertEditorVisible() {
|
|
325
|
+
cy.get(this.editorSelectors.editor).should('be.visible')
|
|
326
|
+
return this
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
static assertBlockPickerVisible() {
|
|
330
|
+
cy.get(this.editorSelectors.blockPicker).should('be.visible')
|
|
331
|
+
return this
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
static assertBlockInPicker(blockSlug: string) {
|
|
335
|
+
cy.get(this.editorSelectors.blockItem(blockSlug)).should('be.visible')
|
|
336
|
+
return this
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static assertBlockNotInPicker(blockSlug: string) {
|
|
340
|
+
cy.get(this.editorSelectors.blockItem(blockSlug)).should('not.exist')
|
|
341
|
+
return this
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
static assertBlockCount(count: number) {
|
|
345
|
+
cy.get(this.editorSelectors.sortableBlockGeneric).should('have.length', count)
|
|
346
|
+
return this
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static assertTitleValue(title: string) {
|
|
350
|
+
cy.get(this.editorSelectors.titleInput).should('have.value', title)
|
|
351
|
+
return this
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
static assertSlugValue(slug: string) {
|
|
355
|
+
cy.get(this.editorSelectors.slugInput).should('have.value', slug)
|
|
356
|
+
return this
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
static assertSaveSuccess() {
|
|
360
|
+
cy.contains('saved', { matchCase: false }).should('be.visible')
|
|
361
|
+
return this
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
static assertPublishSuccess() {
|
|
365
|
+
cy.contains('published', { matchCase: false }).should('be.visible')
|
|
366
|
+
return this
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export default PostEditorPOM
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts List POM
|
|
3
|
+
*
|
|
4
|
+
* Page Object Model for the Posts list page in the admin dashboard.
|
|
5
|
+
* Covers list viewing, search, filtering, and navigation to create/edit posts.
|
|
6
|
+
*
|
|
7
|
+
* Convention: posts-{component}-{element}
|
|
8
|
+
* Based on selectors documented in tests.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ApiInterceptor } from '../helpers/ApiInterceptor'
|
|
12
|
+
|
|
13
|
+
export interface PostFormData {
|
|
14
|
+
title: string
|
|
15
|
+
slug: string
|
|
16
|
+
excerpt?: string
|
|
17
|
+
featuredImage?: string
|
|
18
|
+
categoryIds?: string[]
|
|
19
|
+
published?: boolean
|
|
20
|
+
locale?: 'en' | 'es'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class PostsListPOM {
|
|
24
|
+
// ============================================
|
|
25
|
+
// STATIC CONFIG
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
static get slug() {
|
|
29
|
+
return 'posts'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================
|
|
33
|
+
// API INTERCEPTOR (for deterministic waits)
|
|
34
|
+
// ============================================
|
|
35
|
+
|
|
36
|
+
private static _api: ApiInterceptor | null = null
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the API interceptor instance for posts
|
|
40
|
+
* Lazy-initialized on first access
|
|
41
|
+
*/
|
|
42
|
+
static get api(): ApiInterceptor {
|
|
43
|
+
if (!this._api) {
|
|
44
|
+
this._api = new ApiInterceptor(this.slug)
|
|
45
|
+
}
|
|
46
|
+
return this._api
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Setup API intercepts for CRUD operations
|
|
51
|
+
* Call this in beforeEach BEFORE navigation
|
|
52
|
+
*/
|
|
53
|
+
static setupApiIntercepts(): typeof PostsListPOM {
|
|
54
|
+
this.api.setupCrudIntercepts()
|
|
55
|
+
return this
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// SELECTORS - Posts List
|
|
60
|
+
// ============================================
|
|
61
|
+
|
|
62
|
+
static get listSelectors() {
|
|
63
|
+
// Using entity testing convention: {slug}-{component}
|
|
64
|
+
// Based on createCyId(entityConfig.slug, component) from testing-utils.ts
|
|
65
|
+
return {
|
|
66
|
+
page: '[data-cy="posts-page"]',
|
|
67
|
+
title: '[data-cy="posts-title"]',
|
|
68
|
+
table: '[data-cy="posts-table"], table',
|
|
69
|
+
createBtn: '[data-cy="posts-add"]',
|
|
70
|
+
searchContainer: '[data-cy="posts-search"]',
|
|
71
|
+
searchInput: '[data-cy="posts-search-input"]',
|
|
72
|
+
categoryFilter: '[data-cy="posts-filter-categoryId"]',
|
|
73
|
+
statusFilter: '[data-cy="posts-filter-status"]',
|
|
74
|
+
pagination: '[data-cy="posts-pagination"]',
|
|
75
|
+
row: (id: string) => `[data-cy="posts-row-${id}"]`,
|
|
76
|
+
rowGeneric: 'table tbody tr',
|
|
77
|
+
// Row menu actions (EntityTable patterns)
|
|
78
|
+
menuTrigger: (id: string) => `[data-cy="posts-menu-${id}"]`,
|
|
79
|
+
menuEdit: (id: string) => `[data-cy="posts-menu-edit-${id}"]`,
|
|
80
|
+
menuDelete: (id: string) => `[data-cy="posts-menu-delete-${id}"]`,
|
|
81
|
+
menuView: (id: string) => `[data-cy="posts-menu-view-${id}"]`,
|
|
82
|
+
confirmDelete: '[data-cy="posts-confirm-delete"], [role="dialog"]',
|
|
83
|
+
confirmDeleteBtn: '[data-cy="posts-confirm-delete-btn"], [role="dialog"] button:contains("Delete"), button.bg-destructive',
|
|
84
|
+
cancelDeleteBtn: '[data-cy="posts-cancel-delete-btn"], [role="dialog"] button:contains("Cancel"),[role="dialog"] button[type="button"]:not(.bg-destructive)',
|
|
85
|
+
emptyState: 'td:contains("No posts")',
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================
|
|
90
|
+
// NAVIGATION
|
|
91
|
+
// ============================================
|
|
92
|
+
|
|
93
|
+
static visitList() {
|
|
94
|
+
cy.visit('/dashboard/posts')
|
|
95
|
+
return this
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static visitCreate() {
|
|
99
|
+
cy.visit('/dashboard/posts/create')
|
|
100
|
+
return this
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static visitEdit(id: string) {
|
|
104
|
+
cy.visit(`/dashboard/posts/${id}/edit`)
|
|
105
|
+
return this
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================
|
|
109
|
+
// API-AWARE NAVIGATION
|
|
110
|
+
// ============================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Visit list page with API intercepts and wait for data load
|
|
114
|
+
*/
|
|
115
|
+
static visitListWithApiWait(): typeof PostsListPOM {
|
|
116
|
+
this.setupApiIntercepts()
|
|
117
|
+
this.visitList()
|
|
118
|
+
this.api.waitForList()
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================
|
|
123
|
+
// WAIT METHODS
|
|
124
|
+
// ============================================
|
|
125
|
+
|
|
126
|
+
static waitForListLoad() {
|
|
127
|
+
cy.url().should('include', '/dashboard/posts')
|
|
128
|
+
cy.get(this.listSelectors.page, { timeout: 15000 }).should('exist')
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================
|
|
133
|
+
// LIST PAGE INTERACTIONS
|
|
134
|
+
// ============================================
|
|
135
|
+
|
|
136
|
+
static clickCreatePost() {
|
|
137
|
+
cy.get(this.listSelectors.createBtn).click()
|
|
138
|
+
return this
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static searchPosts(term: string) {
|
|
142
|
+
cy.get(this.listSelectors.searchInput).clear().type(term)
|
|
143
|
+
return this
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static clearSearch() {
|
|
147
|
+
cy.get(this.listSelectors.searchInput).clear()
|
|
148
|
+
return this
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static filterByCategory(categoryName: string) {
|
|
152
|
+
cy.get(this.listSelectors.categoryFilter).click()
|
|
153
|
+
cy.contains('[role="option"]', categoryName).click()
|
|
154
|
+
return this
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static filterByStatus(status: 'all' | 'published' | 'draft') {
|
|
158
|
+
cy.get(this.listSelectors.statusFilter).click()
|
|
159
|
+
cy.contains('[role="option"]', status).click()
|
|
160
|
+
return this
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static openRowMenu(id: string) {
|
|
164
|
+
cy.get(this.listSelectors.menuTrigger(id)).click()
|
|
165
|
+
return this
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static clickMenuEdit(id: string) {
|
|
169
|
+
cy.get(this.listSelectors.menuEdit(id)).click()
|
|
170
|
+
return this
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static clickMenuDelete(id: string) {
|
|
174
|
+
cy.get(this.listSelectors.menuDelete(id)).click()
|
|
175
|
+
return this
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static clickMenuView(id: string) {
|
|
179
|
+
cy.get(this.listSelectors.menuView(id)).click()
|
|
180
|
+
return this
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static confirmDelete() {
|
|
184
|
+
cy.get(this.listSelectors.confirmDeleteBtn).click()
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static cancelDelete() {
|
|
189
|
+
cy.get(this.listSelectors.cancelDeleteBtn).click()
|
|
190
|
+
return this
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================
|
|
194
|
+
// ASSERTIONS
|
|
195
|
+
// ============================================
|
|
196
|
+
|
|
197
|
+
static assertListPageVisible() {
|
|
198
|
+
cy.get(this.listSelectors.page).should('exist')
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static assertPostInList(title: string) {
|
|
203
|
+
cy.contains(this.listSelectors.rowGeneric, title).should('be.visible')
|
|
204
|
+
return this
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static assertPostNotInList(title: string) {
|
|
208
|
+
cy.contains(this.listSelectors.rowGeneric, title).should('not.exist')
|
|
209
|
+
return this
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static assertEmptyList() {
|
|
213
|
+
cy.get(this.listSelectors.emptyState).should('be.visible')
|
|
214
|
+
return this
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static assertCategoryBadgeVisible(categoryName: string) {
|
|
218
|
+
cy.contains(this.listSelectors.table, categoryName).should('be.visible')
|
|
219
|
+
return this
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default PostsListPOM
|