@nextsparkjs/theme-blog 0.1.0-beta.37 → 0.1.0-beta.39
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/lib/selectors.ts +2 -3
- package/package.json +3 -2
- package/tests/cypress/e2e/categories/categories-crud.cy.ts +3 -3
- package/tests/cypress/src/components/EntityForm.ts +21 -0
- package/tests/cypress/src/components/EntityList.ts +18 -0
- package/tests/cypress/src/session-helpers.ts +78 -16
- package/tests/cypress.config.ts +8 -23
package/lib/selectors.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* - Cypress tests (via tests/cypress/src/selectors.ts)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { createSelectorHelpers } from '@nextsparkjs/
|
|
13
|
-
import { CORE_SELECTORS } from '@nextsparkjs/core/lib/test/core-selectors'
|
|
12
|
+
import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/testing/selectors'
|
|
14
13
|
|
|
15
14
|
// =============================================================================
|
|
16
15
|
// BLOCK SELECTORS
|
|
@@ -175,5 +174,5 @@ export const entitySelectors = helpers.entitySelectors
|
|
|
175
174
|
export type ThemeSelectorsType = typeof THEME_SELECTORS
|
|
176
175
|
export type BlockSelectorsType = typeof BLOCK_SELECTORS
|
|
177
176
|
export type EntitySelectorsType = typeof ENTITY_SELECTORS
|
|
178
|
-
export type { Replacements } from '@nextsparkjs/
|
|
177
|
+
export type { Replacements } from '@nextsparkjs/testing/selectors'
|
|
179
178
|
export { CORE_SELECTORS }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/theme-blog",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.39",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./config/theme.config.ts",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"react": "^19.0.0",
|
|
14
14
|
"react-dom": "^19.0.0",
|
|
15
15
|
"zod": "^4.0.0",
|
|
16
|
-
"@nextsparkjs/core": "0.1.0-beta.
|
|
16
|
+
"@nextsparkjs/core": "0.1.0-beta.39",
|
|
17
|
+
"@nextsparkjs/testing": "0.1.0-beta.39"
|
|
17
18
|
},
|
|
18
19
|
"nextspark": {
|
|
19
20
|
"type": "theme",
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* Theme Mode: single-user (isolated blogs, no team collaboration)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { EntityList } from '
|
|
13
|
-
import { EntityForm } from '
|
|
14
|
-
import { loginAsBlogAuthor } from '
|
|
12
|
+
import { EntityList } from '../../src/components/EntityList'
|
|
13
|
+
import { EntityForm } from '../../src/components/EntityForm'
|
|
14
|
+
import { loginAsBlogAuthor } from '../../src/session-helpers'
|
|
15
15
|
|
|
16
16
|
describe('Categories CRUD - Blog Author (Full Access)', () => {
|
|
17
17
|
const categoryList = new EntityList('categories')
|
|
@@ -239,6 +239,27 @@ export class EntityForm {
|
|
|
239
239
|
return this
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Fill a field (auto-detects input or textarea)
|
|
244
|
+
* Alias for typeInField that also handles textareas
|
|
245
|
+
*/
|
|
246
|
+
fillField(fieldName: string, value: string) {
|
|
247
|
+
cy.get(this.selectors.field(fieldName)).then($field => {
|
|
248
|
+
const $input = $field.find('input')
|
|
249
|
+
const $textarea = $field.find('textarea')
|
|
250
|
+
|
|
251
|
+
if ($input.length > 0) {
|
|
252
|
+
cy.wrap($input).clear().type(value)
|
|
253
|
+
} else if ($textarea.length > 0) {
|
|
254
|
+
cy.wrap($textarea).clear().type(value)
|
|
255
|
+
} else {
|
|
256
|
+
// Fallback to trying input selector directly
|
|
257
|
+
cy.get(this.selectors.fieldInput(fieldName)).clear().type(value)
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
return this
|
|
261
|
+
}
|
|
262
|
+
|
|
242
263
|
// ============================================
|
|
243
264
|
// FORM SUBMISSION METHODS
|
|
244
265
|
// ============================================
|
|
@@ -334,6 +334,24 @@ export class EntityList {
|
|
|
334
334
|
return this
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
// ============================================
|
|
338
|
+
// ALIASES FOR COMPATIBILITY
|
|
339
|
+
// ============================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Alias for validateTableVisible
|
|
343
|
+
*/
|
|
344
|
+
validateListVisible() {
|
|
345
|
+
return this.validateTableVisible()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Alias for clickCreate
|
|
350
|
+
*/
|
|
351
|
+
clickAdd() {
|
|
352
|
+
return this.clickCreate()
|
|
353
|
+
}
|
|
354
|
+
|
|
337
355
|
// ============================================
|
|
338
356
|
// WAIT METHODS
|
|
339
357
|
// ============================================
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Isolated login helpers for blog theme tests.
|
|
5
5
|
* Uses cy.session() for cached authentication sessions.
|
|
6
|
+
* Uses API-based login for faster and more stable authentication.
|
|
6
7
|
*
|
|
7
8
|
* Theme Mode: single-user (isolated blogs, no team collaboration)
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
import { DevKeyring } from '../../../../../../test/cypress/src/classes/components/auth/DevKeyring.js'
|
|
11
|
-
|
|
12
11
|
/**
|
|
13
12
|
* Blog theme test users
|
|
14
13
|
* Each user owns their own individual blog
|
|
@@ -33,6 +32,59 @@ export const BLOG_USERS = {
|
|
|
33
32
|
|
|
34
33
|
export type BlogAuthor = keyof typeof BLOG_USERS
|
|
35
34
|
|
|
35
|
+
// Default test password
|
|
36
|
+
const TEST_PASSWORD = Cypress.env('TEST_PASSWORD') || 'Test1234'
|
|
37
|
+
const API_TIMEOUT = 60000
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* API login helper
|
|
41
|
+
*/
|
|
42
|
+
function apiLogin(email: string, password: string = TEST_PASSWORD): Cypress.Chainable<boolean> {
|
|
43
|
+
return cy.request({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
url: '/api/auth/sign-in/email',
|
|
46
|
+
body: { email, password },
|
|
47
|
+
timeout: API_TIMEOUT,
|
|
48
|
+
failOnStatusCode: false
|
|
49
|
+
}).then((response) => {
|
|
50
|
+
if (response.status === 200) {
|
|
51
|
+
return true
|
|
52
|
+
} else {
|
|
53
|
+
cy.log(`⚠️ API login failed with status ${response.status}`)
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Setup team context after login (blog theme uses personal teams)
|
|
61
|
+
*/
|
|
62
|
+
function setupTeamContext() {
|
|
63
|
+
cy.request({
|
|
64
|
+
method: 'GET',
|
|
65
|
+
url: '/api/v1/teams',
|
|
66
|
+
timeout: API_TIMEOUT,
|
|
67
|
+
failOnStatusCode: false
|
|
68
|
+
}).then((teamsResponse) => {
|
|
69
|
+
if (teamsResponse.status === 200 && teamsResponse.body?.data?.length > 0) {
|
|
70
|
+
const teams = teamsResponse.body.data
|
|
71
|
+
const selectedTeam = teams[0]
|
|
72
|
+
const teamId = selectedTeam.id
|
|
73
|
+
cy.log(`✅ Setting active team: ${selectedTeam.name} (${teamId})`)
|
|
74
|
+
cy.window().then((win) => {
|
|
75
|
+
win.localStorage.setItem('activeTeamId', teamId)
|
|
76
|
+
})
|
|
77
|
+
cy.request({
|
|
78
|
+
method: 'POST',
|
|
79
|
+
url: '/api/v1/teams/switch',
|
|
80
|
+
body: { teamId },
|
|
81
|
+
timeout: API_TIMEOUT,
|
|
82
|
+
failOnStatusCode: false
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
36
88
|
/**
|
|
37
89
|
* Login as a blog author
|
|
38
90
|
* Session is cached and reused across tests for optimal performance
|
|
@@ -43,15 +95,20 @@ export function loginAsBlogAuthor(author: BlogAuthor = 'MARCOS') {
|
|
|
43
95
|
const user = BLOG_USERS[author]
|
|
44
96
|
|
|
45
97
|
cy.session(`blog-author-${author.toLowerCase()}`, () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
98
|
+
apiLogin(user.email, user.password).then((success) => {
|
|
99
|
+
if (success) {
|
|
100
|
+
cy.visit('/dashboard', { timeout: 60000 })
|
|
101
|
+
}
|
|
102
|
+
cy.url().should('include', '/dashboard')
|
|
103
|
+
setupTeamContext()
|
|
104
|
+
})
|
|
51
105
|
}, {
|
|
52
106
|
validate: () => {
|
|
53
|
-
cy.
|
|
54
|
-
|
|
107
|
+
cy.request({
|
|
108
|
+
url: '/api/auth/get-session',
|
|
109
|
+
timeout: API_TIMEOUT,
|
|
110
|
+
failOnStatusCode: false
|
|
111
|
+
}).its('status').should('eq', 200)
|
|
55
112
|
}
|
|
56
113
|
})
|
|
57
114
|
}
|
|
@@ -75,15 +132,20 @@ export function loginWithBlogEmail(email: string, sessionName?: string) {
|
|
|
75
132
|
const name = sessionName || `blog-session-${email.split('@')[0]}`
|
|
76
133
|
|
|
77
134
|
cy.session(name, () => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
135
|
+
apiLogin(email).then((success) => {
|
|
136
|
+
if (success) {
|
|
137
|
+
cy.visit('/dashboard', { timeout: 60000 })
|
|
138
|
+
}
|
|
139
|
+
cy.url().should('include', '/dashboard')
|
|
140
|
+
setupTeamContext()
|
|
141
|
+
})
|
|
83
142
|
}, {
|
|
84
143
|
validate: () => {
|
|
85
|
-
cy.
|
|
86
|
-
|
|
144
|
+
cy.request({
|
|
145
|
+
url: '/api/auth/get-session',
|
|
146
|
+
timeout: API_TIMEOUT,
|
|
147
|
+
failOnStatusCode: false
|
|
148
|
+
}).its('status').should('eq', 200)
|
|
87
149
|
}
|
|
88
150
|
})
|
|
89
151
|
}
|
package/tests/cypress.config.ts
CHANGED
|
@@ -8,20 +8,14 @@
|
|
|
8
8
|
import { defineConfig } from 'cypress'
|
|
9
9
|
import path from 'path'
|
|
10
10
|
import fs from 'fs'
|
|
11
|
-
import { fileURLToPath } from 'url'
|
|
12
11
|
|
|
13
|
-
//
|
|
14
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
-
const __dirname = path.dirname(__filename)
|
|
12
|
+
// __dirname works natively with CommonJS module resolution (tsconfig.cypress.json)
|
|
16
13
|
|
|
17
14
|
// Paths relative to this config file
|
|
18
15
|
const themeRoot = path.resolve(__dirname, '..')
|
|
19
16
|
const projectRoot = path.resolve(__dirname, '../../../..')
|
|
20
17
|
const narrationsOutputDir = path.join(__dirname, 'cypress/videos/narrations')
|
|
21
18
|
|
|
22
|
-
// Detect if running in npm mode (no packages/core folder) vs monorepo
|
|
23
|
-
const isNpmMode = !fs.existsSync(path.join(projectRoot, 'packages/core'))
|
|
24
|
-
|
|
25
19
|
// Load environment variables
|
|
26
20
|
import dotenv from 'dotenv'
|
|
27
21
|
dotenv.config({ path: path.join(projectRoot, '.env') })
|
|
@@ -34,22 +28,13 @@ export default defineConfig({
|
|
|
34
28
|
// Base URL for the application
|
|
35
29
|
baseUrl: `http://localhost:${port}`,
|
|
36
30
|
|
|
37
|
-
// Spec patterns: theme tests
|
|
38
|
-
specPattern:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Monorepo: core tests + theme tests
|
|
45
|
-
path.join(projectRoot, 'packages/core/tests/cypress/e2e/core/**/*.cy.{js,ts}'),
|
|
46
|
-
path.join(__dirname, 'cypress/e2e/**/*.cy.{js,ts}'),
|
|
47
|
-
],
|
|
48
|
-
|
|
49
|
-
// Support file (theme-local in npm mode, core in monorepo)
|
|
50
|
-
supportFile: isNpmMode
|
|
51
|
-
? path.join(__dirname, 'cypress/support/e2e.ts')
|
|
52
|
-
: path.join(projectRoot, 'packages/core/tests/cypress/support/e2e.ts'),
|
|
31
|
+
// Spec patterns: theme tests only
|
|
32
|
+
specPattern: [
|
|
33
|
+
path.join(__dirname, 'cypress/e2e/**/*.cy.{js,ts}'),
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
// Support file (always theme-local)
|
|
37
|
+
supportFile: path.join(__dirname, 'cypress/support/e2e.ts'),
|
|
53
38
|
|
|
54
39
|
// Fixtures folder (theme-specific)
|
|
55
40
|
fixturesFolder: path.join(__dirname, 'cypress/fixtures'),
|