dash-devtools 1.0.0__py3-none-any.whl

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 (53) hide show
  1. dash_devtools/__init__.py +8 -0
  2. dash_devtools/__main__.py +11 -0
  3. dash_devtools/ai_engine.py +441 -0
  4. dash_devtools/browser.py +541 -0
  5. dash_devtools/cli.py +1452 -0
  6. dash_devtools/database.py +338 -0
  7. dash_devtools/dbdiagram.py +183 -0
  8. dash_devtools/e2e.py +329 -0
  9. dash_devtools/fixers/__init__.py +57 -0
  10. dash_devtools/fixers/migration_fixer.py +115 -0
  11. dash_devtools/fixers/ux_fixer.py +106 -0
  12. dash_devtools/fixers/version_bumper.py +115 -0
  13. dash_devtools/gas_mes_test.py +1241 -0
  14. dash_devtools/generators/__init__.py +84 -0
  15. dash_devtools/health.py +476 -0
  16. dash_devtools/hooks/__init__.py +250 -0
  17. dash_devtools/hooks/pre_commit.py +161 -0
  18. dash_devtools/hooks/pre_push.py +275 -0
  19. dash_devtools/init_test.py +352 -0
  20. dash_devtools/markdown_report.py +309 -0
  21. dash_devtools/migrators/__init__.py +21 -0
  22. dash_devtools/perf.py +321 -0
  23. dash_devtools/report.py +667 -0
  24. dash_devtools/reporters/__init__.py +11 -0
  25. dash_devtools/spec.py +230 -0
  26. dash_devtools/stats.py +355 -0
  27. dash_devtools/test_suite.py +690 -0
  28. dash_devtools/testing.py +416 -0
  29. dash_devtools/validators/__init__.py +157 -0
  30. dash_devtools/validators/backend/__init__.py +12 -0
  31. dash_devtools/validators/backend/nodejs.py +245 -0
  32. dash_devtools/validators/backend/python.py +439 -0
  33. dash_devtools/validators/code_quality.py +243 -0
  34. dash_devtools/validators/common/__init__.py +11 -0
  35. dash_devtools/validators/common/quality.py +319 -0
  36. dash_devtools/validators/common/security.py +270 -0
  37. dash_devtools/validators/common/spec.py +273 -0
  38. dash_devtools/validators/detector.py +394 -0
  39. dash_devtools/validators/frontend/__init__.py +14 -0
  40. dash_devtools/validators/frontend/angular.py +245 -0
  41. dash_devtools/validators/frontend/gas.py +310 -0
  42. dash_devtools/validators/frontend/vite.py +539 -0
  43. dash_devtools/validators/migration.py +292 -0
  44. dash_devtools/validators/performance.py +167 -0
  45. dash_devtools/validators/security.py +205 -0
  46. dash_devtools/vision/__init__.py +368 -0
  47. dash_devtools/watch.py +266 -0
  48. dash_devtools/word_report.py +690 -0
  49. dash_devtools-1.0.0.dist-info/METADATA +834 -0
  50. dash_devtools-1.0.0.dist-info/RECORD +53 -0
  51. dash_devtools-1.0.0.dist-info/WHEEL +5 -0
  52. dash_devtools-1.0.0.dist-info/entry_points.txt +2 -0
  53. dash_devtools-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,352 @@
