@nextsparkjs/theme-default 0.1.0-beta.44 → 0.1.0-beta.45

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 (48) hide show
  1. package/components/ai-chat/ChatPanel.tsx +7 -7
  2. package/components/ai-chat/Message.tsx +2 -2
  3. package/components/ai-chat/MessageInput.tsx +3 -3
  4. package/components/ai-chat/MessageList.tsx +3 -3
  5. package/components/ai-chat/TypingIndicator.tsx +2 -2
  6. package/entities/customers/api/docs.md +107 -0
  7. package/entities/customers/api/presets.ts +80 -0
  8. package/entities/pages/api/docs.md +114 -0
  9. package/entities/pages/api/presets.ts +72 -0
  10. package/entities/posts/api/docs.md +120 -0
  11. package/entities/posts/api/presets.ts +74 -0
  12. package/entities/tasks/api/docs.md +126 -0
  13. package/entities/tasks/api/presets.ts +84 -0
  14. package/lib/selectors.ts +2 -2
  15. package/messages/de/admin.json +45 -0
  16. package/messages/en/admin.json +45 -0
  17. package/messages/es/admin.json +45 -0
  18. package/messages/fr/admin.json +45 -0
  19. package/messages/it/admin.json +45 -0
  20. package/messages/pt/admin.json +45 -0
  21. package/package.json +3 -3
  22. package/styles/globals.css +24 -0
  23. package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +491 -0
  24. package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +475 -0
  25. package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
  26. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +14 -14
  27. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +3 -3
  28. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +38 -73
  29. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +21 -42
  30. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +117 -38
  31. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +35 -12
  32. package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +50 -59
  33. package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +15 -23
  34. package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +395 -155
  35. package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +795 -174
  36. package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
  37. package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
  38. package/tests/cypress/src/core/BlockEditorBasePOM.ts +269 -99
  39. package/tests/cypress/src/core/DashboardEntityPOM.ts +1 -1
  40. package/tests/cypress/src/features/DashboardPOM.ts +49 -28
  41. package/tests/cypress/src/features/PageBuilderPOM.ts +20 -0
  42. package/tests/cypress/src/features/SettingsPOM.ts +511 -166
  43. package/tests/cypress/src/features/SuperadminPOM.ts +679 -159
  44. package/tests/cypress/src/features/index.ts +10 -10
  45. package/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  46. package/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  47. package/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  48. package/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Teams API - Security Tests
