@nextsparkjs/theme-blog 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/README.md +170 -0
- package/tests/cypress/e2e/categories/categories-crud.cy.ts +322 -0
- package/tests/cypress/e2e/categories/categories-crud.md +73 -0
- package/tests/cypress/e2e/posts/posts-crud.cy.ts +460 -0
- package/tests/cypress/e2e/posts/posts-crud.md +115 -0
- package/tests/cypress/e2e/posts/posts-editor.cy.ts +290 -0
- package/tests/cypress/e2e/posts/posts-editor.md +139 -0
- package/tests/cypress/e2e/posts/posts-status-workflow.cy.ts +302 -0
- package/tests/cypress/e2e/posts/posts-status-workflow.md +83 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +42 -0
- package/tests/cypress/src/FeaturedImageUpload.js +131 -0
- package/tests/cypress/src/PostEditor.js +386 -0
- package/tests/cypress/src/PostsList.js +350 -0
- package/tests/cypress/src/WysiwygEditor.js +373 -0
- package/tests/cypress/src/components/EntityForm.ts +357 -0
- package/tests/cypress/src/components/EntityList.ts +360 -0
- package/tests/cypress/src/components/PostEditorPOM.ts +447 -0
- package/tests/cypress/src/components/PostsPOM.ts +362 -0
- package/tests/cypress/src/components/index.ts +18 -0
- package/tests/cypress/src/index.js +33 -0
- package/tests/cypress/src/selectors.ts +49 -0
- package/tests/cypress/src/session-helpers.ts +89 -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,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Entity Form POM
|
|
3
|
+
*
|
|
4
|
+
* Page Object Model for entity create/edit forms in Blog theme.
|
|
5
|
+
* Uses standardized data-cy selectors from entities.json.
|
|
6
|
+
*
|
|
7
|
+
* Convention: {slug}-{component}-{detail}
|
|
8
|
+
* Examples: posts-form, categories-field-name, posts-form-submit
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const postForm = EntityForm.for('posts')
|
|
12
|
+
* const categoryForm = EntityForm.for('categories')
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Import entity configs from theme
|
|
16
|
+
import entitiesConfig from '../../fixtures/entities.json'
|
|
17
|
+
|
|
18
|
+
export interface EntityConfig {
|
|
19
|
+
slug: string
|
|
20
|
+
singular: string
|
|
21
|
+
plural: string
|
|
22
|
+
tableName: string
|
|
23
|
+
fields: string[]
|
|
24
|
+
sections: string[]
|
|
25
|
+
filters: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class EntityForm {
|
|
29
|
+
protected config: EntityConfig
|
|
30
|
+
protected slug: string
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new EntityForm POM instance from entity config
|
|
34
|
+
*/
|
|
35
|
+
constructor(entityKey: string) {
|
|
36
|
+
const config = entitiesConfig.entities[entityKey as keyof typeof entitiesConfig.entities]
|
|
37
|
+
if (!config) {
|
|
38
|
+
throw new Error(`Unknown entity: ${entityKey}. Available: ${Object.keys(entitiesConfig.entities).join(', ')}`)
|
|
39
|
+
}
|
|
40
|
+
this.config = config as EntityConfig
|
|
41
|
+
this.slug = config.slug
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// STATIC FACTORY METHOD
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create an EntityForm from entity key
|
|
50
|
+
*/
|
|
51
|
+
static for(entityKey: string): EntityForm {
|
|
52
|
+
return new EntityForm(entityKey)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================
|
|
56
|
+
// DYNAMIC SELECTORS (from entities.json convention)
|
|
57
|
+
// ============================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get selectors for this entity form following the standard convention
|
|
61
|
+
*/
|
|
62
|
+
get selectors() {
|
|
63
|
+
const slug = this.slug
|
|
64
|
+
return {
|
|
65
|
+
// Page and form containers
|
|
66
|
+
page: `[data-cy="${slug}-form-page"]`,
|
|
67
|
+
form: `[data-cy="${slug}-form"]`,
|
|
68
|
+
pageTitle: '[data-cy="page-title"]',
|
|
69
|
+
|
|
70
|
+
// Buttons
|
|
71
|
+
submitButton: `[data-cy="${slug}-form-submit"]`,
|
|
72
|
+
cancelButton: `[data-cy="${slug}-form-cancel"]`,
|
|
73
|
+
|
|
74
|
+
// Sections
|
|
75
|
+
section: (sectionName: string) => `[data-cy="${slug}-section-${sectionName}"]`,
|
|
76
|
+
|
|
77
|
+
// Fields
|
|
78
|
+
field: (fieldName: string) => `[data-cy="${slug}-field-${fieldName}"]`,
|
|
79
|
+
fieldInput: (fieldName: string) => `[data-cy="${slug}-field-${fieldName}"] input`,
|
|
80
|
+
fieldTextarea: (fieldName: string) => `[data-cy="${slug}-field-${fieldName}"] textarea`,
|
|
81
|
+
fieldSelect: (fieldName: string) => `[data-cy="${slug}-field-${fieldName}"] [role="combobox"]`,
|
|
82
|
+
fieldCheckbox: (fieldName: string) => `[data-cy="${slug}-field-${fieldName}"] input[type="checkbox"]`,
|
|
83
|
+
fieldOption: (fieldName: string, value: string) => `[data-cy="${slug}-field-${fieldName}-option-${value}"]`,
|
|
84
|
+
fieldError: (fieldName: string) => `[data-cy="${slug}-field-${fieldName}-error"]`,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the entity config
|
|
90
|
+
*/
|
|
91
|
+
get entityConfig(): EntityConfig {
|
|
92
|
+
return this.config
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get available fields for this entity
|
|
97
|
+
*/
|
|
98
|
+
get fields(): string[] {
|
|
99
|
+
return this.config.fields
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get available sections for this entity
|
|
104
|
+
*/
|
|
105
|
+
get sections(): string[] {
|
|
106
|
+
return this.config.sections
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================
|
|
110
|
+
// VALIDATION METHODS
|
|
111
|
+
// ============================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate the form page is visible
|
|
115
|
+
*/
|
|
116
|
+
validatePageVisible() {
|
|
117
|
+
cy.get(this.selectors.page).should('be.visible')
|
|
118
|
+
return this
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate the form is visible
|
|
123
|
+
*/
|
|
124
|
+
validateFormVisible() {
|
|
125
|
+
cy.get(this.selectors.form).should('be.visible')
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate the page title text
|
|
131
|
+
*/
|
|
132
|
+
validatePageTitle(expectedTitle: string) {
|
|
133
|
+
cy.get(this.selectors.pageTitle).should('contain.text', expectedTitle)
|
|
134
|
+
return this
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validate a section is visible
|
|
139
|
+
*/
|
|
140
|
+
validateSectionVisible(sectionName: string) {
|
|
141
|
+
cy.get(this.selectors.section(sectionName)).should('be.visible')
|
|
142
|
+
return this
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate a field is visible
|
|
147
|
+
*/
|
|
148
|
+
validateFieldVisible(fieldName: string) {
|
|
149
|
+
cy.get(this.selectors.field(fieldName)).should('be.visible')
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validate a field has an error message
|
|
155
|
+
*/
|
|
156
|
+
validateFieldHasError(fieldName: string, errorMessage?: string) {
|
|
157
|
+
const errorSelector = this.selectors.fieldError(fieldName)
|
|
158
|
+
cy.get(errorSelector).should('be.visible')
|
|
159
|
+
if (errorMessage) {
|
|
160
|
+
cy.get(errorSelector).should('contain.text', errorMessage)
|
|
161
|
+
}
|
|
162
|
+
return this
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validate submit button is enabled
|
|
167
|
+
*/
|
|
168
|
+
validateSubmitEnabled() {
|
|
169
|
+
cy.get(this.selectors.submitButton).should('not.be.disabled')
|
|
170
|
+
return this
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validate submit button is disabled
|
|
175
|
+
*/
|
|
176
|
+
validateSubmitDisabled() {
|
|
177
|
+
cy.get(this.selectors.submitButton).should('be.disabled')
|
|
178
|
+
return this
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ============================================
|
|
182
|
+
// INPUT METHODS
|
|
183
|
+
// ============================================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Type into a text input field
|
|
187
|
+
*/
|
|
188
|
+
typeInField(fieldName: string, value: string) {
|
|
189
|
+
cy.get(this.selectors.fieldInput(fieldName)).clear().type(value)
|
|
190
|
+
return this
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Type into a textarea field
|
|
195
|
+
*/
|
|
196
|
+
typeInTextarea(fieldName: string, value: string) {
|
|
197
|
+
cy.get(this.selectors.fieldTextarea(fieldName)).clear().type(value)
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Clear a text input field
|
|
203
|
+
*/
|
|
204
|
+
clearField(fieldName: string) {
|
|
205
|
+
cy.get(this.selectors.fieldInput(fieldName)).clear()
|
|
206
|
+
return this
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Select an option from a select/combobox field
|
|
211
|
+
*/
|
|
212
|
+
selectOption(fieldName: string, optionValue: string) {
|
|
213
|
+
cy.get(this.selectors.fieldSelect(fieldName)).click()
|
|
214
|
+
cy.get(this.selectors.fieldOption(fieldName, optionValue)).click()
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check a checkbox field
|
|
220
|
+
*/
|
|
221
|
+
checkField(fieldName: string) {
|
|
222
|
+
cy.get(this.selectors.fieldCheckbox(fieldName)).check()
|
|
223
|
+
return this
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Uncheck a checkbox field
|
|
228
|
+
*/
|
|
229
|
+
uncheckField(fieldName: string) {
|
|
230
|
+
cy.get(this.selectors.fieldCheckbox(fieldName)).uncheck()
|
|
231
|
+
return this
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Fill a date input field
|
|
236
|
+
*/
|
|
237
|
+
fillDate(fieldName: string, dateString: string) {
|
|
238
|
+
cy.get(this.selectors.fieldInput(fieldName)).clear().type(dateString)
|
|
239
|
+
return this
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ============================================
|
|
243
|
+
// FORM SUBMISSION METHODS
|
|
244
|
+
// ============================================
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Click the submit button
|
|
248
|
+
*/
|
|
249
|
+
submit() {
|
|
250
|
+
cy.get(this.selectors.submitButton).click()
|
|
251
|
+
return this
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Click the cancel button
|
|
256
|
+
*/
|
|
257
|
+
cancel() {
|
|
258
|
+
cy.get(this.selectors.cancelButton).click()
|
|
259
|
+
return this
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================
|
|
263
|
+
// BULK FILL METHODS
|
|
264
|
+
// ============================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Fill multiple fields at once
|
|
268
|
+
*/
|
|
269
|
+
fillForm(data: Record<string, string | boolean>) {
|
|
270
|
+
Object.entries(data).forEach(([fieldName, value]) => {
|
|
271
|
+
if (typeof value === 'boolean') {
|
|
272
|
+
if (value) {
|
|
273
|
+
this.checkField(fieldName)
|
|
274
|
+
} else {
|
|
275
|
+
this.uncheckField(fieldName)
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Try input first, then textarea
|
|
279
|
+
cy.get(this.selectors.field(fieldName)).then($field => {
|
|
280
|
+
const hasInput = $field.find('input:not([type="checkbox"])').length > 0
|
|
281
|
+
const hasTextarea = $field.find('textarea').length > 0
|
|
282
|
+
const hasSelect = $field.find('[role="combobox"]').length > 0
|
|
283
|
+
|
|
284
|
+
if (hasInput) {
|
|
285
|
+
this.typeInField(fieldName, value)
|
|
286
|
+
} else if (hasTextarea) {
|
|
287
|
+
this.typeInTextarea(fieldName, value)
|
|
288
|
+
} else if (hasSelect) {
|
|
289
|
+
this.selectOption(fieldName, value)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
return this
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ============================================
|
|
298
|
+
// NAVIGATION METHODS
|
|
299
|
+
// ============================================
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Visit the create form page
|
|
303
|
+
*/
|
|
304
|
+
visitCreate() {
|
|
305
|
+
cy.visit(`/dashboard/${this.slug}/create`)
|
|
306
|
+
this.validatePageVisible()
|
|
307
|
+
return this
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Visit the edit form page
|
|
312
|
+
*/
|
|
313
|
+
visitEdit(id: string) {
|
|
314
|
+
cy.visit(`/dashboard/${this.slug}/${id}/edit`)
|
|
315
|
+
this.validatePageVisible()
|
|
316
|
+
return this
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Wait for form to be ready (all fields loaded)
|
|
321
|
+
*/
|
|
322
|
+
waitForFormReady() {
|
|
323
|
+
cy.get(this.selectors.form).should('be.visible')
|
|
324
|
+
cy.get(this.selectors.submitButton).should('be.visible')
|
|
325
|
+
return this
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ============================================
|
|
329
|
+
// ASSERTION HELPERS
|
|
330
|
+
// ============================================
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Assert field has specific value
|
|
334
|
+
*/
|
|
335
|
+
assertFieldValue(fieldName: string, expectedValue: string) {
|
|
336
|
+
cy.get(this.selectors.fieldInput(fieldName)).should('have.value', expectedValue)
|
|
337
|
+
return this
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Assert textarea has specific value
|
|
342
|
+
*/
|
|
343
|
+
assertTextareaValue(fieldName: string, expectedValue: string) {
|
|
344
|
+
cy.get(this.selectors.fieldTextarea(fieldName)).should('have.value', expectedValue)
|
|
345
|
+
return this
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Assert select displays specific option
|
|
350
|
+
*/
|
|
351
|
+
assertSelectValue(fieldName: string, expectedText: string) {
|
|
352
|
+
cy.get(this.selectors.fieldSelect(fieldName)).should('contain.text', expectedText)
|
|
353
|
+
return this
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export default EntityForm
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Entity List POM
|
|
3
|
+
*
|
|
4
|
+
* Page Object Model for entity list pages in Blog theme.
|
|
5
|
+
* Uses standardized data-cy selectors from entities.json.
|
|
6
|
+
*
|
|
7
|
+
* Convention: {slug}-{component}-{detail}
|
|
8
|
+
* Examples: posts-table, categories-create-btn, posts-row-{id}
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const postsList = EntityList.for('posts')
|
|
12
|
+
* const categoriesList = EntityList.for('categories')
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Import entity configs from theme
|
|
16
|
+
import entitiesConfig from '../../fixtures/entities.json'
|
|
17
|
+
|
|
18
|
+
export interface EntityConfig {
|
|
19
|
+
slug: string
|
|
20
|
+
singular: string
|
|
21
|
+
plural: string
|
|
22
|
+
tableName: string
|
|
23
|
+
fields: string[]
|
|
24
|
+
sections: string[]
|
|
25
|
+
filters: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class EntityList {
|
|
29
|
+
protected config: EntityConfig
|
|
30
|
+
protected slug: string
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new EntityList POM instance from entity config
|
|
34
|
+
*/
|
|
35
|
+
constructor(entityKey: string) {
|
|
36
|
+
const config = entitiesConfig.entities[entityKey as keyof typeof entitiesConfig.entities]
|
|
37
|
+
if (!config) {
|
|
38
|
+
throw new Error(`Unknown entity: ${entityKey}. Available: ${Object.keys(entitiesConfig.entities).join(', ')}`)
|
|
39
|
+
}
|
|
40
|
+
this.config = config as EntityConfig
|
|
41
|
+
this.slug = config.slug
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// STATIC FACTORY METHOD
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create an EntityList from entity key
|
|
50
|
+
*/
|
|
51
|
+
static for(entityKey: string): EntityList {
|
|
52
|
+
return new EntityList(entityKey)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================
|
|
56
|
+
// DYNAMIC SELECTORS (from entities.json convention)
|
|
57
|
+
// ============================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get selectors for this entity following the standard convention
|
|
61
|
+
*/
|
|
62
|
+
get selectors() {
|
|
63
|
+
const slug = this.slug
|
|
64
|
+
return {
|
|
65
|
+
// Page elements
|
|
66
|
+
page: `[data-cy="${slug}-page"]`,
|
|
67
|
+
pageTitle: '[data-cy="page-title"]',
|
|
68
|
+
|
|
69
|
+
// Table
|
|
70
|
+
table: `[data-cy="${slug}-table"]`,
|
|
71
|
+
|
|
72
|
+
// Create button
|
|
73
|
+
createButton: `[data-cy="${slug}-create-btn"]`,
|
|
74
|
+
|
|
75
|
+
// Search
|
|
76
|
+
search: `[data-cy="${slug}-search"]`,
|
|
77
|
+
searchInput: `[data-cy="${slug}-search-input"]`,
|
|
78
|
+
|
|
79
|
+
// Filters
|
|
80
|
+
filter: (fieldName: string) => `[data-cy="${slug}-filter-${fieldName}"]`,
|
|
81
|
+
filterTrigger: (fieldName: string) => `[data-cy="${slug}-filter-${fieldName}-trigger"]`,
|
|
82
|
+
filterOption: (fieldName: string, value: string) => `[data-cy="${slug}-filter-${fieldName}-option-${value}"]`,
|
|
83
|
+
|
|
84
|
+
// Rows
|
|
85
|
+
row: (id: string) => `[data-cy="${slug}-row-${id}"]`,
|
|
86
|
+
rowGeneric: `[data-cy^="${slug}-row-"]`,
|
|
87
|
+
|
|
88
|
+
// Cards (for grid view)
|
|
89
|
+
card: (id: string) => `[data-cy="${slug}-card-${id}"]`,
|
|
90
|
+
cardGeneric: `[data-cy^="${slug}-card-"]`,
|
|
91
|
+
|
|
92
|
+
// Actions
|
|
93
|
+
actionEdit: (id: string) => `[data-cy="${slug}-action-edit-${id}"]`,
|
|
94
|
+
actionDelete: (id: string) => `[data-cy="${slug}-action-delete-${id}"]`,
|
|
95
|
+
actionView: (id: string) => `[data-cy="${slug}-action-view-${id}"]`,
|
|
96
|
+
actionsDropdown: (id: string) => `[data-cy="${slug}-actions-${id}"]`,
|
|
97
|
+
actionsTrigger: (id: string) => `[data-cy="${slug}-actions-trigger-${id}"]`,
|
|
98
|
+
|
|
99
|
+
// Pagination
|
|
100
|
+
pagination: `[data-cy="${slug}-pagination"]`,
|
|
101
|
+
paginationPrev: `[data-cy="${slug}-pagination-prev"]`,
|
|
102
|
+
paginationNext: `[data-cy="${slug}-pagination-next"]`,
|
|
103
|
+
|
|
104
|
+
// Bulk actions
|
|
105
|
+
bulkActions: `[data-cy="${slug}-bulk-actions"]`,
|
|
106
|
+
|
|
107
|
+
// Empty state
|
|
108
|
+
emptyState: `[data-cy="${slug}-empty"]`,
|
|
109
|
+
|
|
110
|
+
// Dialogs
|
|
111
|
+
confirmDelete: `[data-cy="${slug}-confirm-delete"]`,
|
|
112
|
+
confirmDeleteBtn: `[data-cy="${slug}-confirm-delete-btn"]`,
|
|
113
|
+
cancelDeleteBtn: `[data-cy="${slug}-cancel-delete-btn"]`,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the entity config
|
|
119
|
+
*/
|
|
120
|
+
get entityConfig(): EntityConfig {
|
|
121
|
+
return this.config
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================
|
|
125
|
+
// VALIDATION METHODS
|
|
126
|
+
// ============================================
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validate the list page is visible
|
|
130
|
+
*/
|
|
131
|
+
validatePageVisible() {
|
|
132
|
+
cy.get(this.selectors.page).should('be.visible')
|
|
133
|
+
return this
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validate the table is visible
|
|
138
|
+
*/
|
|
139
|
+
validateTableVisible() {
|
|
140
|
+
cy.get(this.selectors.table).should('be.visible')
|
|
141
|
+
return this
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate the page title text
|
|
146
|
+
*/
|
|
147
|
+
validatePageTitle(expectedTitle: string) {
|
|
148
|
+
cy.get(this.selectors.pageTitle).should('contain.text', expectedTitle)
|
|
149
|
+
return this
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate the create button is visible
|
|
154
|
+
*/
|
|
155
|
+
validateCreateButtonVisible() {
|
|
156
|
+
cy.get(this.selectors.createButton).should('be.visible')
|
|
157
|
+
return this
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate the table has rows
|
|
162
|
+
*/
|
|
163
|
+
validateTableHasRows() {
|
|
164
|
+
cy.get(this.selectors.table).find('tbody tr').should('have.length.at.least', 1)
|
|
165
|
+
return this
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Validate the table is empty
|
|
170
|
+
*/
|
|
171
|
+
validateTableEmpty() {
|
|
172
|
+
cy.get(this.selectors.table).find('tbody tr').should('have.length', 0)
|
|
173
|
+
return this
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================
|
|
177
|
+
// INTERACTION METHODS
|
|
178
|
+
// ============================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Click the create button
|
|
182
|
+
*/
|
|
183
|
+
clickCreate() {
|
|
184
|
+
cy.get(this.selectors.createButton).click()
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Search for a term
|
|
190
|
+
*/
|
|
191
|
+
search(term: string) {
|
|
192
|
+
cy.get(this.selectors.searchInput).clear().type(term)
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Clear the search input
|
|
198
|
+
*/
|
|
199
|
+
clearSearch() {
|
|
200
|
+
cy.get(this.selectors.searchInput).clear()
|
|
201
|
+
return this
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Select a filter option
|
|
206
|
+
*/
|
|
207
|
+
selectFilter(fieldName: string, value: string) {
|
|
208
|
+
cy.get(this.selectors.filterTrigger(fieldName)).click()
|
|
209
|
+
cy.get(this.selectors.filterOption(fieldName, value)).click()
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Click on a table row by index (0-based)
|
|
215
|
+
*/
|
|
216
|
+
clickRowByIndex(index: number) {
|
|
217
|
+
cy.get(this.selectors.table).find('tbody tr').eq(index).click()
|
|
218
|
+
return this
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Click on a table row by text content
|
|
223
|
+
*/
|
|
224
|
+
clickRowByText(text: string) {
|
|
225
|
+
cy.get(this.selectors.table).find('tbody tr').contains(text).click()
|
|
226
|
+
return this
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Click on a specific row by ID
|
|
231
|
+
*/
|
|
232
|
+
clickRowById(id: string) {
|
|
233
|
+
cy.get(this.selectors.row(id)).click()
|
|
234
|
+
return this
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Click edit action for a row
|
|
239
|
+
*/
|
|
240
|
+
clickEditAction(id: string) {
|
|
241
|
+
cy.get(this.selectors.actionEdit(id)).click()
|
|
242
|
+
return this
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Click delete action for a row
|
|
247
|
+
*/
|
|
248
|
+
clickDeleteAction(id: string) {
|
|
249
|
+
cy.get(this.selectors.actionDelete(id)).click()
|
|
250
|
+
return this
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Open actions dropdown for a row
|
|
255
|
+
*/
|
|
256
|
+
openActionsDropdown(id: string) {
|
|
257
|
+
cy.get(this.selectors.actionsTrigger(id)).click()
|
|
258
|
+
return this
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ============================================
|
|
262
|
+
// PAGINATION METHODS
|
|
263
|
+
// ============================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Go to next page
|
|
267
|
+
*/
|
|
268
|
+
nextPage() {
|
|
269
|
+
cy.get(this.selectors.paginationNext).click()
|
|
270
|
+
return this
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Go to previous page
|
|
275
|
+
*/
|
|
276
|
+
previousPage() {
|
|
277
|
+
cy.get(this.selectors.paginationPrev).click()
|
|
278
|
+
return this
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ============================================
|
|
282
|
+
// BULK ACTIONS METHODS
|
|
283
|
+
// ============================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Select all rows using the header checkbox
|
|
287
|
+
*/
|
|
288
|
+
selectAll() {
|
|
289
|
+
cy.get(this.selectors.table).find('thead input[type="checkbox"]').check()
|
|
290
|
+
return this
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Deselect all rows
|
|
295
|
+
*/
|
|
296
|
+
deselectAll() {
|
|
297
|
+
cy.get(this.selectors.table).find('thead input[type="checkbox"]').uncheck()
|
|
298
|
+
return this
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Select a row by index
|
|
303
|
+
*/
|
|
304
|
+
selectRowByIndex(index: number) {
|
|
305
|
+
cy.get(this.selectors.table).find('tbody tr').eq(index).find('input[type="checkbox"]').check()
|
|
306
|
+
return this
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Validate bulk actions panel is visible
|
|
311
|
+
*/
|
|
312
|
+
validateBulkActionsVisible() {
|
|
313
|
+
cy.get(this.selectors.bulkActions).should('be.visible')
|
|
314
|
+
return this
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================
|
|
318
|
+
// DELETE CONFIRMATION
|
|
319
|
+
// ============================================
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Confirm deletion in dialog
|
|
323
|
+
*/
|
|
324
|
+
confirmDelete() {
|
|
325
|
+
cy.get(this.selectors.confirmDeleteBtn).click()
|
|
326
|
+
return this
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Cancel deletion in dialog
|
|
331
|
+
*/
|
|
332
|
+
cancelDelete() {
|
|
333
|
+
cy.get(this.selectors.cancelDeleteBtn).click()
|
|
334
|
+
return this
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================
|
|
338
|
+
// WAIT METHODS
|
|
339
|
+
// ============================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Wait for the table to load
|
|
343
|
+
*/
|
|
344
|
+
waitForTableLoad() {
|
|
345
|
+
cy.get(this.selectors.table).should('exist')
|
|
346
|
+
cy.get(this.selectors.page).find('[data-loading]').should('not.exist')
|
|
347
|
+
return this
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Wait for page load
|
|
352
|
+
*/
|
|
353
|
+
waitForPageLoad() {
|
|
354
|
+
cy.url().should('include', `/dashboard/${this.slug}`)
|
|
355
|
+
cy.get(this.selectors.page).should('be.visible')
|
|
356
|
+
return this
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export default EntityList
|