@hua-labs/hua-ux 0.1.0-alpha.0.1

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 (210) hide show
  1. package/README.md +839 -0
  2. package/dist/framework/a11y/components/LiveRegion.d.ts +64 -0
  3. package/dist/framework/a11y/components/LiveRegion.d.ts.map +1 -0
  4. package/dist/framework/a11y/components/LiveRegion.js +43 -0
  5. package/dist/framework/a11y/components/SkipToContent.d.ts +62 -0
  6. package/dist/framework/a11y/components/SkipToContent.d.ts.map +1 -0
  7. package/dist/framework/a11y/components/SkipToContent.js +60 -0
  8. package/dist/framework/a11y/hooks/useFocusManagement.d.ts +60 -0
  9. package/dist/framework/a11y/hooks/useFocusManagement.d.ts.map +1 -0
  10. package/dist/framework/a11y/hooks/useFocusManagement.js +71 -0
  11. package/dist/framework/a11y/hooks/useFocusTrap.d.ts +64 -0
  12. package/dist/framework/a11y/hooks/useFocusTrap.d.ts.map +1 -0
  13. package/dist/framework/a11y/hooks/useFocusTrap.js +185 -0
  14. package/dist/framework/a11y/hooks/useLiveRegion.d.ts +56 -0
  15. package/dist/framework/a11y/hooks/useLiveRegion.d.ts.map +1 -0
  16. package/dist/framework/a11y/hooks/useLiveRegion.js +60 -0
  17. package/dist/framework/a11y/index.d.ts +16 -0
  18. package/dist/framework/a11y/index.d.ts.map +1 -0
  19. package/dist/framework/a11y/index.js +11 -0
  20. package/dist/framework/branding/context.d.ts +52 -0
  21. package/dist/framework/branding/context.d.ts.map +1 -0
  22. package/dist/framework/branding/context.js +96 -0
  23. package/dist/framework/branding/css-vars.d.ts +34 -0
  24. package/dist/framework/branding/css-vars.d.ts.map +1 -0
  25. package/dist/framework/branding/css-vars.js +95 -0
  26. package/dist/framework/branding/tailwind-config.d.ts +38 -0
  27. package/dist/framework/branding/tailwind-config.d.ts.map +1 -0
  28. package/dist/framework/branding/tailwind-config.js +66 -0
  29. package/dist/framework/components/BrandedButton.d.ts +53 -0
  30. package/dist/framework/components/BrandedButton.d.ts.map +1 -0
  31. package/dist/framework/components/BrandedButton.js +40 -0
  32. package/dist/framework/components/BrandedCard.d.ts +52 -0
  33. package/dist/framework/components/BrandedCard.d.ts.map +1 -0
  34. package/dist/framework/components/BrandedCard.js +73 -0
  35. package/dist/framework/components/ErrorBoundary.d.ts +92 -0
  36. package/dist/framework/components/ErrorBoundary.d.ts.map +1 -0
  37. package/dist/framework/components/ErrorBoundary.js +121 -0
  38. package/dist/framework/components/HuaUxLayout.d.ts +29 -0
  39. package/dist/framework/components/HuaUxLayout.d.ts.map +1 -0
  40. package/dist/framework/components/HuaUxLayout.js +32 -0
  41. package/dist/framework/components/HuaUxPage.d.ts +48 -0
  42. package/dist/framework/components/HuaUxPage.d.ts.map +1 -0
  43. package/dist/framework/components/HuaUxPage.js +105 -0
  44. package/dist/framework/components/Providers.d.ts +17 -0
  45. package/dist/framework/components/Providers.d.ts.map +1 -0
  46. package/dist/framework/components/Providers.js +72 -0
  47. package/dist/framework/components/WelcomePage.d.ts +44 -0
  48. package/dist/framework/components/WelcomePage.d.ts.map +1 -0
  49. package/dist/framework/components/WelcomePage.js +80 -0
  50. package/dist/framework/config/index.d.ts +182 -0
  51. package/dist/framework/config/index.d.ts.map +1 -0
  52. package/dist/framework/config/index.js +329 -0
  53. package/dist/framework/config/merge.d.ts +26 -0
  54. package/dist/framework/config/merge.d.ts.map +1 -0
  55. package/dist/framework/config/merge.js +160 -0
  56. package/dist/framework/config/schema.d.ts +25 -0
  57. package/dist/framework/config/schema.d.ts.map +1 -0
  58. package/dist/framework/config/schema.js +122 -0
  59. package/dist/framework/hooks/useMotion.d.ts +45 -0
  60. package/dist/framework/hooks/useMotion.d.ts.map +1 -0
  61. package/dist/framework/hooks/useMotion.js +40 -0
  62. package/dist/framework/index.d.ts +37 -0
  63. package/dist/framework/index.d.ts.map +1 -0
  64. package/dist/framework/index.js +42 -0
  65. package/dist/framework/license/errors.d.ts +15 -0
  66. package/dist/framework/license/errors.d.ts.map +1 -0
  67. package/dist/framework/license/errors.js +52 -0
  68. package/dist/framework/license/index.d.ts +70 -0
  69. package/dist/framework/license/index.d.ts.map +1 -0
  70. package/dist/framework/license/index.js +124 -0
  71. package/dist/framework/license/loader.d.ts +26 -0
  72. package/dist/framework/license/loader.d.ts.map +1 -0
  73. package/dist/framework/license/loader.js +137 -0
  74. package/dist/framework/license/types.d.ts +67 -0
  75. package/dist/framework/license/types.d.ts.map +1 -0
  76. package/dist/framework/license/types.js +18 -0
  77. package/dist/framework/loading/components/SkeletonGroup.d.ts +44 -0
  78. package/dist/framework/loading/components/SkeletonGroup.d.ts.map +1 -0
  79. package/dist/framework/loading/components/SkeletonGroup.js +34 -0
  80. package/dist/framework/loading/components/SuspenseWrapper.d.ts +58 -0
  81. package/dist/framework/loading/components/SuspenseWrapper.d.ts.map +1 -0
  82. package/dist/framework/loading/components/SuspenseWrapper.js +40 -0
  83. package/dist/framework/loading/hoc/withSuspense.d.ts +46 -0
  84. package/dist/framework/loading/hoc/withSuspense.d.ts.map +1 -0
  85. package/dist/framework/loading/hoc/withSuspense.js +54 -0
  86. package/dist/framework/loading/hooks/useDelayedLoading.d.ts +56 -0
  87. package/dist/framework/loading/hooks/useDelayedLoading.d.ts.map +1 -0
  88. package/dist/framework/loading/hooks/useDelayedLoading.js +97 -0
  89. package/dist/framework/loading/hooks/useLoadingState.d.ts +69 -0
  90. package/dist/framework/loading/hooks/useLoadingState.d.ts.map +1 -0
  91. package/dist/framework/loading/hooks/useLoadingState.js +59 -0
  92. package/dist/framework/loading/index.d.ts +16 -0
  93. package/dist/framework/loading/index.d.ts.map +1 -0
  94. package/dist/framework/loading/index.js +13 -0
  95. package/dist/framework/middleware/i18n.d.ts +90 -0
  96. package/dist/framework/middleware/i18n.d.ts.map +1 -0
  97. package/dist/framework/middleware/i18n.js +99 -0
  98. package/dist/framework/plugins/index.d.ts +8 -0
  99. package/dist/framework/plugins/index.d.ts.map +1 -0
  100. package/dist/framework/plugins/index.js +6 -0
  101. package/dist/framework/plugins/registry.d.ts +95 -0
  102. package/dist/framework/plugins/registry.d.ts.map +1 -0
  103. package/dist/framework/plugins/registry.js +160 -0
  104. package/dist/framework/plugins/types.d.ts +97 -0
  105. package/dist/framework/plugins/types.d.ts.map +1 -0
  106. package/dist/framework/plugins/types.js +6 -0
  107. package/dist/framework/seo/geo/examples.d.ts +87 -0
  108. package/dist/framework/seo/geo/examples.d.ts.map +1 -0
  109. package/dist/framework/seo/geo/examples.js +295 -0
  110. package/dist/framework/seo/geo/generateGEOMetadata.d.ts +107 -0
  111. package/dist/framework/seo/geo/generateGEOMetadata.d.ts.map +1 -0
  112. package/dist/framework/seo/geo/generateGEOMetadata.js +404 -0
  113. package/dist/framework/seo/geo/index.d.ts +19 -0
  114. package/dist/framework/seo/geo/index.d.ts.map +1 -0
  115. package/dist/framework/seo/geo/index.js +21 -0
  116. package/dist/framework/seo/geo/presets.d.ts +52 -0
  117. package/dist/framework/seo/geo/presets.d.ts.map +1 -0
  118. package/dist/framework/seo/geo/presets.js +47 -0
  119. package/dist/framework/seo/geo/structuredData.d.ts +187 -0
  120. package/dist/framework/seo/geo/structuredData.d.ts.map +1 -0
  121. package/dist/framework/seo/geo/structuredData.js +354 -0
  122. package/dist/framework/seo/geo/test-utils.d.ts +78 -0
  123. package/dist/framework/seo/geo/test-utils.d.ts.map +1 -0
  124. package/dist/framework/seo/geo/test-utils.js +139 -0
  125. package/dist/framework/seo/geo/types.d.ts +225 -0
  126. package/dist/framework/seo/geo/types.d.ts.map +1 -0
  127. package/dist/framework/seo/geo/types.js +51 -0
  128. package/dist/framework/types/index.d.ts +577 -0
  129. package/dist/framework/types/index.d.ts.map +1 -0
  130. package/dist/framework/types/index.js +6 -0
  131. package/dist/framework/utils/data-fetching.d.ts +45 -0
  132. package/dist/framework/utils/data-fetching.d.ts.map +1 -0
  133. package/dist/framework/utils/data-fetching.js +74 -0
  134. package/dist/framework/utils/file-structure.d.ts +29 -0
  135. package/dist/framework/utils/file-structure.d.ts.map +1 -0
  136. package/dist/framework/utils/file-structure.js +72 -0
  137. package/dist/framework/utils/metadata.d.ts +109 -0
  138. package/dist/framework/utils/metadata.d.ts.map +1 -0
  139. package/dist/framework/utils/metadata.js +105 -0
  140. package/dist/index.d.ts +15 -0
  141. package/dist/index.d.ts.map +1 -0
  142. package/dist/index.js +21 -0
  143. package/dist/presets/index.d.ts +8 -0
  144. package/dist/presets/index.d.ts.map +1 -0
  145. package/dist/presets/index.js +7 -0
  146. package/dist/presets/marketing.d.ts +41 -0
  147. package/dist/presets/marketing.d.ts.map +1 -0
  148. package/dist/presets/marketing.js +81 -0
  149. package/dist/presets/product.d.ts +41 -0
  150. package/dist/presets/product.d.ts.map +1 -0
  151. package/dist/presets/product.js +74 -0
  152. package/package.json +91 -0
  153. package/src/framework/README.md +329 -0
  154. package/src/framework/__tests__/branding/css-vars.test.ts +147 -0
  155. package/src/framework/__tests__/components/ErrorBoundary.test.tsx +146 -0
  156. package/src/framework/__tests__/config/defineConfig.test.ts +138 -0
  157. package/src/framework/__tests__/hooks/useMotion.test.ts +105 -0
  158. package/src/framework/__tests__/seo/geo/generateGEOMetadata.test.ts +207 -0
  159. package/src/framework/__tests__/seo/geo/structuredData.test.ts +262 -0
  160. package/src/framework/a11y/components/LiveRegion.tsx +89 -0
  161. package/src/framework/a11y/components/SkipToContent.tsx +103 -0
  162. package/src/framework/a11y/hooks/useFocusManagement.ts +125 -0
  163. package/src/framework/a11y/hooks/useFocusTrap.ts +239 -0
  164. package/src/framework/a11y/hooks/useLiveRegion.ts +95 -0
  165. package/src/framework/a11y/index.ts +17 -0
  166. package/src/framework/branding/context.tsx +135 -0
  167. package/src/framework/branding/css-vars.ts +110 -0
  168. package/src/framework/branding/tailwind-config.ts +90 -0
  169. package/src/framework/components/BrandedButton.tsx +94 -0
  170. package/src/framework/components/BrandedCard.tsx +87 -0
  171. package/src/framework/components/ErrorBoundary.tsx +215 -0
  172. package/src/framework/components/HuaUxLayout.tsx +36 -0
  173. package/src/framework/components/HuaUxPage.tsx +138 -0
  174. package/src/framework/components/Providers.tsx +98 -0
  175. package/src/framework/components/WelcomePage.tsx +207 -0
  176. package/src/framework/config/index.ts +349 -0
  177. package/src/framework/config/merge.ts +190 -0
  178. package/src/framework/config/schema.ts +140 -0
  179. package/src/framework/hooks/useMotion.ts +57 -0
  180. package/src/framework/index.ts +122 -0
  181. package/src/framework/license/errors.ts +63 -0
  182. package/src/framework/license/index.ts +137 -0
  183. package/src/framework/license/loader.ts +158 -0
  184. package/src/framework/license/types.ts +95 -0
  185. package/src/framework/loading/components/SkeletonGroup.tsx +70 -0
  186. package/src/framework/loading/components/SuspenseWrapper.tsx +88 -0
  187. package/src/framework/loading/hoc/withSuspense.tsx +96 -0
  188. package/src/framework/loading/hooks/useDelayedLoading.ts +127 -0
  189. package/src/framework/loading/hooks/useLoadingState.ts +103 -0
  190. package/src/framework/loading/index.ts +19 -0
  191. package/src/framework/middleware/i18n.ts +161 -0
  192. package/src/framework/middleware/index.ts +7 -0
  193. package/src/framework/plugins/index.ts +13 -0
  194. package/src/framework/plugins/registry.ts +186 -0
  195. package/src/framework/plugins/types.ts +106 -0
  196. package/src/framework/seo/geo/examples.tsx +415 -0
  197. package/src/framework/seo/geo/generateGEOMetadata.ts +441 -0
  198. package/src/framework/seo/geo/index.ts +61 -0
  199. package/src/framework/seo/geo/presets.ts +58 -0
  200. package/src/framework/seo/geo/structuredData.ts +422 -0
  201. package/src/framework/seo/geo/test-utils.ts +179 -0
  202. package/src/framework/seo/geo/types.ts +315 -0
  203. package/src/framework/types/index.ts +623 -0
  204. package/src/framework/utils/data-fetching.ts +95 -0
  205. package/src/framework/utils/file-structure.ts +88 -0
  206. package/src/framework/utils/metadata.ts +152 -0
  207. package/src/index.ts +31 -0
  208. package/src/presets/index.ts +8 -0
  209. package/src/presets/marketing.ts +88 -0
  210. package/src/presets/product.ts +81 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - ErrorBoundary Component Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+ import { render, screen } from '@testing-library/react';
