@nextsparkjs/core 0.1.0-beta.39 → 0.1.0-beta.40

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 (57) hide show
  1. package/dist/styles/classes.json +1 -1
  2. package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.bdd.md +278 -0
  3. package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +22 -14
  4. package/dist/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  5. package/dist/templates/contents/themes/starter/tests/cypress/src/components/EntityForm.ts +375 -0
  6. package/dist/templates/contents/themes/starter/tests/cypress/src/components/EntityList.ts +389 -0
  7. package/dist/templates/contents/themes/starter/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  8. package/dist/templates/contents/themes/starter/tests/cypress/src/components/index.ts +13 -0
  9. package/dist/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  10. package/dist/templates/contents/themes/starter/tests/cypress/src/core/index.ts +2 -0
  11. package/dist/templates/contents/themes/starter/tests/cypress/{e2e/uat/entities/tasks → src/entities}/TasksPOM.ts +1 -1
  12. package/dist/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +10 -0
  13. package/dist/templates/contents/themes/starter/tests/cypress/src/features/BillingPOM.ts +385 -0
  14. package/dist/templates/contents/themes/starter/tests/cypress/src/features/DashboardPOM.ts +245 -0
  15. package/dist/templates/contents/themes/starter/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
  16. package/dist/templates/contents/themes/starter/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  17. package/dist/templates/contents/themes/starter/tests/cypress/src/features/SettingsPOM.ts +362 -0
  18. package/dist/templates/contents/themes/starter/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  19. package/dist/templates/contents/themes/starter/tests/cypress/src/features/index.ts +18 -0
  20. package/dist/templates/contents/themes/starter/tests/cypress/src/index.ts +88 -0
  21. package/dist/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +332 -88
  22. package/dist/templates/contents/themes/starter/tests/cypress.config.ts +4 -1
  23. package/package.json +1 -1
  24. package/scripts/test/jest-theme.mjs +7 -3
  25. package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.bdd.md +278 -0
  26. package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +22 -14
  27. package/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  28. package/templates/contents/themes/starter/tests/cypress/src/components/EntityForm.ts +375 -0
  29. package/templates/contents/themes/starter/tests/cypress/src/components/EntityList.ts +389 -0
  30. package/templates/contents/themes/starter/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  31. package/templates/contents/themes/starter/tests/cypress/src/components/index.ts +13 -0
  32. package/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  33. package/templates/contents/themes/starter/tests/cypress/src/core/index.ts +2 -0
  34. package/templates/contents/themes/starter/tests/cypress/{e2e/uat/entities/tasks → src/entities}/TasksPOM.ts +1 -1
  35. package/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +10 -0
  36. package/templates/contents/themes/starter/tests/cypress/src/features/BillingPOM.ts +385 -0
  37. package/templates/contents/themes/starter/tests/cypress/src/features/DashboardPOM.ts +245 -0
  38. package/templates/contents/themes/starter/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
  39. package/templates/contents/themes/starter/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  40. package/templates/contents/themes/starter/tests/cypress/src/features/SettingsPOM.ts +362 -0
  41. package/templates/contents/themes/starter/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  42. package/templates/contents/themes/starter/tests/cypress/src/features/index.ts +18 -0
  43. package/templates/contents/themes/starter/tests/cypress/src/index.ts +88 -0
  44. package/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +332 -88
  45. package/templates/contents/themes/starter/tests/cypress.config.ts +4 -1
  46. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  47. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  48. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  49. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
  50. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/public.cy.ts +0 -112
  51. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +0 -126
  52. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  53. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  54. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  55. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
  56. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/public.cy.ts +0 -112
  57. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +0 -126
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.39",
3
+ "version": "0.1.0-beta.40",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -47,12 +47,16 @@ console.log(`Config: ${configPath}\n`)
47
47
  const cliArgs = process.argv.slice(2)
48
48
  const args = ['--config', absoluteConfigPath, ...cliArgs]
49
49
 
