@nextsparkjs/theme-blog 0.1.0-beta.38 → 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 CHANGED
@@ -9,8 +9,7 @@
9
9
  * - Cypress tests (via tests/cypress/src/selectors.ts)
10
10
  */
11
11
 
12
- import { createSelectorHelpers } from '@nextsparkjs/core/lib/test/selector-factory'
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/core/lib/test/selector-factory'
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.38",
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.38"
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 '../../../../../../../test/cypress/src/classes/components/entities/EntityList.js'
13
- import { EntityForm } from '../../../../src/classes/components/entities/EntityForm.js'
14
- import { loginAsBlogAuthor } from '../../../src/session-helpers'
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
- cy.visit('/login')
47
- const devKeyring = new DevKeyring()
48
- devKeyring.validateVisible()
49
- devKeyring.quickLoginByEmail(user.email)
50
- cy.url().should('include', '/dashboard')
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.visit('/dashboard')
54
- cy.url().should('include', '/dashboard')
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
- cy.visit('/login')
79
- const devKeyring = new DevKeyring()
80
- devKeyring.validateVisible()
81
- devKeyring.quickLoginByEmail(email)
82
- cy.url().should('include', '/dashboard')
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.visit('/dashboard')
86
- cy.url().should('include', '/dashboard')
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
  }
@@ -16,9 +16,6 @@ const themeRoot = path.resolve(__dirname, '..')
16
16
  const projectRoot = path.resolve(__dirname, '../../../..')
17
17
  const narrationsOutputDir = path.join(__dirname, 'cypress/videos/narrations')
18
18
 
19
- // Detect if running in npm mode (no packages/core folder) vs monorepo
20
- const isNpmMode = !fs.existsSync(path.join(projectRoot, 'packages/core'))
21
-
22
19
  // Load environment variables
23
20
  import dotenv from 'dotenv'
24
21
  dotenv.config({ path: path.join(projectRoot, '.env') })
@@ -31,22 +28,13 @@ export default defineConfig({
31
28
  // Base URL for the application
32
29
  baseUrl: `http://localhost:${port}`,
33
30
 
34
- // Spec patterns: theme tests (core tests only in monorepo)
35
- specPattern: isNpmMode
36
- ? [
37
- // npm mode: only theme tests
38
- path.join(__dirname, 'cypress/e2e/**/*.cy.{js,ts}'),
39
- ]
40
- : [
41
- // Monorepo: core tests + theme tests
42
- path.join(projectRoot, 'packages/core/tests/cypress/e2e/core/**/*.cy.{js,ts}'),
43
- path.join(__dirname, 'cypress/e2e/**/*.cy.{js,ts}'),
44
- ],
45
-
46
- // Support file (theme-local in npm mode, core in monorepo)
47
- supportFile: isNpmMode
48
- ? path.join(__dirname, 'cypress/support/e2e.ts')
49
- : 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'),
50
38
 
51
39
  // Fixtures folder (theme-specific)
52
40
  fixturesFolder: path.join(__dirname, 'cypress/fixtures'),