@nextsparkjs/theme-crm 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/api/activities/activities-crud.cy.ts +686 -0
- package/tests/cypress/e2e/api/campaigns/campaigns-crud.cy.ts +592 -0
- package/tests/cypress/e2e/api/companies/companies-crud.cy.ts +682 -0
- package/tests/cypress/e2e/api/contacts/contacts-crud.cy.ts +668 -0
- package/tests/cypress/e2e/api/leads/leads-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/notes/notes-crud.cy.ts +424 -0
- package/tests/cypress/e2e/api/opportunities/opportunities-crud.cy.ts +865 -0
- package/tests/cypress/e2e/api/pipelines/pipelines-crud.cy.ts +545 -0
- package/tests/cypress/e2e/api/products/products-crud.cy.ts +447 -0
- package/tests/cypress/e2e/ui/activities/activities-admin.cy.ts +268 -0
- package/tests/cypress/e2e/ui/activities/activities-member.cy.ts +257 -0
- package/tests/cypress/e2e/ui/activities/activities-owner.cy.ts +268 -0
- package/tests/cypress/e2e/ui/companies/companies-admin.cy.ts +188 -0
- package/tests/cypress/e2e/ui/companies/companies-member.cy.ts +166 -0
- package/tests/cypress/e2e/ui/companies/companies-owner.cy.ts +189 -0
- package/tests/cypress/e2e/ui/contacts/contacts-admin.cy.ts +252 -0
- package/tests/cypress/e2e/ui/contacts/contacts-member.cy.ts +224 -0
- package/tests/cypress/e2e/ui/contacts/contacts-owner.cy.ts +236 -0
- package/tests/cypress/e2e/ui/leads/leads-admin.cy.ts +286 -0
- package/tests/cypress/e2e/ui/leads/leads-member.cy.ts +193 -0
- package/tests/cypress/e2e/ui/leads/leads-owner.cy.ts +210 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-admin.cy.ts +197 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-member.cy.ts +229 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-owner.cy.ts +196 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-admin.cy.ts +320 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-member.cy.ts +262 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-owner.cy.ts +282 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +240 -0
- package/tests/cypress/src/components/CRMDataTable.js +223 -0
- package/tests/cypress/src/components/CRMMobileNav.js +138 -0
- package/tests/cypress/src/components/CRMSidebar.js +145 -0
- package/tests/cypress/src/components/CRMTopBar.js +194 -0
- package/tests/cypress/src/components/DealCard.js +197 -0
- package/tests/cypress/src/components/EntityDetail.ts +290 -0
- package/tests/cypress/src/components/EntityForm.ts +357 -0
- package/tests/cypress/src/components/EntityList.ts +360 -0
- package/tests/cypress/src/components/PipelineKanban.js +204 -0
- package/tests/cypress/src/components/StageColumn.js +196 -0
- package/tests/cypress/src/components/index.js +13 -0
- package/tests/cypress/src/components/index.ts +22 -0
- package/tests/cypress/src/controllers/ActivityAPIController.ts +113 -0
- package/tests/cypress/src/controllers/BaseAPIController.ts +307 -0
- package/tests/cypress/src/controllers/CampaignAPIController.ts +114 -0
- package/tests/cypress/src/controllers/CompanyAPIController.ts +112 -0
- package/tests/cypress/src/controllers/ContactAPIController.ts +104 -0
- package/tests/cypress/src/controllers/LeadAPIController.ts +96 -0
- package/tests/cypress/src/controllers/NoteAPIController.ts +130 -0
- package/tests/cypress/src/controllers/OpportunityAPIController.ts +134 -0
- package/tests/cypress/src/controllers/PipelineAPIController.ts +116 -0
- package/tests/cypress/src/controllers/ProductAPIController.ts +113 -0
- package/tests/cypress/src/controllers/index.ts +35 -0
- package/tests/cypress/src/entities/ActivitiesPOM.ts +130 -0
- package/tests/cypress/src/entities/CompaniesPOM.ts +117 -0
- package/tests/cypress/src/entities/ContactsPOM.ts +117 -0
- package/tests/cypress/src/entities/LeadsPOM.ts +129 -0
- package/tests/cypress/src/entities/OpportunitiesPOM.ts +178 -0
- package/tests/cypress/src/entities/PipelinesPOM.ts +341 -0
- package/tests/cypress/src/entities/index.ts +31 -0
- package/tests/cypress/src/forms/OpportunityForm.js +316 -0
- package/tests/cypress/src/forms/PipelineForm.js +243 -0
- package/tests/cypress/src/forms/index.js +8 -0
- package/tests/cypress/src/index.js +22 -0
- package/tests/cypress/src/index.ts +68 -0
- package/tests/cypress/src/selectors.ts +50 -0
- package/tests/cypress/src/session-helpers.ts +94 -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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRM Theme Session Helpers
|
|
3
|
+
*
|
|
4
|
+
* Direct login functions for CRM theme tests using CRM-specific users.
|
|
5
|
+
* These helpers don't depend on ACTIVE_THEME environment variable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DevKeyring } from '../../../../../../test/cypress/src/classes/components/auth/DevKeyring.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CRM Test Users - hardcoded for CRM theme tests
|
|
12
|
+
*/
|
|
13
|
+
export const CRM_USERS = {
|
|
14
|
+
OWNER: 'crm_owner_roberto@nextspark.dev', // CEO
|
|
15
|
+
ADMIN: 'crm_admin_sofia@nextspark.dev', // Sales Manager
|
|
16
|
+
MEMBER: 'crm_member_miguel@nextspark.dev', // Sales Rep
|
|
17
|
+
LAURA: 'crm_member_laura@nextspark.dev', // Marketing
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Login as CRM Owner (CEO)
|
|
22
|
+
* Session is cached and reused across tests
|
|
23
|
+
*/
|
|
24
|
+
export function loginAsCrmOwner() {
|
|
25
|
+
cy.session('crm-owner-session', () => {
|
|
26
|
+
cy.visit('/login')
|
|
27
|
+
const devKeyring = new DevKeyring()
|
|
28
|
+
devKeyring.validateVisible()
|
|
29
|
+
devKeyring.quickLoginByEmail(CRM_USERS.OWNER)
|
|
30
|
+
cy.url().should('include', '/dashboard')
|
|
31
|
+
}, {
|
|
32
|
+
validate: () => {
|
|
33
|
+
cy.visit('/dashboard')
|
|
34
|
+
cy.url().should('include', '/dashboard')
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Login as CRM Admin (Sales Manager)
|
|
41
|
+
* Session is cached and reused across tests
|
|
42
|
+
*/
|
|
43
|
+
export function loginAsCrmAdmin() {
|
|
44
|
+
cy.session('crm-admin-session', () => {
|
|
45
|
+
cy.visit('/login')
|
|
46
|
+
const devKeyring = new DevKeyring()
|
|
47
|
+
devKeyring.validateVisible()
|
|
48
|
+
devKeyring.quickLoginByEmail(CRM_USERS.ADMIN)
|
|
49
|
+
cy.url().should('include', '/dashboard')
|
|
50
|
+
}, {
|
|
51
|
+
validate: () => {
|
|
52
|
+
cy.visit('/dashboard')
|
|
53
|
+
cy.url().should('include', '/dashboard')
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Login as CRM Member (Sales Rep)
|
|
60
|
+
* Session is cached and reused across tests
|
|
61
|
+
*/
|
|
62
|
+
export function loginAsCrmMember() {
|
|
63
|
+
cy.session('crm-member-session', () => {
|
|
64
|
+
cy.visit('/login')
|
|
65
|
+
const devKeyring = new DevKeyring()
|
|
66
|
+
devKeyring.validateVisible()
|
|
67
|
+
devKeyring.quickLoginByEmail(CRM_USERS.MEMBER)
|
|
68
|
+
cy.url().should('include', '/dashboard')
|
|
69
|
+
}, {
|
|
70
|
+
validate: () => {
|
|
71
|
+
cy.visit('/dashboard')
|
|
72
|
+
cy.url().should('include', '/dashboard')
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Login as CRM Laura (Marketing)
|
|
79
|
+
* Session is cached and reused across tests
|
|
80
|
+
*/
|
|
81
|
+
export function loginAsCrmLaura() {
|
|
82
|
+
cy.session('crm-laura-session', () => {
|
|
83
|
+
cy.visit('/login')
|
|
84
|
+
const devKeyring = new DevKeyring()
|
|
85
|
+
devKeyring.validateVisible()
|
|
86
|
+
devKeyring.quickLoginByEmail(CRM_USERS.LAURA)
|
|
87
|
+
cy.url().should('include', '/dashboard')
|
|
88
|
+
}, {
|
|
89
|
+
validate: () => {
|
|
90
|
+
cy.visit('/dashboard')
|
|
91
|
+
cy.url().should('include', '/dashboard')
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
import registerCypressGrep from '@cypress/grep'
|
|
16
|
+
registerCypressGrep()
|
|
17
|
+
|
|
18
|
+
// Doc commands are optional (require cypress-slow-down)
|
|
19
|
+
// Uncomment if you have cypress-slow-down installed:
|
|
20
|
+
// import './doc-commands'
|
|
21
|
+
|
|
22
|
+
// Global error handling
|
|
23
|
+
Cypress.on('uncaught:exception', (err) => {
|
|
24
|
+
// Ignore React hydration errors
|
|
25
|
+
if (err.message.includes('Hydration')) {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
// Ignore ResizeObserver errors
|
|
29
|
+
if (err.message.includes('ResizeObserver')) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
return true
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Global before hook
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
cy.clearCookies()
|
|
38
|
+
cy.clearLocalStorage()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Type declarations for @cypress/grep and custom commands
|
|
42
|
+
declare global {
|
|
43
|
+
namespace Cypress {
|
|
44
|
+
interface Chainable {
|
|
45
|
+
/**
|
|
46
|
+
* Custom command to make API requests with better error handling
|
|
47
|
+
*/
|
|
48
|
+
apiRequest(options: Partial<Cypress.RequestOptions>): Chainable<Cypress.Response<any>>
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Login command for authenticated tests
|
|
52
|
+
*/
|
|
53
|
+
login(email: string, password: string): Chainable<void>
|
|
54
|
+
}
|
|
55
|
+
interface SuiteConfigOverrides {
|
|
56
|
+
tags?: string | string[]
|
|
57
|
+
}
|
|
58
|
+
interface TestConfigOverrides {
|
|
59
|
+
tags?: string | string[]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Custom API request command
|
|
65
|
+
Cypress.Commands.add('apiRequest', (options) => {
|
|
66
|
+
const defaultOptions = {
|
|
67
|
+
failOnStatusCode: false,
|
|
68
|
+
timeout: 15000,
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
...options.headers
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return cy.request({
|
|
75
|
+
...defaultOptions,
|
|
76
|
+
...options
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Login command for authenticated tests
|
|
81
|
+
Cypress.Commands.add('login', (email: string, password: string) => {
|
|
82
|
+
cy.session([email, password], () => {
|
|
83
|
+
cy.visit('/login')
|
|
84
|
+
cy.get('[data-testid="email-input"], input[name="email"]').type(email)
|
|
85
|
+
cy.get('[data-testid="password-input"], input[name="password"]').type(password)
|
|
86
|
+
cy.get('[data-testid="submit-button"], button[type="submit"]').click()
|
|
87
|
+
cy.url().should('include', '/dashboard')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cypress Configuration for crm 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 path from 'path'
|
|
10
|
+
import fs from 'fs'
|
|
11
|
+
import { fileURLToPath } from 'url'
|
|
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
|
+
// Detect if running in npm mode (no packages/core folder) vs monorepo
|
|
23
|
+
const isNpmMode = !fs.existsSync(path.join(projectRoot, 'packages/core'))
|
|
24
|
+
|
|
25
|
+
// Load environment variables
|
|
26
|
+
import dotenv from 'dotenv'
|
|
27
|
+
dotenv.config({ path: path.join(projectRoot, '.env') })
|
|
28
|
+
|
|
29
|
+
// Server port (from .env or default 3000)
|
|
30
|
+
const port = process.env.PORT || 3000
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
e2e: {
|
|
34
|
+
// Base URL for the application
|
|
35
|
+
baseUrl: `http://localhost:${port}`,
|
|
36
|
+
|
|
37
|
+
// Spec patterns: theme tests (core tests only in monorepo)
|
|
38
|
+
specPattern: isNpmMode
|
|
39
|
+
? [
|
|
40
|
+
// npm mode: only theme tests
|
|
41
|
+
path.join(__dirname, 'cypress/e2e/**/*.cy.{js,ts}'),
|
|
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'),
|
|
53
|
+
|
|
54
|
+
// Fixtures folder (theme-specific)
|
|
55
|
+
fixturesFolder: path.join(__dirname, 'cypress/fixtures'),
|
|
56
|
+
|
|
57
|
+
// Output folders (theme-specific)
|
|
58
|
+
downloadsFolder: path.join(__dirname, 'cypress/downloads'),
|
|
59
|
+
screenshotsFolder: path.join(__dirname, 'cypress/screenshots'),
|
|
60
|
+
videosFolder: path.join(__dirname, 'cypress/videos'),
|
|
61
|
+
|
|
62
|
+
// Viewport settings
|
|
63
|
+
viewportWidth: 1280,
|
|
64
|
+
viewportHeight: 720,
|
|
65
|
+
|
|
66
|
+
// Video and screenshot settings
|
|
67
|
+
video: true,
|
|
68
|
+
screenshotOnRunFailure: true,
|
|
69
|
+
|
|
70
|
+
// Timeouts
|
|
71
|
+
defaultCommandTimeout: 10000,
|
|
72
|
+
requestTimeout: 10000,
|
|
73
|
+
responseTimeout: 10000,
|
|
74
|
+
pageLoadTimeout: 30000,
|
|
75
|
+
|
|
76
|
+
// Browser settings
|
|
77
|
+
chromeWebSecurity: false,
|
|
78
|
+
|
|
79
|
+
// Test isolation
|
|
80
|
+
testIsolation: true,
|
|
81
|
+
|
|
82
|
+
// Retry settings
|
|
83
|
+
retries: {
|
|
84
|
+
runMode: 1,
|
|
85
|
+
openMode: 0,
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Environment variables
|
|
89
|
+
env: {
|
|
90
|
+
// Theme info
|
|
91
|
+
ACTIVE_THEME: 'crm',
|
|
92
|
+
THEME_PATH: themeRoot,
|
|
93
|
+
|
|
94
|
+
// Test user credentials
|
|
95
|
+
TEST_USER_EMAIL: 'user@example.com',
|
|
96
|
+
TEST_USER_PASSWORD: 'Testing1234',
|
|
97
|
+
|
|
98
|
+
// Feature flags
|
|
99
|
+
ENABLE_ALLURE: true,
|
|
100
|
+
|
|
101
|
+
// Allure reporting
|
|
102
|
+
allureResultsPath: path.join(__dirname, 'cypress/allure-results'),
|
|
103
|
+
|
|
104
|
+
// API settings
|
|
105
|
+
API_URL: `http://localhost:${port}/api`,
|
|
106
|
+
API_BASE_URL: `http://localhost:${port}`,
|
|
107
|
+
|
|
108
|
+
// @cypress/grep - filter specs by tags
|
|
109
|
+
grepFilterSpecs: true,
|
|
110
|
+
grepOmitFiltered: true,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async setupNodeEvents(on, config) {
|
|
114
|
+
// Allure plugin setup (allure-cypress)
|
|
115
|
+
const { allureCypress } = await import('allure-cypress/reporter')
|
|
116
|
+
allureCypress(on, config, {
|
|
117
|
+
resultsDir: path.join(__dirname, 'cypress/allure-results'),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// @cypress/grep plugin for test filtering by tags
|
|
121
|
+
const grepPlugin = await import('@cypress/grep/src/plugin.js')
|
|
122
|
+
;(grepPlugin.default || grepPlugin)(config)
|
|
123
|
+
|
|
124
|
+
// Documentation video tasks
|
|
125
|
+
on('task', {
|
|
126
|
+
/**
|
|
127
|
+
* Save narrations to JSON file for post-processing
|
|
128
|
+
*/
|
|
129
|
+
saveNarrations({ specName, narrations }: { specName: string; narrations: unknown[] }) {
|
|
130
|
+
// Ensure output directory exists
|
|
131
|
+
if (!fs.existsSync(narrationsOutputDir)) {
|
|
132
|
+
fs.mkdirSync(narrationsOutputDir, { recursive: true })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const filename = `${specName}-narrations.json`
|
|
136
|
+
const filepath = path.join(narrationsOutputDir, filename)
|
|
137
|
+
|
|
138
|
+
fs.writeFileSync(filepath, JSON.stringify(narrations, null, 2))
|
|
139
|
+
console.log(`📝 Narrations saved to: ${filepath}`)
|
|
140
|
+
|
|
141
|
+
return null
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Add narration entry (called per narration)
|
|
146
|
+
*/
|
|
147
|
+
addNarration(narration: unknown) {
|
|
148
|
+
// This could be used for real-time streaming to a narration service
|
|
149
|
+
console.log('🎙️ Narration:', narration)
|
|
150
|
+
return null
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return config
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// Component testing (future use)
|
|
159
|
+
component: {
|
|
160
|
+
devServer: {
|
|
161
|
+
framework: 'next',
|
|
162
|
+
bundler: 'webpack',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
})
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Configuration for CRM Theme
|
|
3
|
+
*
|
|
4
|
+
* This config works in both monorepo and npm mode.
|
|
5
|
+
* Run with: pnpm test:theme
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
|
|
11
|
+
// Paths relative to this config file
|
|
12
|
+
const themeTestsRoot = __dirname
|
|
13
|
+
const themeRoot = path.resolve(__dirname, '../..')
|
|
14
|
+
|
|
15
|
+
// In monorepo: themes/crm/tests/jest -> apps/dev (via symlink in contents/)
|
|
16
|
+
// In npm mode: contents/themes/crm/tests/jest -> project root (5 levels up)
|
|
17
|
+
const monorepoRepoRoot = path.resolve(__dirname, '../../../..')
|
|
18
|
+
const npmModeRoot = path.resolve(__dirname, '../../../../..')
|
|
19
|
+
|
|
20
|
+
// Detect if running in npm mode (no packages/core folder) vs monorepo
|
|
21
|
+
const isNpmMode = !fs.existsSync(path.join(monorepoRepoRoot, 'packages/core'))
|
|
22
|
+
|
|
23
|
+
// In monorepo, use apps/dev as rootDir since contents/ symlinks to themes/
|
|
24
|
+
const monorepoAppRoot = path.join(monorepoRepoRoot, 'apps/dev')
|
|
25
|
+
const projectRoot = isNpmMode ? npmModeRoot : monorepoAppRoot
|
|
26
|
+
|
|
27
|
+
// Module name mapper based on mode
|
|
28
|
+
const moduleNameMapper = isNpmMode
|
|
29
|
+
? {
|
|
30
|
+
// NPM mode: resolve from node_modules
|
|
31
|
+
'^@nextsparkjs/core/(.*)$': '@nextsparkjs/core/$1',
|
|
32
|
+
'^@nextsparkjs/core$': '@nextsparkjs/core',
|
|
33
|
+
'^@/contents/(.*)$': '<rootDir>/contents/$1',
|
|
34
|
+
'^@/entities/(.*)$': '<rootDir>/contents/entities/$1',
|
|
35
|
+
'^@/plugins/(.*)$': '<rootDir>/contents/plugins/$1',
|
|
36
|
+
'^@/themes/(.*)$': '<rootDir>/contents/themes/$1',
|
|
37
|
+
'^@/(.*)$': '<rootDir>/$1',
|
|
38
|
+
// Mocks from theme-local folder
|
|
39
|
+
'next/server': path.join(themeTestsRoot, '__mocks__/next-server.js'),
|
|
40
|
+
'^jose$': path.join(themeTestsRoot, '__mocks__/jose.js'),
|
|
41
|
+
'^jose/(.*)$': path.join(themeTestsRoot, '__mocks__/jose.js'),
|
|
42
|
+
}
|
|
43
|
+
: {
|
|
44
|
+
// Monorepo mode: resolve from packages/core/src (rootDir is apps/dev)
|
|
45
|
+
'^@nextsparkjs/core/(.*)$': '<rootDir>/../../packages/core/src/$1',
|
|
46
|
+
'^@nextsparkjs/core$': '<rootDir>/../../packages/core/src',
|
|
47
|
+
'^@/contents/(.*)$': '<rootDir>/contents/$1',
|
|
48
|
+
'^@/entities/(.*)$': '<rootDir>/contents/entities/$1',
|
|
49
|
+
'^@/plugins/(.*)$': '<rootDir>/contents/plugins/$1',
|
|
50
|
+
'^@/themes/(.*)$': '<rootDir>/contents/themes/$1',
|
|
51
|
+
'^@/(.*)$': '<rootDir>/$1',
|
|
52
|
+
// Mocks from core
|
|
53
|
+
'next/server': '<rootDir>/../../packages/core/tests/jest/__mocks__/next-server.js',
|
|
54
|
+
'^jose$': '<rootDir>/../../packages/core/tests/jest/__mocks__/jose.js',
|
|
55
|
+
'^jose/(.*)$': '<rootDir>/../../packages/core/tests/jest/__mocks__/jose.js',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Setup files based on mode
|
|
59
|
+
const setupFilesAfterEnv = isNpmMode
|
|
60
|
+
? [
|
|
61
|
+
// NPM mode: use theme's local setup only (it includes everything needed)
|
|
62
|
+
path.join(themeTestsRoot, 'setup.ts'),
|
|
63
|
+
]
|
|
64
|
+
: [
|
|
65
|
+
// Monorepo mode: use local core setup (rootDir is apps/dev)
|
|
66
|
+
'<rootDir>/../../packages/core/tests/jest/setup.ts',
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
/** @type {import('jest').Config} */
|
|
70
|
+
module.exports = {
|
|
71
|
+
displayName: 'theme-crm',
|
|
72
|
+
rootDir: projectRoot,
|
|
73
|
+
|
|
74
|
+
// Use roots to explicitly set test location (handles symlinks better)
|
|
75
|
+
roots: [themeTestsRoot],
|
|
76
|
+
|
|
77
|
+
// Test file patterns
|
|
78
|
+
testMatch: [
|
|
79
|
+
'**/*.{test,spec}.{js,ts,tsx}',
|
|
80
|
+
],
|
|
81
|
+
testPathIgnorePatterns: [
|
|
82
|
+
'<rootDir>/node_modules/',
|
|
83
|
+
'<rootDir>/.next/',
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
// Preset and environment
|
|
87
|
+
preset: 'ts-jest',
|
|
88
|
+
testEnvironment: 'jsdom',
|
|
89
|
+
|
|
90
|
+
// Module resolution
|
|
91
|
+
moduleNameMapper,
|
|
92
|
+
|
|
93
|
+
// Setup files
|
|
94
|
+
setupFilesAfterEnv,
|
|
95
|
+
|
|
96
|
+
// Transform configuration
|
|
97
|
+
transform: {
|
|
98
|
+
'^.+\\.(ts|tsx)$': ['ts-jest', {
|
|
99
|
+
tsconfig: path.join(projectRoot, 'tsconfig.json'),
|
|
100
|
+
}],
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Transform ignore patterns - allow TypeScript from core's jest-setup
|
|
104
|
+
transformIgnorePatterns: [
|
|
105
|
+
'node_modules/(?!(uncrypto|better-auth|@noble|.*jose.*|remark.*|unified.*|@nextsparkjs/core/tests|.*\\.mjs$))',
|
|
106
|
+
'node_modules/\\.pnpm/(?!(.*uncrypto.*|.*better-auth.*|.*@noble.*|.*jose.*|.*remark.*|.*unified.*|@nextsparkjs.*core.*tests|.*\\.mjs$))',
|
|
107
|
+
],
|
|
108
|
+
|
|
109
|
+
// File extensions
|
|
110
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
|
111
|
+
|
|
112
|
+
// Test timeout
|
|
113
|
+
testTimeout: 10000,
|
|
114
|
+
|
|
115
|
+
// Verbose output
|
|
116
|
+
verbose: true,
|
|
117
|
+
|
|
118
|
+
// Force exit after tests complete
|
|
119
|
+
forceExit: true,
|
|
120
|
+
|
|
121
|
+
// Disable watchman for symlink support
|
|
122
|
+
watchman: false,
|
|
123
|
+
|
|
124
|
+
// Coverage output directory
|
|
125
|
+
coverageDirectory: path.join(themeTestsRoot, 'coverage'),
|
|
126
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
127
|
+
}
|