50
- // Run jest from packages/core where it's installed
51
- const coreDir = resolve(process.cwd(), 'packages/core')
50
+ // Determine working directory
51
+ // In monorepo: run from packages/core where jest is installed
52
+ // In npm mode: run from project root
53
+ const cwd = isMonorepo
54
+ ? resolve(process.cwd(), 'packages/core')
55
+ : process.cwd()
52
56
 
53
57
  const child = spawn('npx', ['jest', ...args], {
54
58
  stdio: 'inherit',
55
- cwd: coreDir,
59
+ cwd,
56
60
  shell: process.platform === 'win32' // Only use shell on Windows
57
61
  })
58
62
 
@@ -0,0 +1,278 @@
1
+ ---
2
+ feature: Tasks CRUD UAT Tests
3
+ priority: high
4
+ tags: [uat, tasks, crud, entity]
5
+ grepTags: [uat, tasks, crud, TASKS_CRUD_001, TASKS_CRUD_002, TASKS_CRUD_003, TASKS_CRUD_004, TASKS_CRUD_005, TASKS_CRUD_006]
6
+ coverage: 6
7
+ ---
8
+
9
+ # Tasks CRUD UAT Tests
10
+
11
+ > Complete CRUD test suite for the Tasks entity. Demonstrates login using session helpers, creating, reading, updating, and deleting tasks, filtering and searching tasks, and using POM pattern for maintainability.
12
+
13
+ ## @test TASKS_CRUD_001: Create Task
14
+
15
+ ### Metadata
16
+ - **Priority:** High
17
+ - **Type:** UAT
18
+ - **Tags:** create, task, form
19
+ - **Grep:** `@uat` `@tasks` `@TASKS_CRUD_001`
20
+ - **Status:** Active - 3 tests
21
+
22
+ ```gherkin:en
23
+ Scenario: Create a new task with minimal data
24
+
25
+ Given I am logged in as owner
26
+ And I am on the tasks list page
27
+ When I click the add button
28
+ And I fill the title field with a unique task name
29
+ And I submit the form
30
+ Then I should be redirected to the tasks list
31
+ And the new task should appear in the list
32
+ ```
33
+
34
+ ```gherkin:es
35
+ Scenario: Crear una nueva tarea con datos minimos
36
+
37
+ Given estoy logueado como owner
38
+ And estoy en la pagina de lista de tareas
39
+ When hago clic en el boton agregar
40
+ And lleno el campo titulo con un nombre unico de tarea
41
+ And envio el formulario
42
+ Then deberia ser redirigido a la lista de tareas
43
+ And la nueva tarea deberia aparecer en la lista
44
+ ```
45
+
46
+ ### Expected Results
47
+ - Task is created successfully
48
+ - User is redirected to list
49
+ - New task appears in list with correct title
50
+
51
+ ---
52
+
53
+ ## @test TASKS_CRUD_002: Read/View Task
54
+
55
+ ### Metadata
56
+ - **Priority:** High
57
+ - **Type:** UAT
58
+ - **Tags:** read, view, task, detail
59
+ - **Grep:** `@uat` `@tasks` `@TASKS_CRUD_002`
60
+ - **Status:** Active - 3 tests
61
+
62
+ ```gherkin:en
63
+ Scenario: View task in list and detail page
64
+
65
+ Given I am logged in as owner
66
+ And a test task exists via API
67
+ When I navigate to the tasks list
68
+ Then the task should be visible in the list
69
+ When I click on the task row
70
+ Then I should be on the task detail page
71
+ And the task details should be displayed correctly
72
+ ```
73
+
74
+ ```gherkin:es
75
+ Scenario: Ver tarea en lista y pagina de detalle
76
+
77
+ Given estoy logueado como owner
78
+ And existe una tarea de prueba via API
79
+ When navego a la lista de tareas
80
+ Then la tarea deberia ser visible en la lista
81
+ When hago clic en la fila de la tarea
82
+ Then deberia estar en la pagina de detalle de la tarea
83
+ And los detalles de la tarea deberian mostrarse correctamente
84
+ ```
85
+
86
+ ### Expected Results
87
+ - Task appears in list
88
+ - Navigation to detail page works
89
+ - Task details are displayed correctly
90
+
91
+ ---
92
+
93
+ ## @test TASKS_CRUD_003: Update Task
94
+
95
+ ### Metadata
96
+ - **Priority:** High
97
+ - **Type:** UAT
98
+ - **Tags:** update, edit, task, form
99
+ - **Grep:** `@uat` `@tasks` `@TASKS_CRUD_003`
100
+ - **Status:** Active - 3 tests
101
+
102
+ ```gherkin:en
103
+ Scenario: Update task title, status, and priority
104
+
105
+ Given I am logged in as owner
106
+ And a test task exists via API
107
+ When I navigate to the task edit page
108
+ And I change the title to a new value
109
+ And I submit the form
110
+ Then the task list should show the updated title
111
+ When I navigate to edit and change the status
112
+ Then the task should reflect the new status
113
+ When I navigate to edit and change the priority
114
+ Then the task should reflect the new priority
115
+ ```
116
+
117
+ ```gherkin:es
118
+ Scenario: Actualizar titulo, estado y prioridad de tarea
119
+
120
+ Given estoy logueado como owner
121
+ And existe una tarea de prueba via API
122
+ When navego a la pagina de edicion de la tarea
123
+ And cambio el titulo a un nuevo valor
124
+ And envio el formulario
125
+ Then la lista de tareas deberia mostrar el titulo actualizado
126
+ When navego a editar y cambio el estado
127
+ Then la tarea deberia reflejar el nuevo estado
128
+ When navego a editar y cambio la prioridad
129
+ Then la tarea deberia reflejar la nueva prioridad
130
+ ```
131
+
132
+ ### Expected Results
133
+ - Title updates correctly
134
+ - Status updates correctly
135
+ - Priority updates correctly
136
+
137
+ ---
138
+
139
+ ## @test TASKS_CRUD_004: Delete Task
140
+
141
+ ### Metadata
142
+ - **Priority:** High
143
+ - **Type:** UAT
144
+ - **Tags:** delete, task, confirm
145
+ - **Grep:** `@uat` `@tasks` `@TASKS_CRUD_004`
146
+ - **Status:** Active - 2 tests
147
+
148
+ ```gherkin:en
149
+ Scenario: Delete task from detail page
150
+
151
+ Given I am logged in as owner
152
+ And a test task exists via API
153
+ When I navigate to the task detail page
154
+ And I click the delete button
155
+ And I confirm the deletion
156
+ Then I should be redirected to the tasks list
157
+ And the task should no longer appear in the list
158
+
159
+ Scenario: Cancel delete operation
160
+
161
+ Given I am logged in as owner
162
+ And a test task exists via API
163
+ When I navigate to the task detail page
164
+ And I click the delete button
165
+ And I cancel the deletion
166
+ Then I should still be on the detail page
167
+ And the task should still exist
168
+ ```
169
+
170
+ ```gherkin:es
171
+ Scenario: Eliminar tarea desde pagina de detalle
172
+
173
+ Given estoy logueado como owner
174
+ And existe una tarea de prueba via API
175
+ When navego a la pagina de detalle de la tarea
176
+ And hago clic en el boton eliminar
177
+ And confirmo la eliminacion
178
+ Then deberia ser redirigido a la lista de tareas
179
+ And la tarea ya no deberia aparecer en la lista
180
+
181
+ Scenario: Cancelar operacion de eliminacion
182
+
183
+ Given estoy logueado como owner
184
+ And existe una tarea de prueba via API
185
+ When navego a la pagina de detalle de la tarea
186
+ And hago clic en el boton eliminar
187
+ And cancelo la eliminacion
188
+ Then deberia seguir en la pagina de detalle
189
+ And la tarea deberia seguir existiendo
190
+ ```
191
+
192
+ ### Expected Results
193
+ - Delete with confirmation removes task
194
+ - Cancel delete preserves task
195
+
196
+ ---
197
+
198
+ ## @test TASKS_CRUD_005: Filter Tasks
199
+
200
+ ### Metadata
201
+ - **Priority:** Medium
202
+ - **Type:** UAT
203
+ - **Tags:** filter, tasks, status, priority
204
+ - **Grep:** `@uat` `@tasks` `@TASKS_CRUD_005`
205
+ - **Status:** Active - 3 tests
206
+
207
+ ```gherkin:en
208
+ Scenario: Filter tasks by status and priority
209
+
210
+ Given I am logged in as owner
211
+ And multiple tasks exist with different statuses and priorities
212
+ When I filter by status "todo"
213
+ Then only tasks with status "todo" should be visible
214
+ When I filter by priority "high"
215
+ Then only tasks with priority "high" should be visible
216
+ When I clear the filters
217
+ Then all tasks should be visible again
218
+ ```
219
+
220
+ ```gherkin:es
221
+ Scenario: Filtrar tareas por estado y prioridad
222
+
223
+ Given estoy logueado como owner
224
+ And existen multiples tareas con diferentes estados y prioridades
225
+ When filtro por estado "todo"
226
+ Then solo las tareas con estado "todo" deberian ser visibles
227
+ When filtro por prioridad "high"
228
+ Then solo las tareas con prioridad "high" deberian ser visibles
229
+ When limpio los filtros
230
+ Then todas las tareas deberian ser visibles nuevamente
231
+ ```
232
+
233
+ ### Expected Results
234
+ - Status filter works correctly
235
+ - Priority filter works correctly
236
+ - Clearing filters shows all tasks
237
+
238
+ ---
239
+
240
+ ## @test TASKS_CRUD_006: Search Tasks
241
+
242
+ ### Metadata
243
+ - **Priority:** Medium
244
+ - **Type:** UAT
245
+ - **Tags:** search, tasks, title
246
+ - **Grep:** `@uat` `@tasks` `@TASKS_CRUD_006`
247
+ - **Status:** Active - 3 tests
248
+
249
+ ```gherkin:en
250
+ Scenario: Search tasks by title
251
+
252
+ Given I am logged in as owner
253
+ And tasks exist with various titles
254
+ When I search for a specific term
255
+ Then only matching tasks should be visible
256
+ When I search for a non-existent term
257
+ Then no tasks should be visible
258
+ When I clear the search
259
+ Then all tasks should be visible again
260
+ ```
261
+
262
+ ```gherkin:es
263
+ Scenario: Buscar tareas por titulo
264
+
265
+ Given estoy logueado como owner
266
+ And existen tareas con varios titulos
267
+ When busco un termino especifico
268
+ Then solo las tareas coincidentes deberian ser visibles
269
+ When busco un termino que no existe
270
+ Then no deberian verse tareas
271
+ When limpio la busqueda
272
+ Then todas las tareas deberian ser visibles nuevamente
273
+ ```
274
+
275
+ ### Expected Results
276
+ - Search finds matching tasks
277
+ - Search shows empty for no matches
278
+ - Clearing search shows all tasks
@@ -10,13 +10,21 @@
10
10
  * - Filtering and searching tasks