7
+ import React from 'react';
8
+ import { ErrorBoundary } from '../../components/ErrorBoundary';
9
+
10
+ // 에러를 발생시키는 컴포넌트
11
+ function ThrowError({ shouldThrow = false }: { shouldThrow?: boolean }) {
12
+ if (shouldThrow) {
13
+ throw new Error('Test error');
14
+ }
15
+ return <div>No error</div>;
16
+ }
17
+
18
+ describe('ErrorBoundary', () => {
19
+ beforeEach(() => {
20
+ // console.error를 모킹하여 테스트 중 에러 로그 방지
21
+ vi.spyOn(console, 'error').mockImplementation(() => {});
22
+ });
23
+
24
+ afterEach(() => {
25
+ vi.restoreAllMocks();
26
+ });
27
+
28
+ it('should render children when no error occurs', () => {
29
+ render(
30
+ <ErrorBoundary>
31
+ <ThrowError shouldThrow={false} />
32
+ </ErrorBoundary>
33
+ );
34
+
35
+ expect(screen.getByText('No error')).toBeInTheDocument();
36
+ });
37
+
38
+ it('should render default fallback when error occurs', () => {
39
+ render(
40
+ <ErrorBoundary>
41
+ <ThrowError shouldThrow={true} />
42
+ </ErrorBoundary>
43
+ );
44
+
45
+ expect(screen.getByText(/오류가 발생했습니다|An error occurred/)).toBeInTheDocument();
46
+ });
47
+
48
+ it('should render custom fallback when provided', () => {
49
+ const customFallback = <div>Custom error message</div>;
50
+
51
+ render(
52
+ <ErrorBoundary fallback={customFallback}>
53
+ <ThrowError shouldThrow={true} />
54
+ </ErrorBoundary>
55
+ );
56
+
57
+ expect(screen.getByText('Custom error message')).toBeInTheDocument();
58
+ });
59
+
60
+ it('should call onError callback when error occurs', () => {
61
+ const onError = vi.fn();
62
+
63
+ render(
64
+ <ErrorBoundary onError={onError}>
65
+ <ThrowError shouldThrow={true} />
66
+ </ErrorBoundary>
67
+ );
68
+
69
+ expect(onError).toHaveBeenCalled();
70
+ expect(onError).toHaveBeenCalledWith(
71
+ expect.any(Error),
72
+ expect.objectContaining({
73
+ componentStack: expect.any(String),
74
+ })
75
+ );
76
+ });
77
+
78
+ it('should support function fallback with error and reset', () => {
79
+ const fallbackFn = vi.fn((error, reset) => (
80
+ <div>
81
+ <p>Error: {error.message}</p>
82
+ <button onClick={reset}>Reset</button>
83
+ </div>
84
+ ));
85
+
86
+ render(
87
+ <ErrorBoundary fallback={fallbackFn}>
88
+ <ThrowError shouldThrow={true} />
89
+ </ErrorBoundary>
90
+ );
91
+
92
+ expect(fallbackFn).toHaveBeenCalled();
93
+ expect(fallbackFn).toHaveBeenCalledWith(
94
+ expect.any(Error),
95
+ expect.any(Function)
96
+ );
97
+ });
98
+
99
+ it('should reset error state when reset is called', () => {
100
+ const fallbackFn = (error: Error, reset: () => void) => (
101
+ <div>
102
+ <p>Error: {error.message}</p>
103
+ <button onClick={reset}>Reset</button>
104
+ </div>
105
+ );
106
+
107
+ const { rerender } = render(
108
+ <ErrorBoundary fallback={fallbackFn}>
109
+ <ThrowError shouldThrow={true} />
110
+ </ErrorBoundary>
111
+ );
112
+
113
+ expect(screen.getByText(/Error: Test error/)).toBeInTheDocument();
114
+
115
+ // Reset 버튼 클릭 시뮬레이션
116
+ const resetButton = screen.getByText('Reset');
117
+ resetButton.click();
118
+
119
+ // 에러가 리셋되어 children이 다시 렌더링되어야 함
120
+ rerender(
121
+ <ErrorBoundary fallback={fallbackFn}>
122
+ <ThrowError shouldThrow={false} />
123
+ </ErrorBoundary>
124
+ );
125
+
126
+ expect(screen.getByText('No error')).toBeInTheDocument();
127
+ });
128
+
129
+ it('should call onReset callback when reset is called', () => {
130
+ const onReset = vi.fn();
131
+ const fallbackFn = (error: Error, reset: () => void) => (
132
+ <button onClick={reset}>Reset</button>
133
+ );
134
+
135
+ render(
136
+ <ErrorBoundary fallback={fallbackFn} onReset={onReset}>
137
+ <ThrowError shouldThrow={true} />
138
+ </ErrorBoundary>
139
+ );
140
+
141
+ const resetButton = screen.getByText('Reset');
142
+ resetButton.click();
143
+
144
+ expect(onReset).toHaveBeenCalled();
145
+ });
146
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - Config Tests
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { defineConfig, getConfig, setConfig, resetConfig } from '../../config';
7
+ import type { HuaUxConfig } from '../../types';
8
+
9
+ describe('defineConfig', () => {
10
+ it('should create config with preset', () => {
11
+ const config = defineConfig({
12
+ preset: 'product',
13
+ });
14
+
15
+ expect(config).toBeDefined();
16
+ expect(config.preset).toBe('product');
17
+ });
18
+
19
+ it('should create config with custom settings', () => {
20
+ const config = defineConfig({
21
+ i18n: {
22
+ defaultLanguage: 'ko',
23
+ supportedLanguages: ['ko', 'en'],
24
+ namespaces: ['common'],
25
+ },
26
+ });
27
+
28
+ expect(config.i18n).toBeDefined();
29
+ expect(config.i18n?.defaultLanguage).toBe('ko');
30
+ expect(config.i18n?.supportedLanguages).toEqual(['ko', 'en']);
31
+ });
32
+
33
+ it('should merge preset with custom settings', () => {
34
+ const config = defineConfig({
35
+ preset: 'product',
36
+ motion: {
37
+ enableAnimations: false,
38
+ },
39
+ });
40
+
41
+ expect(config.preset).toBe('product');
42
+ expect(config.motion?.enableAnimations).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe('getConfig', () => {
47
+ beforeEach(() => {
48
+ resetConfig();
49
+ });
50
+
51
+ afterEach(() => {
52
+ resetConfig();
53
+ });
54
+
55
+ it('should return default config when no config is set', () => {
56
+ resetConfig();
57
+ const config = getConfig();
58
+
59
+ expect(config).toBeDefined();
60
+ // jsdom 환경에서는 window가 정의되어 있어 기본값(product preset)이 반환됨
61
+ // getConfig는 항상 설정을 반환하므로 motion, i18n 등이 정의되어 있어야 함
62
+ expect(config.motion).toBeDefined();
63
+ expect(config.i18n).toBeDefined();
64
+ });
65
+
66
+ it('should return set config', () => {
67
+ resetConfig();
68
+ const customConfig = defineConfig({
69
+ preset: 'marketing',
70
+ });
71
+
72
+ setConfig(customConfig);
73
+ const config = getConfig();
74
+
75
+ // setConfig 후에는 설정된 config가 반환되어야 함
76
+ expect(config.preset).toBe('marketing');
77
+ });
78
+ });
79
+
80
+ describe('setConfig', () => {
81
+ beforeEach(() => {
82
+ resetConfig();
83
+ });
84
+
85
+ afterEach(() => {
86
+ resetConfig();
87
+ });
88
+
89
+ it('should set config', () => {
90
+ resetConfig();
91
+ const customConfig = defineConfig({
92
+ preset: 'marketing',
93
+ });
94
+
95
+ setConfig(customConfig);
96
+ const config = getConfig();
97
+
98
+ expect(config.preset).toBe('marketing');
99
+ });
100
+
101
+ it('should override previous config', () => {
102
+ resetConfig();
103
+ const config1 = defineConfig({ preset: 'product' });
104
+ const config2 = defineConfig({ preset: 'marketing' });
105
+
106
+ setConfig(config1);
107
+ expect(getConfig().preset).toBe('product');
108
+
109
+ setConfig(config2);
110
+ expect(getConfig().preset).toBe('marketing');
111
+ });
112
+ });
113
+
114
+ describe('resetConfig', () => {
115
+ beforeEach(() => {
116
+ resetConfig();
117
+ });
118
+
119
+ afterEach(() => {
120
+ resetConfig();
121
+ });
122
+
123
+ it('should reset config to default', () => {
124
+ resetConfig();
125
+ const customConfig = defineConfig({
126
+ preset: 'marketing',
127
+ });
128
+
129
+ setConfig(customConfig);
130
+ expect(getConfig().preset).toBe('marketing');
131
+
132
+ resetConfig();
133
+ const defaultConfig = getConfig();
134
+
135
+ // 기본값으로 리셋됨 (product preset)
136
+ expect(defaultConfig.preset).toBeDefined();
137
+ });
138
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - useMotion Hook Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { renderHook } from '@testing-library/react';
7
+ import { useMotion } from '../../hooks/useMotion';
8
+
9
+ // motion-core의 useUnifiedMotion 모킹
10
+ const mockMotionResult = {
11
+ ref: { current: null },
12
+ style: { opacity: 0 },
13
+ isVisible: false,
14
+ isAnimating: false,
15
+ progress: 0,
16
+ start: vi.fn(),
17
+ reset: vi.fn(),
18
+ stop: vi.fn(),
19
+ };
20
+
21
+ vi.mock('@hua-labs/motion-core', () => ({
22
+ useUnifiedMotion: vi.fn(() => mockMotionResult),
23
+ }));
24
+
25
+ describe('useMotion', () => {
26
+ beforeEach(() => {
27
+ vi.clearAllMocks();
28
+ });
29
+
30
+ it('should call useUnifiedMotion with correct options', async () => {
31
+ const { useUnifiedMotion } = await import('@hua-labs/motion-core');
32
+
33
+ renderHook(() =>
34
+ useMotion({
35
+ type: 'fadeIn',
36
+ duration: 600,
37
+ delay: 100,
38
+ easing: 'ease-in-out',
39
+ })
40
+ );
41
+
42
+ expect(useUnifiedMotion).toHaveBeenCalledWith({
43
+ type: 'fadeIn',
44
+ duration: 600,
45
+ delay: 100,
46
+ easing: 'ease-in-out',
47
+ autoStart: undefined,
48
+ });
49
+ });
50
+
51
+ it('should return motion result', () => {
52
+ const { result } = renderHook(() =>
53
+ useMotion({
54
+ type: 'fadeIn',
55
+ duration: 600,
56
+ })
57
+ );
58
+
59
+ expect(result.current).toBeDefined();
60
+ expect(result.current.ref).toBeDefined();
61
+ expect(result.current.start).toBeDefined();
62
+ expect(result.current.reset).toBeDefined();
63
+ expect(result.current.stop).toBeDefined();
64
+ });
65
+
66
+ it('should pass all motion types to useUnifiedMotion', async () => {
67
+ const { useUnifiedMotion } = await import('@hua-labs/motion-core');
68
+
69
+ const types = ['fadeIn', 'slideUp', 'slideLeft', 'slideRight', 'scaleIn', 'bounceIn'] as const;
70
+
71
+ for (const type of types) {
72
+ vi.clearAllMocks();
73
+ renderHook(() =>
74
+ useMotion({
75
+ type,
76
+ duration: 600,
77
+ })
78
+ );
79
+
80
+ expect(useUnifiedMotion).toHaveBeenCalledWith(
81
+ expect.objectContaining({
82
+ type,
83
+ })
84
+ );
85
+ }
86
+ });
87
+
88
+ it('should handle autoStart option', async () => {
89
+ const { useUnifiedMotion } = await import('@hua-labs/motion-core');
90
+
91
+ renderHook(() =>
92
+ useMotion({
93
+ type: 'fadeIn',
94
+ duration: 600,
95
+ autoStart: true,
96
+ })
97
+ );
98
+
99
+ expect(useUnifiedMotion).toHaveBeenCalledWith(
100
+ expect.objectContaining({
101
+ autoStart: true,
102
+ })
103
+ );
104
+ });
105
+ });
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @hua-labs/hua-ux/framework - GEO Metadata Generator Tests
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { generateGEOMetadata, createAIContext } from '../../../seo/geo/generateGEOMetadata';
7
+ import type { GEOConfig } from '../../../seo/geo/types';
8
+
9
+ describe('generateGEOMetadata', () => {
10
+ const baseConfig: GEOConfig = {
11
+ name: 'Test App',
12
+ description: 'A test application',
13
+ };
14
+
15
+ it('should generate basic metadata', () => {
16
+ const result = generateGEOMetadata(baseConfig);
17
+
18
+ expect(result).toBeDefined();
19
+ expect(result.meta).toBeDefined();
20
+ expect(result.jsonLd).toBeDefined();
21
+ expect(result.openGraph).toBeDefined();
22
+ expect(result.twitter).toBeDefined();
23
+ });
24
+
25
+ it('should include description in meta tags', () => {
26
+ const result = generateGEOMetadata(baseConfig);
27
+
28
+ const descriptionMeta = result.meta.find((m) => m.name === 'description');
29
+ expect(descriptionMeta).toBeDefined();
30
+ expect(descriptionMeta?.content).toBe('A test application');
31
+ });
32
+
33
+ it('should include keywords when provided', () => {
34
+ const config: GEOConfig = {
35
+ ...baseConfig,
36
+ keywords: ['react', 'nextjs', 'typescript'],
37
+ };
38
+
39
+ const result = generateGEOMetadata(config);
40
+
41
+ const keywordsMeta = result.meta.find((m) => m.name === 'keywords');
42
+ expect(keywordsMeta).toBeDefined();
43
+ expect(keywordsMeta?.content).toBe('react, nextjs, typescript');
44
+ });
45
+
46
+ it('should include version when provided', () => {
47
+ const config: GEOConfig = {
48
+ ...baseConfig,
49
+ version: '1.0.0',
50
+ };
51
+
52
+ const result = generateGEOMetadata(config);
53
+
54
+ const versionMeta = result.meta.find((m) => m.name === 'software:version');
55
+ expect(versionMeta).toBeDefined();
56
+ expect(versionMeta?.content).toBe('1.0.0');
57
+ });
58
+
59
+ it('should include application category when provided', () => {
60
+ const config: GEOConfig = {
61
+ ...baseConfig,
62
+ applicationCategory: 'UX Framework',
63
+ };
64
+
65
+ const result = generateGEOMetadata(config);
66
+
67
+ const categoryMeta = result.meta.find((m) => m.name === 'software:category');
68
+ expect(categoryMeta).toBeDefined();
69
+ expect(categoryMeta?.content).toBe('UX Framework');
70
+ });
71
+
72
+ it('should handle multiple application categories', () => {
73
+ const config: GEOConfig = {
74
+ ...baseConfig,
75
+ applicationCategory: ['UX Framework', 'Developer Tool'],
76
+ };
77
+
78
+ const result = generateGEOMetadata(config);
79
+
80
+ const categoryMeta = result.meta.find((m) => m.name === 'software:category');
81
+ expect(categoryMeta).toBeDefined();
82
+ expect(categoryMeta?.content).toBe('UX Framework, Developer Tool');
83
+ });
84
+
85
+ it('should include programming language when provided', () => {
86
+ const config: GEOConfig = {
87
+ ...baseConfig,
88
+ programmingLanguage: 'TypeScript',
89
+ };
90
+
91
+ const result = generateGEOMetadata(config);
92
+
93
+ const languageMeta = result.meta.find((m) => m.name === 'software:language');
94
+ expect(languageMeta).toBeDefined();
95
+ expect(languageMeta?.content).toBe('TypeScript');
96
+ });
97
+
98
+ it('should generate Open Graph tags', () => {
99
+ const result = generateGEOMetadata(baseConfig);
100
+
101
+ expect(result.openGraph).toBeDefined();
102
+ expect(result.openGraph?.length).toBeGreaterThan(0);
103
+
104
+ const ogTitle = result.openGraph?.find((og) => og.property === 'og:title');
105
+ expect(ogTitle).toBeDefined();
106
+ expect(ogTitle?.content).toBe('Test App');
107
+ });
108
+
109
+ it('should generate Twitter Card tags', () => {
110
+ const result = generateGEOMetadata(baseConfig);
111
+
112
+ expect(result.twitter).toBeDefined();
113
+ expect(result.twitter?.length).toBeGreaterThan(0);
114
+
115
+ const twitterCard = result.twitter?.find((t) => t.name === 'twitter:card');
116
+ expect(twitterCard).toBeDefined();
117
+ expect(twitterCard?.content).toBe('summary_large_image');
118
+ });
119
+
120
+ it('should include URL in Open Graph when provided', () => {
121
+ const config: GEOConfig = {
122
+ ...baseConfig,
123
+ url: 'https://example.com',
124
+ };
125
+
126
+ const result = generateGEOMetadata(config);
127
+
128
+ const ogUrl = result.openGraph?.find((og) => og.property === 'og:url');
129
+ expect(ogUrl).toBeDefined();
130
+ expect(ogUrl?.content).toBe('https://example.com');
131
+ });
132
+
133
+ it('should generate JSON-LD structured data', () => {
134
+ const result = generateGEOMetadata(baseConfig);
135
+
136
+ expect(result.jsonLd).toBeDefined();
137
+ expect(result.jsonLd.length).toBeGreaterThan(0);
138
+
139
+ const jsonLd = result.jsonLd[0];
140
+ expect(jsonLd['@context']).toBe('https://schema.org');
141
+ expect(jsonLd['@type']).toBe('SoftwareApplication');
142
+ expect(jsonLd.name).toBe('Test App');
143
+ expect(jsonLd.description).toBe('A test application');
144
+ });
145
+ });
146
+
147
+ describe('createAIContext', () => {
148
+ it('should create basic context', () => {
149
+ const config: GEOConfig = {
150
+ name: 'Test App',
151
+ description: 'A test application',
152
+ };
153
+
154
+ const context = createAIContext(config);
155
+
156
+ expect(context).toContain('Test App');
157
+ expect(context).toContain('A test application');
158
+ });
159
+
160
+ it('should include features when provided', () => {
161
+ const config: GEOConfig = {
162
+ name: 'Test App',
163
+ description: 'A test application',
164
+ features: ['i18n', 'Motion', 'Accessibility'],
165
+ };
166
+
167
+ const context = createAIContext(config);
168
+
169
+ expect(context).toContain('Key features include: i18n, Motion, Accessibility');
170
+ });
171
+
172
+ it('should include use cases when provided', () => {
173
+ const config: GEOConfig = {
174
+ name: 'Test App',
175
+ description: 'A test application',
176
+ useCases: ['Multilingual apps', 'Accessible UX'],
177
+ };
178
+
179
+ const context = createAIContext(config);
180
+
181
+ expect(context).toContain('Common use cases: Multilingual apps, Accessible UX');
182
+ });
183
+
184
+ it('should include programming language when provided', () => {
185
+ const config: GEOConfig = {
186
+ name: 'Test App',
187
+ description: 'A test application',
188
+ programmingLanguage: ['TypeScript', 'React'],
189
+ };
190
+
191
+ const context = createAIContext(config);
192
+
193
+ expect(context).toContain('Built with: TypeScript, React');
194
+ });
195
+
196
+ it('should include software requirements when provided', () => {
197
+ const config: GEOConfig = {
198
+ name: 'Test App',
199
+ description: 'A test application',
200
+ softwareRequirements: ['Node.js 18+', 'React 19+'],
201
+ };
202
+
203
+ const context = createAIContext(config);
204
+
205
+ expect(context).toContain('Requires: Node.js 18+, React 19+');
206
+ });
207
+ });