1
+ """
2
+ 測試框架初始化工具
3
+
4
+ 自動為專案設定測試框架:
5
+ - Vite 專案 → Vitest
6
+ - Angular 專案 → Jest
7
+ - 加入範例測試
8
+ """
9
+
10
+ import json
11
+ import subprocess
12
+ from pathlib import Path
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.prompt import Confirm
16
+
17
+ console = Console()
18
+
19
+
20
+ # Vitest 設定模板
21
+ VITEST_CONFIG = '''import { defineConfig } from 'vitest/config'
22
+
23
+ export default defineConfig({
24
+ test: {
25
+ globals: true,
26
+ environment: 'jsdom',
27
+ coverage: {
28
+ provider: 'v8',
29
+ reporter: ['text', 'html'],
30
+ exclude: ['node_modules/', 'dist/']
31
+ }
32
+ }
33
+ })
34
+ '''
35
+
36
+ # 範例測試 (Vitest)
37
+ VITEST_EXAMPLE = '''import { describe, it, expect } from 'vitest'
38
+
39
+ describe('範例測試', () => {
40
+ it('應該正確計算加法', () => {
41
+ expect(1 + 1).toBe(2)
42
+ })
43
+
44
+ it('應該正確處理字串', () => {
45
+ const name = '測試'
46
+ expect(name).toContain('測')
47
+ })
48
+ })
49
+ '''
50
+
51
+ # Jest 設定 (Angular)
52
+ JEST_CONFIG_ANGULAR = '''module.exports = {
53
+ preset: 'jest-preset-angular',
54
+ setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
55
+ testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/'],
56
+ coverageDirectory: 'coverage',
57
+ collectCoverageFrom: [
58
+ 'src/**/*.ts',
59
+ '!src/**/*.module.ts',
60
+ '!src/main.ts',
61
+ '!src/polyfills.ts'
62
+ ]
63
+ };
64
+ '''
65
+
66
+ JEST_SETUP_ANGULAR = '''import 'jest-preset-angular/setup-jest';
67
+ '''
68
+
69
+ # Playwright 設定
70
+ PLAYWRIGHT_CONFIG = '''import { defineConfig, devices } from '@playwright/test';
71
+
72
+ export default defineConfig({
73
+ testDir: './e2e',
74
+ timeout: 30000,
75
+ expect: { timeout: 5000 },
76
+ fullyParallel: true,
77
+ retries: process.env.CI ? 2 : 0,
78
+ reporter: 'html',
79
+ use: {
80
+ baseURL: 'http://localhost:5173',
81
+ trace: 'on-first-retry',
82
+ screenshot: 'only-on-failure'
83
+ },
84
+ projects: [
85
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
86
+ { name: 'Mobile Safari', use: { ...devices['iPhone 12'] } }
87
+ ],
88
+ webServer: {
89
+ command: 'npm run dev',
90
+ url: 'http://localhost:5173',
91
+ reuseExistingServer: !process.env.CI
92
+ }
93
+ });
94
+ '''
95
+
96
+ PLAYWRIGHT_EXAMPLE = '''import { test, expect } from '@playwright/test';
97
+
98
+ test('首頁應該正確載入', async ({ page }) => {
99
+ await page.goto('/');
100
+
101
+ // 檢查標題
102
+ await expect(page).toHaveTitle(/./);
103
+
104
+ // 檢查主要內容區塊存在
105
+ const main = page.locator('main, #app, .container');
106
+ await expect(main).toBeVisible();
107
+ });
108
+
109
+ test('應該可以導航', async ({ page }) => {
110
+ await page.goto('/');
111
+
112
+ // 點擊連結並確認導航
113
+ // await page.click('text=關於');
114
+ // await expect(page).toHaveURL(/about/);
115
+ });
116
+ '''
117
+
118
+
119
+ def detect_project_type(project_path: Path) -> dict:
120
+ """偵測專案類型"""
121
+ package_json = project_path / 'package.json'
122
+
123
+ if not package_json.exists():
124
+ return {'type': 'unknown'}
125
+
126
+ try:
127
+ pkg = json.loads(package_json.read_text())
128
+ deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
129
+
130
+ if '@angular/core' in deps:
131
+ return {'type': 'angular', 'pkg': pkg}
132
+ elif 'vite' in deps:
133
+ return {'type': 'vite', 'pkg': pkg}
134
+ elif 'react' in deps:
135
+ return {'type': 'react', 'pkg': pkg}
136
+ else:
137
+ return {'type': 'vanilla', 'pkg': pkg}
138
+
139
+ except Exception:
140
+ return {'type': 'unknown'}
141
+
142
+
143
+ def init_vitest(project_path: Path, pkg: dict) -> dict:
144
+ """初始化 Vitest"""
145
+ results = {'success': True, 'steps': []}
146
+
147
+ # 1. 安裝依賴
148
+ console.print("[cyan]安裝 Vitest...[/cyan]")
149
+ proc = subprocess.run(
150
+ ['npm', 'install', '-D', 'vitest', '@vitest/coverage-v8', 'jsdom'],
151
+ cwd=project_path,
152
+ capture_output=True,
153
+ text=True
154
+ )
155
+ if proc.returncode != 0:
156
+ results['success'] = False
157
+ results['error'] = proc.stderr
158
+ return results
159
+ results['steps'].append('安裝 vitest, @vitest/coverage-v8, jsdom')
160
+
161
+ # 2. 建立設定檔
162
+ config_path = project_path / 'vitest.config.ts'
163
+ if not config_path.exists():
164
+ config_path.write_text(VITEST_CONFIG)
165
+ results['steps'].append('建立 vitest.config.ts')
166
+
167
+ # 3. 建立測試目錄和範例
168
+ tests_dir = project_path / 'tests'
169
+ tests_dir.mkdir(exist_ok=True)
170
+
171
+ example_path = tests_dir / 'example.test.ts'
172
+ if not example_path.exists():
173
+ example_path.write_text(VITEST_EXAMPLE)
174
+ results['steps'].append('建立 tests/example.test.ts')
175
+
176
+ # 4. 更新 package.json scripts
177
+ package_json = project_path / 'package.json'
178
+ pkg = json.loads(package_json.read_text())
179
+
180
+ if 'scripts' not in pkg:
181
+ pkg['scripts'] = {}
182
+
183
+ pkg['scripts']['test'] = 'vitest'
184
+ pkg['scripts']['test:run'] = 'vitest run'
185
+ pkg['scripts']['test:coverage'] = 'vitest run --coverage'
186
+
187
+ package_json.write_text(json.dumps(pkg, indent=2, ensure_ascii=False))
188
+ results['steps'].append('更新 package.json scripts')
189
+
190
+ return results
191
+
192
+
193
+ def init_jest_angular(project_path: Path, pkg: dict) -> dict:
194
+ """初始化 Jest (Angular)"""
195
+ results = {'success': True, 'steps': []}
196
+
197
+ # 1. 安裝依賴
198
+ console.print("[cyan]安裝 Jest for Angular...[/cyan]")
199
+ proc = subprocess.run(
200
+ ['npm', 'install', '-D', 'jest', 'jest-preset-angular', '@types/jest'],
201
+ cwd=project_path,
202
+ capture_output=True,
203
+ text=True
204
+ )
205
+ if proc.returncode != 0:
206
+ results['success'] = False
207
+ results['error'] = proc.stderr
208
+ return results
209
+ results['steps'].append('安裝 jest, jest-preset-angular')
210
+
211
+ # 2. 建立設定檔
212
+ config_path = project_path / 'jest.config.js'
213
+ if not config_path.exists():
214
+ config_path.write_text(JEST_CONFIG_ANGULAR)
215
+ results['steps'].append('建立 jest.config.js')
216
+
217
+ setup_path = project_path / 'setup-jest.ts'
218
+ if not setup_path.exists():
219
+ setup_path.write_text(JEST_SETUP_ANGULAR)
220
+ results['steps'].append('建立 setup-jest.ts')
221
+
222
+ # 3. 更新 package.json
223
+ package_json = project_path / 'package.json'
224
+ pkg = json.loads(package_json.read_text())
225
+
226
+ if 'scripts' not in pkg:
227
+ pkg['scripts'] = {}
228
+
229
+ pkg['scripts']['test'] = 'jest'
230
+ pkg['scripts']['test:watch'] = 'jest --watch'
231
+ pkg['scripts']['test:coverage'] = 'jest --coverage'
232
+
233
+ package_json.write_text(json.dumps(pkg, indent=2, ensure_ascii=False))
234
+ results['steps'].append('更新 package.json scripts')
235
+
236
+ return results
237
+
238
+
239
+ def init_playwright(project_path: Path) -> dict:
240
+ """初始化 Playwright E2E"""
241
+ results = {'success': True, 'steps': []}
242
+
243
+ # 1. 安裝依賴
244
+ console.print("[cyan]安裝 Playwright...[/cyan]")
245
+ proc = subprocess.run(
246
+ ['npm', 'install', '-D', '@playwright/test'],
247
+ cwd=project_path,
248
+ capture_output=True,
249
+ text=True
250
+ )
251
+ if proc.returncode != 0:
252
+ results['success'] = False
253
+ results['error'] = proc.stderr
254
+ return results
255
+ results['steps'].append('安裝 @playwright/test')
256
+
257
+ # 安裝瀏覽器
258
+ console.print("[cyan]安裝瀏覽器...[/cyan]")
259
+ subprocess.run(
260
+ ['npx', 'playwright', 'install', 'chromium'],
261
+ cwd=project_path,
262
+ capture_output=True
263
+ )
264
+ results['steps'].append('安裝 Chromium')
265
+
266
+ # 2. 建立設定檔
267
+ config_path = project_path / 'playwright.config.ts'
268
+ if not config_path.exists():
269
+ config_path.write_text(PLAYWRIGHT_CONFIG)
270
+ results['steps'].append('建立 playwright.config.ts')
271
+
272
+ # 3. 建立 e2e 目錄和範例
273
+ e2e_dir = project_path / 'e2e'
274
+ e2e_dir.mkdir(exist_ok=True)
275
+
276
+ example_path = e2e_dir / 'home.spec.ts'
277
+ if not example_path.exists():
278
+ example_path.write_text(PLAYWRIGHT_EXAMPLE)
279
+ results['steps'].append('建立 e2e/home.spec.ts')
280
+
281
+ # 4. 更新 package.json
282
+ package_json = project_path / 'package.json'
283
+ pkg = json.loads(package_json.read_text())
284
+
285
+ pkg['scripts']['e2e'] = 'playwright test'
286
+ pkg['scripts']['e2e:ui'] = 'playwright test --ui'
287
+ pkg['scripts']['e2e:report'] = 'playwright show-report'
288
+
289
+ package_json.write_text(json.dumps(pkg, indent=2, ensure_ascii=False))
290
+ results['steps'].append('更新 package.json scripts')
291
+
292
+ return results
293
+
294
+
295
+ def run_init_test(project_path: str, include_e2e: bool = False) -> dict:
296
+ """初始化測試框架"""
297
+ project = Path(project_path)
298
+ project_name = project.name
299
+
300
+ # 偵測專案類型
301
+ info = detect_project_type(project)
302
+
303
+ console.print(Panel(
304
+ f"[bold]{project_name}[/bold] 測試框架初始化",
305
+ border_style="cyan"
306
+ ))
307
+
308
+ if info['type'] == 'unknown':
309
+ console.print("[red]無法偵測專案類型 (缺少 package.json)[/red]")
310
+ return {'success': False}
311
+
312
+ console.print(f"[dim]專案類型:[/dim] {info['type']}")
313
+ console.print()
314
+
315
+ results = {'unit': None, 'e2e': None}
316
+
317
+ # 單元測試
318
+ if info['type'] == 'angular':
319
+ console.print("[bold]設定 Jest (Angular)[/bold]")
320
+ results['unit'] = init_jest_angular(project, info.get('pkg', {}))
321
+ else:
322
+ console.print("[bold]設定 Vitest[/bold]")
323
+ results['unit'] = init_vitest(project, info.get('pkg', {}))
324
+
325
+ if results['unit']['success']:
326
+ for step in results['unit']['steps']:
327
+ console.print(f" [green]✓[/green] {step}")
328
+ else:
329
+ console.print(f" [red]✗[/red] {results['unit'].get('error', '未知錯誤')}")
330
+
331
+ # E2E 測試
332
+ if include_e2e:
333
+ console.print()
334
+ console.print("[bold]設定 Playwright (E2E)[/bold]")
335
+ results['e2e'] = init_playwright(project)
336
+
337
+ if results['e2e']['success']:
338
+ for step in results['e2e']['steps']:
339
+ console.print(f" [green]✓[/green] {step}")
340
+
341
+ # 完成訊息
342
+ console.print()
343
+ console.print("[green]測試框架設定完成![/green]")
344
+ console.print()
345
+ console.print("[dim]使用方式:[/dim]")
346
+ console.print(" npm test # 執行測試")
347
+ console.print(" npm run test:coverage # 產生覆蓋率報告")
348
+ if include_e2e:
349
+ console.print(" npm run e2e # 執行 E2E 測試")
350
+ console.print(" npm run e2e:ui # 開啟 Playwright UI")
351
+
352
+ return results
@@ -0,0 +1,309 @@
1
+ """
2
+ Markdown 測試報告生成模組
3
+
4
+ 生成專業的 Markdown 格式測試報告
5
+ - 測試摘要
6
+ - 測試結果表格
7
+ - 測試案例明細
8
+ - ASCII 圖表
9
+ """
10
+
11
+ from pathlib import Path
12
+ from datetime import datetime
13
+ from typing import Dict, List
14
+
15
+ from rich.console import Console
16
+
17
+ console = Console()
18
+
19
+
20
+ def create_ascii_progress_bar(passed: int, failed: int, width: int = 30) -> str:
21
+ """建立 ASCII 進度條"""
22
+ total = passed + failed
23
+ if total == 0:
24
+ return "[" + "-" * width + "] N/A"
25
+
26
+ pass_rate = passed / total
27
+ filled = int(width * pass_rate)
28
+
29
+ bar = "[" + "=" * filled + "-" * (width - filled) + "]"
30
+ return f"{bar} {pass_rate * 100:.1f}%"
31
+
32
+
33
+ def format_duration(duration: float) -> str:
34
+ """格式化時間"""
35
+ if duration <= 0:
36
+ return "-"
37
+ elif duration < 0.001:
38
+ return f"{duration * 1000000:.0f}us"
39
+ elif duration < 0.1:
40
+ return f"{duration * 1000:.2f}ms"
41
+ elif duration < 1:
42
+ return f"{duration * 1000:.0f}ms"
43
+ else:
44
+ return f"{duration:.2f}s"
45
+
46
+
47
+ def generate_markdown_report(
48
+ project_name: str,
49
+ test_results: Dict,
50
+ output_path: str,
51
+ ) -> str:
52
+ """
53
+ 生成 Markdown 測試報告
54
+
55
+ Args:
56
+ project_name: 專案名稱
57
+ test_results: 測試結果字典 (來自 test_suite)
58
+ output_path: 輸出檔案路徑
59
+
60
+ Returns:
61
+ 輸出檔案路徑
62
+ """
63
+ lines = []
64
+
65
+ # ========== 標題 ==========
66
+ lines.append(f"# {project_name} 測試報告")
67
+ lines.append("")
68
+
69
+ timestamp = test_results.get('timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
70
+ lines.append(f"> 報告產生時間: {timestamp}")
71
+ lines.append("")
72
+
73
+ # 狀態標示
74
+ overall_success = test_results.get('overall_success', True)
75
+ if overall_success:
76
+ lines.append("![Status](https://img.shields.io/badge/Status-PASSED-success)")
77
+ else:
78
+ lines.append("![Status](https://img.shields.io/badge/Status-FAILED-critical)")
79
+ lines.append("")
80
+
81
+ # ========== 測試摘要 ==========
82
+ lines.append("## 測試摘要")
83
+ lines.append("")
84
+
85
+ summary = test_results.get('summary', {})
86
+ total_passed = summary.get('total_passed', 0)
87
+ total_failed = summary.get('total_failed', 0)
88
+ total_duration = summary.get('total_duration', 0)
89
+ coverage = summary.get('coverage', 0)
90
+
91
+ # 從 test_cases 計算總時間
92
+ if total_duration == 0:
93
+ tests = test_results.get('tests', {})
94
+ for result in tests.values():
95
+ test_cases = result.get('test_cases', [])
96
+ total_duration += sum(tc.get('duration', 0) for tc in test_cases)
97
+
98
+ # 進度條
99
+ lines.append("```")
100
+ lines.append(create_ascii_progress_bar(total_passed, total_failed, 40))
101
+ lines.append("```")
102
+ lines.append("")
103
+
104
+ # 摘要表格
105
+ lines.append("| 項目 | 數值 |")
106
+ lines.append("|------|------|")
107
+ lines.append(f"| 總測試數 | {total_passed + total_failed} |")
108
+ lines.append(f"| 通過 | {total_passed} |")
109
+ lines.append(f"| 失敗 | {total_failed} |")
110
+ lines.append(f"| 執行時間 | {format_duration(total_duration)} |")
111
+ lines.append(f"| 程式碼覆蓋率 | {coverage:.1f}% |" if coverage > 0 else "| 程式碼覆蓋率 | N/A |")
112
+ lines.append("")
113
+
114
+ # ========== 各類型測試結果 ==========
115
+ lines.append("## 各類型測試結果")
116
+ lines.append("")
117
+
118
+ tests = test_results.get('tests', {})
119
+ configured_tests = {k: v for k, v in tests.items() if not v.get('not_configured', False)}
120
+
121
+ if not configured_tests:
122
+ lines.append("*此專案未設定任何測試框架*")
123
+ lines.append("")
124
+ else:
125
+ type_labels = {
126
+ 'UIT': '單元測試 (UIT)',
127
+ 'SMOKE': '煙霧測試 (Smoke)',
128
+ 'E2E': '端對端測試 (E2E)',
129
+ 'UAT': '驗收測試 (UAT)'
130
+ }
131
+
132
+ lines.append("| 測試類型 | 狀態 | 通過 | 失敗 | 時間 |")
133
+ lines.append("|----------|------|------|------|------|")
134
+
135
+ for test_type, result in configured_tests.items():
136
+ label = type_labels.get(test_type, test_type)
137
+ success = result.get('success', True)
138
+ status = "PASS" if success else "FAIL"
139
+ passed = result.get('passed', 0)
140
+ failed = result.get('failed', 0)
141
+
142
+ duration = result.get('duration', 0)
143
+ if duration == 0:
144
+ test_cases = result.get('test_cases', [])
145
+ duration = sum(tc.get('duration', 0) for tc in test_cases)
146
+
147
+ lines.append(f"| {label} | {status} | {passed} | {failed} | {format_duration(duration)} |")
148
+
149
+ lines.append("")
150
+
151
+ # ========== 測試案例明細 ==========
152
+ for test_type, result in configured_tests.items():
153
+ test_cases = result.get('test_cases', [])
154
+ if not test_cases:
155
+ continue
156
+
157
+ type_labels = {
158
+ 'UIT': '單元測試 (UIT)',
159
+ 'SMOKE': '煙霧測試 (Smoke)',
160
+ 'E2E': '端對端測試 (E2E)',
161
+ 'UAT': '驗收測試 (UAT)'
162
+ }
163
+
164
+ lines.append(f"### {type_labels.get(test_type, test_type)} 明細")
165
+ lines.append("")
166
+
167
+ # 分類顯示
168
+ passed_cases = [tc for tc in test_cases if tc.get('status') == 'passed']
169
+ failed_cases = [tc for tc in test_cases if tc.get('status') == 'failed']
170
+ skipped_cases = [tc for tc in test_cases if tc.get('status') == 'skipped']
171
+
172
+ if passed_cases:
173
+ lines.append("<details>")
174
+ lines.append(f"<summary>通過 ({len(passed_cases)})</summary>")
175
+ lines.append("")
176
+ for tc in passed_cases:
177
+ name = tc.get('name', '')
178
+ duration = tc.get('duration', 0)
179
+ lines.append(f"- [x] {name} ({format_duration(duration)})")
180
+ lines.append("")
181
+ lines.append("</details>")
182
+ lines.append("")
183
+
184
+ if failed_cases:
185
+ lines.append(f"**失敗 ({len(failed_cases)})**")
186
+ lines.append("")
187
+ for tc in failed_cases:
188
+ name = tc.get('name', '')
189
+ duration = tc.get('duration', 0)
190
+ error = tc.get('error', '')
191
+ lines.append(f"- [ ] {name} ({format_duration(duration)})")
192
+ if error:
193
+ lines.append(f" - Error: `{error[:100]}`")
194
+ lines.append("")
195
+
196
+ if skipped_cases:
197
+ lines.append(f"**跳過 ({len(skipped_cases)})**")
198
+ lines.append("")
199
+ for tc in skipped_cases:
200
+ name = tc.get('name', '')
201
+ lines.append(f"- [ ] ~{name}~")
202
+ lines.append("")
203
+
204
+ # ========== 測試類型說明 ==========
205
+ lines.append("---")
206
+ lines.append("")
207
+ lines.append("## 測試類型說明")
208
+ lines.append("")
209
+
210
+ descriptions = [
211
+ ('UIT', 'Unit Integration Testing', '單元測試驗證各個模組、函數的正確性。使用 Vitest/Jest 框架執行,並產生程式碼覆蓋率報告。'),
212
+ ('Smoke', '煙霧測試', '快速驗證系統關鍵路徑是否正常運作。包含應用程式啟動、頁面載入、API 健康檢查等基本功能。'),
213
+ ('E2E', 'End-to-End Testing', '端對端測試模擬真實使用情境,驗證完整的使用者流程。使用 Playwright 自動化測試框架執行。'),
214
+ ('UAT', 'User Acceptance Testing', '使用者驗收測試從業務角度驗證系統符合需求規格。測試案例依據使用者角色設計,確保系統滿足業務需求。'),
215
+ ]
216
+
217
+ for abbr, full, desc in descriptions:
218
+ lines.append(f"- **{abbr}** ({full}): {desc}")
219
+
220
+ lines.append("")
221
+ lines.append("---")
222
+ lines.append("")
223
+ lines.append("*Generated by DashAI DevTools*")
224
+
225
+ # 儲存
226
+ content = "\n".join(lines)
227
+ output_file = Path(output_path)
228
+ output_file.write_text(content, encoding='utf-8')
229
+
230
+ return str(output_file)
231
+
232
+
233
+ def run_and_generate_markdown_report(
234
+ project_path: str,
235
+ output_path: str = None,
236
+ test_types: List[str] = None,
237
+ ) -> Dict:
238
+ """
239
+ 執行測試並生成 Markdown 報告
240
+
241
+ Args:
242
+ project_path: 專案路徑
243
+ output_path: 輸出路徑 (預設為專案目錄下的 test-report.md)
244
+ test_types: 測試類型列表
245
+
246
+ Returns:
247
+ 結果字典
248
+ """
249
+ from .test_suite import TestSuiteRunner
250
+
251
+ project = Path(project_path).resolve() # 使用 resolve() 取得完整路徑
252
+ project_name = project.name
253
+
254
+ if not output_path:
255
+ output_path = str(project / f'{project_name}-test-report.md')
256
+
257
+ # 執行測試
258
+ console.print(f"[cyan]執行 {project_name} 測試套件...[/cyan]")
259
+ runner = TestSuiteRunner(project_path)
260
+ suite_result = runner.run_all(test_types)
261
+
262
+ # 準備測試結果
263
+ test_results = {
264
+ 'project': project_name,
265
+ 'timestamp': suite_result.timestamp,
266
+ 'overall_success': suite_result.overall_success,
267
+ 'summary': {
268
+ 'total_passed': suite_result.total_passed,
269
+ 'total_failed': suite_result.total_failed,
270
+ 'total_duration': suite_result.total_duration,
271
+ 'coverage': suite_result.coverage
272
+ },
273
+ 'tests': {
274
+ k: {
275
+ 'success': v.success,
276
+ 'passed': v.passed,
277
+ 'failed': v.failed,
278
+ 'duration': v.duration,
279
+ 'coverage': v.coverage,
280
+ 'not_configured': v.not_configured,
281
+ 'test_cases': [
282
+ {
283
+ 'name': tc.name,
284
+ 'status': tc.status,
285
+ 'duration': tc.duration,
286
+ 'error': tc.error,
287
+ }
288
+ for tc in v.test_cases
289
+ ]
290
+ }
291
+ for k, v in suite_result.results.items()
292
+ }
293
+ }
294
+
295
+ # 生成報告
296
+ console.print(f"[cyan]生成 Markdown 報告...[/cyan]")
297
+ report_path = generate_markdown_report(
298
+ project_name=project_name,
299
+ test_results=test_results,
300
+ output_path=output_path,
301
+ )
302
+
303
+ console.print(f"[green]報告已生成: {report_path}[/green]")
304
+
305
+ return {
306
+ 'success': True,
307
+ 'report_path': report_path,
308
+ 'test_results': test_results
309
+ }
@@ -0,0 +1,21 @@
1
+ """
2
+ 遷移工具集
3
+
4
+ 注意:Shoelace → DaisyUI 遷移器已棄用
5
+ 目前維持 Shoelace 作為非 Angular 專案的標準 UI 框架
6
+ """
7
+
8
+ __all__ = ['run_migration']
9
+
10
+
11
+ def run_migration(project, dry_run=False, from_framework='shoelace', to_framework='daisyui'):
12
+ """執行遷移
13
+
14
+ 注意:此功能已暫停使用
15
+ - Shoelace 維持為標準 UI 框架
16
+ - 遷移需要完整理解設計邏輯後才能進行
17
+ """
18
+ return {
19
+ 'success': False,
20
+ 'error': '遷移功能已暫停。UI 框架遷移需要完整理解設計邏輯後手動進行。'
21
+ }