@nextsparkjs/core 0.1.0-beta.22 → 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.
Files changed (34) hide show
  1. package/dist/styles/classes.json +1 -1
  2. package/dist/styles/ui.css +1 -1
  3. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_selectors/auth.cy.ts +199 -0
  4. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +104 -0
  5. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_selectors/tasks.cy.ts +274 -0
  6. package/dist/templates/contents/themes/starter/tests/cypress/support/e2e.ts +36 -4
  7. package/dist/templates/contents/themes/starter/tests/cypress.config.ts +18 -15
  8. package/dist/templates/contents/themes/starter/tests/jest/__mocks__/jose.js +22 -0
  9. package/dist/templates/contents/themes/starter/tests/jest/__mocks__/next-server.js +56 -0
  10. package/dist/templates/contents/themes/starter/tests/jest/example.test.ts +87 -0
  11. package/dist/templates/contents/themes/starter/tests/jest/jest.config.cjs +90 -0
  12. package/dist/templates/contents/themes/starter/tests/jest/services/tasks.service.test.ts +547 -0
  13. package/dist/templates/contents/themes/starter/tests/jest/setup.ts +170 -0
  14. package/package.json +12 -12
  15. package/scripts/build/docs-registry.mjs +0 -0
  16. package/scripts/create-theme.mjs +0 -0
  17. package/scripts/deploy/release-version.mjs +0 -0
  18. package/scripts/deploy/vercel-deploy.mjs +0 -0
  19. package/scripts/dev/watch-plugins.mjs +0 -0
  20. package/scripts/maintenance/update-core.mjs +0 -0
  21. package/scripts/setup/npm-postinstall.mjs +0 -0
  22. package/scripts/setup/setup-claude.mjs +0 -0
  23. package/scripts/validation/check-imports.sh +0 -0
  24. package/templates/contents/themes/starter/tests/cypress/e2e/_selectors/auth.cy.ts +199 -0
  25. package/templates/contents/themes/starter/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +104 -0
  26. package/templates/contents/themes/starter/tests/cypress/e2e/_selectors/tasks.cy.ts +274 -0
  27. package/templates/contents/themes/starter/tests/cypress/support/e2e.ts +36 -4
  28. package/templates/contents/themes/starter/tests/cypress.config.ts +18 -15
  29. package/templates/contents/themes/starter/tests/jest/__mocks__/jose.js +22 -0
  30. package/templates/contents/themes/starter/tests/jest/__mocks__/next-server.js +56 -0
  31. package/templates/contents/themes/starter/tests/jest/example.test.ts +87 -0
  32. package/templates/contents/themes/starter/tests/jest/jest.config.cjs +90 -0
  33. package/templates/contents/themes/starter/tests/jest/services/tasks.service.test.ts +547 -0
  34. package/templates/contents/themes/starter/tests/jest/setup.ts +170 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Jest Setup for Starter 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
+ }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.22",
3
+ "version": "0.1.0-beta.24",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -223,16 +223,6 @@
223
223
  "README.md",
224
224
  "scripts"
225
225
  ],
226
- "scripts": {
227
- "build": "tsup && pnpm build:dts && pnpm build:ui-css",
228
- "build:js": "tsup",
229
- "build:dts": "tsc -p tsconfig.dts.json || echo '⚠️ DTS generation completed with some type errors (partial coverage)'",
230
- "build:ui-css": "node scripts/build/build-ui-css.mjs",
231
- "dev": "tsup --watch",
232
- "test": "jest",
233
- "test:watch": "jest --watch",
234
- "test:coverage": "jest --coverage"
235
- },
236
226
  "peerDependencies": {
237
227
  "next": ">=14.0.0",
238
228
  "react": ">=18.0.0",
@@ -317,5 +307,15 @@
317
307
  "tailwind-merge": "^3.3.1",
318
308
  "uuid": "^13.0.0",
319
309
  "zod": "^4.1.5"
310
+ },
311
+ "scripts": {
312
+ "build": "tsup && pnpm build:dts && pnpm build:ui-css",
313
+ "build:js": "tsup",
314
+ "build:dts": "tsc -p tsconfig.dts.json || echo '⚠️ DTS generation completed with some type errors (partial coverage)'",
315
+ "build:ui-css": "node scripts/build/build-ui-css.mjs",
316
+ "dev": "tsup --watch",
317
+ "test": "jest",
318
+ "test:watch": "jest --watch",
319
+ "test:coverage": "jest --coverage"
320
320
  }
