@blastlabs/utils 1.22.0 → 2.1.0

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 (42) hide show
  1. package/README.md +102 -8
  2. package/dist/components/dev/DevPanel.d.ts +16 -11
  3. package/dist/components/dev/DevPanel.d.ts.map +1 -1
  4. package/dist/components/dev/DevPanel.js +71 -77
  5. package/dist/components/dev/DevPanel.test.d.ts +2 -0
  6. package/dist/components/dev/DevPanel.test.d.ts.map +1 -0
  7. package/dist/components/dev/DevPanel.test.js +194 -0
  8. package/dist/components/dev/DevToolsProvider/DevToolsProvider.d.ts +97 -0
  9. package/dist/components/dev/DevToolsProvider/DevToolsProvider.d.ts.map +1 -0
  10. package/dist/components/dev/DevToolsProvider/DevToolsProvider.js +122 -0
  11. package/dist/components/dev/DevToolsProvider/DevToolsProvider.test.d.ts +2 -0
  12. package/dist/components/dev/DevToolsProvider/DevToolsProvider.test.d.ts.map +1 -0
  13. package/dist/components/dev/DevToolsProvider/DevToolsProvider.test.js +104 -0
  14. package/dist/components/dev/DevToolsProvider/index.d.ts +3 -0
  15. package/dist/components/dev/DevToolsProvider/index.d.ts.map +1 -0
  16. package/dist/components/dev/DevToolsProvider/index.js +1 -0
  17. package/dist/components/dev/FormDevTools/FormDevTools.d.ts +5 -70
  18. package/dist/components/dev/FormDevTools/FormDevTools.d.ts.map +1 -1
  19. package/dist/components/dev/FormDevTools/FormDevTools.js +163 -236
  20. package/dist/components/dev/FormDevTools/FormDevToolsContent.d.ts +27 -0
  21. package/dist/components/dev/FormDevTools/FormDevToolsContent.d.ts.map +1 -0
  22. package/dist/components/dev/FormDevTools/FormDevToolsContent.js +298 -0
  23. package/dist/components/dev/TimezoneDevTools/TimezoneDevTools.d.ts +29 -0
  24. package/dist/components/dev/TimezoneDevTools/TimezoneDevTools.d.ts.map +1 -0
  25. package/dist/components/dev/TimezoneDevTools/TimezoneDevTools.js +122 -0
  26. package/dist/components/dev/TimezoneDevTools/TimezoneDevToolsContent.d.ts +4 -0
  27. package/dist/components/dev/TimezoneDevTools/TimezoneDevToolsContent.d.ts.map +1 -0
  28. package/dist/components/dev/TimezoneDevTools/TimezoneDevToolsContent.js +121 -0
  29. package/dist/components/dev/TimezoneDevTools/index.d.ts +3 -0
  30. package/dist/components/dev/TimezoneDevTools/index.d.ts.map +1 -0
  31. package/dist/components/dev/TimezoneDevTools/index.js +1 -0
  32. package/dist/components/dev/TimezoneDevTools/styles.d.ts +12 -0
  33. package/dist/components/dev/TimezoneDevTools/styles.d.ts.map +1 -0
  34. package/dist/components/dev/TimezoneDevTools/styles.js +65 -0
  35. package/dist/components/dev/ZIndexDebugger.js +1 -1
  36. package/dist/components/dev/index.d.ts +4 -2
  37. package/dist/components/dev/index.d.ts.map +1 -1
  38. package/dist/components/dev/index.js +6 -1
  39. package/dist/date/index.d.ts +11 -0
  40. package/dist/date/index.d.ts.map +1 -1
  41. package/dist/date/index.js +43 -0
  42. package/package.json +2 -1