11
11
  * - Using POM pattern for maintainability
12
12
  *
13
+ * Test IDs:
14
+ * - TASKS_CRUD_001: Create Task
15
+ * - TASKS_CRUD_002: Read/View Task
16
+ * - TASKS_CRUD_003: Update Task
17
+ * - TASKS_CRUD_004: Delete Task
18
+ * - TASKS_CRUD_005: Filter Tasks
19
+ * - TASKS_CRUD_006: Search Tasks
20
+ *
13
21
  * Run with: npx cypress run --spec "**\/tasks-crud.cy.ts"
14
22
  */
15
23
 
16
- import { TasksPOM, type TaskFormData } from './TasksPOM'
24
+ import { TasksPOM, type TaskFormData } from '../../../../src/entities/TasksPOM'
17
25
  const TaskAPIController = require('../../../api/entities/tasks/TaskAPIController')
18
26
 
19
- describe('Tasks CRUD', () => {
27
+ describe('Tasks CRUD', { tags: ['@uat', '@tasks', '@crud'] }, () => {
20
28
  const tasks = new TasksPOM()
21
29
  let createdTaskIds: string[] = []
22
30
 
@@ -51,9 +59,9 @@ describe('Tasks CRUD', () => {
51
59
  })
52
60
 
53
61
  // ============================================================
54
- // CREATE
62
+ // TASKS_CRUD_001: CREATE
55
63
  // ============================================================
56
- describe('Create Task', () => {
64
+ describe('TASKS_CRUD_001: Create Task', { tags: '@TASKS_CRUD_001' }, () => {
57
65
  it('should create a new task with minimal data', () => {
58
66
  const taskData: TaskFormData = {
59
67
  title: `Test Task ${Date.now()}`
@@ -102,9 +110,9 @@ describe('Tasks CRUD', () => {
102
110
  })
103
111
 
104
112
  // ============================================================
105
- // READ
113
+ // TASKS_CRUD_002: READ
106
114
  // ============================================================
107
- describe('Read/View Task', () => {
115
+ describe('TASKS_CRUD_002: Read/View Task', { tags: '@TASKS_CRUD_002' }, () => {
108
116
  let testTaskId: string
109
117
 
110
118
  beforeEach(() => {
@@ -153,9 +161,9 @@ describe('Tasks CRUD', () => {
153
161
  })
154
162
 
155
163
  // ============================================================
156
- // UPDATE
164
+ // TASKS_CRUD_003: UPDATE
157
165
  // ============================================================
158
- describe('Update Task', () => {
166
+ describe('TASKS_CRUD_003: Update Task', { tags: '@TASKS_CRUD_003' }, () => {
159
167
  let testTaskId: string
160
168
  let originalTitle: string
161
169
 
@@ -225,9 +233,9 @@ describe('Tasks CRUD', () => {
225
233
  })
226
234
 
227
235
  // ============================================================
228
- // DELETE
236
+ // TASKS_CRUD_004: DELETE
229
237
  // ============================================================
230
- describe('Delete Task', () => {
238
+ describe('TASKS_CRUD_004: Delete Task', { tags: '@TASKS_CRUD_004' }, () => {
231
239
  let testTaskId: string
232
240
  let taskTitle: string
233
241
 
@@ -284,9 +292,9 @@ describe('Tasks CRUD', () => {
284
292
  })
285
293
 
286
294
  // ============================================================
287
- // FILTER
295
+ // TASKS_CRUD_005: FILTER
288
296
  // ============================================================
289
- describe('Filter Tasks', () => {
297
+ describe('TASKS_CRUD_005: Filter Tasks', { tags: '@TASKS_CRUD_005' }, () => {
290
298
  beforeEach(() => {
291
299
  // Create tasks with different statuses
292
300
  const statuses = ['todo', 'in-progress', 'done']
@@ -350,9 +358,9 @@ describe('Tasks CRUD', () => {
350
358
  })
351
359
 
352
360
  // ============================================================
353
- // SEARCH
361
+ // TASKS_CRUD_006: SEARCH
354
362
  // ============================================================
355
- describe('Search Tasks', () => {
363
+ describe('TASKS_CRUD_006: Search Tasks', { tags: '@TASKS_CRUD_006' }, () => {
356
364
  const searchTerm = `Searchable${Date.now()}`
357
365
 
358
366
  beforeEach(() => {
@@ -0,0 +1,160 @@
1
+ /**
2
+ * DevKeyringPOM - Page Object Model for Development Keyring
3
+ *
4
+ * POM for the development keyring (test credential selector).
5
+ * This component allows quick switching between test users in development mode.
6
+ *
7
+ * NOTE: DevKeyring only FILLS the login form with credentials.
8
+ * You must still submit the form to complete login.
9
+ *
10
+ * IMPORTANT: Always use email-based methods (selectUserByEmail, quickLoginByEmail)
11
+ * to make tests resilient to user order changes in the DevKeyring config.
12
+ *
13
+ * @version 3.0 - Uses centralized selectors from cySelector()
14
+ */
15
+ import { BasePOM } from '../core/BasePOM'
16
+ import { cySelector } from '../selectors'
17
+
18
+ export class DevKeyringPOM extends BasePOM {
19
+ /**
20
+ * Selectors using centralized cySelector()
21
+ */
22
+ get selectors() {
23
+ return {
24
+ container: cySelector('auth.devKeyring.container'),
25
+ trigger: cySelector('auth.devKeyring.trigger'),
26
+ content: cySelector('auth.devKeyring.content'),
27
+ // Prefix selector for all user items (cySelector doesn't support prefix matching)
28
+ userItem: '[data-cy^="devkeyring-user-"]',
29
+ userByIndex: (index: number) => cySelector('auth.devKeyring.user', { index }),
30
+ // Login form selectors (used after filling credentials)
31
+ loginSubmit: cySelector('auth.login.submit'),
32
+ loginEmail: '#email',
33
+ loginPassword: '#password',
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Factory method - creates a new instance
39
+ */
40
+ static create(): DevKeyringPOM {
41
+ return new DevKeyringPOM()
42
+ }
43
+
44
+ // ============================================
45
+ // Validation Methods
46
+ // ============================================
47
+
48
+ /**
49
+ * Validate keyring container is visible
50
+ */
51
+ validateVisible(): this {
52
+ cy.get(this.selectors.container).should('be.visible')
53
+ return this
54
+ }
55
+
56
+ /**
57
+ * Validate keyring is not visible (production mode)
58
+ */
59
+ validateNotVisible(): this {
60
+ cy.get(this.selectors.container).should('not.exist')
61
+ return this
62
+ }
63
+
64
+ // ============================================
65
+ // Dropdown Methods
66
+ // ============================================
67
+
68
+ /**
69
+ * Open the keyring dropdown
70
+ */
71
+ open(): this {
72
+ cy.get(this.selectors.trigger).click()
73
+ cy.get(this.selectors.content).should('be.visible')
74
+ return this
75
+ }
76
+
77
+ /**
78
+ * Close the keyring dropdown
79
+ */
80
+ close(): this {
81
+ cy.get('body').click(0, 0)
82
+ cy.get(this.selectors.content).should('not.be.visible')
83
+ return this
84
+ }
85
+
86
+ // ============================================
87
+ // User Selection Methods
88
+ // ============================================
89
+
90
+ /**
91
+ * Select a user by email (fills the form, does NOT submit)
92
+ */
93
+ selectUserByEmail(email: string): this {
94
+ this.open()
95
+ cy.get(this.selectors.userItem).contains(email).click()
96
+ // Wait for form to be filled
97
+ cy.get(this.selectors.loginEmail).should('have.value', email)
98
+ return this
99
+ }
100
+
101
+ /**
102
+ * Submit the login form after credentials are filled
103
+ */
104
+ submitLogin(): this {
105
+ cy.get(this.selectors.loginSubmit).click()
106
+ return this
107
+ }
108
+
109
+ /**
110
+ * Quick login with a specific user by email (fills form AND submits)
111
+ * This is the preferred method for login as it's resilient to user order changes.
112
+ */
113
+ quickLoginByEmail(email: string): this {
114
+ // 1. Select user by email (opens dropdown + fills form)
115
+ this.selectUserByEmail(email)
116
+
117
+ // 2. Submit the login form
118
+ this.submitLogin()
119
+
120
+ // 3. Wait for login to complete
121
+ cy.url().should('include', '/dashboard', { timeout: 10000 })
122
+
123
+ return this
124
+ }
125
+
126
+ // ============================================
127
+ // User Validation Methods
128
+ // ============================================
129
+
130
+ /**
131
+ * Validate the number of available users
132
+ */
133
+ validateUserCount(count: number): this {
134
+ this.open()
135
+ cy.get(this.selectors.userItem).should('have.length', count)
136
+ return this
137
+ }
138
+
139
+ /**
140
+ * Validate a user exists in the keyring
141
+ */
142
+ validateUserExists(email: string): this {
143
+ this.open()
144
+ cy.get(this.selectors.userItem).contains(email).should('exist')
145
+ return this
146
+ }
147
+
148
+ /**
149
+ * Get all user emails from the keyring
150
+ */
151
+ getUserEmails(): Cypress.Chainable<string[]> {
152
+ this.open()
153
+ return cy.get(this.selectors.userItem)
154
+ .then($elements => {
155
+ return Cypress._.map($elements, el => el.innerText.trim())
156
+ })
157
+ }
158
+ }
159
+
160
+ export default DevKeyringPOM