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