@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.
Files changed (74) hide show
  1. package/package.json +3 -3
  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/jest/__mocks__/jose.js +22 -0
  71. package/tests/jest/__mocks__/next-server.js +56 -0
  72. package/tests/jest/jest.config.cjs +127 -0
  73. package/tests/jest/setup.ts +170 -0
  74. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,268 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Activities 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 { ActivitiesPOM } from '../../../src/entities/ActivitiesPOM'
11
+ import { loginAsCrmAdmin } from '../../../src/session-helpers'
12
+
13
+ describe('Activities CRUD - Admin Role (Full Access)', () => {
14
+ const activities = new ActivitiesPOM()
15
+
16
+ beforeEach(() => {
17
+ loginAsCrmAdmin()
18
+ cy.visit('/dashboard/activities')
19
+ activities.list.validatePageVisible()
20
+ })
21
+
22
+ describe('CREATE - Admin can create activities', () => {
23
+ it('ADMIN_ACTIV_CREATE_001: should create new call activity', () => {
24
+ const timestamp = Date.now()
25
+ const subject = `Admin Call ${timestamp}`
26
+
27
+ activities.list.clickCreate()
28
+ activities.form.validateFormVisible()
29
+
30
+ activities.fillActivityForm({
31
+ type: 'call',
32
+ subject
33
+ })
34
+
35
+ activities.submitForm()
36
+
37
+ cy.url().should('include', '/dashboard/activities')
38
+ cy.contains(subject).should('be.visible')
39
+ })
40
+
41
+ it('ADMIN_ACTIV_CREATE_002: should create meeting activity with all fields', () => {
42
+ const timestamp = Date.now()
43
+ const subject = `Admin Meeting ${timestamp}`
44
+
45
+ activities.list.clickCreate()
46
+ activities.form.validateFormVisible()
47
+
48
+ activities.fillActivityForm({
49
+ type: 'meeting',
50
+ subject,
51
+ description: 'Q1 planning meeting'
52
+ })
53
+
54
+ activities.submitForm()
55
+
56
+ cy.url().should('include', '/dashboard/activities')
57
+ cy.contains(subject).should('be.visible')
58
+ })
59
+
60
+ it('ADMIN_ACTIV_CREATE_003: should create email activity', () => {
61
+ const timestamp = Date.now()
62
+ const subject = `Admin Email ${timestamp}`
63
+
64
+ activities.list.clickCreate()
65
+ activities.form.validateFormVisible()
66
+
67
+ activities.fillActivityForm({
68
+ type: 'email',
69
+ subject
70
+ })
71
+
72
+ activities.submitForm()
73
+
74
+ cy.url().should('include', '/dashboard/activities')
75
+ cy.contains(subject).should('be.visible')
76
+ })
77
+
78
+ it('ADMIN_ACTIV_CREATE_004: should create task activity', () => {
79
+ const timestamp = Date.now()
80
+ const subject = `Admin Task ${timestamp}`
81
+
82
+ activities.list.clickCreate()
83
+ activities.form.validateFormVisible()
84
+
85
+ activities.fillActivityForm({
86
+ type: 'task',
87
+ subject,
88
+ description: 'Review documents'
89
+ })
90
+
91
+ activities.submitForm()
92
+
93
+ cy.url().should('include', '/dashboard/activities')
94
+ cy.contains(subject).should('be.visible')
95
+ })
96
+
97
+ it('ADMIN_ACTIV_CREATE_005: should validate required fields', () => {
98
+ activities.list.clickCreate()
99
+ activities.form.validateFormVisible()
100
+
101
+ // Try to submit without filling required fields
102
+ activities.submitForm()
103
+
104
+ // Form should still be visible (validation failed)
105
+ activities.form.validateFormVisible()
106
+ })
107
+
108
+ it('ADMIN_ACTIV_CREATE_006: should cancel activity creation', () => {
109
+ activities.list.clickCreate()
110
+ activities.form.validateFormVisible()
111
+
112
+ activities.fillActivityForm({
113
+ type: 'call',
114
+ subject: 'Should not be created'
115
+ })
116
+
117
+ activities.form.cancel()
118
+ cy.url().should('not.include', '/create')
119
+ })
120
+ })
121
+
122
+ describe('READ - Admin can read activities', () => {
123
+ it('ADMIN_ACTIV_READ_001: should view activity list', () => {
124
+ activities.list.validatePageVisible()
125
+ activities.list.validateTableVisible()
126
+ })
127
+
128
+ it('ADMIN_ACTIV_READ_002: should view activity details', () => {
129
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
130
+ if ($rows.length > 0) {
131
+ activities.list.clickRowByIndex(0)
132
+ cy.url().should('match', /\/dashboard\/activities\/[a-z0-9-]+/)
133
+ }
134
+ })
135
+ })
136
+
137
+ it('ADMIN_ACTIV_READ_003: should search activities', () => {
138
+ activities.list.search('test')
139
+ cy.wait(500)
140
+ activities.list.clearSearch()
141
+ })
142
+ })
143
+
144
+ describe('UPDATE - Admin can update activities', () => {
145
+ it('ADMIN_ACTIV_UPDATE_001: should edit activity subject', () => {
146
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
147
+ if ($rows.length > 0) {
148
+ activities.list.clickRowByIndex(0)
149
+
150
+ cy.url().then(url => {
151
+ const activityId = url.split('/').pop()
152
+ activities.form.visitEdit(activityId!)
153
+ activities.form.validateFormVisible()
154
+
155
+ const updatedSubject = `Admin Updated ${Date.now()}`
156
+ activities.form.typeInField('subject', updatedSubject)
157
+
158
+ activities.submitForm()
159
+
160
+ cy.url().should('include', '/dashboard/activities')
161
+ cy.contains(updatedSubject).should('be.visible')
162
+ })
163
+ }
164
+ })
165
+ })
166
+
167
+ it('ADMIN_ACTIV_UPDATE_002: should update activity type', () => {
168
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
169
+ if ($rows.length > 0) {
170
+ activities.list.clickRowByIndex(0)
171
+
172
+ cy.url().then(url => {
173
+ const activityId = url.split('/').pop()
174
+ activities.form.visitEdit(activityId!)
175
+ activities.form.validateFormVisible()
176
+
177
+ activities.form.selectOption('type', 'meeting')
178
+
179
+ activities.submitForm()
180
+ cy.url().should('include', '/dashboard/activities')
181
+ })
182
+ }
183
+ })
184
+ })
185
+
186
+ it('ADMIN_ACTIV_UPDATE_003: should cancel update without saving', () => {
187
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
188
+ if ($rows.length > 0) {
189
+ activities.list.clickRowByIndex(0)
190
+
191
+ cy.url().then(url => {
192
+ const activityId = url.split('/').pop()
193
+ activities.form.visitEdit(activityId!)
194
+ activities.form.validateFormVisible()
195
+
196
+ activities.form.typeInField('subject', 'ShouldNotSave')
197
+
198
+ activities.form.cancel()
199
+ cy.url().should('not.include', '/edit')
200
+ })
201
+ }
202
+ })
203
+ })
204
+ })
205
+
206
+ describe('DELETE - Admin can delete activities', () => {
207
+ it('ADMIN_ACTIV_DELETE_001: should delete activity successfully', () => {
208
+ const timestamp = Date.now()
209
+ const subject = `Admin Delete Test ${timestamp}`
210
+
211
+ activities.list.clickCreate()
212
+ activities.fillActivityForm({
213
+ type: 'task',
214
+ subject
215
+ })
216
+ activities.submitForm()
217
+
218
+ cy.contains(subject).should('be.visible')
219
+
220
+ activities.list.search(subject)
221
+
222
+ cy.get(activities.list.selectors.rowGeneric).first().within(() => {
223
+ cy.get('[data-cy*="delete"]').click()
224
+ })
225
+
226
+ activities.list.confirmDelete()
227
+
228
+ cy.wait(1000)
229
+ activities.list.search(subject)
230
+ cy.get(activities.list.selectors.emptyState).should('be.visible')
231
+ })
232
+
233
+ it('ADMIN_ACTIV_DELETE_002: should cancel deletion', () => {
234
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
235
+ if ($rows.length > 0) {
236
+ cy.get(activities.list.selectors.rowGeneric).first().within(() => {
237
+ cy.get('[data-cy*="delete"]').click()
238
+ })
239
+
240
+ activities.list.cancelDelete()
241
+ activities.list.validateTableVisible()
242
+ }
243
+ })
244
+ })
245
+ })
246
+
247
+ describe('PERMISSIONS - Admin role capabilities', () => {
248
+ it('ADMIN_ACTIV_PERM_001: should have full CRUD access', () => {
249
+ activities.list.validatePageVisible()
250
+
251
+ // Check for create button
252
+ cy.get(activities.list.selectors.createButton).should('be.visible')
253
+ })
254
+
255
+ it('ADMIN_ACTIV_PERM_002: should have edit access', () => {
256
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
257
+ if ($rows.length > 0) {
258
+ activities.list.clickRowByIndex(0)
259
+ cy.url().then(url => {
260
+ const activityId = url.split('/').pop()
261
+ activities.form.visitEdit(activityId!)
262
+ activities.form.validateFormVisible()
263
+ })
264
+ }
265
+ })
266
+ })
267
+ })
268
+ })
@@ -0,0 +1,257 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Activities CRUD - Member Role (Read + Update Only)
5
+ *
6
+ * Uses CRM theme-specific POMs with Entity Testing Convention.
7
+ * Selectors follow the pattern: {slug}-{component}-{detail}
8
+ */
9
+
10
+ import { ActivitiesPOM } from '../../../src/entities/ActivitiesPOM'
11
+ import { loginAsCrmMember } from '../../../src/session-helpers'
12
+
13
+ describe('Activities CRUD - Member Role (Read + Update Only)', () => {
14
+ const activities = new ActivitiesPOM()
15
+
16
+ beforeEach(() => {
17
+ loginAsCrmMember()
18
+ cy.visit('/dashboard/activities')
19
+ activities.list.validatePageVisible()
20
+ })
21
+
22
+ describe('READ - Member CAN view activities', () => {
23
+ it('MEMBER_ACTIV_READ_001: should view activity list', () => {
24
+ activities.list.validatePageVisible()
25
+ activities.list.validateTableVisible()
26
+ })
27
+
28
+ it('MEMBER_ACTIV_READ_002: should view activity details', () => {
29
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
30
+ if ($rows.length > 0) {
31
+ activities.list.clickRowByIndex(0)
32
+ cy.url().should('match', /\/dashboard\/activities\/[a-z0-9-]+/)
33
+ }
34
+ })
35
+ })
36
+
37
+ it('MEMBER_ACTIV_READ_003: should search activities', () => {
38
+ activities.list.search('test')
39
+ cy.wait(500)
40
+ activities.list.clearSearch()
41
+ })
42
+ })
43
+
44
+ describe('UPDATE - Member CAN edit activities', () => {
45
+ it('MEMBER_ACTIV_UPDATE_001: should edit existing activity', () => {
46
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
47
+ if ($rows.length > 0) {
48
+ activities.list.clickRowByIndex(0)
49
+
50
+ cy.url().then(url => {
51
+ const activityId = url.split('/').pop()
52
+ activities.form.visitEdit(activityId!)
53
+ activities.form.validateFormVisible()
54
+
55
+ // Verify fields are enabled (Member can edit)
56
+ cy.get(activities.form.selectors.field('subject')).within(() => {
57
+ cy.get('input, textarea').should('not.be.disabled')
58
+ })
59
+ })
60
+ }
61
+ })
62
+ })
63
+
64
+ it('MEMBER_ACTIV_UPDATE_002: should update activity subject', () => {
65
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
66
+ if ($rows.length > 0) {
67
+ activities.list.clickRowByIndex(0)
68
+
69
+ cy.url().then(url => {
70
+ const activityId = url.split('/').pop()
71
+ activities.form.visitEdit(activityId!)
72
+ activities.form.validateFormVisible()
73
+
74
+ const updatedSubject = `Member Updated ${Date.now()}`
75
+ activities.form.typeInField('subject', updatedSubject)
76
+
77
+ activities.submitForm()
78
+ cy.url().should('include', '/dashboard/activities')
79
+ cy.contains(updatedSubject).should('be.visible')
80
+ })
81
+ }
82
+ })
83
+ })
84
+
85
+ it('MEMBER_ACTIV_UPDATE_003: should update activity type', () => {
86
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
87
+ if ($rows.length > 0) {
88
+ activities.list.clickRowByIndex(0)
89
+
90
+ cy.url().then(url => {
91
+ const activityId = url.split('/').pop()
92
+ activities.form.visitEdit(activityId!)
93
+ activities.form.validateFormVisible()
94
+
95
+ activities.form.selectOption('type', 'meeting')
96
+
97
+ activities.submitForm()
98
+ cy.url().should('include', '/dashboard/activities')
99
+ })
100
+ }
101
+ })
102
+ })
103
+
104
+ it('MEMBER_ACTIV_UPDATE_004: should cancel update without saving', () => {
105
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
106
+ if ($rows.length > 0) {
107
+ activities.list.clickRowByIndex(0)
108
+
109
+ cy.url().then(url => {
110
+ const activityId = url.split('/').pop()
111
+ activities.form.visitEdit(activityId!)
112
+ activities.form.validateFormVisible()
113
+
114
+ activities.form.typeInField('subject', 'ShouldNotSave')
115
+
116
+ activities.form.cancel()
117
+ cy.url().should('not.include', '/edit')
118
+ })
119
+ }
120
+ })
121
+ })
122
+ })
123
+
124
+ describe('CREATE - Member CANNOT create activities', () => {
125
+ it('MEMBER_ACTIV_CREATE_001: create button should be hidden', () => {
126
+ cy.get(activities.list.selectors.createButton).should('not.exist')
127
+ })
128
+
129
+ it('MEMBER_ACTIV_CREATE_002: should not be able to access create form via UI', () => {
130
+ // Verify no visible way to access create form
131
+ cy.get('[data-cy*="create"], [data-cy*="add"]').should('not.exist')
132
+ })
133
+
134
+ it('MEMBER_ACTIV_CREATE_003: direct navigation to create form should be blocked', () => {
135
+ cy.visit('/dashboard/activities/create', { failOnStatusCode: false })
136
+
137
+ // Should either redirect or show access denied
138
+ cy.url().then(url => {
139
+ if (url.includes('/dashboard/activities/create')) {
140
+ // If still on create page, should show error
141
+ cy.get('body').should('not.contain', activities.form.selectors.form)
142
+ } else {
143
+ // Should redirect away from create page
144
+ cy.url().should('not.include', '/create')
145
+ }
146
+ })
147
+ })
148
+ })
149
+
150
+ describe('DELETE - Member CANNOT delete activities', () => {
151
+ it('MEMBER_ACTIV_DELETE_001: delete buttons should be hidden', () => {
152
+ cy.get('[data-cy*="delete"]').should('not.exist')
153
+ })
154
+
155
+ it('MEMBER_ACTIV_DELETE_002: should not see delete action in activity list', () => {
156
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
157
+ if ($rows.length > 0) {
158
+ cy.get(activities.list.selectors.rowGeneric).first().within(() => {
159
+ cy.get('[data-cy*="delete"]').should('not.exist')
160
+ })
161
+ }
162
+ })
163
+ })
164
+
165
+ it('MEMBER_ACTIV_DELETE_003: should not see delete action in detail view', () => {
166
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
167
+ if ($rows.length > 0) {
168
+ activities.list.clickRowByIndex(0)
169
+ cy.url().should('match', /\/dashboard\/activities\/[a-z0-9-]+/)
170
+
171
+ cy.get('[data-cy*="delete"]').should('not.exist')
172
+ }
173
+ })
174
+ })
175
+
176
+ it('MEMBER_ACTIV_DELETE_004: should NOT delete activity via API', () => {
177
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
178
+ if ($rows.length > 0) {
179
+ cy.get(activities.list.selectors.rowGeneric).first().invoke('attr', 'data-cy').then(dataCy => {
180
+ if (dataCy) {
181
+ const activityId = dataCy.replace('activities-row-', '')
182
+
183
+ cy.request({
184
+ method: 'DELETE',
185
+ url: `/api/v1/activities/${activityId}`,
186
+ failOnStatusCode: false,
187
+ }).then(response => {
188
+ expect(response.status).to.be.oneOf([401, 403, 404])
189
+ })
190
+ }
191
+ })
192
+ }
193
+ })
194
+ })
195
+ })
196
+
197
+ describe('PERMISSIONS - Member role capabilities', () => {
198
+ it('MEMBER_ACTIV_PERM_001: should have read and update access only', () => {
199
+ activities.list.validatePageVisible()
200
+
201
+ // Check for no create button
202
+ cy.get(activities.list.selectors.createButton).should('not.exist')
203
+
204
+ // Check for no delete buttons
205
+ cy.get('[data-cy*="delete"]').should('not.exist')
206
+ })
207
+
208
+ it('MEMBER_ACTIV_PERM_002: should have edit access to existing activities', () => {
209
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
210
+ if ($rows.length > 0) {
211
+ activities.list.clickRowByIndex(0)
212
+ cy.url().then(url => {
213
+ const activityId = url.split('/').pop()
214
+ activities.form.visitEdit(activityId!)
215
+ activities.form.validateFormVisible()
216
+ })
217
+ }
218
+ })
219
+ })
220
+
221
+ it('MEMBER_ACTIV_PERM_003: should see all organization activities', () => {
222
+ activities.list.validatePageVisible()
223
+
224
+ cy.get(activities.list.selectors.rowGeneric).then($rows => {
225
+ if ($rows.length > 0) {
226
+ cy.get(activities.list.selectors.rowGeneric).should('have.length.at.least', 1)
227
+ }
228
+ })
229
+ })
230
+
231
+ it('MEMBER_ACTIV_PERM_004: should maintain restrictions after page refresh', () => {
232
+ cy.reload()
233
+
234
+ activities.list.validatePageVisible()
235
+
236
+ // Verify restrictions persist
237
+ cy.get(activities.list.selectors.createButton).should('not.exist')
238
+ cy.get('[data-cy*="delete"]').should('not.exist')
239
+ })
240
+ })
241
+
242
+ describe('EDGE CASES - Member Role', () => {
243
+ it('MEMBER_ACTIV_EDGE_001: should handle empty activity list gracefully', () => {
244
+ activities.list.validatePageVisible()
245
+ })
246
+
247
+ it('MEMBER_ACTIV_EDGE_002: should not bypass create restriction via URL', () => {
248
+ cy.visit('/dashboard/activities?action=create', { failOnStatusCode: false })
249
+ cy.get(activities.list.selectors.createButton).should('not.exist')
250
+ })
251
+
252
+ it('MEMBER_ACTIV_EDGE_003: should not bypass delete restriction via URL', () => {
253
+ cy.visit('/dashboard/activities?action=delete', { failOnStatusCode: false })
254
+ cy.get('[data-cy*="delete"]').should('not.exist')
255
+ })
256
+ })
257
+ })