@nextsparkjs/theme-default 0.1.0-beta.22 → 0.1.0-beta.25
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/LICENSE +21 -0
- package/package.json +3 -3
- package/tests/jest/__mocks__/@nextsparkjs/core/components/ui/badge.js +16 -0
- package/tests/jest/__mocks__/@nextsparkjs/core/lib/db.js +11 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +155 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/theme-registry.ts +68 -0
- package/tests/jest/__mocks__/jose.js +22 -0
- package/tests/jest/__mocks__/next/image.js +15 -0
- package/tests/jest/__mocks__/next-server.js +56 -0
- package/tests/jest/jest.config.cjs +138 -0
- package/tests/jest/langchain/streaming.test.ts +6 -3
- package/tests/jest/services/tasks.service.test.ts +707 -0
- package/tests/jest/setup.ts +170 -0
- package/tests/jest/tsconfig.jest.json +6 -0
- package/tests/jest/config/role-config.test.ts +0 -529
- package/tests/jest/jest.config.ts +0 -81
- package/tests/jest/user-roles/role-helpers.test.ts +0 -432
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 NextSpark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/theme-default",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.25",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./config/theme.config.ts",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
],
|
|
10
10
|
"dependencies": {},
|
|
11
11
|
"peerDependencies": {
|
|
12
|
-
"@nextsparkjs/core": "
|
|
12
|
+
"@nextsparkjs/core": "0.1.0-beta.24",
|
|
13
13
|
"@tanstack/react-query": "^5.0.0",
|
|
14
14
|
"lucide-react": "^0.539.0",
|
|
15
15
|
"next": "^15.0.0",
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
"type": "theme",
|
|
24
24
|
"name": "default"
|
|
25
25
|
}
|
|
26
|
-
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @nextsparkjs/core/components/ui/badge
|
|
3
|
+
*/
|
|
4
|
+
const React = require('react')
|
|
5
|
+
|
|
6
|
+
const Badge = ({ children, variant = 'default', className = '', ...props }) => {
|
|
7
|
+
return React.createElement('span', {
|
|
8
|
+
className: `badge badge-${variant} ${className}`.trim(),
|
|
9
|
+
'data-testid': 'badge',
|
|
10
|
+
...props,
|
|
11
|
+
}, children)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = { Badge }
|
|
15
|
+
module.exports.Badge = Badge
|
|
16
|
+
module.exports.default = Badge
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Permissions Registry for Jest tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type Permission = string
|
|
6
|
+
|
|
7
|
+
export interface ResolvedPermission {
|
|
8
|
+
id: string
|
|
9
|
+
label: string
|
|
10
|
+
description: string
|
|
11
|
+
category: string
|
|
12
|
+
roles: string[]
|
|
13
|
+
dangerous: boolean
|
|
14
|
+
source: 'core' | 'theme'
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PermissionUISection {
|
|
19
|
+
id: string
|
|
20
|
+
label: string
|
|
21
|
+
description: string
|
|
22
|
+
categories: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RolesConfig {
|
|
26
|
+
additionalRoles?: string[]
|
|
27
|
+
hierarchy?: Record<string, number>
|
|
28
|
+
displayNames?: Record<string, string>
|
|
29
|
+
descriptions?: Record<string, string>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Mock permissions data
|
|
33
|
+
export const ALL_RESOLVED_PERMISSIONS: ResolvedPermission[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'tasks.create',
|
|
36
|
+
label: 'Create tasks',
|
|
37
|
+
description: 'Can create new tasks',
|
|
38
|
+
category: 'Tasks',
|
|
39
|
+
roles: ['owner', 'admin', 'member'],
|
|
40
|
+
dangerous: false,
|
|
41
|
+
source: 'theme',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'tasks.read',
|
|
45
|
+
label: 'View tasks',
|
|
46
|
+
description: 'Can view task details',
|
|
47
|
+
category: 'Tasks',
|
|
48
|
+
roles: ['owner', 'admin', 'member'],
|
|
49
|
+
dangerous: false,
|
|
50
|
+
source: 'theme',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'tasks.update',
|
|
54
|
+
label: 'Edit tasks',
|
|
55
|
+
description: 'Can modify task information',
|
|
56
|
+
category: 'Tasks',
|
|
57
|
+
roles: ['owner', 'admin', 'member'],
|
|
58
|
+
dangerous: false,
|
|
59
|
+
source: 'theme',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'tasks.delete',
|
|
63
|
+
label: 'Delete tasks',
|
|
64
|
+
description: 'Can delete tasks',
|
|
65
|
+
category: 'Tasks',
|
|
66
|
+
roles: ['owner', 'admin'],
|
|
67
|
+
dangerous: true,
|
|
68
|
+
source: 'theme',
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export const ALL_PERMISSIONS: Permission[] = ALL_RESOLVED_PERMISSIONS.map(p => p.id)
|
|
73
|
+
|
|
74
|
+
export const ALL_PERMISSIONS_SET = new Set(ALL_PERMISSIONS)
|
|
75
|
+
|
|
76
|
+
export const AVAILABLE_ROLES: readonly string[] = ['owner', 'admin', 'member', 'viewer']
|
|
77
|
+
|
|
78
|
+
export const ROLE_HIERARCHY: Record<string, number> = {
|
|
79
|
+
owner: 100,
|
|
80
|
+
admin: 50,
|
|
81
|
+
member: 10,
|
|
82
|
+
viewer: 1,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const ROLE_DISPLAY_NAMES: Record<string, string> = {
|
|
86
|
+
owner: 'common.teamRoles.owner',
|
|
87
|
+
admin: 'common.teamRoles.admin',
|
|
88
|
+
member: 'common.teamRoles.member',
|
|
89
|
+
viewer: 'common.teamRoles.viewer',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const ROLE_DESCRIPTIONS: Record<string, string> = {
|
|
93
|
+
owner: 'Full team control, cannot be removed',
|
|
94
|
+
admin: 'Manage team members and settings',
|
|
95
|
+
member: 'Standard team access',
|
|
96
|
+
viewer: 'Read-only access to team resources',
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const CUSTOM_ROLES: RolesConfig = {
|
|
100
|
+
additionalRoles: [],
|
|
101
|
+
hierarchy: {},
|
|
102
|
+
displayNames: {},
|
|
103
|
+
descriptions: {},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const PERMISSIONS_BY_ROLE: Record<string, Set<Permission>> = {
|
|
107
|
+
owner: new Set(ALL_PERMISSIONS),
|
|
108
|
+
admin: new Set(['tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete']),
|
|
109
|
+
member: new Set(['tasks.create', 'tasks.read', 'tasks.update']),
|
|
110
|
+
viewer: new Set(['tasks.read']),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const ROLE_PERMISSIONS_ARRAY: Record<string, Permission[]> = {
|
|
114
|
+
owner: [...ALL_PERMISSIONS],
|
|
115
|
+
admin: ['tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete'],
|
|
116
|
+
member: ['tasks.create', 'tasks.read', 'tasks.update'],
|
|
117
|
+
viewer: ['tasks.read'],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const PERMISSIONS_BY_CATEGORY: Record<string, ResolvedPermission[]> = {
|
|
121
|
+
Tasks: ALL_RESOLVED_PERMISSIONS,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const FULL_MATRIX: Record<Permission, Record<string, boolean>> = {}
|
|
125
|
+
for (const perm of ALL_RESOLVED_PERMISSIONS) {
|
|
126
|
+
FULL_MATRIX[perm.id] = {}
|
|
127
|
+
for (const role of AVAILABLE_ROLES) {
|
|
128
|
+
FULL_MATRIX[perm.id][role] = PERMISSIONS_BY_ROLE[role]?.has(perm.id) ?? false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const UI_SECTIONS: PermissionUISection[] = [
|
|
133
|
+
{
|
|
134
|
+
id: 'entities',
|
|
135
|
+
label: 'Entities',
|
|
136
|
+
description: 'Entity-specific permissions',
|
|
137
|
+
categories: ['Tasks'],
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
export const TEAM_PERMISSIONS_RAW = []
|
|
142
|
+
export const TEAM_PERMISSIONS_BY_ROLE: Record<string, string[]> = {}
|
|
143
|
+
|
|
144
|
+
export const PERMISSIONS_METADATA = {
|
|
145
|
+
totalPermissions: ALL_PERMISSIONS.length,
|
|
146
|
+
corePermissions: 0,
|
|
147
|
+
teamPermissions: 0,
|
|
148
|
+
featurePermissions: 0,
|
|
149
|
+
entityPermissions: ALL_RESOLVED_PERMISSIONS.length,
|
|
150
|
+
customRoles: 0,
|
|
151
|
+
availableRoles: AVAILABLE_ROLES.length,
|
|
152
|
+
categories: Object.keys(PERMISSIONS_BY_CATEGORY).length,
|
|
153
|
+
generatedAt: new Date().toISOString(),
|
|
154
|
+
theme: 'default',
|
|
155
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Theme Registry for Jest tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ThemeEntity {
|
|
6
|
+
name: string
|
|
7
|
+
source: string
|
|
8
|
+
config: any
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ThemeRouteFile {
|
|
12
|
+
path: string
|
|
13
|
+
type: 'api' | 'page'
|
|
14
|
+
methods?: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ThemeRegistryEntry {
|
|
18
|
+
name: string
|
|
19
|
+
slug: string
|
|
20
|
+
displayName: string
|
|
21
|
+
description: string
|
|
22
|
+
version: string
|
|
23
|
+
entities: ThemeEntity[]
|
|
24
|
+
routes: ThemeRouteFile[]
|
|
25
|
+
middlewares: any[]
|
|
26
|
+
plugins: string[]
|
|
27
|
+
path: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const THEME_REGISTRY: Record<string, ThemeRegistryEntry> = {
|
|
31
|
+
default: {
|
|
32
|
+
name: 'default',
|
|
33
|
+
slug: 'default',
|
|
34
|
+
displayName: 'Default Theme',
|
|
35
|
+
description: 'Default theme for NextSpark',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
entities: [
|
|
38
|
+
{ name: 'posts', source: 'default', config: { name: 'posts', label: 'Posts' } }
|
|
39
|
+
],
|
|
40
|
+
routes: [
|
|
41
|
+
{ path: '/api/v1/posts', type: 'api', methods: ['GET', 'POST'] }
|
|
42
|
+
],
|
|
43
|
+
middlewares: [],
|
|
44
|
+
plugins: [],
|
|
45
|
+
path: 'contents/themes/default',
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type ThemeName = keyof typeof THEME_REGISTRY | string
|
|
50
|
+
|
|
51
|
+
export const THEME_METADATA = {
|
|
52
|
+
generated: new Date().toISOString(),
|
|
53
|
+
activeTheme: 'default',
|
|
54
|
+
totalThemes: Object.keys(THEME_REGISTRY).length,
|
|
55
|
+
totalThemeEntities: 1,
|
|
56
|
+
totalThemeRoutes: 1,
|
|
57
|
+
themes: Object.keys(THEME_REGISTRY),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Helper function for getting active theme
|
|
61
|
+
export function getActiveTheme(): ThemeRegistryEntry | undefined {
|
|
62
|
+
return THEME_REGISTRY[THEME_METADATA.activeTheme]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Helper function for getting theme by name
|
|
66
|
+
export function getTheme(name: string): ThemeRegistryEntry | undefined {
|
|
67
|
+
return THEME_REGISTRY[name]
|
|
68
|
+
}
|
|
@@ -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,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for next/image
|
|
3
|
+
*/
|
|
4
|
+
const React = require('react')
|
|
5
|
+
|
|
6
|
+
const Image = ({ src, alt, ...props }) => {
|
|
7
|
+
return React.createElement('img', {
|
|
8
|
+
src: typeof src === 'object' ? src.src : src,
|
|
9
|
+
alt: alt || '',
|
|
10
|
+
...props,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = Image
|
|
15
|
+
module.exports.default = Image
|
|
@@ -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,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Configuration for Default 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/default/tests/jest -> apps/dev (via symlink in contents/)
|
|
16
|
+
// In npm mode: contents/themes/default/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: Mock core UI components (ESM can't be transformed by Jest)
|
|
31
|
+
'^@nextsparkjs/core/components/ui/badge$': path.join(themeTestsRoot, '__mocks__/@nextsparkjs/core/components/ui/badge.js'),
|
|
32
|
+
// NPM mode: Mock core lib modules that are ESM
|
|
33
|
+
'^@nextsparkjs/core/lib/db$': path.join(themeTestsRoot, '__mocks__/@nextsparkjs/core/lib/db.js'),
|
|
34
|
+
// NPM mode: explicitly resolve @nextsparkjs/core subpaths to dist directory
|
|
35
|
+
// Jest doesn't respect package.json exports, so we map directly to dist files
|
|
36
|
+
'^@nextsparkjs/core/lib/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/lib/$1',
|
|
37
|
+
'^@nextsparkjs/core/hooks/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/hooks/$1',
|
|
38
|
+
'^@nextsparkjs/core/components/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/components/$1',
|
|
39
|
+
'^@nextsparkjs/core/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/$1',
|
|
40
|
+
'^@nextsparkjs/core$': '<rootDir>/node_modules/@nextsparkjs/core/dist/index.js',
|
|
41
|
+
'^@nextsparkjs/registries/(.*)$': path.join(themeTestsRoot, '__mocks__/@nextsparkjs/registries/$1'),
|
|
42
|
+
'^@/contents/(.*)$': '<rootDir>/contents/$1',
|
|
43
|
+
'^@/entities/(.*)$': '<rootDir>/contents/entities/$1',
|
|
44
|
+
'^@/plugins/(.*)$': '<rootDir>/contents/plugins/$1',
|
|
45
|
+
'^@/themes/(.*)$': '<rootDir>/contents/themes/$1',
|
|
46
|
+
'^@/(.*)$': '<rootDir>/$1',
|
|
47
|
+
// Mocks from theme-local folder
|
|
48
|
+
'next/server': path.join(themeTestsRoot, '__mocks__/next-server.js'),
|
|
49
|
+
'next/image': path.join(themeTestsRoot, '__mocks__/next/image.js'),
|
|
50
|
+
'^jose$': path.join(themeTestsRoot, '__mocks__/jose.js'),
|
|
51
|
+
'^jose/(.*)$': path.join(themeTestsRoot, '__mocks__/jose.js'),
|
|
52
|
+
}
|
|
53
|
+
: {
|
|
54
|
+
// Monorepo mode: resolve from packages/core/src (rootDir is apps/dev)
|
|
55
|
+
'^@nextsparkjs/core/(.*)$': '<rootDir>/../../packages/core/src/$1',
|
|
56
|
+
'^@nextsparkjs/core$': '<rootDir>/../../packages/core/src',
|
|
57
|
+
'^@nextsparkjs/registries/(.*)$': '<rootDir>/../../packages/core/tests/jest/__mocks__/@nextsparkjs/registries/$1',
|
|
58
|
+
'^@/contents/(.*)$': '<rootDir>/contents/$1',
|
|
59
|
+
'^@/entities/(.*)$': '<rootDir>/contents/entities/$1',
|
|
60
|
+
'^@/plugins/(.*)$': '<rootDir>/contents/plugins/$1',
|
|
61
|
+
'^@/themes/(.*)$': '<rootDir>/contents/themes/$1',
|
|
62
|
+
'^@/(.*)$': '<rootDir>/$1',
|
|
63
|
+
// Mocks from core
|
|
64
|
+
'next/server': '<rootDir>/../../packages/core/tests/jest/__mocks__/next-server.js',
|
|
65
|
+
'^jose$': '<rootDir>/../../packages/core/tests/jest/__mocks__/jose.js',
|
|
66
|
+
'^jose/(.*)$': '<rootDir>/../../packages/core/tests/jest/__mocks__/jose.js',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Setup files based on mode
|
|
70
|
+
const setupFilesAfterEnv = isNpmMode
|
|
71
|
+
? [
|
|
72
|
+
// NPM mode: use theme's local setup only (it includes everything needed)
|
|
73
|
+
path.join(themeTestsRoot, 'setup.ts'),
|
|
74
|
+
]
|
|
75
|
+
: [
|
|
76
|
+
// Monorepo mode: use local core setup (rootDir is apps/dev)
|
|
77
|
+
'<rootDir>/../../packages/core/tests/jest/setup.ts',
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
/** @type {import('jest').Config} */
|
|
81
|
+
module.exports = {
|
|
82
|
+
displayName: 'theme-default',
|
|
83
|
+
rootDir: projectRoot,
|
|
84
|
+
|
|
85
|
+
// Use roots to explicitly set test location (handles symlinks better)
|
|
86
|
+
roots: [themeTestsRoot],
|
|
87
|
+
|
|
88
|
+
// Test file patterns
|
|
89
|
+
testMatch: [
|
|
90
|
+
'**/*.{test,spec}.{js,ts,tsx}',
|
|
91
|
+
],
|
|
92
|
+
testPathIgnorePatterns: [
|
|
93
|
+
'<rootDir>/node_modules/',
|
|
94
|
+
'<rootDir>/.next/',
|
|
95
|
+
],
|
|
96
|
+
|
|
97
|
+
// Preset and environment
|
|
98
|
+
preset: 'ts-jest',
|
|
99
|
+
testEnvironment: 'jsdom',
|
|
100
|
+
|
|
101
|
+
// Module resolution
|
|
102
|
+
moduleNameMapper,
|
|
103
|
+
|
|
104
|
+
// Setup files
|
|
105
|
+
setupFilesAfterEnv,
|
|
106
|
+
|
|
107
|
+
// Transform configuration
|
|
108
|
+
transform: {
|
|
109
|
+
'^.+\\.(ts|tsx)$': ['ts-jest', {
|
|
110
|
+
tsconfig: path.join(themeTestsRoot, 'tsconfig.jest.json'),
|
|
111
|
+
}],
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Transform ignore patterns - allow TypeScript from core's jest-setup
|
|
115
|
+
transformIgnorePatterns: [
|
|
116
|
+
'node_modules/(?!(uncrypto|better-auth|@noble|.*jose.*|remark.*|unified.*|@nextsparkjs/core/tests|.*\\.mjs$))',
|
|
117
|
+
'node_modules/\\.pnpm/(?!(.*uncrypto.*|.*better-auth.*|.*@noble.*|.*jose.*|.*remark.*|.*unified.*|@nextsparkjs.*core.*tests|.*\\.mjs$))',
|
|
118
|
+
],
|
|
119
|
+
|
|
120
|
+
// File extensions
|
|
121
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
|
122
|
+
|
|
123
|
+
// Test timeout
|
|
124
|
+
testTimeout: 10000,
|
|
125
|
+
|
|
126
|
+
// Verbose output
|
|
127
|
+
verbose: true,
|
|
128
|
+
|
|
129
|
+
// Force exit after tests complete
|
|
130
|
+
forceExit: true,
|
|
131
|
+
|
|
132
|
+
// Disable watchman for symlink support
|
|
133
|
+
watchman: false,
|
|
134
|
+
|
|
135
|
+
// Coverage output directory
|
|
136
|
+
coverageDirectory: path.join(themeTestsRoot, 'coverage'),
|
|
137
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
138
|
+
}
|
|
@@ -127,7 +127,8 @@ describe('Streaming Service', () => {
|
|
|
127
127
|
const chunk: StreamChunk = { type: 'token', content: 'test' }
|
|
128
128
|
const result = encoder.encode(chunk)
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
// Use constructor name check to avoid jsdom cross-realm issues
|
|
131
|
+
expect(result.constructor.name).toBe('Uint8Array')
|
|
131
132
|
})
|
|
132
133
|
})
|
|
133
134
|
|
|
@@ -142,7 +143,8 @@ describe('Streaming Service', () => {
|
|
|
142
143
|
it('should return Uint8Array', () => {
|
|
143
144
|
const result = encoder.encodeDone()
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
// Use constructor name check to avoid jsdom cross-realm issues
|
|
147
|
+
expect(result.constructor.name).toBe('Uint8Array')
|
|
146
148
|
})
|
|
147
149
|
})
|
|
148
150
|
})
|
|
@@ -360,7 +362,8 @@ describe('Streaming Service', () => {
|
|
|
360
362
|
|
|
361
363
|
expect(results).toHaveLength(100)
|
|
362
364
|
results.forEach(result => {
|
|
363
|
-
|
|
365
|
+
// Use constructor name check to avoid jsdom cross-realm issues
|
|
366
|
+
expect(result.constructor.name).toBe('Uint8Array')
|
|
364
367
|
})
|
|
365
368
|
})
|
|
366
369
|
})
|