package/README.md CHANGED
@@ -74,25 +74,64 @@ function App() {
74
74
  #### FormDevTools
75
75
 
76
76
  react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트입니다.
77
+ DevPanel과 통합하거나 별도로 사용할 수 있습니다.
77
78
 
79
+ **DevPanel과 함께 사용 (권장):**
78
80
  ```tsx
81
+ import { DevToolsProvider, DevPanel, useRegisterForm } from '@blastlabs/utils/components/dev';
79
82
  import { useForm } from 'react-hook-form';
83
+
84
+ // Layout.tsx - Provider로 감싸기
85
+ function Layout() {
86
+ return (
87
+ <DevToolsProvider>
88
+ <YourApp />
89
+ </DevToolsProvider>
90
+ );
91
+ }
92
+
93
+ // 페이지에서 form 등록
94
+ function MyFormPage() {
95
+ const form = useForm({
96
+ defaultValues: { username: '', email: '' }
97
+ });
98
+
99
+ // form을 DevPanel에 등록
100
+ useRegisterForm(form);
101
+
102
+ return (
103
+ <form>
104
+ <input {...form.register('username')} />
105
+ <input {...form.register('email')} />
106
+ <button>Submit</button>
107
+ </form>
108
+ );
109
+ }
110
+
111
+ // App.tsx - DevPanel 렌더링
112
+ function App() {
113
+ return (
114
+ <div>
115
+ {import.meta.env.DEV && <DevPanel />}
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ **별도 사용:**
122
+ ```tsx
80
123
  import { FormDevTools } from '@blastlabs/utils/components/dev';
81
124
 
82
125
  function MyForm() {
83
126
  const form = useForm({
84
- defaultValues: {
85
- username: '',
86
- email: '',
87
- age: 0,
88
- }
127
+ defaultValues: { username: '', email: '' }
89
128
  });
90
129
 
91
130
  return (
92
- <form onSubmit={form.handleSubmit(onSubmit)}>
131
+ <form>
93
132
  <input {...form.register('username')} />
94
133
  <input {...form.register('email')} />
95
- <button type="submit">Submit</button>
134
+ <button>Submit</button>
96
135
 
97
136
  {import.meta.env.DEV && <FormDevTools form={form} />}
98
137
  </form>
@@ -105,9 +144,64 @@ function MyForm() {
105
144
  - 에러(errors) 상태 확인
106
145
  - 변경된 필드(dirtyFields) 추적
107
146
  - 터치된 필드(touchedFields) 확인
108
- - Mock 데이터 생성 기능
109
147
  - 드래그 & 리사이즈 가능한 패널
110
148
 
149
+ #### DevPanel
150
+
151
+ 여러 개발 도구를 하나의 패널에서 통합 관리하는 컴포넌트입니다.
152
+
153
+ ```tsx
154
+ import { DevToolsProvider, DevPanel, useRegisterForm } from '@blastlabs/utils/components/dev';
155
+
156
+ // Layout.tsx - 앱 상단에서 한 번만 감싸기
157
+ function Layout() {
158
+ return (
159
+ <DevToolsProvider>
160
+ <YourApp />
161
+ </DevToolsProvider>
162
+ );
163
+ }
164
+
165
+ // 페이지에서 form 등록
166
+ function MyFormPage() {
167
+ const form = useForm({
168
+ defaultValues: { username: '', email: '' }
169
+ });
170
+
171
+ // Form DevTools 사용을 위해 form 등록
172
+ useRegisterForm(form);
173
+
174
+ return (
175
+ <form>
176
+ <input {...form.register('username')} />
177
+ <input {...form.register('email')} />
178
+ <button>Submit</button>
179
+ </form>
180
+ );
181
+ }
182
+
183
+ // App.tsx - DevPanel 렌더링
184
+ function App() {
185
+ return (
186
+ <div>
187
+ {import.meta.env.DEV && <DevPanel />}
188
+ </div>
189
+ );
190
+ }
191
+ ```
192
+
193
+ **주요 기능:**
194
+ - 📝 Form DevTools - react-hook-form 상태 시각화 (form 등록 필요)
195
+ - 🌍 Timezone DevTools - 타임존 정보 확인
196
+ - 📐 Window Size - 윈도우 크기 표시 (토글 가능)
197
+ - 🔍 Z-Index Debugger - z-index 값 시각화 (토글 가능)
198
+ - 각 도구 독립적 토글
199
+ - 드래그 & 리사이즈 가능한 패널
200
+
201
+ **주의사항:**
202
+ - DevPanel을 사용하려면 앱 상단에서 DevToolsProvider로 감싸야 합니다
203
+ - Form DevTools를 사용하려면 페이지에서 useRegisterForm로 form을 등록해야 합니다
204
+
111
205
  #### ApiLogger
112
206
 
113
207
  API 요청/응답을 로깅하고 모니터링하는 컴포넌트입니다.
@@ -5,26 +5,31 @@ type Props = {
5
5
  };
6
6
  /**
7
7
  * 개발자 도구 패널
8
- * 여러 개발용 도구를 하나의 패널에서 관리할 있습니다.
8
+ * Layout에 번만 배치하고, 페이지에서 hook으로 form을 주입받습니다.
9
9
  *
10
10
  * @example
11
11
  * ```tsx
12
- * // Vite 프로젝트
12
+ * // Layout.tsx (한 번만)
13
13
  * import { DevPanel } from '@blastlabs/utils/components/dev';
14
14
  *
15
- * function App() {
15
+ * function Layout() {
16
16
  * return (
17
- * <div>
18
- * {import.meta.env.DEV && <DevPanel />}
19
- * </div>
17
+ * <>
18
+ * <Outlet />
19
+ * <DevPanel />
20
+ * </>
20
21
  * );
21
22
  * }
22
- * ```
23
23
  *
24
- * @example
25
- * ```tsx
26
- * // Create React App 프로젝트
27
- * {process.env.NODE_ENV === 'development' && <DevPanel position="top-left" />}
24
+ * // 페이지에서 form 주입
25
+ * import { useRegisterForm } from '@blastlabs/utils/components/dev';
26
+ *
27
+ * function MyPage() {
28
+ * const form = useForm();
29
+ * useRegisterForm(form);
30
+ *
31
+ * return <form>...</form>;
32
+ * }
28
33
  * ```
29
34
  */
30
35
  export default function DevPanel({ position }: Props): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"DevPanel.d.ts","sourceRoot":"","sources":["../../../src/components/dev/DevPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAG/D,KAAK,KAAK,GAAG;IACX,qCAAqC;IACrC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;CACtE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAE,QAAyB,EAAE,EAAE,KAAK,qBA6OpE"}
1
+ {"version":3,"file":"DevPanel.d.ts","sourceRoot":"","sources":["../../../src/components/dev/DevPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAOlE,KAAK,KAAK,GAAG;IACX,qCAAqC;IACrC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;CACtE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAE,QAAyB,EAAE,EAAE,KAAK,qBAkPpE"}
@@ -1,37 +1,47 @@
1
- import React, { useState, useRef } from 'react';
1
+ 'use client';
2
+ import React, { useState, useEffect } from 'react';
2
3
  import { useWindowSize } from '../../hooks';
4
+ import { useDevTools } from './DevToolsProvider';
5
+ import FormDevToolsContent from './FormDevTools/FormDevToolsContent';
6
+ import TimezoneDevToolsContent from './TimezoneDevTools/TimezoneDevToolsContent';
7
+ import ZIndexDebugger from './ZIndexDebugger';
3
8
  /**
4
9
  * 개발자 도구 패널
5
- * 여러 개발용 도구를 하나의 패널에서 관리할 있습니다.
10
+ * Layout에 번만 배치하고, 페이지에서 hook으로 form을 주입받습니다.
6
11
  *
7
12
  * @example
8
13
  * ```tsx
9
- * // Vite 프로젝트
14
+ * // Layout.tsx (한 번만)
10
15
  * import { DevPanel } from '@blastlabs/utils/components/dev';
11
16
  *
12
- * function App() {
17
+ * function Layout() {
13
18
  * return (
14
- * <div>
15
- * {import.meta.env.DEV && <DevPanel />}
16
- * </div>
19
+ * <>
20
+ * <Outlet />
21
+ * <DevPanel />
22
+ * </>
17
23
  * );
18
24
  * }
19
- * ```
20
25
  *
21
- * @example
22
- * ```tsx
23
- * // Create React App 프로젝트
24
- * {process.env.NODE_ENV === 'development' && <DevPanel position="top-left" />}
26
+ * // 페이지에서 form 주입
27
+ * import { useRegisterForm } from '@blastlabs/utils/components/dev';
28
+ *
29
+ * function MyPage() {
30
+ * const form = useForm();
31
+ * useRegisterForm(form);
32
+ *
33
+ * return <form>...</form>;
34
+ * }
25
35
  * ```
26
36
  */
27
37
  export default function DevPanel({ position = 'bottom-right' }) {
28
- const [isOpen, setIsOpen] = useState(false);
29
- const [showWindowSize, setShowWindowSize] = useState(false);
30
- const [showRenderCount, setShowRenderCount] = useState(false);
31
- const renderCount = useRef(0);
38
+ const { tools, toggleTool, getForm, isMenuOpen, toggleMenu, showWindowOverlay, toggleWindowOverlay, showZIndexDebugger, toggleZIndexDebugger, } = useDevTools();
32
39
  const { width, height } = useWindowSize();
33
- // 렌더 카운트 증가
34
- renderCount.current += 1;
40
+ const [mounted, setMounted] = useState(false);
41
+ useEffect(() => {
42
+ setMounted(true);
43
+ }, []);
44
+ const form = getForm();
35
45
  const positionStyles = {
36
46
  'top-left': { top: 16, left: 16 },
37
47
  'top-right': { top: 16, right: 16 },
@@ -118,79 +128,63 @@ export default function DevPanel({ position = 'bottom-right' }) {
118
128
  left: isOn ? '18px' : '2px',
119
129
  transition: 'left 0.2s',
120
130
  });
121
- const overlayStyle = {
122
- position: 'fixed',
123
- padding: '8px 12px',
124
- backgroundColor: 'rgba(0, 0, 0, 0.8)',
125
- color: 'white',
126
- fontSize: '14px',
127
- fontFamily: 'monospace',
128
- borderRadius: '8px',
129
- zIndex: 99998,
130
- };
131
- const handleClearLocalStorage = () => {
132
- if (confirm('LocalStorage를 모두 삭제하시겠습니까?')) {
133
- localStorage.clear();
134
- alert('LocalStorage가 삭제되었습니다.');
135
- }
136
- };
137
- const handleClearSessionStorage = () => {
138
- if (confirm('SessionStorage를 모두 삭제하시겠습니까?')) {
139
- sessionStorage.clear();
140
- alert('SessionStorage가 삭제되었습니다.');
141
- }
131
+ const disabledItemStyle = {
132
+ ...toggleItemStyle,
133
+ opacity: 0.5,
134
+ cursor: 'not-allowed',
142
135
  };
143
136
  return (React.createElement(React.Fragment, null,
144
137
  React.createElement("div", { style: containerStyle },
145
- React.createElement("button", { onClick: () => setIsOpen(!isOpen), style: toggleButtonStyle, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = '#2563eb'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = '#3b82f6') }, isOpen ? '✕' : '🛠'),
146
- isOpen && (React.createElement("div", { style: panelStyle },
138
+ React.createElement("button", { onClick: () => toggleMenu(), style: toggleButtonStyle, onMouseEnter: e => (e.currentTarget.style.backgroundColor = '#2563eb'), onMouseLeave: e => (e.currentTarget.style.backgroundColor = '#3b82f6') }, isMenuOpen ? '✕' : '🛠'),
139
+ isMenuOpen && (React.createElement("div", { style: panelStyle },
147
140
  React.createElement("div", { style: headerStyle }, "\u2699\uFE0F \uAC1C\uBC1C\uC790 \uB3C4\uAD6C"),
148
141
  React.createElement("div", { style: contentStyle },
149
- React.createElement("div", { style: toggleItemStyle, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = '#f3f4f6'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = 'transparent'), onClick: () => setShowWindowSize(!showWindowSize) },
150
- React.createElement("span", null, "\uC708\uB3C4\uC6B0 \uD06C\uAE30 \uD45C\uC2DC"),
151
- React.createElement("div", { style: getSwitchStyle(showWindowSize) },
152
- React.createElement("div", { style: getSwitchKnobStyle(showWindowSize) }))),
153
- React.createElement("div", { style: toggleItemStyle, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = '#f3f4f6'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = 'transparent'), onClick: () => setShowRenderCount(!showRenderCount) },
154
- React.createElement("span", null, "\uB80C\uB354 \uCE74\uC6B4\uD2B8 \uD45C\uC2DC"),
155
- React.createElement("div", { style: getSwitchStyle(showRenderCount) },
156
- React.createElement("div", { style: getSwitchKnobStyle(showRenderCount) }))),
157
- React.createElement("div", { style: { height: '1px', backgroundColor: '#e5e7eb', margin: '8px 0' } }),
158
- React.createElement("button", { onClick: handleClearLocalStorage, style: {
159
- ...toggleItemStyle,
160
- border: '1px solid #e5e7eb',
161
- backgroundColor: 'white',
162
- color: '#dc2626',
163
- fontWeight: 500,
164
- }, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = '#fef2f2'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = 'white') }, "\uD83D\uDDD1\uFE0F LocalStorage \uC0AD\uC81C"),
165
- React.createElement("button", { onClick: handleClearSessionStorage, style: {
166
- ...toggleItemStyle,
167
- border: '1px solid #e5e7eb',
168
- backgroundColor: 'white',
169
- color: '#dc2626',
170
- fontWeight: 500,
171
- }, onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = '#fef2f2'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = 'white') }, "\uD83D\uDDD1\uFE0F SessionStorage \uC0AD\uC81C"))))),
172
- showWindowSize && (React.createElement("div", { style: {
173
- ...overlayStyle,
142
+ form ? (React.createElement("div", { style: toggleItemStyle, onMouseEnter: e => (e.currentTarget.style.backgroundColor = '#f3f4f6'), onMouseLeave: e => (e.currentTarget.style.backgroundColor = 'transparent'), onClick: () => toggleTool('formDevTools') },
143
+ React.createElement("span", null, "\uD83D\uDCDD Form DevTools"),
144
+ React.createElement("div", { style: getSwitchStyle(tools.formDevTools) },
145
+ React.createElement("div", { style: getSwitchKnobStyle(tools.formDevTools) })))) : (React.createElement("div", { style: disabledItemStyle },
146
+ React.createElement("span", null, "\uD83D\uDCDD Form DevTools"),
147
+ React.createElement("div", { style: getSwitchStyle(false) },
148
+ React.createElement("div", { style: getSwitchKnobStyle(false) })))),
149
+ React.createElement("div", { style: toggleItemStyle, onMouseEnter: e => (e.currentTarget.style.backgroundColor = '#f3f4f6'), onMouseLeave: e => (e.currentTarget.style.backgroundColor = 'transparent'), onClick: () => toggleTool('timezoneDevTools') },
150
+ React.createElement("span", null, "\uD83C\uDF0D Timezone DevTools"),
151
+ React.createElement("div", { style: getSwitchStyle(tools.timezoneDevTools) },
152
+ React.createElement("div", { style: getSwitchKnobStyle(tools.timezoneDevTools) }))),
153
+ React.createElement("div", { style: toggleItemStyle, onMouseEnter: e => (e.currentTarget.style.backgroundColor = '#f3f4f6'), onMouseLeave: e => (e.currentTarget.style.backgroundColor = 'transparent'), onClick: () => toggleWindowOverlay() },
154
+ React.createElement("span", null, "\uD83D\uDCD0 Window Size"),
155
+ React.createElement("div", { style: getSwitchStyle(showWindowOverlay) },
156
+ React.createElement("div", { style: getSwitchKnobStyle(showWindowOverlay) }))),
157
+ React.createElement("div", { style: toggleItemStyle, onMouseEnter: e => (e.currentTarget.style.backgroundColor = '#f3f4f6'), onMouseLeave: e => (e.currentTarget.style.backgroundColor = 'transparent'), onClick: () => toggleZIndexDebugger() },
158
+ React.createElement("span", null, "\uD83D\uDD0D Z-Index Debugger"),
159
+ React.createElement("div", { style: getSwitchStyle(showZIndexDebugger) },
160
+ React.createElement("div", { style: getSwitchKnobStyle(showZIndexDebugger) }))))))),
161
+ tools.formDevTools && form && (React.createElement("div", { style: { position: 'fixed', top: 80, right: 16, zIndex: 99998 } },
162
+ React.createElement(FormDevToolsContent, { form: form }))),
163
+ tools.timezoneDevTools && (React.createElement("div", { style: { position: 'fixed', top: 80, left: 16, zIndex: 99998 } },
164
+ React.createElement(TimezoneDevToolsContent, null))),
165
+ showZIndexDebugger && (React.createElement("div", { style: { position: 'fixed', top: 80, right: 16, zIndex: 99997 } },
166
+ React.createElement(ZIndexDebugger, { position: "top-right" }))),
167
+ mounted && showWindowOverlay && (React.createElement("div", { "data-testid": "window-overlay", style: {
168
+ position: 'fixed',
169
+ padding: '8px 12px',
170
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
171
+ color: 'white',
172
+ fontSize: '14px',
173
+ fontFamily: 'monospace',
174
+ borderRadius: '8px',
175
+ zIndex: 99997,
174
176
  top: 16,
175
177
  left: 16,
176
178
  } },
177
179
  React.createElement("span", { style: { color: '#60a5fa' } }, "W:"),
178
- ' ',
180
+ " ",
179
181
  React.createElement("span", { style: { fontWeight: 'bold' } },
180
182
  width,
181
183
  "px"),
182
184
  React.createElement("span", { style: { color: '#9ca3af', margin: '0 4px' } }, "\u00D7"),
183
185
  React.createElement("span", { style: { color: '#4ade80' } }, "H:"),
184
- ' ',
186
+ " ",
185
187
  React.createElement("span", { style: { fontWeight: 'bold' } },
186
188
  height,
187
- "px"))),
188
- showRenderCount && (React.createElement("div", { style: {
189
- ...overlayStyle,
190
- top: 16,
191
- right: 16,
192
- } },
193
- React.createElement("span", { style: { color: '#fbbf24' } }, "Renders:"),
194
- ' ',
195
- React.createElement("span", { style: { fontWeight: 'bold' } }, renderCount.current)))));
189
+ "px")))));
196
190
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DevPanel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevPanel.test.d.ts","sourceRoot":"","sources":["../../../src/components/dev/DevPanel.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,194 @@
1
+ import React from 'react';
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import DevPanel from './DevPanel';
6
+ import { DevToolsProvider, useRegisterForm } from './DevToolsProvider';
7
+ // Mock useWindowSize
8
+ vi.mock('../../hooks', () => ({
9
+ useWindowSize: () => ({ width: 1920, height: 1080 }),
10
+ }));
11
+ // Mock FormDevToolsContent and TimezoneDevToolsContent
12
+ vi.mock('./FormDevTools/FormDevToolsContent', () => ({
13
+ default: () => React.createElement("div", { "data-testid": "form-devtools-panel" }, "Form DevTools Panel"),
14
+ }));
15
+ vi.mock('./TimezoneDevTools/TimezoneDevToolsContent', () => ({
16
+ default: () => React.createElement("div", { "data-testid": "timezone-devtools-panel" }, "Timezone DevTools Panel"),
17
+ }));
18
+ // Note: ZIndexDebugger is not mocked to test actual toggle behavior
19
+ // Mock form
20
+ const createMockForm = () => ({
21
+ formState: {
22
+ errors: {},
23
+ dirtyFields: {},
24
+ touchedFields: {},
25
+ isValid: true,
26
+ isSubmitting: false,
27
+ submitCount: 0,
28
+ defaultValues: { username: '', email: '' },
29
+ },
30
+ watch: vi.fn(() => ({ username: '', email: '' })),
31
+ setValue: vi.fn(),
32
+ trigger: vi.fn(),
33
+ });
34
+ function TestPage({ hasForm = true }) {
35
+ const form = createMockForm();
36
+ if (hasForm) {
37
+ useRegisterForm(form);
38
+ }
39
+ return React.createElement("div", null, "Test Page");
40
+ }
41
+ describe('DevPanel', () => {
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ });
45
+ describe('기본 동작', () => {
46
+ it('메인 토글 버튼이 렌더링된다', () => {
47
+ render(React.createElement(DevToolsProvider, null,
48
+ React.createElement(DevPanel, null)));
49
+ const toggleButton = screen.getByRole('button');
50
+ expect(toggleButton).toBeTruthy();
51
+ expect(toggleButton.textContent).toBe('🛠');
52
+ });
53
+ it('메뉴가 닫혀있을 때는 메뉴 패널이 보이지 않는다', () => {
54
+ render(React.createElement(DevToolsProvider, null,
55
+ React.createElement(DevPanel, null)));
56
+ expect(screen.queryByText('⚙️ 개발자 도구')).toBeNull();
57
+ });
58
+ it('윈도우 사이즈가 항상 표시된다', () => {
59
+ render(React.createElement(DevToolsProvider, null,
60
+ React.createElement(DevPanel, null)));
61
+ expect(screen.getByText(/W:/)).toBeTruthy();
62
+ expect(screen.getByText(/H:/)).toBeTruthy();
63
+ expect(screen.getByText('1920px')).toBeTruthy();
64
+ expect(screen.getByText('1080px')).toBeTruthy();
65
+ });
66
+ });
67
+ describe('메뉴 토글', () => {
68
+ it('메인 버튼 클릭 시 메뉴가 열린다', async () => {
69
+ const user = userEvent.setup();
70
+ render(React.createElement(DevToolsProvider, null,
71
+ React.createElement(DevPanel, null)));
72
+ const toggleButton = screen.getByRole('button');
73
+ expect(toggleButton.textContent).toBe('🛠');
74
+ await user.click(toggleButton);
75
+ expect(screen.getByText('⚙️ 개발자 도구')).toBeTruthy();
76
+ expect(toggleButton.textContent).toBe('✕');
77
+ });
78
+ it('메인 버튼 다시 클릭 시 메뉴가 닫힌다', async () => {
79
+ const user = userEvent.setup();
80
+ render(React.createElement(DevToolsProvider, null,
81
+ React.createElement(DevPanel, null)));
82
+ const toggleButton = screen.getByRole('button');
83
+ // 메뉴 열기
84
+ await user.click(toggleButton);
85
+ expect(screen.getByText('⚙️ 개발자 도구')).toBeTruthy();
86
+ // 메뉴 닫기
87
+ await user.click(toggleButton);
88
+ expect(screen.queryByText('⚙️ 개발자 도구')).toBeNull();
89
+ expect(toggleButton.textContent).toBe('🛠');
90
+ });
91
+ });
92
+ describe('툴 토글', () => {
93
+ it('메뉴에서 Form DevTools 토글 클릭 시 패널이 표시된다', async () => {
94
+ const user = userEvent.setup();
95
+ render(React.createElement(DevToolsProvider, null,
96
+ React.createElement(TestPage, { hasForm: true }),
97
+ React.createElement(DevPanel, null)));
98
+ const toggleButton = screen.getByRole('button');
99
+ await user.click(toggleButton);
100
+ const formDevToolsToggle = screen.getByText('📝 Form DevTools');
101
+ await user.click(formDevToolsToggle);
102
+ expect(screen.getByTestId('form-devtools-panel')).toBeTruthy();
103
+ });
104
+ it('메뉴에서 Timezone DevTools 토글 클릭 시 패널이 표시된다', async () => {
105
+ const user = userEvent.setup();
106
+ render(React.createElement(DevToolsProvider, null,
107
+ React.createElement(DevPanel, null)));
108
+ const toggleButton = screen.getByRole('button');
109
+ await user.click(toggleButton);
110
+ const timezoneDevToolsToggle = screen.getByText('🌍 Timezone DevTools');
111
+ await user.click(timezoneDevToolsToggle);
112
+ expect(screen.getByTestId('timezone-devtools-panel')).toBeTruthy();
113
+ });
114
+ });
115
+ describe('독립적 토글 동작', () => {
116
+ it('메뉴를 닫아도 각 툴 패널은 유지된다', async () => {
117
+ const user = userEvent.setup();
118
+ render(React.createElement(DevToolsProvider, null,
119
+ React.createElement(TestPage, { hasForm: true }),
120
+ React.createElement(DevPanel, null)));
121
+ const toggleButton = screen.getByRole('button');
122
+ // 메뉴 열기
123
+ await user.click(toggleButton);
124
+ // Form DevTools 켜기
125
+ const formToggle = screen.getByText('📝 Form DevTools');
126
+ await user.click(formToggle);
127
+ // 메뉴 닫기
128
+ await user.click(toggleButton);
129
+ // 메뉴는 닫혀야 함
130
+ expect(screen.queryByText('⚙️ 개발자 도구')).toBeNull();
131
+ // Form DevTools 패널은 유지되어야 함
132
+ expect(screen.getByTestId('form-devtools-panel')).toBeTruthy();
133
+ });
134
+ it('각 툴이 독립적으로 토글된다', async () => {
135
+ const user = userEvent.setup();
136
+ render(React.createElement(DevToolsProvider, null,
137
+ React.createElement(TestPage, { hasForm: true }),
138
+ React.createElement(DevPanel, null)));
139
+ const toggleButton = screen.getByRole('button');
140
+ // 메뉴 열기
141
+ await user.click(toggleButton);
142
+ // Form DevTools만 켜기
143
+ const formToggle = screen.getByText('📝 Form DevTools');
144
+ await user.click(formToggle);
145
+ // 메뉴 닫기
146
+ await user.click(toggleButton);
147
+ // Form DevTools 패널만 있어야 함
148
+ expect(screen.getByTestId('form-devtools-panel')).toBeTruthy();
149
+ expect(screen.queryByTestId('timezone-devtools-panel')).toBeNull();
150
+ // 메뉴 다시 열기
151
+ await user.click(toggleButton);
152
+ // Timezone DevTools 켜기
153
+ const timezoneToggle = screen.getByText('🌍 Timezone DevTools');
154
+ await user.click(timezoneToggle);
155
+ // 메뉴 닫기
156
+ await user.click(toggleButton);
157
+ // 두 패널 모두 있어야 함
158
+ expect(screen.getByTestId('form-devtools-panel')).toBeTruthy();
159
+ expect(screen.getByTestId('timezone-devtools-panel')).toBeTruthy();
160
+ });
161
+ });
162
+ describe('패널 토글', () => {
163
+ it('Window Size Overlay 토글', async () => {
164
+ const user = userEvent.setup();
165
+ render(React.createElement(DevToolsProvider, null,
166
+ React.createElement(DevPanel, null)));
167
+ // 윈도우 사이즈가 항상 표시됨
168
+ expect(screen.getByText(/W:/)).toBeTruthy();
169
+ expect(screen.getByText(/H:/)).toBeTruthy();
170
+ expect(screen.getByText('1920px')).toBeTruthy();
171
+ expect(screen.getByText('1080px')).toBeTruthy();
172
+ const toggleButton = screen.getByRole('button');
173
+ await user.click(toggleButton);
174
+ const windowToggle = screen.getByText('📐 Window Size');
175
+ await user.click(windowToggle);
176
+ // 숨겨졸 때 윈도우 사이즈가 사라짐
177
+ expect(screen.queryByText(/W:/)).toBeNull();
178
+ expect(screen.queryByText(/H:/)).toBeNull();
179
+ });
180
+ it('ZIndex Debugger 토글', async () => {
181
+ const user = userEvent.setup();
182
+ render(React.createElement(DevToolsProvider, null,
183
+ React.createElement(DevPanel, null)));
184
+ // 초기 상태에서는 렌더링되지 않음
185
+ expect(screen.queryByTestId('zindex-debugger')).toBeNull();
186
+ const toggleButton = screen.getByRole('button');
187
+ await user.click(toggleButton);
188
+ const zIndexToggle = screen.getByText('🔍 Z-Index Debugger');
189
+ await user.click(zIndexToggle);
190
+ // 토글 후 ZIndexDebugger가 렌더링됨
191
+ expect(screen.getByTestId('zindex-debugger')).toBeTruthy();
192
+ });
193
+ });
194
+ });