@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,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
+ }