@nextsparkjs/theme-crm 0.1.0-beta.19 → 0.1.0-beta.24
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 +3 -3
- package/tests/cypress/e2e/api/activities/activities-crud.cy.ts +686 -0
- package/tests/cypress/e2e/api/campaigns/campaigns-crud.cy.ts +592 -0
- package/tests/cypress/e2e/api/companies/companies-crud.cy.ts +682 -0
- package/tests/cypress/e2e/api/contacts/contacts-crud.cy.ts +668 -0
- package/tests/cypress/e2e/api/leads/leads-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/notes/notes-crud.cy.ts +424 -0
- package/tests/cypress/e2e/api/opportunities/opportunities-crud.cy.ts +865 -0
- package/tests/cypress/e2e/api/pipelines/pipelines-crud.cy.ts +545 -0
- package/tests/cypress/e2e/api/products/products-crud.cy.ts +447 -0
- package/tests/cypress/e2e/ui/activities/activities-admin.cy.ts +268 -0
- package/tests/cypress/e2e/ui/activities/activities-member.cy.ts +257 -0
- package/tests/cypress/e2e/ui/activities/activities-owner.cy.ts +268 -0
- package/tests/cypress/e2e/ui/companies/companies-admin.cy.ts +188 -0
- package/tests/cypress/e2e/ui/companies/companies-member.cy.ts +166 -0
- package/tests/cypress/e2e/ui/companies/companies-owner.cy.ts +189 -0
- package/tests/cypress/e2e/ui/contacts/contacts-admin.cy.ts +252 -0
- package/tests/cypress/e2e/ui/contacts/contacts-member.cy.ts +224 -0
- package/tests/cypress/e2e/ui/contacts/contacts-owner.cy.ts +236 -0
- package/tests/cypress/e2e/ui/leads/leads-admin.cy.ts +286 -0
- package/tests/cypress/e2e/ui/leads/leads-member.cy.ts +193 -0
- package/tests/cypress/e2e/ui/leads/leads-owner.cy.ts +210 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-admin.cy.ts +197 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-member.cy.ts +229 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-owner.cy.ts +196 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-admin.cy.ts +320 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-member.cy.ts +262 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-owner.cy.ts +282 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +240 -0
- package/tests/cypress/src/components/CRMDataTable.js +223 -0
- package/tests/cypress/src/components/CRMMobileNav.js +138 -0
- package/tests/cypress/src/components/CRMSidebar.js +145 -0
- package/tests/cypress/src/components/CRMTopBar.js +194 -0
- package/tests/cypress/src/components/DealCard.js +197 -0
- package/tests/cypress/src/components/EntityDetail.ts +290 -0
- package/tests/cypress/src/components/EntityForm.ts +357 -0
- package/tests/cypress/src/components/EntityList.ts +360 -0
- package/tests/cypress/src/components/PipelineKanban.js +204 -0
- package/tests/cypress/src/components/StageColumn.js +196 -0
- package/tests/cypress/src/components/index.js +13 -0
- package/tests/cypress/src/components/index.ts +22 -0
- package/tests/cypress/src/controllers/ActivityAPIController.ts +113 -0
- package/tests/cypress/src/controllers/BaseAPIController.ts +307 -0
- package/tests/cypress/src/controllers/CampaignAPIController.ts +114 -0
- package/tests/cypress/src/controllers/CompanyAPIController.ts +112 -0
- package/tests/cypress/src/controllers/ContactAPIController.ts +104 -0
- package/tests/cypress/src/controllers/LeadAPIController.ts +96 -0
- package/tests/cypress/src/controllers/NoteAPIController.ts +130 -0
- package/tests/cypress/src/controllers/OpportunityAPIController.ts +134 -0
- package/tests/cypress/src/controllers/PipelineAPIController.ts +116 -0
- package/tests/cypress/src/controllers/ProductAPIController.ts +113 -0
- package/tests/cypress/src/controllers/index.ts +35 -0
- package/tests/cypress/src/entities/ActivitiesPOM.ts +130 -0
- package/tests/cypress/src/entities/CompaniesPOM.ts +117 -0
- package/tests/cypress/src/entities/ContactsPOM.ts +117 -0
- package/tests/cypress/src/entities/LeadsPOM.ts +129 -0
- package/tests/cypress/src/entities/OpportunitiesPOM.ts +178 -0
- package/tests/cypress/src/entities/PipelinesPOM.ts +341 -0
- package/tests/cypress/src/entities/index.ts +31 -0
- package/tests/cypress/src/forms/OpportunityForm.js +316 -0
- package/tests/cypress/src/forms/PipelineForm.js +243 -0
- package/tests/cypress/src/forms/index.js +8 -0
- package/tests/cypress/src/index.js +22 -0
- package/tests/cypress/src/index.ts +68 -0
- package/tests/cypress/src/selectors.ts +50 -0
- package/tests/cypress/src/session-helpers.ts +94 -0
- package/tests/cypress/support/e2e.ts +89 -0
- package/tests/cypress.config.ts +165 -0
- package/tests/jest/__mocks__/jose.js +22 -0
- package/tests/jest/__mocks__/next-server.js +56 -0
- package/tests/jest/jest.config.cjs +127 -0
- package/tests/jest/setup.ts +170 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Entity List POM for CRM Theme
|
|
3
|
+
*
|
|
4
|
+
* Page Object Model for entity list pages in CRM theme.
|
|
5
|
+
* Uses standardized data-cy selectors from entities.json.
|
|
6
|
+
*
|
|
7
|
+
* Convention: {slug}-{component}-{detail}
|
|
8
|
+
* Examples: leads-table, contacts-create-btn, opportunities-row-{id}
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const leadsList = EntityList.for('leads')
|
|
12
|
+
* const contactsList = EntityList.for('contacts')
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Import entity configs from CRM theme
|
|
16
|
+
import entitiesConfig from '../../fixtures/entities.json'
|
|
17
|
+
|
|
18
|
+
export interface EntityConfig {
|
|
19
|
+
slug: string
|
|
20
|
+
singular: string
|
|
21
|
+
plural: string
|
|
22
|
+
tableName: string
|
|
23
|
+
fields: string[]
|
|
24
|
+
sections: string[]
|
|
25
|
+
filters: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class EntityList {
|
|
29
|
+
private config: EntityConfig
|
|
30
|
+
private slug: string
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new EntityList POM instance from entity config
|
|
34
|
+
*/
|
|
35
|
+
constructor(entityKey: string) {
|
|
36
|
+
const config = entitiesConfig.entities[entityKey as keyof typeof entitiesConfig.entities]
|
|
37
|
+
if (!config) {
|
|
38
|
+
throw new Error(`Unknown entity: ${entityKey}. Available: ${Object.keys(entitiesConfig.entities).join(', ')}`)
|
|
39
|
+
}
|
|
40
|
+
this.config = config as EntityConfig
|
|
41
|
+
this.slug = config.slug
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// STATIC FACTORY METHOD
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create an EntityList from entity key
|
|
50
|
+
*/
|
|
51
|
+
static for(entityKey: string): EntityList {
|
|
52
|
+
return new EntityList(entityKey)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================
|
|
56
|
+
// DYNAMIC SELECTORS (from entities.json convention)
|
|
57
|
+
// ============================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get selectors for this entity following the standard convention
|
|
61
|
+
*/
|
|
62
|
+
get selectors() {
|
|
63
|
+
const slug = this.slug
|
|
64
|
+
return {
|
|
65
|
+
// Page elements
|
|
66
|
+
page: `[data-cy="${slug}-page"]`,
|
|
67
|
+
pageTitle: '[data-cy="page-title"]',
|
|
68
|
+
|
|
69
|
+
// Table
|
|
70
|
+
table: `[data-cy="${slug}-table"]`,
|
|
71
|
+
|
|
72
|
+
// Create button
|
|
73
|
+
createButton: `[data-cy="${slug}-create-btn"]`,
|
|
74
|
+
|
|
75
|
+
// Search
|
|
76
|
+
search: `[data-cy="${slug}-search"]`,
|
|
77
|
+
searchInput: `[data-cy="${slug}-search-input"]`,
|
|
78
|
+
|
|
79
|
+
// Filters
|
|
80
|
+
filter: (fieldName: string) => `[data-cy="${slug}-filter-${fieldName}"]`,
|
|
81
|
+
filterTrigger: (fieldName: string) => `[data-cy="${slug}-filter-${fieldName}-trigger"]`,
|
|
82
|
+
filterOption: (fieldName: string, value: string) => `[data-cy="${slug}-filter-${fieldName}-option-${value}"]`,
|
|
83
|
+
|
|
84
|
+
// Rows
|
|
85
|
+
row: (id: string) => `[data-cy="${slug}-row-${id}"]`,
|
|
86
|
+
rowGeneric: `[data-cy^="${slug}-row-"]`,
|
|
87
|
+
|
|
88
|
+
// Cards (for kanban/grid views)
|
|
89
|
+
card: (id: string) => `[data-cy="${slug}-card-${id}"]`,
|
|
90
|
+
cardGeneric: `[data-cy^="${slug}-card-"]`,
|
|
91
|
+
|
|
92
|
+
// Actions
|
|
93
|
+
actionEdit: (id: string) => `[data-cy="${slug}-action-edit-${id}"]`,
|
|
94
|
+
actionDelete: (id: string) => `[data-cy="${slug}-action-delete-${id}"]`,
|
|
95
|
+
actionView: (id: string) => `[data-cy="${slug}-action-view-${id}"]`,
|
|
96
|
+
actionsDropdown: (id: string) => `[data-cy="${slug}-actions-${id}"]`,
|
|
97
|
+
actionsTrigger: (id: string) => `[data-cy="${slug}-actions-trigger-${id}"]`,
|
|
98
|
+
|
|
99
|
+
// Pagination
|
|
100
|
+
pagination: `[data-cy="${slug}-pagination"]`,
|
|
101
|
+
paginationPrev: `[data-cy="${slug}-pagination-prev"]`,
|
|
102
|
+
paginationNext: `[data-cy="${slug}-pagination-next"]`,
|
|
103
|
+
|
|
104
|
+
// Bulk actions
|
|
105
|
+
bulkActions: `[data-cy="${slug}-bulk-actions"]`,
|
|
106
|
+
|
|
107
|
+
// Empty state
|
|
108
|
+
emptyState: `[data-cy="${slug}-empty"]`,
|
|
109
|
+
|
|
110
|
+
// Dialogs
|
|
111
|
+
confirmDelete: `[data-cy="${slug}-confirm-delete"]`,
|
|
112
|
+
confirmDeleteBtn: `[data-cy="${slug}-confirm-delete-btn"]`,
|
|
113
|
+
cancelDeleteBtn: `[data-cy="${slug}-cancel-delete-btn"]`,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the entity config
|
|
119
|
+
*/
|
|
120
|
+
get entityConfig(): EntityConfig {
|
|
121
|
+
return this.config
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================
|
|
125
|
+
// VALIDATION METHODS
|
|
126
|
+
// ============================================
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validate the list page is visible
|
|
130
|
+
*/
|
|
131
|
+
validatePageVisible() {
|
|
132
|
+
cy.get(this.selectors.page).should('be.visible')
|
|
133
|
+
return this
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validate the table is visible
|
|
138
|
+
*/
|
|
139
|
+
validateTableVisible() {
|
|
140
|
+
cy.get(this.selectors.table).should('be.visible')
|
|
141
|
+
return this
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate the page title text
|
|
146
|
+
*/
|
|
147
|
+
validatePageTitle(expectedTitle: string) {
|
|
148
|
+
cy.get(this.selectors.pageTitle).should('contain.text', expectedTitle)
|
|
149
|
+
return this
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate the create button is visible
|
|
154
|
+
*/
|
|
155
|
+
validateCreateButtonVisible() {
|
|
156
|
+
cy.get(this.selectors.createButton).should('be.visible')
|
|
157
|
+
return this
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate the table has rows
|
|
162
|
+
*/
|
|
163
|
+
validateTableHasRows() {
|
|
164
|
+
cy.get(this.selectors.table).find('tbody tr').should('have.length.at.least', 1)
|
|
165
|
+
return this
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Validate the table is empty
|
|
170
|
+
*/
|
|
171
|
+
validateTableEmpty() {
|
|
172
|
+
cy.get(this.selectors.table).find('tbody tr').should('have.length', 0)
|
|
173
|
+
return this
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================
|
|
177
|
+
// INTERACTION METHODS
|
|
178
|
+
// ============================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Click the create button
|
|
182
|
+
*/
|
|
183
|
+
clickCreate() {
|
|
184
|
+
cy.get(this.selectors.createButton).click()
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Search for a term
|
|
190
|
+
*/
|
|
191
|
+
search(term: string) {
|
|
192
|
+
cy.get(this.selectors.searchInput).clear().type(term)
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Clear the search input
|
|
198
|
+
*/
|
|
199
|
+
clearSearch() {
|
|
200
|
+
cy.get(this.selectors.searchInput).clear()
|
|
201
|
+
return this
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Select a filter option
|
|
206
|
+
*/
|
|
207
|
+
selectFilter(fieldName: string, value: string) {
|
|
208
|
+
cy.get(this.selectors.filterTrigger(fieldName)).click()
|
|
209
|
+
cy.get(this.selectors.filterOption(fieldName, value)).click()
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Click on a table row by index (0-based)
|
|
215
|
+
*/
|
|
216
|
+
clickRowByIndex(index: number) {
|
|
217
|
+
cy.get(this.selectors.table).find('tbody tr').eq(index).click()
|
|
218
|
+
return this
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Click on a table row by text content
|
|
223
|
+
*/
|
|
224
|
+
clickRowByText(text: string) {
|
|
225
|
+
cy.get(this.selectors.table).find('tbody tr').contains(text).click()
|
|
226
|
+
return this
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Click on a specific row by ID
|
|
231
|
+
*/
|
|
232
|
+
clickRowById(id: string) {
|
|
233
|
+
cy.get(this.selectors.row(id)).click()
|
|
234
|
+
return this
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Click edit action for a row
|
|
239
|
+
*/
|
|
240
|
+
clickEditAction(id: string) {
|
|
241
|
+
cy.get(this.selectors.actionEdit(id)).click()
|
|
242
|
+
return this
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Click delete action for a row
|
|
247
|
+
*/
|
|
248
|
+
clickDeleteAction(id: string) {
|
|
249
|
+
cy.get(this.selectors.actionDelete(id)).click()
|
|
250
|
+
return this
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Open actions dropdown for a row
|
|
255
|
+
*/
|
|
256
|
+
openActionsDropdown(id: string) {
|
|
257
|
+
cy.get(this.selectors.actionsTrigger(id)).click()
|
|
258
|
+
return this
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ============================================
|
|
262
|
+
// PAGINATION METHODS
|
|
263
|
+
// ============================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Go to next page
|
|
267
|
+
*/
|
|
268
|
+
nextPage() {
|
|
269
|
+
cy.get(this.selectors.paginationNext).click()
|
|
270
|
+
return this
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Go to previous page
|
|
275
|
+
*/
|
|
276
|
+
previousPage() {
|
|
277
|
+
cy.get(this.selectors.paginationPrev).click()
|
|
278
|
+
return this
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ============================================
|
|
282
|
+
// BULK ACTIONS METHODS
|
|
283
|
+
// ============================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Select all rows using the header checkbox
|
|
287
|
+
*/
|
|
288
|
+
selectAll() {
|
|
289
|
+
cy.get(this.selectors.table).find('thead input[type="checkbox"]').check()
|
|
290
|
+
return this
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Deselect all rows
|
|
295
|
+
*/
|
|
296
|
+
deselectAll() {
|
|
297
|
+
cy.get(this.selectors.table).find('thead input[type="checkbox"]').uncheck()
|
|
298
|
+
return this
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Select a row by index
|
|
303
|
+
*/
|
|
304
|
+
selectRowByIndex(index: number) {
|
|
305
|
+
cy.get(this.selectors.table).find('tbody tr').eq(index).find('input[type="checkbox"]').check()
|
|
306
|
+
return this
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Validate bulk actions panel is visible
|
|
311
|
+
*/
|
|
312
|
+
validateBulkActionsVisible() {
|
|
313
|
+
cy.get(this.selectors.bulkActions).should('be.visible')
|
|
314
|
+
return this
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================
|
|
318
|
+
// DELETE CONFIRMATION
|
|
319
|
+
// ============================================
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Confirm deletion in dialog
|
|
323
|
+
*/
|
|
324
|
+
confirmDelete() {
|
|
325
|
+
cy.get(this.selectors.confirmDeleteBtn).click()
|
|
326
|
+
return this
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Cancel deletion in dialog
|
|
331
|
+
*/
|
|
332
|
+
cancelDelete() {
|
|
333
|
+
cy.get(this.selectors.cancelDeleteBtn).click()
|
|
334
|
+
return this
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================
|
|
338
|
+
// WAIT METHODS
|
|
339
|
+
// ============================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Wait for the table to load
|
|
343
|
+
*/
|
|
344
|
+
waitForTableLoad() {
|
|
345
|
+
cy.get(this.selectors.table).should('exist')
|
|
346
|
+
cy.get(this.selectors.page).find('[data-loading]').should('not.exist')
|
|
347
|
+
return this
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Wait for page load
|
|
352
|
+
*/
|
|
353
|
+
waitForPageLoad() {
|
|
354
|
+
cy.url().should('include', `/dashboard/${this.slug}`)
|
|
355
|
+
cy.get(this.selectors.page).should('be.visible')
|
|
356
|
+
return this
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export default EntityList
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PipelineKanban - Page Object Model Class
|
|
3
|
+
*
|
|
4
|
+
* POM for the CRM theme pipeline kanban board.
|
|
5
|
+
* Handles kanban board interactions for pipeline/opportunity management.
|
|
6
|
+
*/
|
|
7
|
+
export class PipelineKanban {
|
|
8
|
+
static selectors = {
|
|
9
|
+
container: '[data-cy="pipeline-kanban"]',
|
|
10
|
+
header: '[data-cy="pipeline-kanban-header"]',
|
|
11
|
+
pipelineSelect: '[data-cy="pipeline-select"]',
|
|
12
|
+
addDealBtn: '[data-cy="pipeline-add-deal"]',
|
|
13
|
+
stats: '[data-cy="pipeline-stats"]',
|
|
14
|
+
statsTotal: '[data-cy="pipeline-stats-total"]',
|
|
15
|
+
statsValue: '[data-cy="pipeline-stats-value"]',
|
|
16
|
+
statsAverage: '[data-cy="pipeline-stats-average"]',
|
|
17
|
+
board: '[data-cy="pipeline-board"]',
|
|
18
|
+
stage: '[data-cy^="pipeline-stage-"]',
|
|
19
|
+
emptyBoard: '[data-cy="pipeline-empty"]',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate kanban board is visible
|
|
24
|
+
*/
|
|
25
|
+
validateVisible() {
|
|
26
|
+
cy.get(PipelineKanban.selectors.container).should('be.visible')
|
|
27
|
+
return this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate header is visible
|
|
32
|
+
*/
|
|
33
|
+
validateHeaderVisible() {
|
|
34
|
+
cy.get(PipelineKanban.selectors.header).should('be.visible')
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate add deal button is visible
|
|
40
|
+
*/
|
|
41
|
+
validateAddDealVisible() {
|
|
42
|
+
cy.get(PipelineKanban.selectors.addDealBtn).should('be.visible')
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Click add deal button
|
|
48
|
+
*/
|
|
49
|
+
addDeal() {
|
|
50
|
+
cy.get(PipelineKanban.selectors.addDealBtn).click()
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Select a pipeline
|
|
56
|
+
* @param {string} pipelineName - Pipeline name
|
|
57
|
+
*/
|
|
58
|
+
selectPipeline(pipelineName) {
|
|
59
|
+
cy.get(PipelineKanban.selectors.pipelineSelect).click()
|
|
60
|
+
cy.contains(pipelineName).click()
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate board is visible
|
|
66
|
+
*/
|
|
67
|
+
validateBoardVisible() {
|
|
68
|
+
cy.get(PipelineKanban.selectors.board).should('be.visible')
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate empty board state
|
|
74
|
+
*/
|
|
75
|
+
validateEmptyBoard() {
|
|
76
|
+
cy.get(PipelineKanban.selectors.emptyBoard).should('be.visible')
|
|
77
|
+
return this
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get a specific stage
|
|
82
|
+
* @param {string} stageId - Stage ID
|
|
83
|
+
*/
|
|
84
|
+
getStage(stageId) {
|
|
85
|
+
return cy.get(`[data-cy="pipeline-stage-${stageId}"]`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate stage exists
|
|
90
|
+
* @param {string} stageId - Stage ID
|
|
91
|
+
*/
|
|
92
|
+
validateStageExists(stageId) {
|
|
93
|
+
this.getStage(stageId).should('exist')
|
|
94
|
+
return this
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the count of stages
|
|
99
|
+
*/
|
|
100
|
+
getStageCount() {
|
|
101
|
+
return cy.get(PipelineKanban.selectors.stage).its('length')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate number of stages
|
|
106
|
+
* @param {number} count - Expected stage count
|
|
107
|
+
*/
|
|
108
|
+
validateStageCount(count) {
|
|
109
|
+
cy.get(PipelineKanban.selectors.stage).should('have.length', count)
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get deals in a specific stage
|
|
115
|
+
* @param {string} stageId - Stage ID
|
|
116
|
+
*/
|
|
117
|
+
getDealsInStage(stageId) {
|
|
118
|
+
return cy.get(`[data-cy="pipeline-stage-${stageId}"]`)
|
|
119
|
+
.find('[data-cy^="deal-card-"]')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validate number of deals in a stage
|
|
124
|
+
* @param {string} stageId - Stage ID
|
|
125
|
+
* @param {number} count - Expected deal count
|
|
126
|
+
*/
|
|
127
|
+
validateDealCountInStage(stageId, count) {
|
|
128
|
+
this.getDealsInStage(stageId).should('have.length', count)
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Drag a deal to a different stage
|
|
134
|
+
* @param {string} dealId - Deal ID
|
|
135
|
+
* @param {string} toStageId - Target stage ID
|
|
136
|
+
*/
|
|
137
|
+
dragDeal(dealId, toStageId) {
|
|
138
|
+
const dealSelector = `[data-cy="deal-card-${dealId}"]`
|
|
139
|
+
const targetSelector = `[data-cy="pipeline-stage-${toStageId}"]`
|
|
140
|
+
|
|
141
|
+
cy.get(dealSelector)
|
|
142
|
+
.trigger('dragstart')
|
|
143
|
+
|
|
144
|
+
cy.get(targetSelector)
|
|
145
|
+
.trigger('drop')
|
|
146
|
+
|
|
147
|
+
cy.get(dealSelector)
|
|
148
|
+
.trigger('dragend')
|
|
149
|
+
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Click a deal card
|
|
155
|
+
* @param {string} dealId - Deal ID
|
|
156
|
+
*/
|
|
157
|
+
clickDeal(dealId) {
|
|
158
|
+
cy.get(`[data-cy="deal-card-${dealId}"]`).click()
|
|
159
|
+
return this
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validate stats are visible
|
|
164
|
+
*/
|
|
165
|
+
validateStatsVisible() {
|
|
166
|
+
cy.get(PipelineKanban.selectors.stats).should('be.visible')
|
|
167
|
+
return this
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Validate total deals stat
|
|
172
|
+
* @param {number} total - Expected total deals
|
|
173
|
+
*/
|
|
174
|
+
validateTotalDeals(total) {
|
|
175
|
+
cy.get(PipelineKanban.selectors.statsTotal).should('contain', total)
|
|
176
|
+
return this
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Validate total value stat
|
|
181
|
+
* @param {string} value - Expected total value (e.g., "$250,000")
|
|
182
|
+
*/
|
|
183
|
+
validateTotalValue(value) {
|
|
184
|
+
cy.get(PipelineKanban.selectors.statsValue).should('contain', value)
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Validate average deal value stat
|
|
190
|
+
* @param {string} average - Expected average value (e.g., "$25,000")
|
|
191
|
+
*/
|
|
192
|
+
validateAverageValue(average) {
|
|
193
|
+
cy.get(PipelineKanban.selectors.statsAverage).should('contain', average)
|
|
194
|
+
return this
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Validate pipeline select is visible
|
|
199
|
+
*/
|
|
200
|
+
validatePipelineSelectVisible() {
|
|
201
|
+
cy.get(PipelineKanban.selectors.pipelineSelect).should('be.visible')
|
|
202
|
+
return this
|
|
203
|
+
}
|
|
204
|
+
}
|