3
+ *
4
+ * Tests for security vulnerabilities in Teams PATCH endpoint:
5
+ * - Issue #1: Type coercion vulnerability (empty strings, 0, false, null)
6
+ * - Issue #2: Permission check order (owner-only fields)
7
+ * - Issue #3: Schema validation (owner vs non-owner schemas)
8
+ *
9
+ * Uses existing sample data users:
10
+ * - Owner: Carlos Mendoza (team-everpoint-001)
11
+ * - Admin: James Wilson (team-everpoint-001)
12
+ * - Member: Emily Johnson (team-everpoint-001)
13
+ *
14
+ * @tags @api @teams @security
15
+ */
16
+
17
+ import * as allure from 'allure-cypress'
18
+ import { loginAsOwner, loginAsAdmin, loginAsMember, BILLING_TEAMS } from '../../../../src/session-helpers'
19
+
20
+ describe('Teams API - Security Tests', { tags: ['@api', '@teams', '@security'] }, () => {
21
+ // Use Everpoint Labs team from sample data (Carlos is owner, James is admin, Emily is member)
22
+ const TEST_TEAM_ID = BILLING_TEAMS.PRO.teamId // team-everpoint-001
23
+ const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:3000'
24
+
25
+ beforeEach(() => {
26
+ allure.epic('API')
27
+ allure.feature('Teams')
28
+ allure.story('Security & Permission Enforcement')
29
+ })
30
+
31
+ describe('Issue #1: Type Coercion Vulnerability', () => {
32
+ context('As Admin (non-owner)', () => {
33
+ beforeEach(() => {
34
+ loginAsAdmin()
35
+ cy.visit('/dashboard') // Required for session cookies
36
+ })
37
+
38
+ it('SEC_TEAMS_001: Should reject PATCH with empty string for name', { tags: '@smoke' }, () => {
39
+ allure.severity('critical')
40
+ cy.request({
41
+ method: 'PATCH',
42
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
43
+ body: { name: '' },
44
+ failOnStatusCode: false,
45
+ }).then((response) => {
46
+ expect(response.status).to.eq(403)
47
+ expect(response.body.success).to.be.false
48
+ expect(response.body.error).to.include('owner')
49
+ expect(response.body.reason).to.eq('OWNER_ONLY')
50
+ })
51
+ })
52
+
53
+ it('SEC_TEAMS_002: Should reject PATCH with null for description', () => {
54
+ cy.request({
55
+ method: 'PATCH',
56
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
57
+ body: { description: null },
58
+ failOnStatusCode: false,
59
+ }).then((response) => {
60
+ expect(response.status).to.eq(403)
61
+ expect(response.body.success).to.be.false
62
+ expect(response.body.error).to.include('owner')
63
+ expect(response.body.reason).to.eq('OWNER_ONLY')
64
+ })
65
+ })
66
+
67
+ it('SEC_TEAMS_003: Should reject PATCH with number (0) for name', () => {
68
+ cy.request({
69
+ method: 'PATCH',
70
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
71
+ body: { name: 0 },
72
+ failOnStatusCode: false,
73
+ }).then((response) => {
74
+ expect(response.status).to.eq(403)
75
+ expect(response.body.success).to.be.false
76
+ expect(response.body.error).to.include('owner')
77
+ expect(response.body.reason).to.eq('OWNER_ONLY')
78
+ })
79
+ })
80
+
81
+ it('SEC_TEAMS_004: Should reject PATCH with boolean (false) for description', () => {
82
+ cy.request({
83
+ method: 'PATCH',
84
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
85
+ body: { description: false },
86
+ failOnStatusCode: false,
87
+ }).then((response) => {
88
+ expect(response.status).to.eq(403)
89
+ expect(response.body.success).to.be.false
90
+ expect(response.body.error).to.include('owner')
91
+ expect(response.body.reason).to.eq('OWNER_ONLY')
92
+ })
93
+ })
94
+
95
+ it('SEC_TEAMS_005: Should reject PATCH with whitespace-only name', () => {
96
+ cy.request({
97
+ method: 'PATCH',
98
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
99
+ body: { name: ' ' },
100
+ failOnStatusCode: false,
101
+ }).then((response) => {
102
+ expect(response.status).to.eq(403)
103
+ expect(response.body.success).to.be.false
104
+ expect(response.body.error).to.include('owner')
105
+ expect(response.body.reason).to.eq('OWNER_ONLY')
106
+ })
107
+ })
108
+ })
109
+
110
+ context('As Member (non-owner)', () => {
111
+ beforeEach(() => {
112
+ loginAsMember()
113
+ cy.visit('/dashboard') // Required for session cookies
114
+ })
115
+
116
+ it('SEC_TEAMS_006: Should reject PATCH with empty string for name', () => {
117
+ cy.request({
118
+ method: 'PATCH',
119
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
120
+ body: { name: '' },
121
+ failOnStatusCode: false,
122
+ }).then((response) => {
123
+ expect(response.status).to.eq(403)
124
+ expect(response.body.success).to.be.false
125
+ expect(response.body.error).to.include('owner')
126
+ expect(response.body.reason).to.eq('OWNER_ONLY')
127
+ })
128
+ })
129
+
130
+ it('SEC_TEAMS_007: Should reject PATCH with description update', () => {
131
+ cy.request({
132
+ method: 'PATCH',
133
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
134
+ body: { description: 'New description' },
135
+ failOnStatusCode: false,
136
+ }).then((response) => {
137
+ expect(response.status).to.eq(403)
138
+ expect(response.body.success).to.be.false
139
+ expect(response.body.error).to.include('owner')
140
+ expect(response.body.reason).to.eq('OWNER_ONLY')
141
+ })
142
+ })
143
+ })
144
+ })
145
+
146
+ describe('Issue #2: Permission Check Order', () => {
147
+ context('Admin user (non-owner)', () => {
148
+ beforeEach(() => {
149
+ loginAsAdmin()
150
+ cy.visit('/dashboard')
151
+ })
152
+
153
+ it('SEC_TEAMS_010: Should return 403 with OWNER_ONLY reason when trying to update name', { tags: '@smoke' }, () => {
154
+ allure.severity('critical')
155
+ cy.request({
156
+ method: 'PATCH',
157
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
158
+ body: { name: 'New Name by Admin' },
159
+ failOnStatusCode: false,
160
+ }).then((response) => {
161
+ expect(response.status).to.eq(403)
162
+ expect(response.body.success).to.be.false
163
+ expect(response.body.reason).to.eq('OWNER_ONLY')
164
+ expect(response.body.error).to.include('Only team owner')
165
+ expect(response.body.error).to.not.include('generic')
166
+ })
167
+ })
168
+
169
+ it('SEC_TEAMS_011: Should return 403 with OWNER_ONLY reason when trying to update description', () => {
170
+ cy.request({
171
+ method: 'PATCH',
172
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
173
+ body: { description: 'New Description by Admin' },
174
+ failOnStatusCode: false,
175
+ }).then((response) => {
176
+ expect(response.status).to.eq(403)
177
+ expect(response.body.success).to.be.false
178
+ expect(response.body.reason).to.eq('OWNER_ONLY')
179
+ expect(response.body.error).to.include('Only team owner')
180
+ })
181
+ })
182
+
183
+ it('SEC_TEAMS_012: Should return specific error, not generic permission error', () => {
184
+ cy.request({
185
+ method: 'PATCH',
186
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
187
+ body: { name: 'Test' },
188
+ failOnStatusCode: false,
189
+ }).then((response) => {
190
+ expect(response.status).to.eq(403)
191
+ expect(response.body.reason).to.eq('OWNER_ONLY')
192
+ // Should NOT be generic "teams.update" permission error
193
+ expect(response.body.reason).to.not.eq('INSUFFICIENT_PERMISSIONS')
194
+ })
195
+ })
196
+ })
197
+
198
+ context('Member user (non-owner)', () => {
199
+ beforeEach(() => {
200
+ loginAsMember()
201
+ cy.visit('/dashboard')
202
+ })
203
+
204
+ it('SEC_TEAMS_013: Should return 403 with OWNER_ONLY reason when trying to update name', () => {
205
+ cy.request({
206
+ method: 'PATCH',
207
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
208
+ body: { name: 'New Name by Member' },
209
+ failOnStatusCode: false,
210
+ }).then((response) => {
211
+ expect(response.status).to.eq(403)
212
+ expect(response.body.success).to.be.false
213
+ expect(response.body.reason).to.eq('OWNER_ONLY')
214
+ })
215
+ })
216
+ })
217
+ })
218
+
219
+ describe('Issue #3: Schema Validation (Owner vs Non-Owner)', () => {
220
+ context('Owner updates', () => {
221
+ beforeEach(() => {
222
+ loginAsOwner()
223
+ cy.visit('/dashboard')
224
+ })
225
+
226
+ it('SEC_TEAMS_020: Should use ownerUpdateTeamSchema for name updates', () => {
227
+ cy.request({
228
+ method: 'PATCH',
229
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
230
+ body: { name: 'A' }, // Too short (min 2 chars)
231
+ failOnStatusCode: false,
232
+ }).then((response) => {
233
+ expect(response.status).to.eq(400)
234
+ expect(response.body.success).to.be.false
235
+ expect(response.body.code).to.eq('VALIDATION_ERROR')
236
+ expect(response.body.error).to.include('at least 2 characters')
237
+ })
238
+ })
239
+
240
+ it('SEC_TEAMS_021: Should use ownerUpdateTeamSchema for description updates', () => {
241
+ cy.request({
242
+ method: 'PATCH',
243
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
244
+ body: { description: null },
245
+ failOnStatusCode: false,
246
+ }).then((response) => {
247
+ // Should accept null for description
248
+ expect(response.status).to.eq(200)
249
+ expect(response.body.success).to.be.true
250
+ })
251
+ })
252
+
253
+ it('SEC_TEAMS_022: Should allow valid owner updates', () => {
254
+ cy.request({
255
+ method: 'PATCH',
256
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
257
+ body: {
258
+ name: 'Everpoint Labs Updated',
259
+ description: 'Updated description',
260
+ },
261
+ }).then((response) => {
262
+ expect(response.status).to.eq(200)
263
+ expect(response.body.success).to.be.true
264
+ expect(response.body.data.name).to.eq('Everpoint Labs Updated')
265
+ expect(response.body.data.description).to.eq('Updated description')
266
+ })
267
+ })
268
+
269
+ // Restore original name
270
+ after(() => {
271
+ cy.request({
272
+ method: 'PATCH',
273
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
274
+ body: { name: 'Everpoint Labs' },
275
+ })
276
+ })
277
+ })
278
+
279
+ context('Admin updates (non-owner fields only)', () => {
280
+ beforeEach(() => {
281
+ loginAsAdmin()
282
+ cy.visit('/dashboard')
283
+ })
284
+
285
+ it('SEC_TEAMS_023: Should reject name/description fields before schema validation', () => {
286
+ // Should fail with 403 OWNER_ONLY, not 400 VALIDATION_ERROR
287
+ cy.request({
288
+ method: 'PATCH',
289
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
290
+ body: { name: 'Test' },
291
+ failOnStatusCode: false,
292
+ }).then((response) => {
293
+ expect(response.status).to.eq(403)
294
+ expect(response.body.reason).to.eq('OWNER_ONLY')
295
+ // Should NOT reach schema validation
296
+ expect(response.body.code).to.not.eq('VALIDATION_ERROR')
297
+ })
298
+ })
299
+
300
+ it('SEC_TEAMS_024: Should allow slug updates by admin', () => {
301
+ cy.request({
302
+ method: 'PATCH',
303
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
304
+ body: { slug: 'everpoint-labs-new' },
305
+ failOnStatusCode: false,
306
+ }).then((response) => {
307
+ // Current implementation: Admins can update slug
308
+ // If this fails, it means the permission system is correctly restricting admin slug updates
309
+ // We document both expected behaviors here
310
+ if (response.status === 200) {
311
+ expect(response.body.success).to.be.true
312
+ cy.log('✅ Admins CAN update slug (current behavior)')
313
+ } else if (response.status === 403) {
314
+ expect(response.body.reason).to.eq('OWNER_ONLY')
315
+ cy.log('✅ Admins CANNOT update slug (stricter behavior)')
316
+ }
317
+ })
318
+ })
319
+
320
+ it('SEC_TEAMS_025: Should allow avatarUrl updates by admin', () => {
321
+ cy.request({
322
+ method: 'PATCH',
323
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
324
+ body: { avatarUrl: 'https://example.com/avatar.png' },
325
+ failOnStatusCode: false,
326
+ }).then((response) => {
327
+ // Similar to slug, this documents expected behavior
328
+ if (response.status === 200) {
329
+ expect(response.body.success).to.be.true
330
+ cy.log('✅ Admins CAN update avatarUrl (current behavior)')
331
+ } else if (response.status === 403) {
332
+ expect(response.body.reason).to.eq('OWNER_ONLY')
333
+ cy.log('✅ Admins CANNOT update avatarUrl (stricter behavior)')
334
+ }
335
+ })
336
+ })
337
+ })
338
+ })
339
+
340
+ describe('Issue #10: Description Max Length Documentation', () => {
341
+ context('Owner updates', () => {
342
+ beforeEach(() => {
343
+ loginAsOwner()
344
+ cy.visit('/dashboard')
345
+ })
346
+
347
+ it('SEC_TEAMS_030: Should accept very long descriptions (TEXT type, no limit)', () => {
348
+ const longDescription = 'A'.repeat(10000) // 10KB text
349
+
350
+ cy.request({
351
+ method: 'PATCH',
352
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
353
+ body: { description: longDescription },
354
+ }).then((response) => {
355
+ expect(response.status).to.eq(200)
356
+ expect(response.body.success).to.be.true
357
+ expect(response.body.data.description).to.eq(longDescription)
358
+ })
359
+ })
360
+
361
+ it('SEC_TEAMS_031: Should accept null for description', () => {
362
+ cy.request({
363
+ method: 'PATCH',
364
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
365
+ body: { description: null },
366
+ }).then((response) => {
367
+ expect(response.status).to.eq(200)
368
+ expect(response.body.success).to.be.true
369
+ expect(response.body.data.description).to.be.null
370
+ })
371
+ })
372
+
373
+ // Restore original description
374
+ after(() => {
375
+ cy.request({
376
+ method: 'PATCH',
377
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
378
+ body: { description: 'Enabling digital innovation through scalable platforms' },
379
+ })
380
+ })
381
+ })
382
+ })
383
+
384
+ describe('Dual Authentication Support', () => {
385
+ it('SEC_TEAMS_040: Should accept session-based auth for owner', () => {
386
+ loginAsOwner()
387
+ cy.visit('/dashboard')
388
+
389
+ cy.request({
390
+ method: 'PATCH',
391
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
392
+ body: { description: 'Session Auth Test' },
393
+ }).then((response) => {
394
+ expect(response.status).to.eq(200)
395
+ expect(response.body.success).to.be.true
396
+ })
397
+ })
398
+
399
+ it('SEC_TEAMS_041: Should reject request without auth', () => {
400
+ cy.clearAllSessionStorage()
401
+ cy.clearAllCookies()
402
+
403
+ cy.request({
404
+ method: 'PATCH',
405
+ url: `/api/v1/teams/${TEST_TEAM_ID}`,
406
+ body: { name: 'No Auth Test' },
407
+ failOnStatusCode: false,
408
+ }).then((response) => {
409
+ expect(response.status).to.eq(401)
410
+ expect(response.body.success).to.be.false
411
+ expect(response.body.code).to.eq('AUTHENTICATION_FAILED')
412
+ })
413
+ })
414
+ })
415
+ })
@@ -0,0 +1,278 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Inline Team Editing - E2E Tests
5
+ *
6
+ * Tests for inline editing of team name and description.
7
+ * Validates owner-only access and proper UI behavior.
8
+ *
9
+ * @see PR#1 for feature context
10
+ * @session 2026-01-11-pr1-translations-pom-fixes-v1
11
+ */
12
+
13
+ import * as allure from 'allure-cypress'
14
+
15
+ import { DevKeyringPOM } from '../../../../src/components/DevKeyringPOM'
16
+ import { SettingsPOM } from '../../../../src/features/SettingsPOM'
17
+
18
+ // Test users
19
+ const USERS = {
20
+ OWNER: 'superadmin@cypress.com', // Team owner (superadmin)
21
+ MEMBER: 'developer@cypress.com', // Team member (developer - not owner of default team)
22
+ }
23
+
24
+ describe('Inline Team Editing', {
25
+ tags: ['@uat', '@feat-teams', '@team-settings', '@regression']
26
+ }, () => {
27
+ const settings = SettingsPOM.create()
28
+
29
+ beforeEach(() => {
30
+ allure.epic('UAT')
31
+ allure.feature('Teams')
32
+ allure.story('Inline Team Editing')
33
+ })
34
+
35
+ // ============================================
36
+ // Owner Can Edit
37
+ // ============================================
38
+
39
+ describe('Owner Can Edit', () => {
40
+ beforeEach(() => {
41
+ cy.session('carlos-owner-inline-edit', () => {
42
+ cy.visit('/login')
43
+ const devKeyring = DevKeyringPOM.create()
44
+ devKeyring.validateVisible()
45
+ devKeyring.quickLoginByEmail(USERS.OWNER)
46
+ cy.url().should('include', '/dashboard')
47
+ })
48
+ cy.visit('/dashboard/settings/team', { timeout: 60000, failOnStatusCode: false })
49
+ settings.waitForTeam()
50
+ })
51
+
52
+ it('TEAM_EDIT_001: Owner can see edit buttons for name and description', { tags: '@smoke' }, () => {
53
+ allure.severity('critical')
54
+ allure.description('Verify that team owners can see edit icons for both team name and description fields')
55
+
56
+ settings.assertTeamNameEditable()
57
+ settings.assertTeamDescriptionEditable()
58
+ })
59
+
60
+ it('TEAM_EDIT_002: Owner can edit and save team name', () => {
61
+ allure.severity('critical')
62
+ allure.description('Verify that team owners can successfully edit and save the team name')
63
+
64
+ const newName = `Test Team ${Date.now()}`
65
+
66
+ settings.editAndSaveTeamName(newName)
67
+
68
+ // Wait for success feedback
69
+ cy.wait(500)
70
+
71
+ // Verify the new name is displayed
72
+ settings.getTeamNameText().should('contain', newName)
73
+ })
74
+
75
+ it('TEAM_EDIT_003: Owner can edit and save team description', () => {
76
+ allure.severity('critical')
77
+ allure.description('Verify that team owners can successfully edit and save the team description')
78
+
79
+ const newDesc = `Updated description ${Date.now()}`
80
+
81
+ settings.editAndSaveTeamDescription(newDesc)
82
+
83
+ // Wait for success feedback
84
+ cy.wait(500)
85
+
86
+ // Verify the new description is displayed
87
+ settings.getTeamDescriptionText().should('contain', newDesc)
88
+ })
89
+
90
+ it('TEAM_EDIT_004: Cancel editing team name preserves original value', () => {
91
+ allure.severity('normal')
92
+ allure.description('Verify that canceling team name editing does not save changes')
93
+
94
+ // Get original name
95
+ settings.getTeamNameText().then((originalName) => {
96
+ // Start editing
97
+ settings.editTeamName()
98
+ settings.typeTeamName('Changed Name That Should Not Save')
99
+ settings.cancelTeamName()
100
+
101
+ // Verify original name is preserved
102
+ settings.getTeamNameText().should('equal', originalName)
103
+ })
104
+ })
105
+
106
+ it('TEAM_EDIT_005: Cancel editing team description preserves original value', () => {
107
+ allure.severity('normal')
108
+ allure.description('Verify that canceling team description editing does not save changes')
109
+
110
+ // Get original description
111
+ settings.getTeamDescriptionText().then((originalDesc) => {
112
+ // Start editing
113
+ settings.editTeamDescription()
114
+ settings.typeTeamDescription('Changed description that should not save')
115
+ settings.cancelTeamDescription()
116
+
117
+ // Verify original description is preserved
118
+ settings.getTeamDescriptionText().should('equal', originalDesc)
119
+ })
120
+ })
121
+ })
122
+
123
+ // ============================================
124
+ // Member Cannot Edit
125
+ // ============================================
126
+
127
+ describe('Member Cannot Edit', () => {
128
+ beforeEach(() => {
129
+ cy.session('emily-member-inline-edit', () => {
130
+ cy.visit('/login')
131
+ const devKeyring = DevKeyringPOM.create()
132
+ devKeyring.validateVisible()
133
+ devKeyring.quickLoginByEmail(USERS.MEMBER)
134
+ cy.url().should('include', '/dashboard')
135
+ })
136
+ cy.visit('/dashboard/settings/team', { timeout: 60000, failOnStatusCode: false })
137
+ settings.waitForTeam()
138
+ })
139
+
140
+ it('TEAM_EDIT_010: Member cannot see edit button for team name', { tags: '@smoke' }, () => {
141
+ allure.severity('critical')
142
+ allure.description('Verify that non-owner team members cannot see edit icons for team name')
143
+
144
+ settings.assertTeamNameNotEditable()
145
+ })
146
+
147
+ it('TEAM_EDIT_011: Member cannot see edit button for team description', { tags: '@smoke' }, () => {
148
+ allure.severity('critical')
149
+ allure.description('Verify that non-owner team members cannot see edit icons for team description')
150
+
151
+ settings.assertTeamDescriptionNotEditable()
152
+ })
153
+ })
154
+
155
+ // ============================================
156
+ // Validation Tests
157
+ // ============================================
158
+
159
+ describe('Validation', () => {
160
+ beforeEach(() => {
161
+ cy.session('carlos-validation-inline-edit', () => {
162
+ cy.visit('/login')
163
+ const devKeyring = DevKeyringPOM.create()
164
+ devKeyring.validateVisible()
165
+ devKeyring.quickLoginByEmail(USERS.OWNER)
166
+ cy.url().should('include', '/dashboard')
167
+ })
168
+ cy.visit('/dashboard/settings/team', { timeout: 60000, failOnStatusCode: false })
169
+ settings.waitForTeam()
170
+ })
171
+
172
+ it('TEAM_EDIT_020: Name validation - minimum length (2 characters)', () => {
173
+ allure.severity('normal')
174
+ allure.description('Verify that team name must have at least 2 characters')
175
+
176
+ // Click edit, enter single character, try to save
177
+ settings.editTeamName()
178
+ settings.typeTeamName('A')
179
+ settings.saveTeamName()
180
+
181
+ // Should show validation error (in any locale)
182
+ cy.contains(/at least 2|mindestens 2|almeno 2|pelo menos 2|au moins 2|mínimo 2/i, { timeout: 5000 })
183
+ .should('be.visible')
184
+ })
185
+
186
+ it('TEAM_EDIT_021: Empty team name shows validation error', () => {
187
+ allure.severity('normal')
188
+ allure.description('Verify that team name cannot be empty')
189
+
190
+ // Click edit, clear input, try to save
191
+ settings.editTeamName()
192
+ cy.get(settings.selectors.teamEditNameInput).clear()
193
+ settings.saveTeamName()
194
+
195
+ // Should show validation error (in any locale)
196
+ cy.contains(/required|erforderlich|obbligatorio|obrigatório|requis/i, { timeout: 5000 })
197
+ .should('be.visible')
198
+ })
199
+
200
+ it('TEAM_EDIT_022: Empty team description is allowed', () => {
201
+ allure.severity('normal')
202
+ allure.description('Verify that team description can be empty (optional field)')
203
+
204
+ // Click edit, clear textarea, save
205
+ settings.editTeamDescription()
206
+ cy.get(settings.selectors.teamEditDescriptionTextarea).clear()
207
+ settings.saveTeamDescription()
208
+
209
+ // Wait for save to complete
210
+ cy.wait(500)
211
+
212
+ // No error should appear - description is optional
213
+ cy.get(settings.selectors.teamEditDescriptionError).should('not.exist')
214
+ })
215
+
216
+ it('TEAM_EDIT_023: Name validation - maximum length (100 characters)', () => {
217
+ allure.severity('normal')
218
+ allure.description('Verify that team name cannot exceed 100 characters')
219
+
220
+ const longName = 'A'.repeat(101) // 101 characters
221
+
222
+ settings.editTeamName()
223
+ settings.typeTeamName(longName)
224
+ settings.saveTeamName()
225
+
226
+ // Should show validation error (in any locale)
227
+ cy.contains(/maximum|maximal|massimo|máximo/i, { timeout: 5000 })
228
+ .should('be.visible')
229
+ })
230
+ })
231
+
232
+ // ============================================
233
+ // Multi-Locale Tests
234
+ // ============================================
235
+
236
+ describe('Multi-Locale Support', () => {
237
+ const locales = [
238
+ { code: 'de', name: 'German' },
239
+ { code: 'fr', name: 'French' },
240
+ { code: 'it', name: 'Italian' },
241
+ { code: 'pt', name: 'Portuguese' },
242
+ ]
243
+
244
+ locales.forEach(({ code, name }) => {
245
+ it(`TEAM_EDIT_030_${code.toUpperCase()}: Inline editing works in ${name} locale`, () => {
246
+ allure.severity('normal')
247
+ allure.description(`Verify that inline team editing displays correctly in ${name} locale`)
248
+
249
+ // Login as owner
250
+ cy.session(`carlos-locale-${code}`, () => {
251
+ cy.visit('/login')
252
+ const devKeyring = DevKeyringPOM.create()
253
+ devKeyring.validateVisible()
254
+ devKeyring.quickLoginByEmail(USERS.OWNER)
255
+ cy.url().should('include', '/dashboard')
256
+ })
257
+
258
+ // Visit with locale query param
259
+ cy.visit(`/dashboard/settings/team?locale=${code}`, { timeout: 60000, failOnStatusCode: false })
260
+ settings.waitForTeam()
261
+
262
+ // Verify edit icons are visible (selectors are locale-independent)
263
+ settings.assertTeamNameEditable()
264
+ settings.assertTeamDescriptionEditable()
265
+
266
+ // Test editing flow
267
+ const testName = `Test ${name} ${Date.now()}`
268
+ settings.editAndSaveTeamName(testName)
269
+
270
+ // Wait for save
271
+ cy.wait(500)
272
+
273
+ // Verify new name appears
274
+ settings.getTeamNameText().should('contain', testName)
275
+ })
276
+ })
277
+ })
278
+ })