@nextsparkjs/theme-default 0.1.0-beta.68 → 0.1.0-beta.72

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.
@@ -72,17 +72,16 @@ export const APP_CONFIG_OVERRIDES = {
72
72
  // =============================================================================
73
73
  api: {
74
74
  cors: {
75
- allowedOrigins: {
75
+ // Theme-specific CORS origins (extends core defaults, does not replace)
76
+ // Core already includes: localhost:3000, localhost:5173, and their 127.0.0.1 variants
77
+ additionalOrigins: {
76
78
  development: [
77
- 'http://localhost:3000',
78
- 'http://localhost:5173',
79
- 'http://127.0.0.1:3000',
80
- 'http://127.0.0.1:5173',
81
- // Project specific development origins
79
+ 'http://localhost:8081', // Expo mobile web
80
+ // Add theme-specific development origins here
82
81
  ],
83
82
  production: [
84
- // Add Project production domains
85
- // 'https://boilerplate-themoneyteam.xyz',
83
+ // Add theme-specific production domains
84
+ // 'https://mobile.boilerplate-themoneyteam.xyz',
86
85
  ],
87
86
  },
88
87
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-default",
3
- "version": "0.1.0-beta.68",
3
+ "version": "0.1.0-beta.72",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
@@ -9,6 +9,8 @@
9
9
  ],
10
10
  "dependencies": {},
11
11
  "peerDependencies": {
12
+ "@nextsparkjs/core": "workspace:*",
13
+ "@nextsparkjs/testing": "workspace:*",
12
14
  "@tanstack/react-query": "^5.0.0",
13
15
  "lucide-react": "^0.539.0",
14
16
  "next": "^15.0.0",
@@ -16,12 +18,10 @@
16
18
  "react": "^19.0.0",
17
19
  "react-dom": "^19.0.0",
18
20
  "react-markdown": "^10.1.0",
19
- "zod": "^4.0.0",
20
- "@nextsparkjs/core": "0.1.0-beta.68",
21
- "@nextsparkjs/testing": "0.1.0-beta.68"
21
+ "zod": "^4.0.0"
22
22
  },
23
23
  "nextspark": {
24
24
  "type": "theme",
25
25
  "name": "default"
26
26
  }
27
- }
27
+ }
@@ -17,6 +17,9 @@
17
17
  @import "@nextsparkjs/core/styles/utilities.css";
18
18
  @import "@nextsparkjs/core/styles/docs.css";
19
19
  @source "../../../**/*.{js,ts,jsx,tsx}";
20
+ /* Core package: monorepo (short path) + npm projects (long path) */
21
+ @source "../node_modules/@nextsparkjs/core/dist/**/*.js";
22
+ @source "../../../../node_modules/@nextsparkjs/core/dist/**/*.js";
20
23
 
21
24
  /* =============================================
22
25
  DEFAULT THEME - LIGHT MODE
@@ -659,4 +659,125 @@ describe('Block Editor Selectors Validation', {
659
659
  })
660
660
  })
661
661
  })
662
+
663
+ // ===========================================================================
664
+ // SEL_BE_010: CONFIG PANEL SEO & CUSTOM FIELDS SELECTORS (v2.1)
665
+ // ===========================================================================
666
+ describe('SEL_BE_010: Config Panel SEO & Custom Fields Selectors', { tags: '@SEL_BE_010' }, () => {
667
+ let testPageId: string
668
+
669
+ before(() => {
670
+ loginAsDefaultDeveloper()
671
+ cy.request({
672
+ method: 'POST',
673
+ url: '/api/v1/pages',
674
+ headers: {
675
+ 'x-team-id': DEVELOPER_TEAM_ID,
676
+ 'Content-Type': 'application/json'
677
+ },
678
+ body: {
679
+ title: `SEO Test Page ${Date.now()}`,
680
+ slug: `seo-test-${Date.now()}`,
681
+ locale: 'en',
682
+ published: false,
683
+ blocks: []
684
+ }
685
+ }).then((response) => {
686
+ testPageId = response.body.data.id
687
+ })
688
+ })
689
+
690
+ beforeEach(() => {
691
+ pom.visitEdit(testPageId)
692
+ pom.waitForEditor()
693
+ cy.get(pom.editorSelectors.viewSettings).click()
694
+ })
695
+
696
+ after(() => {
697
+ if (testPageId) {
698
+ cy.request({
699
+ method: 'DELETE',
700
+ url: `/api/v1/pages/${testPageId}`,
701
+ headers: { 'x-team-id': DEVELOPER_TEAM_ID },
702
+ failOnStatusCode: false
703
+ })
704
+ }
705
+ })
706
+
707
+ // SEO Section tests
708
+ describe('SEO Section', () => {
709
+ it('SEL_BE_010_01: should find SEO section container', { tags: '@SEL_BE_010_01' }, () => {
710
+ cy.get(pom.editorSelectors.configSeoSectionContainer).should('exist').and('be.visible')
711
+ })
712
+
713
+ it('SEL_BE_010_02: should find SEO section trigger', { tags: '@SEL_BE_010_02' }, () => {
714
+ cy.get(pom.editorSelectors.configSeoSectionTrigger).should('exist').and('be.visible')
715
+ })
716
+
717
+ it('SEL_BE_010_03: should find SEO section content after click', { tags: '@SEL_BE_010_03' }, () => {
718
+ cy.get(pom.editorSelectors.configSeoSectionTrigger).click()
719
+ cy.get(pom.editorSelectors.configSeoSectionContent).should('exist').and('be.visible')
720
+ })
721
+
722
+ it('SEL_BE_010_04: should find meta title input', { tags: '@SEL_BE_010_04' }, () => {
723
+ cy.get(pom.editorSelectors.configSeoSectionTrigger).click()
724
+ cy.get(pom.editorSelectors.configSeoMetaTitle).should('exist').and('be.visible')
725
+ })
726
+
727
+ it('SEL_BE_010_05: should find meta description input', { tags: '@SEL_BE_010_05' }, () => {
728
+ cy.get(pom.editorSelectors.configSeoSectionTrigger).click()
729
+ cy.get(pom.editorSelectors.configSeoMetaDescription).should('exist').and('be.visible')
730
+ })
731
+
732
+ it('SEL_BE_010_06: should find meta keywords input', { tags: '@SEL_BE_010_06' }, () => {
733
+ cy.get(pom.editorSelectors.configSeoSectionTrigger).click()
734
+ cy.get(pom.editorSelectors.configSeoMetaKeywords).should('exist').and('be.visible')
735
+ })
736
+
737
+ it('SEL_BE_010_07: should find OG image input', { tags: '@SEL_BE_010_07' }, () => {
738
+ cy.get(pom.editorSelectors.configSeoSectionTrigger).click()
739
+ cy.get(pom.editorSelectors.configSeoOgImage).should('exist').and('be.visible')
740
+ })
741
+ })
742
+
743
+ // Custom Fields Section tests
744
+ describe('Custom Fields Section', () => {
745
+ it('SEL_BE_010_08: should find custom fields section container', { tags: '@SEL_BE_010_08' }, () => {
746
+ cy.get(pom.editorSelectors.configCustomFieldsContainer).should('exist').and('be.visible')
747
+ })
748
+
749
+ it('SEL_BE_010_09: should find custom fields section trigger', { tags: '@SEL_BE_010_09' }, () => {
750
+ cy.get(pom.editorSelectors.configCustomFieldsTrigger).should('exist').and('be.visible')
751
+ })
752
+
753
+ it('SEL_BE_010_10: should find custom fields content after click', { tags: '@SEL_BE_010_10' }, () => {
754
+ cy.get(pom.editorSelectors.configCustomFieldsTrigger).click()
755
+ cy.get(pom.editorSelectors.configCustomFieldsContent).should('exist').and('be.visible')
756
+ })
757
+
758
+ it('SEL_BE_010_11: should find add custom field button', { tags: '@SEL_BE_010_11' }, () => {
759
+ cy.get(pom.editorSelectors.configCustomFieldsTrigger).click()
760
+ cy.get(pom.editorSelectors.configCustomFieldsAddBtn).should('exist').and('be.visible')
761
+ })
762
+
763
+ describe('With custom field added', () => {
764
+ beforeEach(() => {
765
+ cy.get(pom.editorSelectors.configCustomFieldsTrigger).click()
766
+ cy.get(pom.editorSelectors.configCustomFieldsAddBtn).click()
767
+ })
768
+
769
+ it('SEL_BE_010_12: should find custom field key input', { tags: '@SEL_BE_010_12' }, () => {
770
+ cy.get(pom.editorSelectors.configCustomFieldKey(0)).should('exist').and('be.visible')
771
+ })
772
+
773
+ it('SEL_BE_010_13: should find custom field value input', { tags: '@SEL_BE_010_13' }, () => {
774
+ cy.get(pom.editorSelectors.configCustomFieldValue(0)).should('exist').and('be.visible')
775
+ })
776
+
777
+ it('SEL_BE_010_14: should find custom field remove button', { tags: '@SEL_BE_010_14' }, () => {
778
+ cy.get(pom.editorSelectors.configCustomFieldRemove(0)).should('exist').and('be.visible')
779
+ })
780
+ })
781
+ })
782
+ })
662
783
  })
@@ -0,0 +1,134 @@
1
+ /// <reference types="cypress" />
2
+
3
+ import * as allure from 'allure-cypress'
4
+ import { loginAsOwner } from '../../../../src/session-helpers'
5
+
6
+ /**
7
+ * Suspense Loading States - Performance UAT Tests
8
+ *
9
+ * Tests that loading.tsx files provide proper loading states
10
+ * while data is being fetched, improving perceived performance.
11
+ *
12
+ * PERF-002: Suspense Boundaries implementation
13
+ *
14
+ * These tests verify:
15
+ * - Loading skeletons appear during navigation
16
+ * - Content replaces skeletons after load
17
+ * - No flash of unstyled content (FOUC)
18
+ */
19
+
20
+ describe('Suspense Loading States', {
21
+ tags: ['@uat', '@performance', '@suspense', '@regression']
22
+ }, () => {
23
+
24
+ beforeEach(() => {
25
+ allure.epic('UAT')
26
+ allure.feature('Performance')
27
+ allure.story('Suspense Loading States')
28
+
29
+ loginAsOwner()
30
+ })
31
+
32
+ describe('Dashboard Loading', () => {
33
+ it('should show skeleton while dashboard home loads', () => {
34
+ allure.severity('normal')
35
+
36
+ // Navigate to dashboard with slow network simulation
37
+ cy.intercept('GET', '/api/**', (req) => {
38
+ req.on('response', (res) => {
39
+ res.setDelay(100)
40
+ })
41
+ }).as('apiCalls')
42
+
43
+ cy.visit('/dashboard')
44
+
45
+ // Wait for content to load
46
+ cy.get('[data-cy="dashboard-welcome"]', { timeout: 10000 }).should('be.visible')
47
+ })
48
+ })
49
+
50
+ describe('Settings Loading', () => {
51
+ it('should show skeleton while settings page loads', () => {
52
+ allure.severity('normal')
53
+
54
+ cy.visit('/dashboard/settings')
55
+
56
+ // Settings overview should load
57
+ cy.get('[data-cy="settings-overview-container"]', { timeout: 10000 }).should('be.visible')
58
+ })
59
+
60
+ it('should show skeleton while profile page loads', () => {
61
+ allure.severity('normal')
62
+
63
+ cy.visit('/dashboard/settings/profile')
64
+
65
+ // Profile form should load
66
+ cy.get('[data-cy="settings-profile-container"]', { timeout: 10000 }).should('be.visible')
67
+ })
68
+
69
+ it('should show skeleton while teams page loads', () => {
70
+ allure.severity('normal')
71
+
72
+ cy.visit('/dashboard/settings/teams')
73
+
74
+ // Teams container should load
75
+ cy.get('[data-cy="settings-teams-container"]', { timeout: 10000 }).should('be.visible')
76
+ })
77
+ })
78
+
79
+ describe('Skeleton Animation Performance', () => {
80
+ it('should have GPU-accelerated skeleton animations', () => {
81
+ allure.severity('minor')
82
+
83
+ cy.visit('/dashboard/settings')
84
+
85
+ // Check that skeleton elements exist with proper CSS classes
86
+ cy.document().then((doc) => {
87
+ const styles = doc.styleSheets
88
+ let hasSkeletonStyles = false
89
+
90
+ // Check if skeleton-pulse animation is defined
91
+ for (let i = 0; i < styles.length; i++) {
92
+ try {
93
+ const rules = styles[i].cssRules || styles[i].rules
94
+ for (let j = 0; j < rules.length; j++) {
95
+ const rule = rules[j]
96
+ if (rule.cssText && rule.cssText.includes('skeleton-pulse')) {
97
+ hasSkeletonStyles = true
98
+ break
99
+ }
100
+ }
101
+ } catch {
102
+ // Cross-origin stylesheets may throw
103
+ }
104
+ }
105
+
106
+ // Note: This test may pass even if styles aren't found due to CORS
107
+ // The important thing is that the page loads without errors
108
+ cy.log(`Skeleton styles found: ${hasSkeletonStyles}`)
109
+ })
110
+
111
+ // Settings should load successfully
112
+ cy.get('[data-cy="settings-overview-container"]', { timeout: 10000 }).should('be.visible')
113
+ })
114
+
115
+ it('should respect prefers-reduced-motion', () => {
116
+ allure.severity('minor')
117
+
118
+ // Set reduced motion preference
119
+ cy.wrap(
120
+ Cypress.automation('remote:debugger:protocol', {
121
+ command: 'Emulation.setEmulatedMedia',
122
+ params: {
123
+ features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
124
+ }
125
+ })
126
+ )
127
+
128
+ cy.visit('/dashboard/settings')
129
+
130
+ // Page should still load correctly with reduced motion
131
+ cy.get('[data-cy="settings-overview-container"]', { timeout: 10000 }).should('be.visible')
132
+ })
133
+ })
134
+ })
@@ -165,6 +165,35 @@ export abstract class BlockEditorBasePOM extends BasePOM {
165
165
  configMetaTitle: cySelector('blockEditor.configPanel.seoMetaSection.metaTitle'),
166
166
  configMetaDescription: cySelector('blockEditor.configPanel.seoMetaSection.metaDescription'),
167
167
 
168
+ // =========================================================================
169
+ // CONFIG PANEL - SEO SECTION (standalone box - v2.1)
170
+ // =========================================================================
171
+ configSeoSectionContainer: cySelector('blockEditor.configPanel.seoSection.container'),
172
+ configSeoSectionTrigger: cySelector('blockEditor.configPanel.seoSection.trigger'),
173
+ configSeoSectionContent: cySelector('blockEditor.configPanel.seoSection.content'),
174
+ configSeoMetaTitle: cySelector('blockEditor.configPanel.seoSection.metaTitle'),
175
+ configSeoMetaDescription: cySelector('blockEditor.configPanel.seoSection.metaDescription'),
176
+ configSeoMetaKeywords: cySelector('blockEditor.configPanel.seoSection.metaKeywords'),
177
+ configSeoOgImage: cySelector('blockEditor.configPanel.seoSection.ogImage'),
178
+
179
+ // =========================================================================
180
+ // CONFIG PANEL - CUSTOM FIELDS SECTION (standalone box - v2.1)
181
+ // =========================================================================
182
+ configCustomFieldsContainer: cySelector('blockEditor.configPanel.customFieldsSection.container'),
183
+ configCustomFieldsTrigger: cySelector('blockEditor.configPanel.customFieldsSection.trigger'),
184
+ configCustomFieldsContent: cySelector('blockEditor.configPanel.customFieldsSection.content'),
185
+ configCustomFieldsAddBtn: cySelector('blockEditor.configPanel.customFieldsSection.addButton'),
186
+ configCustomFieldKey: (index: number) =>
187
+ cySelector('blockEditor.configPanel.customFieldsSection.fieldKey', { index: String(index) }),
188
+ configCustomFieldValue: (index: number) =>
189
+ cySelector('blockEditor.configPanel.customFieldsSection.fieldValue', { index: String(index) }),
190
+ configCustomFieldRemove: (index: number) =>
191
+ cySelector('blockEditor.configPanel.customFieldsSection.fieldRemove', { index: String(index) }),
192
+ // Generic selectors for counting
193
+ configCustomFieldKeyGeneric: '[data-cy^="builder-config-custom-key-"]',
194
+ configCustomFieldValueGeneric: '[data-cy^="builder-config-custom-value-"]',
195
+ configCustomFieldRemoveGeneric: '[data-cy^="builder-config-custom-remove-"]',
196
+
168
197
  // =========================================================================
169
198
  // ENTITY FIELDS PANEL - DEPRECATED (moved to configPanel in v2.0)
170
199
  // =========================================================================
package/LICENSE DELETED
@@ -1,21 +0,0 @@
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.