@nextsparkjs/theme-crm 0.1.0-beta.19 → 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,236 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Contacts CRUD - Owner Role (Full Access)
5
+ *
6
+ * Uses CRM theme-specific POMs with Entity Testing Convention.
7
+ * Selectors follow the pattern: {slug}-{component}-{detail}
8
+ */
9
+
10
+ import { ContactsPOM } from '../../../src/entities/ContactsPOM'
11
+ import { loginAsCrmOwner } from '../../../src/session-helpers'
12
+
13
+ describe('Contacts CRUD - Owner Role (Full Access)', () => {
14
+ const contacts = new ContactsPOM()
15
+
16
+ beforeEach(() => {
17
+ loginAsCrmOwner()
18
+ cy.visit('/dashboard/contacts')
19
+ contacts.list.validatePageVisible()
20
+ })
21
+
22
+ describe('CREATE - Owner can create contacts', () => {
23
+ it('OWNER_CONTACT_CREATE_001: should create new contact with required fields only', () => {
24
+ const firstName = `Roberto${Date.now()}`
25
+ const lastName = 'TestOwner'
26
+ const email = `roberto.owner.${Date.now()}@test.com`
27
+
28
+ contacts.list.clickCreate()
29
+ contacts.form.validateFormVisible()
30
+
31
+ contacts.fillContactForm({
32
+ firstName,
33
+ lastName,
34
+ email
35
+ })
36
+
37
+ contacts.submitForm()
38
+
39
+ cy.url().should('include', '/dashboard/contacts')
40
+ cy.contains(firstName).should('be.visible')
41
+ cy.contains(lastName).should('be.visible')
42
+ })
43
+
44
+ it('OWNER_CONTACT_CREATE_002: should create contact with all fields', () => {
45
+ const timestamp = Date.now()
46
+
47
+ contacts.list.clickCreate()
48
+ contacts.form.validateFormVisible()
49
+
50
+ contacts.fillContactForm({
51
+ firstName: `Carlos${timestamp}`,
52
+ lastName: 'Rodriguez',
53
+ email: `carlos.full.${timestamp}@test.com`,
54
+ phone: '+34 91 123 4567',
55
+ title: 'Sales Director'
56
+ })
57
+
58
+ contacts.submitForm()
59
+
60
+ cy.url().should('include', '/dashboard/contacts')
61
+ cy.contains(`Carlos${timestamp}`).should('be.visible')
62
+ })
63
+
64
+ it('OWNER_CONTACT_CREATE_003: should validate required fields', () => {
65
+ contacts.list.clickCreate()
66
+ contacts.form.validateFormVisible()
67
+
68
+ // Try to submit empty form
69
+ contacts.submitForm()
70
+
71
+ // Should show validation or stay on form
72
+ cy.get('body').then($body => {
73
+ const isOnCreatePage = window.location.pathname.includes('/create')
74
+ if (isOnCreatePage) {
75
+ cy.log('Form validation prevented submission')
76
+ }
77
+ })
78
+ })
79
+
80
+ it('OWNER_CONTACT_CREATE_004: should validate email format', () => {
81
+ const firstName = `Invalid${Date.now()}`
82
+ const lastName = 'Email'
83
+
84
+ contacts.list.clickCreate()
85
+ contacts.form.validateFormVisible()
86
+
87
+ contacts.fillContactForm({
88
+ firstName,
89
+ lastName,
90
+ email: 'not-an-email'
91
+ })
92
+
93
+ contacts.submitForm()
94
+
95
+ // Should show validation error
96
+ cy.get('body').then($body => {
97
+ const isOnCreatePage = window.location.pathname.includes('/create')
98
+ if (isOnCreatePage) {
99
+ cy.log('Email validation working')
100
+ }
101
+ })
102
+ })
103
+ })
104
+
105
+ describe('READ - Owner can read contacts', () => {
106
+ it('OWNER_CONTACT_READ_001: should view contact list', () => {
107
+ contacts.list.validatePageVisible()
108
+ contacts.list.validateTableVisible()
109
+ })
110
+
111
+ it('OWNER_CONTACT_READ_002: should view contact details', () => {
112
+ cy.get(contacts.list.selectors.rowGeneric).then($rows => {
113
+ if ($rows.length > 0) {
114
+ contacts.list.clickRowByIndex(0)
115
+ cy.url().should('match', /\/dashboard\/contacts\/[a-z0-9-]+/)
116
+ }
117
+ })
118
+ })
119
+
120
+ it('OWNER_CONTACT_READ_003: should search contacts', () => {
121
+ contacts.list.search('test')
122
+ cy.wait(500)
123
+ contacts.list.clearSearch()
124
+ })
125
+ })
126
+
127
+ describe('UPDATE - Owner can update contacts', () => {
128
+ it('OWNER_CONTACT_UPDATE_001: should edit contact successfully', () => {
129
+ cy.get(contacts.list.selectors.rowGeneric).then($rows => {
130
+ if ($rows.length > 0) {
131
+ contacts.list.clickRowByIndex(0)
132
+
133
+ cy.url().then(url => {
134
+ const contactId = url.split('/').pop()
135
+ contacts.form.visitEdit(contactId!)
136
+ contacts.form.validateFormVisible()
137
+
138
+ const updatedFirstName = `UpdatedOwner${Date.now()}`
139
+ contacts.form.typeInField('firstName', updatedFirstName)
140
+
141
+ contacts.submitForm()
142
+
143
+ cy.url().should('include', '/dashboard/contacts')
144
+ cy.contains(updatedFirstName).should('be.visible')
145
+ })
146
+ }
147
+ })
148
+ })
149
+
150
+ it('OWNER_CONTACT_UPDATE_002: should update all contact fields', () => {
151
+ const firstName = `FullUpdate${Date.now()}`
152
+ const lastName = 'Owner'
153
+ const email = `full.update.${Date.now()}@test.com`
154
+
155
+ contacts.list.clickCreate()
156
+ contacts.fillContactForm({ firstName, lastName, email })
157
+ contacts.submitForm()
158
+
159
+ cy.contains(firstName).should('be.visible')
160
+
161
+ // Edit the contact
162
+ cy.get(contacts.list.selectors.rowGeneric).first().within(() => {
163
+ cy.get('[data-cy*="edit"]').click()
164
+ })
165
+
166
+ contacts.form.validateFormVisible()
167
+
168
+ contacts.form.typeInField('firstName', `${firstName}_Updated`)
169
+ contacts.form.typeInField('phone', '+34 91 999 8888')
170
+ contacts.form.typeInField('title', 'CEO')
171
+
172
+ contacts.submitForm()
173
+
174
+ cy.url().should('include', '/dashboard/contacts')
175
+ cy.contains(`${firstName}_Updated`).should('be.visible')
176
+ })
177
+
178
+ it('OWNER_CONTACT_UPDATE_003: should cancel update without saving', () => {
179
+ cy.get(contacts.list.selectors.rowGeneric).then($rows => {
180
+ if ($rows.length > 0) {
181
+ contacts.list.clickRowByIndex(0)
182
+
183
+ cy.url().then(url => {
184
+ const contactId = url.split('/').pop()
185
+ contacts.form.visitEdit(contactId!)
186
+ contacts.form.validateFormVisible()
187
+
188
+ contacts.form.typeInField('firstName', 'ShouldNotSave')
189
+
190
+ contacts.form.cancel()
191
+ cy.url().should('not.include', '/edit')
192
+ })
193
+ }
194
+ })
195
+ })
196
+ })
197
+
198
+ describe('DELETE - Owner can delete contacts', () => {
199
+ it('OWNER_CONTACT_DELETE_001: should delete contact successfully', () => {
200
+ const firstName = `DeleteTest${Date.now()}`
201
+ const lastName = 'Owner'
202
+ const email = `delete.owner.${Date.now()}@test.com`
203
+
204
+ contacts.list.clickCreate()
205
+ contacts.fillContactForm({ firstName, lastName, email })
206
+ contacts.submitForm()
207
+
208
+ cy.contains(firstName).should('be.visible')
209
+
210
+ contacts.list.search(firstName)
211
+
212
+ cy.get(contacts.list.selectors.rowGeneric).first().within(() => {
213
+ cy.get('[data-cy*="delete"]').click()
214
+ })
215
+
216
+ contacts.list.confirmDelete()
217
+
218
+ cy.wait(1000)
219
+ contacts.list.search(firstName)
220
+ cy.get(contacts.list.selectors.emptyState).should('be.visible')
221
+ })
222
+
223
+ it('OWNER_CONTACT_DELETE_002: should cancel contact deletion', () => {
224
+ cy.get(contacts.list.selectors.rowGeneric).then($rows => {
225
+ if ($rows.length > 0) {
226
+ cy.get(contacts.list.selectors.rowGeneric).first().within(() => {
227
+ cy.get('[data-cy*="delete"]').click()
228
+ })
229
+
230
+ contacts.list.cancelDelete()
231
+ contacts.list.validateTableVisible()
232
+ }
233
+ })
234
+ })
235
+ })
236
+ })
@@ -0,0 +1,286 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Leads CRUD - Admin Role (Full Access)
5
+ *
6
+ * Uses CRM theme-specific POMs with Entity Testing Convention.
7
+ * Selectors follow the pattern: {slug}-{component}-{detail}
8
+ */
9
+
10
+ import { LeadsPOM } from '../../../../src/entities/LeadsPOM'
11
+ import { loginAsCrmAdmin } from '../../../../src/session-helpers'
12
+
13
+ describe('Leads CRUD - Admin Role (Full Access)', () => {
14
+ const leads = new LeadsPOM()
15
+
16
+ beforeEach(() => {
17
+ loginAsCrmAdmin()
18
+ cy.visit('/dashboard/leads')
19
+ leads.list.validatePageVisible()
20
+ })
21
+
22
+ describe('CREATE - Admin can create leads', () => {
23
+ it('ADMIN_LEAD_CREATE_001: should create new lead with required fields', () => {
24
+ const timestamp = Date.now()
25
+ const firstName = `Admin`
26
+ const lastName = `Lead ${timestamp}`
27
+ const email = `admin-lead${timestamp}@example.com`
28
+
29
+ // Click create button
30
+ leads.list.clickCreate()
31
+
32
+ // Validate form is visible
33
+ leads.form.validateFormVisible()
34
+
35
+ // Fill required lead fields using POM
36
+ leads.fillLeadForm({
37
+ firstName,
38
+ lastName,
39
+ email,
40
+ status: 'new'
41
+ })
42
+
43
+ // Submit form
44
+ leads.submitForm()
45
+
46
+ // Validate redirect to list
47
+ cy.url().should('include', '/dashboard/leads')
48
+
49
+ // Validate lead appears in list
50
+ cy.contains(lastName).should('be.visible')
51
+ })
52
+
53
+ it('ADMIN_LEAD_CREATE_002: should create qualified lead', () => {
54
+ const timestamp = Date.now()
55
+ const firstName = `Qualified`
56
+ const lastName = `Lead ${timestamp}`
57
+ const email = `qualified${timestamp}@example.com`
58
+
59
+ leads.list.clickCreate()
60
+ leads.form.validateFormVisible()
61
+
62
+ leads.fillLeadForm({
63
+ firstName,
64
+ lastName,
65
+ email,
66
+ status: 'qualified',
67
+ company: 'Enterprise Corp'
68
+ })
69
+
70
+ leads.submitForm()
71
+
72
+ cy.url().should('include', '/dashboard/leads')
73
+ cy.contains(lastName).should('be.visible')
74
+ })
75
+
76
+ it('ADMIN_LEAD_CREATE_003: should validate email format', () => {
77
+ const timestamp = Date.now()
78
+
79
+ leads.list.clickCreate()
80
+ leads.form.validateFormVisible()
81
+
82
+ leads.fillLeadForm({
83
+ firstName: 'Test',
84
+ lastName: `Lead ${timestamp}`,
85
+ email: 'invalid-email',
86
+ status: 'new'
87
+ })
88
+
89
+ leads.submitForm()
90
+
91
+ // Form should still be visible (validation failed)
92
+ leads.form.validateFormVisible()
93
+ })
94
+
95
+ it('ADMIN_LEAD_CREATE_004: should cancel lead creation', () => {
96
+ leads.list.clickCreate()
97
+ leads.form.validateFormVisible()
98
+
99
+ leads.fillLeadForm({
100
+ firstName: 'Cancel',
101
+ lastName: 'Test'
102
+ })
103
+
104
+ leads.form.cancel()
105
+
106
+ // Should return to list
107
+ leads.list.validatePageVisible()
108
+ })
109
+ })
110
+
111
+ describe('READ - Admin can read leads', () => {
112
+ it('ADMIN_LEAD_READ_001: should view lead list', () => {
113
+ leads.list.validatePageVisible()
114
+ leads.list.validateTableVisible()
115
+ })
116
+
117
+ it('ADMIN_LEAD_READ_002: should view lead details', () => {
118
+ // Check if there are leads to view
119
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
120
+ if ($rows.length > 0) {
121
+ leads.list.clickRowByIndex(0)
122
+ cy.url().should('match', /\/dashboard\/leads\/[a-z0-9-]+/)
123
+ }
124
+ })
125
+ })
126
+
127
+ it('ADMIN_LEAD_READ_003: should search and filter leads', () => {
128
+ leads.list.search('Admin')
129
+ cy.wait(500)
130
+ leads.list.clearSearch()
131
+ })
132
+
133
+ it('ADMIN_LEAD_READ_004: should paginate through leads', () => {
134
+ leads.list.validatePageVisible()
135
+
136
+ cy.get(leads.list.selectors.pagination).then($pagination => {
137
+ if ($pagination.length > 0) {
138
+ leads.list.nextPage()
139
+ }
140
+ })
141
+ })
142
+ })
143
+
144
+ describe('UPDATE - Admin can update leads', () => {
145
+ it('ADMIN_LEAD_UPDATE_001: should edit lead information', () => {
146
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
147
+ if ($rows.length > 0) {
148
+ // Click on first row to view details
149
+ leads.list.clickRowByIndex(0)
150
+
151
+ cy.url().then(url => {
152
+ const leadId = url.split('/').pop()
153
+
154
+ // Navigate to edit page
155
+ leads.form.visitEdit(leadId!)
156
+ leads.form.validateFormVisible()
157
+
158
+ // Update first name
159
+ const updatedFirstName = `UpdatedAdmin`
160
+ leads.form.typeInField('firstName', updatedFirstName)
161
+
162
+ leads.submitForm()
163
+
164
+ cy.url().should('include', '/dashboard/leads')
165
+ cy.contains(updatedFirstName).should('be.visible')
166
+ })
167
+ }
168
+ })
169
+ })
170
+
171
+ it('ADMIN_LEAD_UPDATE_002: should progress lead through sales pipeline', () => {
172
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
173
+ if ($rows.length > 0) {
174
+ leads.list.clickRowByIndex(0)
175
+
176
+ cy.url().then(url => {
177
+ const leadId = url.split('/').pop()
178
+
179
+ leads.form.visitEdit(leadId!)
180
+ leads.form.validateFormVisible()
181
+
182
+ // Progress status
183
+ leads.form.selectOption('status', 'contacted')
184
+
185
+ leads.submitForm()
186
+ cy.url().should('include', '/dashboard/leads')
187
+ })
188
+ }
189
+ })
190
+ })
191
+
192
+ it('ADMIN_LEAD_UPDATE_003: should cancel update without saving', () => {
193
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
194
+ if ($rows.length > 0) {
195
+ leads.list.clickRowByIndex(0)
196
+
197
+ cy.url().then(url => {
198
+ const leadId = url.split('/').pop()
199
+
200
+ leads.form.visitEdit(leadId!)
201
+ leads.form.validateFormVisible()
202
+
203
+ leads.form.typeInField('firstName', 'ShouldNotSave')
204
+
205
+ leads.form.cancel()
206
+ cy.url().should('not.include', '/edit')
207
+ })
208
+ }
209
+ })
210
+ })
211
+ })
212
+
213
+ describe('DELETE - Admin can delete leads', () => {
214
+ it('ADMIN_LEAD_DELETE_001: should delete lead', () => {
215
+ // First, create a lead to delete
216
+ const timestamp = Date.now()
217
+ const firstName = `AdminDelete`
218
+ const lastName = `Test ${timestamp}`
219
+ const email = `admin-delete${timestamp}@example.com`
220
+
221
+ leads.list.clickCreate()
222
+ leads.fillLeadForm({
223
+ firstName,
224
+ lastName,
225
+ email,
226
+ status: 'new'
227
+ })
228
+ leads.submitForm()
229
+
230
+ // Wait for lead to appear
231
+ cy.contains(lastName).should('be.visible')
232
+
233
+ // Search for the lead
234
+ leads.list.search(lastName)
235
+
236
+ // Delete it
237
+ cy.get(leads.list.selectors.rowGeneric).first().within(() => {
238
+ cy.get('[data-cy*="delete"]').click()
239
+ })
240
+
241
+ // Confirm deletion
242
+ leads.list.confirmDelete()
243
+
244
+ // Validate deletion
245
+ leads.list.search(lastName)
246
+ cy.get(leads.list.selectors.emptyState).should('be.visible')
247
+ })
248
+
249
+ it('ADMIN_LEAD_DELETE_002: should cancel deletion', () => {
250
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
251
+ if ($rows.length > 0) {
252
+ cy.get(leads.list.selectors.rowGeneric).first().within(() => {
253
+ cy.get('[data-cy*="delete"]').click()
254
+ })
255
+
256
+ leads.list.cancelDelete()
257
+ leads.list.validateTableVisible()
258
+ }
259
+ })
260
+ })
261
+
262
+ it('ADMIN_LEAD_DELETE_003: should handle bulk delete', () => {
263
+ leads.list.selectAll()
264
+ leads.list.validateBulkActionsVisible()
265
+ })
266
+ })
267
+
268
+ describe('CONVERT - Admin can convert leads', () => {
269
+ it('ADMIN_LEAD_CONVERT_001: should convert lead to contact', () => {
270
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
271
+ if ($rows.length > 0) {
272
+ leads.list.clickRowByIndex(0)
273
+
274
+ // Check if convert button exists
275
+ cy.get('[data-cy="lead-convert-btn"]').then($btn => {
276
+ if ($btn.length > 0) {
277
+ leads.convertToContact()
278
+ // Should navigate to contact or show success
279
+ cy.url().should('include', '/contacts')
280
+ }
281
+ })
282
+ }
283
+ })
284
+ })
285
+ })
286
+ })
@@ -0,0 +1,193 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Leads CRUD - Member Role (Create + Read + Update, No Delete)
5
+ *
6
+ * Uses CRM theme-specific POMs with Entity Testing Convention.
7
+ * Selectors follow the pattern: {slug}-{component}-{detail}
8
+ */
9
+
10
+ import { LeadsPOM } from '../../../../src/entities/LeadsPOM'
11
+ import { loginAsCrmMember } from '../../../../src/session-helpers'
12
+
13
+ describe('Leads CRUD - Member Role (Create + Read + Update)', () => {
14
+ const leads = new LeadsPOM()
15
+
16
+ beforeEach(() => {
17
+ loginAsCrmMember()
18
+ cy.visit('/dashboard/leads')
19
+ leads.list.validatePageVisible()
20
+ })
21
+
22
+ describe('READ - Member CAN view leads', () => {
23
+ it('MEMBER_LEAD_READ_001: should view lead list', () => {
24
+ leads.list.validatePageVisible()
25
+ leads.list.validateTableVisible()
26
+ })
27
+
28
+ it('MEMBER_LEAD_READ_002: should view lead details', () => {
29
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
30
+ if ($rows.length > 0) {
31
+ leads.list.clickRowByIndex(0)
32
+ cy.url().should('match', /\/dashboard\/leads\/[a-z0-9-]+/)
33
+ }
34
+ })
35
+ })
36
+
37
+ it('MEMBER_LEAD_READ_003: should search and filter leads', () => {
38
+ leads.list.search('test')
39
+ cy.wait(500)
40
+ leads.list.clearSearch()
41
+ })
42
+ })
43
+
44
+ describe('UPDATE - Member CAN edit leads', () => {
45
+ it('MEMBER_LEAD_UPDATE_001: should edit existing lead', () => {
46
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
47
+ if ($rows.length > 0) {
48
+ leads.list.clickRowByIndex(0)
49
+
50
+ cy.url().then(url => {
51
+ const leadId = url.split('/').pop()
52
+ leads.form.visitEdit(leadId!)
53
+ leads.form.validateFormVisible()
54
+
55
+ // Verify fields are enabled (Member can edit)
56
+ cy.get(leads.form.selectors.field('firstName')).within(() => {
57
+ cy.get('input, textarea').should('not.be.disabled')
58
+ })
59
+ })
60
+ }
61
+ })
62
+ })
63
+
64
+ it('MEMBER_LEAD_UPDATE_002: should update lead status after contact', () => {
65
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
66
+ if ($rows.length > 0) {
67
+ leads.list.clickRowByIndex(0)
68
+
69
+ cy.url().then(url => {
70
+ const leadId = url.split('/').pop()
71
+ leads.form.visitEdit(leadId!)
72
+ leads.form.validateFormVisible()
73
+
74
+ leads.form.selectOption('status', 'contacted')
75
+
76
+ leads.submitForm()
77
+ cy.url().should('include', '/dashboard/leads')
78
+ })
79
+ }
80
+ })
81
+ })
82
+
83
+ it('MEMBER_LEAD_UPDATE_003: should add notes to lead', () => {
84
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
85
+ if ($rows.length > 0) {
86
+ leads.list.clickRowByIndex(0)
87
+
88
+ cy.url().then(url => {
89
+ const leadId = url.split('/').pop()
90
+ leads.form.visitEdit(leadId!)
91
+ leads.form.validateFormVisible()
92
+
93
+ // Add notes if field exists
94
+ cy.get('body').then($body => {
95
+ if ($body.find(leads.form.selectors.field('notes')).length > 0) {
96
+ const timestamp = new Date().toISOString()
97
+ leads.form.typeInTextarea('notes', `Member follow-up: Called on ${timestamp}`)
98
+
99
+ leads.submitForm()
100
+ cy.url().should('include', '/dashboard/leads')
101
+ }
102
+ })
103
+ })
104
+ }
105
+ })
106
+ })
107
+ })
108
+
109
+ describe('CREATE - Member CAN create leads', () => {
110
+ it('MEMBER_LEAD_CREATE_001: should show add button', () => {
111
+ cy.get(leads.list.selectors.createButton).should('be.visible')
112
+ })
113
+
114
+ it('MEMBER_LEAD_CREATE_002: should create new lead successfully', () => {
115
+ leads.list.clickCreate()
116
+ leads.form.validateFormVisible()
117
+
118
+ const timestamp = Date.now()
119
+
120
+ leads.fillLeadForm({
121
+ firstName: `MemberLead${timestamp}`,
122
+ lastName: 'Test',
123
+ email: `memberlead${timestamp}@test.com`,
124
+ phone: '+34 600 123 456',
125
+ source: 'website'
126
+ })
127
+
128
+ leads.submitForm()
129
+
130
+ cy.url().should('include', '/dashboard/leads')
131
+ cy.contains(`MemberLead${timestamp}`).should('be.visible')
132
+ })
133
+
134
+ it('MEMBER_LEAD_CREATE_003: should create lead with minimal required fields', () => {
135
+ leads.list.clickCreate()
136
+ leads.form.validateFormVisible()
137
+
138
+ const timestamp = Date.now()
139
+ leads.fillLeadForm({
140
+ firstName: `MinimalMember${timestamp}`,
141
+ email: `minimal${timestamp}@test.com`
142
+ })
143
+
144
+ leads.submitForm()
145
+ cy.url().should('include', '/dashboard/leads')
146
+ })
147
+ })
148
+
149
+ describe('DELETE - Member CANNOT delete leads', () => {
150
+ it('MEMBER_LEAD_DELETE_001: delete buttons should be hidden', () => {
151
+ cy.get('[data-cy*="delete"]').should('not.exist')
152
+ })
153
+
154
+ it('MEMBER_LEAD_DELETE_002: should not see delete action in list view', () => {
155
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
156
+ if ($rows.length > 0) {
157
+ cy.get('[data-cy*="delete"]').should('not.exist')
158
+ }
159
+ })
160
+ })
161
+
162
+ it('MEMBER_LEAD_DELETE_003: should not see delete action in detail view', () => {
163
+ cy.get(leads.list.selectors.rowGeneric).then($rows => {
164
+ if ($rows.length > 0) {
165
+ leads.list.clickRowByIndex(0)
166
+ cy.url().should('match', /\/dashboard\/leads\/[a-z0-9-]+/)
167
+
168
+ cy.get('[data-cy="lead-delete-btn"]').should('not.exist')
169
+ cy.get('button:contains("Delete")').should('not.exist')
170
+ }
171
+ })
172
+ })
173
+ })
174
+
175
+ describe('PERMISSIONS - Member role capabilities', () => {
176
+ it('MEMBER_LEAD_PERM_001: should have create, read, and update permissions', () => {
177
+ leads.list.validatePageVisible()
178
+
179
+ // Verify create button exists (create permission)
180
+ cy.get(leads.list.selectors.createButton).should('be.visible')
181
+
182
+ // Verify no delete buttons (no delete permission)
183
+ cy.get('[data-cy*="delete"]').should('not.exist')
184
+ })
185
+
186
+ it('MEMBER_LEAD_PERM_002: should be able to create and work with leads', () => {
187
+ leads.list.validatePageVisible()
188
+
189
+ // Member can create leads
190
+ cy.get(leads.list.selectors.createButton).should('be.visible')
191
+ })
192
+ })
193
+ })