@nextsparkjs/theme-crm 0.1.0-beta.18 → 0.1.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/package.json +2 -2
  2. package/tests/cypress/e2e/api/activities/activities-crud.cy.ts +686 -0
  3. package/tests/cypress/e2e/api/campaigns/campaigns-crud.cy.ts +592 -0
  4. package/tests/cypress/e2e/api/companies/companies-crud.cy.ts +682 -0
  5. package/tests/cypress/e2e/api/contacts/contacts-crud.cy.ts +668 -0
  6. package/tests/cypress/e2e/api/leads/leads-crud.cy.ts +648 -0
  7. package/tests/cypress/e2e/api/notes/notes-crud.cy.ts +424 -0
  8. package/tests/cypress/e2e/api/opportunities/opportunities-crud.cy.ts +865 -0
  9. package/tests/cypress/e2e/api/pipelines/pipelines-crud.cy.ts +545 -0
  10. package/tests/cypress/e2e/api/products/products-crud.cy.ts +447 -0
  11. package/tests/cypress/e2e/ui/activities/activities-admin.cy.ts +268 -0
  12. package/tests/cypress/e2e/ui/activities/activities-member.cy.ts +257 -0
  13. package/tests/cypress/e2e/ui/activities/activities-owner.cy.ts +268 -0
  14. package/tests/cypress/e2e/ui/companies/companies-admin.cy.ts +188 -0
  15. package/tests/cypress/e2e/ui/companies/companies-member.cy.ts +166 -0
  16. package/tests/cypress/e2e/ui/companies/companies-owner.cy.ts +189 -0
  17. package/tests/cypress/e2e/ui/contacts/contacts-admin.cy.ts +252 -0
  18. package/tests/cypress/e2e/ui/contacts/contacts-member.cy.ts +224 -0
  19. package/tests/cypress/e2e/ui/contacts/contacts-owner.cy.ts +236 -0
  20. package/tests/cypress/e2e/ui/leads/leads-admin.cy.ts +286 -0
  21. package/tests/cypress/e2e/ui/leads/leads-member.cy.ts +193 -0
  22. package/tests/cypress/e2e/ui/leads/leads-owner.cy.ts +210 -0
  23. package/tests/cypress/e2e/ui/opportunities/opportunities-admin.cy.ts +197 -0
  24. package/tests/cypress/e2e/ui/opportunities/opportunities-member.cy.ts +229 -0
  25. package/tests/cypress/e2e/ui/opportunities/opportunities-owner.cy.ts +196 -0
  26. package/tests/cypress/e2e/ui/pipelines/pipelines-admin.cy.ts +320 -0
  27. package/tests/cypress/e2e/ui/pipelines/pipelines-member.cy.ts +262 -0
  28. package/tests/cypress/e2e/ui/pipelines/pipelines-owner.cy.ts +282 -0
  29. package/tests/cypress/fixtures/blocks.json +9 -0
  30. package/tests/cypress/fixtures/entities.json +240 -0
  31. package/tests/cypress/src/components/CRMDataTable.js +223 -0
  32. package/tests/cypress/src/components/CRMMobileNav.js +138 -0
  33. package/tests/cypress/src/components/CRMSidebar.js +145 -0
  34. package/tests/cypress/src/components/CRMTopBar.js +194 -0
  35. package/tests/cypress/src/components/DealCard.js +197 -0
  36. package/tests/cypress/src/components/EntityDetail.ts +290 -0
  37. package/tests/cypress/src/components/EntityForm.ts +357 -0
  38. package/tests/cypress/src/components/EntityList.ts +360 -0
  39. package/tests/cypress/src/components/PipelineKanban.js +204 -0
  40. package/tests/cypress/src/components/StageColumn.js +196 -0
  41. package/tests/cypress/src/components/index.js +13 -0
  42. package/tests/cypress/src/components/index.ts +22 -0
  43. package/tests/cypress/src/controllers/ActivityAPIController.ts +113 -0
  44. package/tests/cypress/src/controllers/BaseAPIController.ts +307 -0
  45. package/tests/cypress/src/controllers/CampaignAPIController.ts +114 -0
  46. package/tests/cypress/src/controllers/CompanyAPIController.ts +112 -0
  47. package/tests/cypress/src/controllers/ContactAPIController.ts +104 -0
  48. package/tests/cypress/src/controllers/LeadAPIController.ts +96 -0
  49. package/tests/cypress/src/controllers/NoteAPIController.ts +130 -0
  50. package/tests/cypress/src/controllers/OpportunityAPIController.ts +134 -0
  51. package/tests/cypress/src/controllers/PipelineAPIController.ts +116 -0
  52. package/tests/cypress/src/controllers/ProductAPIController.ts +113 -0
  53. package/tests/cypress/src/controllers/index.ts +35 -0
  54. package/tests/cypress/src/entities/ActivitiesPOM.ts +130 -0
  55. package/tests/cypress/src/entities/CompaniesPOM.ts +117 -0
  56. package/tests/cypress/src/entities/ContactsPOM.ts +117 -0
  57. package/tests/cypress/src/entities/LeadsPOM.ts +129 -0
  58. package/tests/cypress/src/entities/OpportunitiesPOM.ts +178 -0
  59. package/tests/cypress/src/entities/PipelinesPOM.ts +341 -0
  60. package/tests/cypress/src/entities/index.ts +31 -0
  61. package/tests/cypress/src/forms/OpportunityForm.js +316 -0
  62. package/tests/cypress/src/forms/PipelineForm.js +243 -0
  63. package/tests/cypress/src/forms/index.js +8 -0
  64. package/tests/cypress/src/index.js +22 -0
  65. package/tests/cypress/src/index.ts +68 -0
  66. package/tests/cypress/src/selectors.ts +50 -0
  67. package/tests/cypress/src/session-helpers.ts +94 -0
  68. package/tests/cypress/support/e2e.ts +89 -0
  69. package/tests/cypress.config.ts +165 -0
  70. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Leads Page Object Model for CRM Theme
