@nextsparkjs/theme-crm 0.1.0-beta.20 → 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 CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-crm",
3
- "version": "0.1.0-beta.20",
3
+ "version": "0.1.0-beta.24",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
7
7
  "requiredPlugins": [],
8
8
  "dependencies": {},
9
9
  "peerDependencies": {
10
+ "@nextsparkjs/core": "0.1.0-beta.24",
10
11
  "lucide-react": "^0.539.0",
11
12
  "next": "^15.0.0",
12
13
  "next-intl": "^4.0.0",
13
14
  "react": "^19.0.0",
14
15
  "react-dom": "^19.0.0",
15
- "zod": "^4.0.0",
16
- "@nextsparkjs/core": "0.1.0-beta.23"
16
+ "zod": "^4.0.0"
17
17
  },
18
18
  "nextspark": {
19
19
  "type": "theme",
@@ -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
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Jest Setup for CRM Theme
3
+ *
4
+ * This file provides complete Jest setup for npm mode.
5
+ * It includes all necessary polyfills and mocks.
6
+ */
7
+
8
+ import '@testing-library/jest-dom'
9
+ import { TextEncoder, TextDecoder } from 'util'
10
+
11
+ // Polyfill TextEncoder/TextDecoder for jsdom
12
+ global.TextEncoder = TextEncoder
13
+ global.TextDecoder = TextDecoder as typeof global.TextDecoder
14
+
15
+ // Polyfill Web APIs for Next.js server components testing
16
+ class MockHeaders {
17
+ private _headers: Map<string, string> = new Map()
18
+
19
+ constructor(init?: HeadersInit) {
20
+ if (init) {
21
+ if (init instanceof MockHeaders) {
22
+ init._headers.forEach((value, key) => this._headers.set(key, value))
23
+ } else if (Array.isArray(init)) {
24
+ init.forEach(([key, value]) => this._headers.set(key.toLowerCase(), value))
25
+ } else {
26
+ Object.entries(init).forEach(([key, value]) => this._headers.set(key.toLowerCase(), value))
27
+ }
28
+ }
29
+ }
30
+
31
+ get(name: string) { return this._headers.get(name.toLowerCase()) || null }
32
+ set(name: string, value: string) { this._headers.set(name.toLowerCase(), value) }
33
+ has(name: string) { return this._headers.has(name.toLowerCase()) }
34
+ delete(name: string) { this._headers.delete(name.toLowerCase()) }
35
+ forEach(cb: (value: string, key: string) => void) { this._headers.forEach(cb) }
36
+ entries() { return this._headers.entries() }
37
+ keys() { return this._headers.keys() }
38
+ values() { return this._headers.values() }
39
+ }
40
+
41
+ class MockRequest {
42
+ url: string
43
+ method: string
44
+ headers: MockHeaders
45
+ private _body: any
46
+
47
+ constructor(input: string | URL, init?: RequestInit) {
48
+ this.url = typeof input === 'string' ? input : input.toString()
49
+ this.method = init?.method || 'GET'
50
+ this.headers = new MockHeaders(init?.headers as HeadersInit)
51
+ this._body = init?.body
52
+ }
53
+
54
+ async json() {
55
+ if (!this._body) return {}
56
+ return typeof this._body === 'string' ? JSON.parse(this._body) : this._body
57
+ }
58
+
59
+ async text() {
60
+ return this._body?.toString() || ''
61
+ }
62
+
63
+ clone() {
64
+ return new MockRequest(this.url, {
65
+ method: this.method,
66
+ headers: Object.fromEntries(this.headers.entries()),
67
+ body: this._body,
68
+ })
69
+ }
70
+ }
71
+
72
+ class MockResponse {
73
+ body: any
74
+ status: number
75
+ statusText: string
76
+ headers: MockHeaders
77
+ ok: boolean
78
+
79
+ constructor(body?: any, init?: ResponseInit) {
80
+ this.body = body
81
+ this.status = init?.status || 200
82
+ this.statusText = init?.statusText || 'OK'
83
+ this.headers = new MockHeaders(init?.headers as HeadersInit)
84
+ this.ok = this.status >= 200 && this.status < 300
85
+ }
86
+
87
+ async json() {
88
+ if (!this.body) return {}
89
+ return typeof this.body === 'string' ? JSON.parse(this.body) : this.body
90
+ }
91
+
92
+ async text() {
93
+ return this.body?.toString() || ''
94
+ }
95
+
96
+ clone() {
97
+ return new MockResponse(this.body, {
98
+ status: this.status,
99
+ statusText: this.statusText,
100
+ headers: Object.fromEntries(this.headers.entries()),
101
+ })
102
+ }
103
+
104
+ static json(data: any, init?: ResponseInit) {
105
+ return new MockResponse(JSON.stringify(data), {
106
+ ...init,
107
+ headers: { 'Content-Type': 'application/json', ...(init?.headers || {}) as object },
108
+ })
109
+ }
110
+ }
111
+
112
+ // Assign to global scope
113
+ global.Headers = MockHeaders as any
114
+ global.Request = MockRequest as any
115
+ global.Response = MockResponse as any
116
+
117
+ // Mock fetch
118
+ global.fetch = jest.fn().mockResolvedValue(new MockResponse('{}', { status: 200 }))
119
+
120
+ // Mock Web Crypto API
121
+ const crypto = require('crypto')
122
+ Object.defineProperty(global, 'crypto', {
123
+ value: {
124
+ getRandomValues: (arr: Uint8Array) => crypto.randomFillSync(arr),
125
+ randomUUID: () => crypto.randomUUID(),
126
+ subtle: {
127
+ digest: async (algorithm: string, data: BufferSource) => {
128
+ const hash = crypto.createHash(algorithm.toLowerCase().replace('-', ''))
129
+ hash.update(Buffer.from(data as ArrayBuffer))
130
+ return hash.digest().buffer
131
+ },
132
+ encrypt: jest.fn(),
133
+ decrypt: jest.fn(),
134
+ sign: jest.fn(),
135
+ verify: jest.fn(),
136
+ generateKey: jest.fn(),
137
+ importKey: jest.fn(),
138
+ exportKey: jest.fn(),
139
+ },
140
+ },
141
+ })
142
+
143
+ // Mock matchMedia for component tests with media queries
144
+ Object.defineProperty(window, 'matchMedia', {
145
+ writable: true,
146
+ value: jest.fn().mockImplementation((query) => ({
147
+ matches: false,
148
+ media: query,
149
+ onchange: null,
150
+ addListener: jest.fn(),
151
+ removeListener: jest.fn(),
152
+ addEventListener: jest.fn(),
153
+ removeEventListener: jest.fn(),
154
+ dispatchEvent: jest.fn(),
155
+ })),
156
+ })
157
+
158
+ // Mock ResizeObserver
159
+ global.ResizeObserver = jest.fn().mockImplementation(() => ({
160
+ observe: jest.fn(),
161
+ unobserve: jest.fn(),
162
+ disconnect: jest.fn(),
163
+ }))
164
+
165
+ // Mock IntersectionObserver
166
+ global.IntersectionObserver = jest.fn().mockImplementation(() => ({
167
+ observe: jest.fn(),
168
+ unobserve: jest.fn(),
169
+ disconnect: jest.fn(),
170
+ }))