@nextsparkjs/theme-productivity 0.1.0-beta.17 → 0.1.0-beta.171
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/config/app.config.ts +4 -5
- package/config/billing.config.ts +4 -7
- package/config/dashboard.config.ts +13 -0
- package/config/permissions.config.ts +11 -0
- package/entities/boards/api/docs.md +131 -0
- package/entities/boards/api/presets.ts +113 -0
- package/entities/cards/api/docs.md +162 -0
- package/entities/cards/api/presets.ts +172 -0
- package/entities/lists/api/docs.md +143 -0
- package/entities/lists/api/presets.ts +81 -0
- package/lib/selectors.ts +2 -3
- package/nextsparkjs-theme-productivity-0.1.0-beta.137.tgz +0 -0
- package/package.json +4 -3
- package/styles/globals.css +37 -1
- package/tests/cypress/e2e/ui/boards/boards-owner.cy.ts +349 -0
- package/tests/cypress/e2e/ui/cards/cards-modal.cy.ts +369 -0
- package/tests/cypress/e2e/ui/kanban/kanban-cards.cy.ts +280 -0
- package/tests/cypress/e2e/ui/kanban/kanban-columns.cy.ts +243 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +60 -0
- package/tests/cypress/src/components/BoardsPOM.ts +353 -0
- package/tests/cypress/src/components/CardsPOM.ts +383 -0
- package/tests/cypress/src/components/KanbanPOM.ts +399 -0
- package/tests/cypress/src/components/index.ts +9 -0
- package/tests/cypress/src/controllers/BoardsAPIController.js +302 -0
- package/tests/cypress/src/controllers/CardsAPIController.js +406 -0
- package/tests/cypress/src/controllers/ListsAPIController.js +299 -0
- package/tests/cypress/src/index.ts +25 -0
- package/tests/cypress/src/selectors.ts +50 -0
- package/tests/cypress/src/session-helpers.ts +176 -0
- package/tests/cypress/support/e2e.ts +90 -0
- package/tests/cypress.config.ts +154 -0
- package/tests/jest/__mocks__/jose.js +22 -0
- package/tests/jest/__mocks__/next-server.js +56 -0
- package/tests/jest/jest.config.cjs +131 -0
- package/tests/jest/setup.ts +170 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Productivity Theme - Cypress Test Classes
|
|
3
|
+
*
|
|
4
|
+
* Exports all POMs, controllers, and helpers for the productivity theme tests.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Session helpers
|
|
8
|
+
export {
|
|
9
|
+
PRODUCTIVITY_USERS,
|
|
10
|
+
loginAsProductivityOwner,
|
|
11
|
+
loginAsProductivityAdmin,
|
|
12
|
+
loginAsProductivityMember,
|
|
13
|
+
loginAsProductivityMemberMarketing,
|
|
14
|
+
loginAsOwner,
|
|
15
|
+
} from './session-helpers'
|
|
16
|
+
|
|
17
|
+
// POMs
|
|
18
|
+
export { BoardsPOM } from './components/BoardsPOM'
|
|
19
|
+
export { KanbanPOM } from './components/KanbanPOM'
|
|
20
|
+
export { CardsPOM } from './components/CardsPOM'
|
|
21
|
+
|
|
22
|
+
// API Controllers (JS - require syntax)
|
|
23
|
+
// Import with: const BoardsAPIController = require('./controllers/BoardsAPIController')
|
|
24
|
+
// const ListsAPIController = require('./controllers/ListsAPIController')
|
|
25
|
+
// const CardsAPIController = require('./controllers/CardsAPIController')
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Selectors for Cypress Tests
|
|
3
|
+
*
|
|
4
|
+
* This file re-exports from the main selectors in lib/.
|
|
5
|
+
* The lib/selectors.ts is the source of truth, placed there so
|
|
6
|
+
* block components can import it (tests/ is excluded from TypeScript).
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - Core selectors: `core/lib/test/core-selectors.ts`
|
|
10
|
+
* - Theme selectors (source): `lib/selectors.ts`
|
|
11
|
+
* - Theme selectors (tests): This file (re-exports)
|
|
12
|
+
*
|
|
13
|
+
* @example POM usage:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { cySelector, sel, SELECTORS } from '../selectors'
|
|
16
|
+
*
|
|
17
|
+
* class MyPOM extends BasePOM {
|
|
18
|
+
* get elements() {
|
|
19
|
+
* return {
|
|
20
|
+
* loginForm: cySelector('auth.login.form'),
|
|
21
|
+
* submitButton: cySelector('auth.login.submit'),
|
|
22
|
+
* boardsList: cySelector('entities.boards.list'),
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// Re-export everything from the lib selectors
|
|
30
|
+
export {
|
|
31
|
+
BLOCK_SELECTORS,
|
|
32
|
+
ENTITY_SELECTORS,
|
|
33
|
+
KANBAN_SELECTORS,
|
|
34
|
+
THEME_SELECTORS,
|
|
35
|
+
SELECTORS,
|
|
36
|
+
sel,
|
|
37
|
+
s,
|
|
38
|
+
selDev,
|
|
39
|
+
cySelector,
|
|
40
|
+
entitySelectors,
|
|
41
|
+
CORE_SELECTORS,
|
|
42
|
+
} from '../../../lib/selectors'
|
|
43
|
+
|
|
44
|
+
export type {
|
|
45
|
+
ThemeSelectorsType,
|
|
46
|
+
BlockSelectorsType,
|
|
47
|
+
EntitySelectorsType,
|
|
48
|
+
KanbanSelectorsType,
|
|
49
|
+
Replacements,
|
|
50
|
+
} from '../../../lib/selectors'
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Productivity Theme Session Helpers
|
|
3
|
+
*
|
|
4
|
+
* Direct login functions for Productivity theme tests using theme-specific users.
|
|
5
|
+
* Uses API-based login for faster and more stable authentication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Productivity Test Users - from app.config.ts devKeyring
|
|
10
|
+
*/
|
|
11
|
+
export const PRODUCTIVITY_USERS = {
|
|
12
|
+
OWNER: 'prod_owner_patricia@nextspark.dev', // Patricia Torres - Product Team (owner), Marketing Hub (owner)
|
|
13
|
+
ADMIN: 'prod_admin_member_lucas@nextspark.dev', // Lucas Luna - Product Team (admin), Marketing Hub (member)
|
|
14
|
+
MEMBER_PRODUCT: 'prod_member_diana@nextspark.dev', // Diana Rios - Product Team (member)
|
|
15
|
+
MEMBER_MARKETING: 'prod_member_marcos@nextspark.dev', // Marcos Silva - Marketing Hub (member)
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
// Default test password for demo users
|
|
19
|
+
const TEST_PASSWORD = Cypress.env('TEST_PASSWORD') || 'Test1234'
|
|
20
|
+
const API_TIMEOUT = 60000
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* API login helper
|
|
24
|
+
*/
|
|
25
|
+
function apiLogin(email: string, password: string = TEST_PASSWORD): Cypress.Chainable<boolean> {
|
|
26
|
+
return cy.request({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
url: '/api/auth/sign-in/email',
|
|
29
|
+
body: { email, password },
|
|
30
|
+
timeout: API_TIMEOUT,
|
|
31
|
+
failOnStatusCode: false
|
|
32
|
+
}).then((response) => {
|
|
33
|
+
if (response.status === 200) {
|
|
34
|
+
return true
|
|
35
|
+
} else {
|
|
36
|
+
cy.log(`⚠️ API login failed with status ${response.status}`)
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Setup team context after login
|
|
44
|
+
*/
|
|
45
|
+
function setupTeamContext(preferredRole?: string) {
|
|
46
|
+
cy.request({
|
|
47
|
+
method: 'GET',
|
|
48
|
+
url: '/api/v1/teams',
|
|
49
|
+
timeout: API_TIMEOUT,
|
|
50
|
+
failOnStatusCode: false
|
|
51
|
+
}).then((teamsResponse) => {
|
|
52
|
+
if (teamsResponse.status === 200 && teamsResponse.body?.data?.length > 0) {
|
|
53
|
+
const teams = teamsResponse.body.data
|
|
54
|
+
let selectedTeam = teams[0]
|
|
55
|
+
if (preferredRole) {
|
|
56
|
+
const teamWithRole = teams.find((t: { role: string }) => t.role === preferredRole)
|
|
57
|
+
if (teamWithRole) {
|
|
58
|
+
selectedTeam = teamWithRole
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const teamId = selectedTeam.id
|
|
62
|
+
cy.log(`✅ Setting active team: ${selectedTeam.name} (${teamId})`)
|
|
63
|
+
cy.window().then((win) => {
|
|
64
|
+
win.localStorage.setItem('activeTeamId', teamId)
|
|
65
|
+
})
|
|
66
|
+
cy.request({
|
|
67
|
+
method: 'POST',
|
|
68
|
+
url: '/api/v1/teams/switch',
|
|
69
|
+
body: { teamId },
|
|
70
|
+
timeout: API_TIMEOUT,
|
|
71
|
+
failOnStatusCode: false
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Login as Productivity Owner (Patricia Torres)
|
|
79
|
+
* Has owner role in Product Team and Marketing Hub
|
|
80
|
+
*/
|
|
81
|
+
export function loginAsProductivityOwner() {
|
|
82
|
+
cy.session('productivity-owner-session', () => {
|
|
83
|
+
apiLogin(PRODUCTIVITY_USERS.OWNER).then((success) => {
|
|
84
|
+
if (success) {
|
|
85
|
+
cy.visit('/dashboard', { timeout: 60000 })
|
|
86
|
+
}
|
|
87
|
+
cy.url().should('include', '/dashboard')
|
|
88
|
+
setupTeamContext()
|
|
89
|
+
})
|
|
90
|
+
}, {
|
|
91
|
+
validate: () => {
|
|
92
|
+
cy.request({
|
|
93
|
+
url: '/api/auth/get-session',
|
|
94
|
+
timeout: API_TIMEOUT,
|
|
95
|
+
failOnStatusCode: false
|
|
96
|
+
}).its('status').should('eq', 200)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Login as Productivity Admin (Lucas Luna)
|
|
103
|
+
* Has admin role in Product Team, member role in Marketing Hub
|
|
104
|
+
*/
|
|
105
|
+
export function loginAsProductivityAdmin() {
|
|
106
|
+
cy.session('productivity-admin-session', () => {
|
|
107
|
+
apiLogin(PRODUCTIVITY_USERS.ADMIN).then((success) => {
|
|
108
|
+
if (success) {
|
|
109
|
+
cy.visit('/dashboard', { timeout: 60000 })
|
|
110
|
+
}
|
|
111
|
+
cy.url().should('include', '/dashboard')
|
|
112
|
+
setupTeamContext()
|
|
113
|
+
})
|
|
114
|
+
}, {
|
|
115
|
+
validate: () => {
|
|
116
|
+
cy.request({
|
|
117
|
+
url: '/api/auth/get-session',
|
|
118
|
+
timeout: API_TIMEOUT,
|
|
119
|
+
failOnStatusCode: false
|
|
120
|
+
}).its('status').should('eq', 200)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Login as Productivity Member - Product Team (Diana Rios)
|
|
127
|
+
* Has member role in Product Team only
|
|
128
|
+
*/
|
|
129
|
+
export function loginAsProductivityMember() {
|
|
130
|
+
cy.session('productivity-member-session', () => {
|
|
131
|
+
apiLogin(PRODUCTIVITY_USERS.MEMBER_PRODUCT).then((success) => {
|
|
132
|
+
if (success) {
|
|
133
|
+
cy.visit('/dashboard', { timeout: 60000 })
|
|
134
|
+
}
|
|
135
|
+
cy.url().should('include', '/dashboard')
|
|
136
|
+
setupTeamContext('member')
|
|
137
|
+
})
|
|
138
|
+
}, {
|
|
139
|
+
validate: () => {
|
|
140
|
+
cy.request({
|
|
141
|
+
url: '/api/auth/get-session',
|
|
142
|
+
timeout: API_TIMEOUT,
|
|
143
|
+
failOnStatusCode: false
|
|
144
|
+
}).its('status').should('eq', 200)
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Login as Productivity Member - Marketing Hub (Marcos Silva)
|
|
151
|
+
* Has member role in Marketing Hub only
|
|
152
|
+
*/
|
|
153
|
+
export function loginAsProductivityMemberMarketing() {
|
|
154
|
+
cy.session('productivity-member-marketing-session', () => {
|
|
155
|
+
apiLogin(PRODUCTIVITY_USERS.MEMBER_MARKETING).then((success) => {
|
|
156
|
+
if (success) {
|
|
157
|
+
cy.visit('/dashboard', { timeout: 60000 })
|
|
158
|
+
}
|
|
159
|
+
cy.url().should('include', '/dashboard')
|
|
160
|
+
setupTeamContext('member')
|
|
161
|
+
})
|
|
162
|
+
}, {
|
|
163
|
+
validate: () => {
|
|
164
|
+
cy.request({
|
|
165
|
+
url: '/api/auth/get-session',
|
|
166
|
+
timeout: API_TIMEOUT,
|
|
167
|
+
failOnStatusCode: false
|
|
168
|
+
}).its('status').should('eq', 200)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Aliases for convenience
|
|
174
|
+
export const loginAsOwner = loginAsProductivityOwner
|
|
175
|
+
export const loginAsAdmin = loginAsProductivityAdmin
|
|
176
|
+
export const loginAsMember = loginAsProductivityMember
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cypress E2E Support File for Theme
|
|
3
|
+
*
|
|
4
|
+
* This file is used when running tests in npm mode (outside monorepo).
|
|
5
|
+
* In monorepo mode, the core support file is used instead via cypress.config.ts.
|
|
6
|
+
*
|
|
7
|
+
* For documentation video commands, install cypress-slow-down:
|
|
8
|
+
* pnpm add -D cypress-slow-down
|
|
9
|
+
* Then uncomment the doc-commands import below.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import '@testing-library/cypress/add-commands'
|
|
13
|
+
|
|
14
|
+
// Import @cypress/grep for test filtering by tags
|
|
15
|
+
// v5.x uses named export { register }
|
|
16
|
+
import { register as registerCypressGrep } from '@cypress/grep'
|
|
17
|
+
registerCypressGrep()
|
|
18
|
+
|
|
19
|
+
// Doc commands are optional (require cypress-slow-down)
|
|
20
|
+
// Uncomment if you have cypress-slow-down installed:
|
|
21
|
+
// import './doc-commands'
|
|
22
|
+
|
|
23
|
+
// Global error handling
|
|
24
|
+
Cypress.on('uncaught:exception', (err) => {
|
|
25
|
+
// Ignore React hydration errors
|
|
26
|
+
if (err.message.includes('Hydration')) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
// Ignore ResizeObserver errors
|
|
30
|
+
if (err.message.includes('ResizeObserver')) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
return true
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Global before hook
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
cy.clearCookies()
|
|
39
|
+
cy.clearLocalStorage()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Type declarations for @cypress/grep and custom commands
|
|
43
|
+
declare global {
|
|
44
|
+
namespace Cypress {
|
|
45
|
+
interface Chainable {
|
|
46
|
+
/**
|
|
47
|
+
* Custom command to make API requests with better error handling
|
|
48
|
+
*/
|
|
49
|
+
apiRequest(options: Partial<Cypress.RequestOptions>): Chainable<Cypress.Response<any>>
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Login command for authenticated tests
|
|
53
|
+
*/
|
|
54
|
+
login(email: string, password: string): Chainable<void>
|
|
55
|
+
}
|
|
56
|
+
interface SuiteConfigOverrides {
|
|
57
|
+
tags?: string | string[]
|
|
58
|
+
}
|
|
59
|
+
interface TestConfigOverrides {
|
|
60
|
+
tags?: string | string[]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Custom API request command
|
|
66
|
+
Cypress.Commands.add('apiRequest', (options) => {
|
|
67
|
+
const defaultOptions = {
|
|
68
|
+
failOnStatusCode: false,
|
|
69
|
+
timeout: 15000,
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
...options.headers
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return cy.request({
|
|
76
|
+
...defaultOptions,
|
|
77
|
+
...options
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Login command for authenticated tests
|
|
82
|
+
Cypress.Commands.add('login', (email: string, password: string) => {
|
|
83
|
+
cy.session([email, password], () => {
|
|
84
|
+
cy.visit('/login')
|
|
85
|
+
cy.get('[data-testid="email-input"], input[name="email"]').type(email)
|
|
86
|
+
cy.get('[data-testid="password-input"], input[name="password"]').type(password)
|
|
87
|
+
cy.get('[data-testid="submit-button"], button[type="submit"]').click()
|
|
88
|
+
cy.url().should('include', '/dashboard')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cypress Configuration for productivity Theme
|
|
3
|
+
*
|
|
4
|
+
* This config works in both monorepo and npm mode.
|
|
5
|
+
* Run with: NEXT_PUBLIC_ACTIVE_THEME=default pnpm cy:open
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineConfig } from 'cypress'
|
|
9
|
+
import { fileURLToPath } from 'url'
|
|
10
|
+
import path from 'path'
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
|
|
13
|
+
// ESM-compatible __dirname
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
+
const __dirname = path.dirname(__filename)
|
|
16
|
+
|
|
17
|
+
// Paths relative to this config file
|
|
18
|
+
const themeRoot = path.resolve(__dirname, '..')
|
|
19
|
+
const projectRoot = path.resolve(__dirname, '../../../..')
|
|
20
|
+
const narrationsOutputDir = path.join(__dirname, 'cypress/videos/narrations')
|
|
21
|
+
|
|
22
|
+
// Load environment variables
|
|
23
|
+
import dotenv from 'dotenv'
|
|
24
|
+
dotenv.config({ path: path.join(projectRoot, '.env') })
|
|
25
|
+
|
|
26
|
+
// Server port (from .env or default 3000)
|
|
27
|
+
const port = process.env.PORT || 3000
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
e2e: {
|
|
31
|
+
// Base URL for the application
|
|
32
|
+
baseUrl: `http://localhost:${port}`,
|
|
33
|
+
|
|
34
|
+
// Spec patterns: theme tests only
|
|
35
|
+
specPattern: [
|
|
36
|
+
path.join(__dirname, 'cypress/e2e/**/*.cy.{js,ts}'),
|
|
37
|
+
],
|
|
38
|
+
|
|
39
|
+
// Support file (always theme-local)
|
|
40
|
+
supportFile: path.join(__dirname, 'cypress/support/e2e.ts'),
|
|
41
|
+
|
|
42
|
+
// Fixtures folder (theme-specific)
|
|
43
|
+
fixturesFolder: path.join(__dirname, 'cypress/fixtures'),
|
|
44
|
+
|
|
45
|
+
// Output folders (theme-specific)
|
|
46
|
+
downloadsFolder: path.join(__dirname, 'cypress/downloads'),
|
|
47
|
+
screenshotsFolder: path.join(__dirname, 'cypress/screenshots'),
|
|
48
|
+
videosFolder: path.join(__dirname, 'cypress/videos'),
|
|
49
|
+
|
|
50
|
+
// Viewport settings
|
|
51
|
+
viewportWidth: 1280,
|
|
52
|
+
viewportHeight: 720,
|
|
53
|
+
|
|
54
|
+
// Video and screenshot settings
|
|
55
|
+
video: true,
|
|
56
|
+
screenshotOnRunFailure: true,
|
|
57
|
+
|
|
58
|
+
// Timeouts
|
|
59
|
+
defaultCommandTimeout: 10000,
|
|
60
|
+
requestTimeout: 10000,
|
|
61
|
+
responseTimeout: 10000,
|
|
62
|
+
pageLoadTimeout: 30000,
|
|
63
|
+
|
|
64
|
+
// Browser settings
|
|
65
|
+
chromeWebSecurity: false,
|
|
66
|
+
|
|
67
|
+
// Test isolation
|
|
68
|
+
testIsolation: true,
|
|
69
|
+
|
|
70
|
+
// Retry settings
|
|
71
|
+
retries: {
|
|
72
|
+
runMode: 1,
|
|
73
|
+
openMode: 0,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Environment variables
|
|
77
|
+
env: {
|
|
78
|
+
// Theme info
|
|
79
|
+
ACTIVE_THEME: 'productivity',
|
|
80
|
+
THEME_PATH: themeRoot,
|
|
81
|
+
|
|
82
|
+
// Test user credentials
|
|
83
|
+
TEST_USER_EMAIL: 'user@example.com',
|
|
84
|
+
TEST_USER_PASSWORD: 'Testing1234',
|
|
85
|
+
|
|
86
|
+
// Feature flags
|
|
87
|
+
ENABLE_ALLURE: true,
|
|
88
|
+
|
|
89
|
+
// Allure reporting
|
|
90
|
+
allureResultsPath: path.join(__dirname, 'cypress/allure-results'),
|
|
91
|
+
|
|
92
|
+
// API settings
|
|
93
|
+
API_URL: `http://localhost:${port}/api`,
|
|
94
|
+
API_BASE_URL: `http://localhost:${port}`,
|
|
95
|
+
|
|
96
|
+
// @cypress/grep - filter specs by tags
|
|
97
|
+
grepFilterSpecs: true,
|
|
98
|
+
grepOmitFiltered: true,
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async setupNodeEvents(on, config) {
|
|
102
|
+
// Allure plugin setup (allure-cypress)
|
|
103
|
+
const { allureCypress } = await import('allure-cypress/reporter')
|
|
104
|
+
allureCypress(on, config, {
|
|
105
|
+
resultsDir: path.join(__dirname, 'cypress/allure-results'),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// @cypress/grep plugin for test filtering by tags
|
|
109
|
+
// v5.x uses named export { plugin } from '@cypress/grep/plugin'
|
|
110
|
+
const { plugin: grepPlugin } = await import('@cypress/grep/plugin')
|
|
111
|
+
grepPlugin(config)
|
|
112
|
+
|
|
113
|
+
// Documentation video tasks
|
|
114
|
+
on('task', {
|
|
115
|
+
/**
|
|
116
|
+
* Save narrations to JSON file for post-processing
|
|
117
|
+
*/
|
|
118
|
+
saveNarrations({ specName, narrations }: { specName: string; narrations: unknown[] }) {
|
|
119
|
+
// Ensure output directory exists
|
|
120
|
+
if (!fs.existsSync(narrationsOutputDir)) {
|
|
121
|
+
fs.mkdirSync(narrationsOutputDir, { recursive: true })
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const filename = `${specName}-narrations.json`
|
|
125
|
+
const filepath = path.join(narrationsOutputDir, filename)
|
|
126
|
+
|
|
127
|
+
fs.writeFileSync(filepath, JSON.stringify(narrations, null, 2))
|
|
128
|
+
console.log(`📝 Narrations saved to: ${filepath}`)
|
|
129
|
+
|
|
130
|
+
return null
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Add narration entry (called per narration)
|
|
135
|
+
*/
|
|
136
|
+
addNarration(narration: unknown) {
|
|
137
|
+
// This could be used for real-time streaming to a narration service
|
|
138
|
+
console.log('🎙️ Narration:', narration)
|
|
139
|
+
return null
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return config
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Component testing (future use)
|
|
148
|
+
component: {
|
|
149
|
+
devServer: {
|
|
150
|
+
framework: 'next',
|
|
151
|
+
bundler: 'webpack',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for jose package
|
|
3
|
+
* Resolves ES module import issues in Jest tests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
jwtVerify: jest.fn(),
|
|
8
|
+
SignJWT: jest.fn().mockImplementation(() => ({
|
|
9
|
+
setProtectedHeader: jest.fn().mockReturnThis(),
|
|
10
|
+
setIssuedAt: jest.fn().mockReturnThis(),
|
|
11
|
+
setExpirationTime: jest.fn().mockReturnThis(),
|
|
12
|
+
sign: jest.fn().mockResolvedValue('mock-jwt-token')
|
|
13
|
+
})),
|
|
14
|
+
importJWK: jest.fn(),
|
|
15
|
+
generateSecret: jest.fn(),
|
|
16
|
+
createRemoteJWKSet: jest.fn(),
|
|
17
|
+
errors: {
|
|
18
|
+
JWTExpired: class JWTExpired extends Error {},
|
|
19
|
+
JWTInvalid: class JWTInvalid extends Error {},
|
|
20
|
+
JWTClaimValidationFailed: class JWTClaimValidationFailed extends Error {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for next/server
|
|
3
|
+
* Provides NextRequest and NextResponse mocks for API testing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class MockNextRequest {
|
|
7
|
+
constructor(url, options = {}) {
|
|
8
|
+
this.url = url
|
|
9
|
+
this.method = options.method || 'GET'
|
|
10
|
+
this.headers = new Map(Object.entries(options.headers || {}))
|
|
11
|
+
this._body = options.body
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async json() {
|
|
15
|
+
if (!this._body) return {}
|
|
16
|
+
try {
|
|
17
|
+
return typeof this._body === 'string' ? JSON.parse(this._body) : this._body
|
|
18
|
+
} catch {
|
|
19
|
+
throw new SyntaxError('Invalid JSON')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async text() {
|
|
24
|
+
return this._body || ''
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class MockNextResponse {
|
|
29
|
+
constructor(body, options = {}) {
|
|
30
|
+
this.body = body
|
|
31
|
+
this.status = options.status || 200
|
|
32
|
+
this.statusText = options.statusText || 'OK'
|
|
33
|
+
this.headers = new Map(Object.entries(options.headers || {}))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async json() {
|
|
37
|
+
if (!this.body) return {}
|
|
38
|
+
try {
|
|
39
|
+
return typeof this.body === 'string' ? JSON.parse(this.body) : this.body
|
|
40
|
+
} catch {
|
|
41
|
+
throw new SyntaxError('Invalid JSON')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static json(data, options = {}) {
|
|
46
|
+
return new MockNextResponse(data, {
|
|
47
|
+
...options,
|
|
48
|
+
headers: { 'Content-Type': 'application/json', ...options.headers }
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
NextRequest: MockNextRequest,
|
|
55
|
+
NextResponse: MockNextResponse
|
|
56
|
+
}
|