3
+ *
4
+ * Entity-specific POM for Leads.
5
+ *
6
+ * Usage:
7
+ * const leads = new LeadsPOM()
8
+ * leads.list.validateTableVisible()
9
+ * leads.form.typeInField('firstName', 'John')
10
+ */
11
+
12
+ import { EntityList } from '../components/EntityList'
13
+ import { EntityForm } from '../components/EntityForm'
14
+ import { EntityDetail } from '../components/EntityDetail'
15
+
16
+ export class LeadsPOM {
17
+ /** Generic list POM for leads */
18
+ readonly list: EntityList
19
+
20
+ /** Generic form POM for leads */
21
+ readonly form: EntityForm
22
+
23
+ /** Generic detail POM for leads */
24
+ readonly detail: EntityDetail
25
+
26
+ /** Lead entity slug */
27
+ readonly slug = 'leads'
28
+
29
+ constructor() {
30
+ this.list = EntityList.for('leads')
31
+ this.form = EntityForm.for('leads')
32
+ this.detail = new EntityDetail('leads', 'lead', ['activities', 'notes'])
33
+ }
34
+
35
+ // ============================================
36
+ // NAVIGATION METHODS
37
+ // ============================================
38
+
39
+ /**
40
+ * Visit the leads list page
41
+ */
42
+ visitList() {
43
+ cy.visit('/dashboard/leads')
44
+ this.list.waitForPageLoad()
45
+ return this
46
+ }
47
+
48
+ /**
49
+ * Visit lead detail page
50
+ */
51
+ visitDetail(leadId: string) {
52
+ this.detail.visit(leadId)
53
+ return this
54
+ }
55
+
56
+ /**
57
+ * Visit create lead page
58
+ */
59
+ visitCreate() {
60
+ this.form.visitCreate()
61
+ return this
62
+ }
63
+
64
+ /**
65
+ * Visit edit lead page
66
+ */
67
+ visitEdit(leadId: string) {
68
+ this.form.visitEdit(leadId)
69
+ return this
70
+ }
71
+
72
+ // ============================================
73
+ // FORM HELPERS
74
+ // ============================================
75
+
76
+ /**
77
+ * Fill lead form with common fields
78
+ */
79
+ fillLeadForm(data: {
80
+ firstName?: string
81
+ lastName?: string
82
+ email?: string
83
+ phone?: string
84
+ company?: string
85
+ source?: string
86
+ status?: string
87
+ }) {
88
+ if (data.firstName) {
89
+ this.form.typeInField('firstName', data.firstName)
90
+ }
91
+ if (data.lastName) {
92
+ this.form.typeInField('lastName', data.lastName)
93
+ }
94
+ if (data.email) {
95
+ this.form.typeInField('email', data.email)
96
+ }
97
+ if (data.phone) {
98
+ this.form.typeInField('phone', data.phone)
99
+ }
100
+ if (data.company) {
101
+ this.form.typeInField('company', data.company)
102
+ }
103
+ if (data.source) {
104
+ this.form.selectOption('source', data.source)
105
+ }
106
+ if (data.status) {
107
+ this.form.selectOption('status', data.status)
108
+ }
109
+ return this
110
+ }
111
+
112
+ /**
113
+ * Submit the lead form
114
+ */
115
+ submitForm() {
116
+ this.form.submit()
117
+ return this
118
+ }
119
+
120
+ /**
121
+ * Convert lead to contact
122
+ */
123
+ convertToContact() {
124
+ cy.get('[data-cy="lead-convert-btn"]').click()
125
+ return this
126
+ }
127
+ }
128
+
129
+ export default LeadsPOM
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Opportunities Page Object Model for CRM Theme
3
+ *
4
+ * Entity-specific POM for Opportunities (Deals).
5
+ * Includes support for both list view and kanban board integration.
6
+ *
7
+ * Usage:
8
+ * const opportunities = new OpportunitiesPOM()
9
+ * opportunities.list.validateTableVisible()
10
+ * opportunities.validateDealCard('deal-123')
11
+ */
12
+
13
+ import { EntityList } from '../components/EntityList'
14
+ import { EntityForm } from '../components/EntityForm'
15
+ import { EntityDetail } from '../components/EntityDetail'
16
+
17
+ export class OpportunitiesPOM {
18
+ /** Generic list POM for opportunities */
19
+ readonly list: EntityList
20
+
21
+ /** Generic form POM for opportunities */
22
+ readonly form: EntityForm
23
+
24
+ /** Generic detail POM for opportunities */
25
+ readonly detail: EntityDetail
26
+
27
+ /** Opportunity entity slug */
28
+ readonly slug = 'opportunities'
29
+
30
+ constructor() {
31
+ this.list = EntityList.for('opportunities')
32
+ this.form = EntityForm.for('opportunities')
33
+ this.detail = new EntityDetail('opportunities', 'opportunity', ['activities', 'notes'])
34
+ }
35
+
36
+ // ============================================
37
+ // DEAL CARD SELECTORS (Kanban)
38
+ // ============================================
39
+
40
+ /**
41
+ * Deal card selectors (used in Kanban board)
42
+ */
43
+ get dealSelectors() {
44
+ return {
45
+ card: (dealId: string) => `[data-cy="deal-card-${dealId}"]`,
46
+ cardName: (dealId: string) => `[data-cy="deal-card-name-${dealId}"]`,
47
+ cardCompany: (dealId: string) => `[data-cy="deal-card-company-${dealId}"]`,
48
+ cardAmount: (dealId: string) => `[data-cy="deal-card-amount-${dealId}"]`,
49
+ }
50
+ }
51
+
52
+ // ============================================
53
+ // DEAL CARD METHODS
54
+ // ============================================
55
+
56
+ /**
57
+ * Validate deal card is visible
58
+ */
59
+ validateDealCardVisible(dealId: string) {
60
+ cy.get(this.dealSelectors.card(dealId)).should('be.visible')
61
+ return this
62
+ }
63
+
64
+ /**
65
+ * Validate deal card displays correct name
66
+ */
67
+ validateDealName(dealId: string, name: string) {
68
+ cy.get(this.dealSelectors.cardName(dealId)).should('contain.text', name)
69
+ return this
70
+ }
71
+
72
+ /**
73
+ * Validate deal card displays correct company
74
+ */
75
+ validateDealCompany(dealId: string, companyName: string) {
76
+ cy.get(this.dealSelectors.cardCompany(dealId)).should('contain.text', companyName)
77
+ return this
78
+ }
79
+
80
+ /**
81
+ * Validate deal card displays correct amount
82
+ */
83
+ validateDealAmount(dealId: string, amount: string) {
84
+ cy.get(this.dealSelectors.cardAmount(dealId)).should('contain.text', amount)
85
+ return this
86
+ }
87
+
88
+ /**
89
+ * Click on a deal card
90
+ */
91
+ clickDealCard(dealId: string) {
92
+ cy.get(this.dealSelectors.card(dealId)).click()
93
+ return this
94
+ }
95
+
96
+ // ============================================
97
+ // NAVIGATION METHODS
98
+ // ============================================
99
+
100
+ /**
101
+ * Visit the opportunities list page
102
+ */
103
+ visitList() {
104
+ cy.visit('/dashboard/opportunities')
105
+ this.list.waitForPageLoad()
106
+ return this
107
+ }
108
+
109
+ /**
110
+ * Visit opportunity detail page
111
+ */
112
+ visitDetail(opportunityId: string) {
113
+ this.detail.visit(opportunityId)
114
+ return this
115
+ }
116
+
117
+ /**
118
+ * Visit create opportunity page
119
+ */
120
+ visitCreate() {
121
+ this.form.visitCreate()
122
+ return this
123
+ }
124
+
125
+ /**
126
+ * Visit edit opportunity page
127
+ */
128
+ visitEdit(opportunityId: string) {
129
+ this.form.visitEdit(opportunityId)
130
+ return this
131
+ }
132
+
133
+ // ============================================
134
+ // FORM HELPERS
135
+ // ============================================
136
+
137
+ /**
138
+ * Fill opportunity form with common fields
139
+ */
140
+ fillOpportunityForm(data: {
141
+ name: string
142
+ amount?: string
143
+ probability?: string
144
+ company?: string
145
+ contact?: string
146
+ stage?: string
147
+ }) {
148
+ if (data.name) {
149
+ this.form.typeInField('name', data.name)
150
+ }
151
+ if (data.amount) {
152
+ this.form.typeInField('amount', data.amount)
153
+ }
154
+ if (data.probability) {
155
+ this.form.typeInField('probability', data.probability)
156
+ }
157
+ if (data.company) {
158
+ this.form.selectOption('companyId', data.company)
159
+ }
160
+ if (data.contact) {
161
+ this.form.selectOption('contactId', data.contact)
162
+ }
163
+ if (data.stage) {
164
+ this.form.selectOption('stageId', data.stage)
165
+ }
166
+ return this
167
+ }
168
+
169
+ /**
170
+ * Submit the opportunity form
171
+ */
172
+ submitForm() {
173
+ this.form.submit()
174
+ return this
175
+ }
176
+ }
177
+
178
+ export default OpportunitiesPOM
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Pipelines Page Object Model for CRM Theme
3
+ *
4
+ * Entity-specific POM for Pipelines with Kanban board support.
5
+ * Extends generic patterns with pipeline-specific functionality.
6
+ *
7
+ * Usage:
8
+ * const pipelines = new PipelinesPOM()
9
+ * pipelines.kanban.validateBoardVisible()
10
+ * pipelines.form.fillStages([{ name: 'Lead', probability: 10 }])
11
+ */
12
+
13
+ import { EntityList } from '../components/EntityList'
14
+ import { EntityForm } from '../components/EntityForm'
15
+ import { EntityDetail } from '../components/EntityDetail'
16
+
17
+ export interface StageInput {
18
+ name: string
19
+ probability: number
20
+ color?: string
21
+ }
22
+
23
+ export class PipelinesPOM {
24
+ /** Generic list POM for pipelines */
25
+ readonly list: EntityList
26
+
27
+ /** Generic form POM for pipelines */
28
+ readonly form: EntityForm
29
+
30
+ /** Generic detail POM for pipelines */
31
+ readonly detail: EntityDetail
32
+
33
+ /** Pipeline entity slug */
34
+ readonly slug = 'pipelines'
35
+
36
+ constructor() {
37
+ this.list = EntityList.for('pipelines')
38
+ this.form = EntityForm.for('pipelines')
39
+ this.detail = new EntityDetail('pipelines', 'pipeline', ['stages'])
40
+ }
41
+
42
+ // ============================================
43
+ // PIPELINE FORM SELECTORS (Custom)
44
+ // ============================================
45
+
46
+ /**
47
+ * Pipeline-specific form selectors
48
+ */
49
+ get formSelectors() {
50
+ return {
51
+ form: '[data-cy="pipeline-form"]',
52
+ nameInput: '[data-cy="pipeline-form-name"]',
53
+ descriptionInput: '[data-cy="pipeline-form-description"]',
54
+ cancelButton: '[data-cy="pipeline-form-cancel"]',
55
+ submitButton: '[data-cy="pipeline-form-submit"]',
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Stages repeater selectors
61
+ */
62
+ get stagesSelectors() {
63
+ return {
64
+ repeater: '[data-cy="stages-repeater"]',
65
+ list: '[data-cy="stages-repeater-list"]',
66
+ count: '[data-cy="stages-repeater-count"]',
67
+ addButton: '[data-cy="stages-repeater-add-btn"]',
68
+ item: (stageId: string) => `[data-cy="stages-repeater-item-${stageId}"]`,
69
+ nameInput: (stageId: string) => `[data-cy="stages-repeater-name-${stageId}"]`,
70
+ probabilityInput: (stageId: string) => `[data-cy="stages-repeater-probability-${stageId}"]`,
71
+ deleteButton: (stageId: string) => `[data-cy="stages-repeater-delete-${stageId}"]`,
72
+ }
73
+ }
74
+
75
+ // ============================================
76
+ // KANBAN BOARD SELECTORS
77
+ // ============================================
78
+
79
+ /**
80
+ * Kanban board selectors
81
+ */
82
+ get kanbanSelectors() {
83
+ return {
84
+ board: '[data-cy="pipeline-kanban"]',
85
+ header: '[data-cy="pipeline-kanban-header"]',
86
+ stats: '[data-cy="pipeline-kanban-stats"]',
87
+ boardContainer: '[data-cy="pipeline-kanban-board"]',
88
+ addDealButton: '[data-cy="pipeline-kanban-add-deal-btn"]',
89
+ // Stage columns
90
+ stageColumn: (stageId: string) => `[data-cy="stage-column-${stageId}"]`,
91
+ stageHeader: (stageId: string) => `[data-cy="stage-column-header-${stageId}"]`,
92
+ stageDeals: (stageId: string) => `[data-cy="stage-column-deals-${stageId}"]`,
93
+ stageEmpty: (stageId: string) => `[data-cy="stage-column-empty-${stageId}"]`,
94
+ stageAddDeal: (stageId: string) => `[data-cy="stage-column-add-deal-${stageId}"]`,
95
+ // Deal cards
96
+ dealCard: (dealId: string) => `[data-cy="deal-card-${dealId}"]`,
97
+ dealCardName: (dealId: string) => `[data-cy="deal-card-name-${dealId}"]`,
98
+ dealCardCompany: (dealId: string) => `[data-cy="deal-card-company-${dealId}"]`,
99
+ dealCardAmount: (dealId: string) => `[data-cy="deal-card-amount-${dealId}"]`,
100
+ }
101
+ }
102
+
103
+ // ============================================
104
+ // PIPELINE FORM METHODS
105
+ // ============================================
106
+
107
+ /**
108
+ * Fill the pipeline form with basic info
109
+ */
110
+ fillPipelineForm(data: { name: string; description?: string }) {
111
+ cy.get(this.formSelectors.nameInput).clear().type(data.name)
112
+ if (data.description) {
113
+ cy.get(this.formSelectors.descriptionInput).clear().type(data.description)
114
+ }
115
+ return this
116
+ }
117
+
118
+ /**
119
+ * Add a new stage to the pipeline
120
+ */
121
+ addStage() {
122
+ cy.get(this.stagesSelectors.addButton).click()
123
+ return this
124
+ }
125
+
126
+ /**
127
+ * Fill stage data by index
128
+ */
129
+ fillStageByIndex(index: number, data: { name: string; probability: number }) {
130
+ cy.get(this.stagesSelectors.list)
131
+ .find('[data-cy^="stages-repeater-item-"]')
132
+ .eq(index)
133
+ .within(() => {
134
+ cy.get('input[data-cy^="stages-repeater-name-"]').clear().type(data.name)
135
+ cy.get('input[data-cy^="stages-repeater-probability-"]').clear().type(data.probability.toString())
136
+ })
137
+ return this
138
+ }
139
+
140
+ /**
141
+ * Delete stage by index
142
+ */
143
+ deleteStageByIndex(index: number) {
144
+ cy.get(this.stagesSelectors.list)
145
+ .find('[data-cy^="stages-repeater-item-"]')
146
+ .eq(index)
147
+ .find('[data-cy^="stages-repeater-delete-"]')
148
+ .click()
149
+ return this
150
+ }
151
+
152
+ /**
153
+ * Submit the pipeline form
154
+ */
155
+ submitPipelineForm() {
156
+ cy.get(this.formSelectors.submitButton).click()
157
+ return this
158
+ }
159
+
160
+ /**
161
+ * Cancel the pipeline form
162
+ */
163
+ cancelPipelineForm() {
164
+ cy.get(this.formSelectors.cancelButton).click()
165
+ return this
166
+ }
167
+
168
+ // ============================================
169
+ // KANBAN BOARD METHODS
170
+ // ============================================
171
+
172
+ /**
173
+ * Validate the kanban board is visible
174
+ */
175
+ validateKanbanVisible() {
176
+ cy.get(this.kanbanSelectors.board).should('be.visible')
177
+ return this
178
+ }
179
+
180
+ /**
181
+ * Validate kanban header is visible with pipeline name
182
+ */
183
+ validateKanbanHeader(pipelineName: string) {
184
+ cy.get(this.kanbanSelectors.header)
185
+ .should('be.visible')
186
+ .and('contain.text', pipelineName)
187
+ return this
188
+ }
189
+
190
+ /**
191
+ * Validate stats cards are visible
192
+ */
193
+ validateStatsVisible() {
194
+ cy.get(this.kanbanSelectors.stats).should('be.visible')
195
+ return this
196
+ }
197
+
198
+ /**
199
+ * Validate a stage column exists
200
+ */
201
+ validateStageColumnVisible(stageId: string) {
202
+ cy.get(this.kanbanSelectors.stageColumn(stageId)).should('be.visible')
203
+ return this
204
+ }
205
+
206
+ /**
207
+ * Validate stage column is empty
208
+ */
209
+ validateStageEmpty(stageId: string) {
210
+ cy.get(this.kanbanSelectors.stageEmpty(stageId)).should('be.visible')
211
+ return this
212
+ }
213
+
214
+ /**
215
+ * Validate deal card exists in stage
216
+ */
217
+ validateDealInStage(dealId: string, stageId: string) {
218
+ cy.get(this.kanbanSelectors.stageDeals(stageId))
219
+ .find(this.kanbanSelectors.dealCard(dealId))
220
+ .should('exist')
221
+ return this
222
+ }
223
+
224
+ /**
225
+ * Click on a deal card
226
+ */
227
+ clickDealCard(dealId: string) {
228
+ cy.get(this.kanbanSelectors.dealCard(dealId)).click()
229
+ return this
230
+ }
231
+
232
+ /**
233
+ * Click add deal button in header
234
+ */
235
+ clickAddDeal() {
236
+ cy.get(this.kanbanSelectors.addDealButton).click()
237
+ return this
238
+ }
239
+
240
+ /**
241
+ * Click add deal in specific stage
242
+ */
243
+ clickAddDealInStage(stageId: string) {
244
+ cy.get(this.kanbanSelectors.stageAddDeal(stageId)).click()
245
+ return this
246
+ }
247
+
248
+ /**
249
+ * Drag deal from one stage to another
250
+ * Note: Uses HTML5 drag and drop simulation
251
+ */
252
+ dragDealToStage(dealId: string, targetStageId: string) {
253
+ const dealCard = this.kanbanSelectors.dealCard(dealId)
254
+ const targetColumn = this.kanbanSelectors.stageDeals(targetStageId)
255
+
256
+ cy.get(dealCard).trigger('dragstart', { dataTransfer: new DataTransfer() })
257
+ cy.get(targetColumn).trigger('dragover')
258
+ cy.get(targetColumn).trigger('drop')
259
+ cy.get(dealCard).trigger('dragend')
260
+
261
+ return this
262
+ }
263
+
264
+ // ============================================
265
+ // NAVIGATION METHODS
266
+ // ============================================
267
+
268
+ /**
269
+ * Visit the pipelines list page
270
+ */
271
+ visitList() {
272
+ cy.visit('/dashboard/pipelines')
273
+ this.list.waitForPageLoad()
274
+ return this
275
+ }
276
+
277
+ /**
278
+ * Visit a specific pipeline's kanban view
279
+ */
280
+ visitKanban(pipelineId: string) {
281
+ cy.visit(`/dashboard/pipelines/${pipelineId}`)
282
+ this.validateKanbanVisible()
283
+ return this
284
+ }
285
+
286
+ /**
287
+ * Visit the create pipeline page
288
+ */
289
+ visitCreate() {
290
+ cy.visit('/dashboard/pipelines/create')
291
+ cy.get(this.formSelectors.form).should('be.visible')
292
+ return this
293
+ }
294
+
295
+ /**
296
+ * Visit the edit pipeline page
297
+ */
298
+ visitEdit(pipelineId: string) {
299
+ cy.visit(`/dashboard/pipelines/${pipelineId}/edit`)
300
+ cy.get(this.formSelectors.form).should('be.visible')
301
+ return this
302
+ }
303
+
304
+ // ============================================
305
+ // VALIDATION METHODS
306
+ // ============================================
307
+
308
+ /**
309
+ * Validate pipeline form is visible
310
+ */
311
+ validateFormVisible() {
312
+ cy.get(this.formSelectors.form).should('be.visible')
313
+ return this
314
+ }
315
+
316
+ /**
317
+ * Validate stages repeater is visible
318
+ */
319
+ validateStagesRepeaterVisible() {
320
+ cy.get(this.stagesSelectors.repeater).should('be.visible')
321
+ return this
322
+ }
323
+
324
+ /**
325
+ * Validate stages count
326
+ */
327
+ validateStagesCount(count: number) {
328
+ cy.get(this.stagesSelectors.count).should('contain.text', `${count} stage`)
329
+ return this
330
+ }
331
+
332
+ /**
333
+ * Validate deal card displays correct amount
334
+ */
335
+ validateDealAmount(dealId: string, amount: string) {
336
+ cy.get(this.kanbanSelectors.dealCardAmount(dealId)).should('contain.text', amount)
337
+ return this
338
+ }
339
+ }
340
+
341
+ export default PipelinesPOM
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CRM Theme - Entity POMs
3
+ *
4
+ * Export all entity-specific Page Object Models for CRM theme testing.
5
+ * Each POM combines generic components (EntityList, EntityForm, EntityDetail)
6
+ * with entity-specific functionality.
7
+ */
8
+
9
+ export { PipelinesPOM } from './PipelinesPOM'
10
+ export { OpportunitiesPOM } from './OpportunitiesPOM'
11
+ export { LeadsPOM } from './LeadsPOM'
12
+ export { ContactsPOM } from './ContactsPOM'
13
+ export { CompaniesPOM } from './CompaniesPOM'
14
+ export { ActivitiesPOM } from './ActivitiesPOM'
15
+
16
+ // Default exports for convenience
17
+ import { PipelinesPOM } from './PipelinesPOM'
18
+ import { OpportunitiesPOM } from './OpportunitiesPOM'
19
+ import { LeadsPOM } from './LeadsPOM'
20
+ import { ContactsPOM } from './ContactsPOM'
21
+ import { CompaniesPOM } from './CompaniesPOM'
22
+ import { ActivitiesPOM } from './ActivitiesPOM'
23
+
24
+ export default {
25
+ PipelinesPOM,
26
+ OpportunitiesPOM,
27
+ LeadsPOM,
28
+ ContactsPOM,
29
+ CompaniesPOM,
30
+ ActivitiesPOM,
31
+ }