@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 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.22",
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": "workspace:*",
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,11 @@
1
+ /**
2
+ * Mock for @nextsparkjs/core/lib/db
3
+ */
4
+
5
+ const queryWithRLS = jest.fn().mockResolvedValue([])
6
+ const mutateWithRLS = jest.fn().mockResolvedValue({ rowCount: 1 })
7
+
8
+ module.exports = {
9
+ queryWithRLS,
10
+ mutateWithRLS,
11
+ }
@@ -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
- expect(result).toBeInstanceOf(Uint8Array)
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
- expect(result).toBeInstanceOf(Uint8Array)
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
- expect(result).toBeInstanceOf(Uint8Array)
365
+ // Use constructor name check to avoid jsdom cross-realm issues
366
+ expect(result.constructor.name).toBe('Uint8Array')
364
367
  })
365
368
  })
366
369
  })