321
- }
321
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,199 @@
1
+ /**
2
+ * UI Selectors Validation: Authentication
3
+ *
4
+ * This test validates that authentication component selectors exist in the DOM.
5
+ * This is a lightweight test that ONLY checks selector presence, not functionality.
6
+ *
7
+ * Purpose:
8
+ * - Validate AuthPOM selectors work correctly
9
+ * - Ensure all auth.* selectors are implemented in UI components
10
+ * - Catch missing data-cy attributes early
11
+ *
12
+ * Scope:
13
+ * - Navigate to auth pages (NO login required - public pages)
14
+ * - Assert elements exist in DOM (no form submissions)
15
+ * - Fast execution (< 30 seconds)
16
+ */
17
+
18
+ import { AuthPOM } from '../../src/core/AuthPOM'
19
+
20
+ describe('Auth Selectors Validation', { tags: ['@ui-selectors'] }, () => {
21
+ const auth = AuthPOM.create()
22
+
23
+ // ============================================
24
+ // LOGIN PAGE SELECTORS
25
+ // ============================================
26
+ describe('Login Page Selectors - Card & Structure', () => {
27
+ beforeEach(() => {
28
+ auth.visitLogin()
29
+ // Wait for card to be visible
30
+ cy.get(auth.selectors.loginCard, { timeout: 10000 }).should('be.visible')
31
+ })
32
+
33
+ it('should find login card', () => {
34
+ cy.get(auth.selectors.loginCard).should('exist')
35
+ })
36
+
37
+ it('should find login header', () => {
38
+ cy.get(auth.selectors.loginHeader).should('exist')
39
+ })
40
+
41
+ it('should find login footer', () => {
42
+ cy.get(auth.selectors.loginFooter).should('exist')
43
+ })
44
+
45
+ it('should find signup link', () => {
46
+ cy.get(auth.selectors.loginSignupLink).should('exist')
47
+ })
48
+
49
+ it('should find Google signin button', () => {
50
+ cy.get(auth.selectors.loginGoogle).should('exist')
51
+ })
52
+
53
+ it('should find show email button', () => {
54
+ cy.get(auth.selectors.loginShowEmail).should('exist')
55
+ })
56
+ })
57
+
58
+ describe('Login Page Selectors - Email Form', () => {
59
+ beforeEach(() => {
60
+ auth.visitLogin()
61
+ // Wait for card and click show email to reveal form
62
+ cy.get(auth.selectors.loginCard, { timeout: 10000 }).should('be.visible')
63
+ cy.get(auth.selectors.loginShowEmail).click()
64
+ cy.get(auth.selectors.loginForm, { timeout: 5000 }).should('be.visible')
65
+ })
66
+
67
+ it('should find login form', () => {
68
+ cy.get(auth.selectors.loginForm).should('exist')
69
+ })
70
+
71
+ it('should find login options', () => {
72
+ cy.get(auth.selectors.loginOptions).should('exist')
73
+ })
74
+
75
+ it('should find email input', () => {
76
+ cy.get(auth.selectors.loginEmail).should('exist')
77
+ })
78
+
79
+ it('should find password input', () => {
80
+ cy.get(auth.selectors.loginPassword).should('exist')
81
+ })
82
+
83
+ it('should find submit button', () => {
84
+ cy.get(auth.selectors.loginSubmit).should('exist')
85
+ })
86
+
87
+ it('should find forgot password link', () => {
88
+ cy.get(auth.selectors.loginForgotPassword).should('exist')
89
+ })
90
+
91
+ it('should find hide email button', () => {
92
+ cy.get(auth.selectors.loginHideEmail).should('exist')
93
+ })
94
+
95
+ it('should find remember checkbox', () => {
96
+ cy.get(auth.selectors.loginRememberCheckbox).should('exist')
97
+ })
98
+ })
99
+
100
+ // ============================================
101
+ // SIGNUP PAGE SELECTORS
102
+ // ============================================
103
+ describe('Signup Page Selectors', () => {
104
+ beforeEach(() => {
105
+ auth.visitSignup()
106
+ cy.get(auth.selectors.signupForm, { timeout: 10000 }).should('be.visible')
107
+ })
108
+
109
+ it('should find signup form', () => {
110
+ cy.get(auth.selectors.signupForm).should('exist')
111
+ })
112
+
113
+ it('should find first name input', () => {
114
+ cy.get(auth.selectors.signupFirstName).should('exist')
115
+ })
116
+
117
+ it('should find last name input', () => {
118
+ cy.get(auth.selectors.signupLastName).should('exist')
119
+ })
120
+
121
+ it('should find email input', () => {
122
+ cy.get(auth.selectors.signupEmail).should('exist')
123
+ })
124
+
125
+ it('should find password input', () => {
126
+ cy.get(auth.selectors.signupPassword).should('exist')
127
+ })
128
+
129
+ it('should find confirm password input', () => {
130
+ cy.get(auth.selectors.signupConfirmPassword).should('exist')
131
+ })
132
+
133
+ it('should find submit button', () => {
134
+ cy.get(auth.selectors.signupSubmit).should('exist')
135
+ })
136
+
137
+ it('should find login link', () => {
138
+ cy.get(auth.selectors.signupLoginLink).should('exist')
139
+ })
140
+
141
+ it('should find Google signup button', () => {
142
+ cy.get(auth.selectors.signupGoogle).should('exist')
143
+ })
144
+ })
145
+
146
+ // ============================================
147
+ // FORGOT PASSWORD PAGE SELECTORS
148
+ // ============================================
149
+ describe('Forgot Password Page Selectors', () => {
150
+ beforeEach(() => {
151
+ auth.visitForgotPassword()
152
+ cy.get(auth.selectors.forgotPasswordForm, { timeout: 10000 }).should('be.visible')
153
+ })
154
+
155
+ it('should find forgot password form', () => {
156
+ cy.get(auth.selectors.forgotPasswordForm).should('exist')
157
+ })
158
+
159
+ it('should find email input', () => {
160
+ cy.get(auth.selectors.forgotPasswordEmail).should('exist')
161
+ })
162
+
163
+ it('should find submit button', () => {
164
+ cy.get(auth.selectors.forgotPasswordSubmit).should('exist')
165
+ })
166
+
167
+ it('should find back to login link', () => {
168
+ cy.get(auth.selectors.forgotPasswordBack).should('exist')
169
+ })
170
+ })
171
+
172
+ // ============================================
173
+ // DEV KEYRING SELECTORS (Development only)
174
+ // ============================================
175
+ describe('Dev Keyring Selectors', () => {
176
+ beforeEach(() => {
177
+ auth.visitLogin()
178
+ cy.get(auth.selectors.loginCard, { timeout: 10000 }).should('be.visible')
179
+ })
180
+
181
+ it('should find dev keyring container', () => {
182
+ cy.get(auth.selectors.devKeyring).should('exist')
183
+ })
184
+
185
+ it('should find dev keyring trigger', () => {
186
+ cy.get(auth.selectors.devKeyringTrigger).should('exist')
187
+ })
188
+
189
+ it('should find dev keyring content when expanded', () => {
190
+ cy.get(auth.selectors.devKeyringTrigger).click()
191
+ cy.get(auth.selectors.devKeyringContent).should('be.visible')
192
+ })
193
+
194
+ it('should find at least one dev user option', () => {
195
+ cy.get(auth.selectors.devKeyringTrigger).click()
196
+ cy.get(auth.selectors.devKeyringUser(0)).should('exist')
197
+ })
198
+ })
199
+ })
@@ -0,0 +1,104 @@
1
+ /**
2
+ * UI Selectors Validation: Dashboard Navigation
3
+ *
4
+ * This test validates that dashboard navigation selectors exist in the DOM.
5
+ * This is a lightweight test that ONLY checks selector presence, not functionality.
6
+ *
7
+ * Purpose:
8
+ * - Validate dashboard shell and navigation selectors work correctly
9
+ * - Ensure core navigation components have data-cy attributes
10
+ * - Catch missing data-cy attributes early
11
+ *
12
+ * Scope:
13
+ * - Navigate to dashboard (requires login)
14
+ * - Assert elements exist in DOM (no form submissions)
15
+ * - Fast execution (< 30 seconds)
16
+ */
17
+
18
+ import { cySelector } from '../../src/selectors'
19
+
20
+ describe('Dashboard Navigation Selectors Validation', { tags: ['@ui-selectors'] }, () => {
21
+ beforeEach(() => {
22
+ cy.loginAsOwner()
23
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
24
+ cy.url().should('include', '/dashboard')
25
+ })
26
+
27
+ // ============================================
28
+ // DASHBOARD SHELL SELECTORS
29
+ // ============================================
30
+ describe('Dashboard Shell', () => {
31
+ it('should find dashboard container', () => {
32
+ cy.get(cySelector('dashboard.shell.container')).should('exist')
33
+ })
34
+ })
35
+
36
+ // ============================================
37
+ // TOPNAV SELECTORS
38
+ // ============================================
39
+ describe('Topnav Selectors', () => {
40
+ it('should find topnav header', () => {
41
+ cy.get(cySelector('dashboard.topnav.header')).should('exist')
42
+ })
43
+
44
+ it('should find sidebar toggle', () => {
45
+ cy.get(cySelector('dashboard.topnav.sidebarToggle')).should('exist')
46
+ })
47
+
48
+ it('should find logo', () => {
49
+ cy.get(cySelector('dashboard.topnav.logo')).should('exist')
50
+ })
51
+
52
+ it('should find actions container', () => {
53
+ cy.get(cySelector('dashboard.topnav.actions')).should('exist')
54
+ })
55
+
56
+ it('should find theme toggle', () => {
57
+ cy.get(cySelector('dashboard.topnav.themeToggle')).should('exist')
58
+ })
59
+
60
+ it('should find user menu trigger', () => {
61
+ cy.get(cySelector('dashboard.topnav.userMenuTrigger')).should('exist')
62
+ })
63
+
64
+ it('should find user menu when clicked', () => {
65
+ cy.get(cySelector('dashboard.topnav.userMenuTrigger')).click()
66
+ cy.get(cySelector('dashboard.topnav.userMenu')).should('be.visible')
67
+ })
68
+ })
69
+
70
+ // ============================================
71
+ // SIDEBAR SELECTORS
72
+ // ============================================
73
+ describe('Sidebar Selectors', () => {
74
+ it('should find sidebar container', () => {
75
+ cy.get(cySelector('dashboard.sidebar.container')).should('exist')
76
+ })
77
+
78
+ it('should find sidebar content', () => {
79
+ cy.get(cySelector('dashboard.sidebar.content')).should('exist')
80
+ })
81
+
82
+ it('should find sidebar footer', () => {
83
+ cy.get(cySelector('dashboard.sidebar.footer')).should('exist')
84
+ })
85
+ })
86
+
87
+ // ============================================
88
+ // NAVIGATION ENTITY LINKS
89
+ // ============================================
90
+ describe('Navigation Entity Links', () => {
91
+ it('should find tasks entity link', () => {
92
+ cy.get(cySelector('dashboard.navigation.entity', { slug: 'tasks' })).should('exist')
93
+ })
94
+ })
95
+
96
+ // ============================================
97
+ // GLOBAL SEARCH (if enabled)
98
+ // ============================================
99
+ describe('Global Search Selectors', () => {
100
+ it('should find search section in topnav', () => {
101
+ cy.get(cySelector('dashboard.topnav.searchSection')).should('exist')
102
+ })
103
+ })
104
+ })