[{"data":1,"prerenderedAt":5452},["ShallowReactive",2],{"blog:\u002Fblog\u002Fnuxt\u002Ftdd-testing-workflow":3},{"id":4,"title":5,"author":6,"body":7,"category":881,"date":5437,"description":5438,"draft":5439,"extension":5440,"image":5441,"meta":5442,"navigation":225,"path":5443,"seo":5444,"series":5445,"seriesOrder":358,"seriesTitle":5446,"stem":5447,"tags":5448,"updatedAt":5441,"__hash__":5451},"blog\u002Fblog\u002Fnuxt\u002Ftdd-testing-workflow\u002Findex.md","TDD 與自動化測試","charles",{"type":8,"value":9,"toc":5386},"minimark",[10,14,18,41,44,48,53,105,109,119,121,125,129,135,550,553,580,585,782,784,804,809,812,814,817,823,827,888,891,897,899,903,906,913,978,982,1077,1079,1082,1085,1511,1515,2136,2138,2142,2146,2603,2606,3114,3116,3120,3124,3626,3630,3888,3890,3894,3897,4022,4025,4031,4035,4102,4104,4107,4110,4259,4262,4322,4324,4327,4330,4336,4342,4354,4359,4494,4497,4502,4506,4638,4641,4646,4651,4655,4771,4773,4777,4781,5107,5111,5225,5227,5230,5233,5291,5293,5296,5338,5340,5343,5382],[11,12,13],"h2",{"id":13},"這篇要解決什麼問題",[15,16,17],"p",{},"測試是確保程式碼品質的關鍵，但很多團隊只把測試當作「有空再補」的事。這篇文章將說明：",[19,20,21,25,28,31,38],"ul",{},[22,23,24],"li",{},"Red → Green → Refactor 的實際操作",[22,26,27],{},"Vitest + @nuxt\u002Ftest-utils 設定",[22,29,30],{},"單元測試 vs Nuxt 環境測試的區分",[22,32,33,37],{},[34,35,36],"code",{},"pnpm check"," 一鍵檢查流程",[22,39,40],{},"測試的 Do's and Don'ts",[42,43],"hr",{},[11,45,47],{"id":46},"tdd-核心理念","TDD 核心理念",[49,50,52],"h3",{"id":51},"為什麼要先寫測試","為什麼要先寫測試？",[54,55,56,69],"table",{},[57,58,59],"thead",{},[60,61,62,66],"tr",{},[63,64,65],"th",{},"傳統開發",[63,67,68],{},"TDD 開發",[70,71,72,81,89,97],"tbody",{},[60,73,74,78],{},[75,76,77],"td",{},"寫完程式碼再補測試",[75,79,80],{},"先寫測試再實作",[60,82,83,86],{},[75,84,85],{},"測試變成負擔",[75,87,88],{},"測試驅動設計",[60,90,91,94],{},[75,92,93],{},"「測試是選項」",[75,95,96],{},"「測試是必須」",[60,98,99,102],{},[75,100,101],{},"容易遺漏邊界案例",[75,103,104],{},"先思考邊界案例",[49,106,108],{"id":107},"red-green-refactor","Red → Green → Refactor",[110,111,116],"pre",{"className":112,"code":114,"language":115},[113],"language-text","┌─────────────────────────────────────────────┐\n│  1. Red: 先寫測試，執行確認失敗              │\n│     - 定義預期行為                          │\n│     - 測試必須失敗（確認測試有效）           │\n│     ↓                                       │\n│  2. Green: 寫最小實作，讓測試通過            │\n│     - 只寫剛好讓測試通過的程式碼             │\n│     - 不要提前優化                          │\n│     ↓                                       │\n│  3. Refactor: 重構程式碼，保持測試通過       │\n│     - 清理程式碼                            │\n│     - 抽取重複邏輯                          │\n│     - 測試必須保持綠燈                       │\n│     ↓                                       │\n│  4. 回到 1，處理下一個需求                   │\n└─────────────────────────────────────────────┘\n","text",[34,117,114],{"__ignoreMap":118},"",[42,120],{},[11,122,124],{"id":123},"tdd-實戰範例","TDD 實戰範例",[49,126,128],{"id":127},"需求格式化價格函式","需求：格式化價格函式",[15,130,131],{},[132,133,134],"strong",{},"Step 1: Red - 先寫測試",[110,136,140],{"className":137,"code":138,"language":139,"meta":118,"style":118},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F test\u002Funit\u002Futils\u002FformatPrice.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { formatPrice } from \"~\u002Futils\u002FformatPrice\";\n\ndescribe(\"formatPrice\", () => {\n  it(\"should format positive number\", () => {\n    expect(formatPrice(1000)).toBe(\"$1,000\");\n  });\n\n  it(\"should format zero\", () => {\n    expect(formatPrice(0)).toBe(\"$0\");\n  });\n\n  it(\"should format decimal number\", () => {\n    expect(formatPrice(1234.56)).toBe(\"$1,234.56\");\n  });\n\n  it(\"should handle negative number\", () => {\n    expect(formatPrice(-500)).toBe(\"-$500\");\n  });\n});\n","typescript",[34,141,142,151,197,220,227,256,280,319,329,334,356,389,398,403,425,458,467,472,494,531,540],{"__ignoreMap":118},[143,144,147],"span",{"class":145,"line":146},"line",1,[143,148,150],{"class":149},"sutJx","\u002F\u002F test\u002Funit\u002Futils\u002FformatPrice.test.ts\n",[143,152,154,158,162,166,169,172,174,177,180,183,187,191,194],{"class":145,"line":153},2,[143,155,157],{"class":156},"sVHd0","import",[143,159,161],{"class":160},"sP7_E"," {",[143,163,165],{"class":164},"su5hD"," describe",[143,167,168],{"class":160},",",[143,170,171],{"class":164}," it",[143,173,168],{"class":160},[143,175,176],{"class":164}," expect",[143,178,179],{"class":160}," }",[143,181,182],{"class":156}," from",[143,184,186],{"class":185},"sjJ54"," \"",[143,188,190],{"class":189},"s_sjI","vitest",[143,192,193],{"class":185},"\"",[143,195,196],{"class":160},";\n",[143,198,200,202,204,207,209,211,213,216,218],{"class":145,"line":199},3,[143,201,157],{"class":156},[143,203,161],{"class":160},[143,205,206],{"class":164}," formatPrice",[143,208,179],{"class":160},[143,210,182],{"class":156},[143,212,186],{"class":185},[143,214,215],{"class":189},"~\u002Futils\u002FformatPrice",[143,217,193],{"class":185},[143,219,196],{"class":160},[143,221,223],{"class":145,"line":222},4,[143,224,226],{"emptyLinePlaceholder":225},true,"\n",[143,228,230,234,237,239,242,244,246,249,253],{"class":145,"line":229},5,[143,231,233],{"class":232},"sGLFI","describe",[143,235,236],{"class":164},"(",[143,238,193],{"class":185},[143,240,241],{"class":189},"formatPrice",[143,243,193],{"class":185},[143,245,168],{"class":160},[143,247,248],{"class":160}," ()",[143,250,252],{"class":251},"sbsja"," =>",[143,254,255],{"class":160}," {\n",[143,257,259,262,265,267,270,272,274,276,278],{"class":145,"line":258},6,[143,260,261],{"class":232},"  it",[143,263,236],{"class":264},"skxfh",[143,266,193],{"class":185},[143,268,269],{"class":189},"should format positive number",[143,271,193],{"class":185},[143,273,168],{"class":160},[143,275,248],{"class":160},[143,277,252],{"class":251},[143,279,255],{"class":160},[143,281,283,286,288,290,292,296,299,302,305,307,309,312,314,317],{"class":145,"line":282},7,[143,284,285],{"class":232},"    expect",[143,287,236],{"class":264},[143,289,241],{"class":232},[143,291,236],{"class":264},[143,293,295],{"class":294},"srdBf","1000",[143,297,298],{"class":264},"))",[143,300,301],{"class":160},".",[143,303,304],{"class":232},"toBe",[143,306,236],{"class":264},[143,308,193],{"class":185},[143,310,311],{"class":189},"$1,000",[143,313,193],{"class":185},[143,315,316],{"class":264},")",[143,318,196],{"class":160},[143,320,322,325,327],{"class":145,"line":321},8,[143,323,324],{"class":160},"  }",[143,326,316],{"class":264},[143,328,196],{"class":160},[143,330,332],{"class":145,"line":331},9,[143,333,226],{"emptyLinePlaceholder":225},[143,335,337,339,341,343,346,348,350,352,354],{"class":145,"line":336},10,[143,338,261],{"class":232},[143,340,236],{"class":264},[143,342,193],{"class":185},[143,344,345],{"class":189},"should format zero",[143,347,193],{"class":185},[143,349,168],{"class":160},[143,351,248],{"class":160},[143,353,252],{"class":251},[143,355,255],{"class":160},[143,357,359,361,363,365,367,370,372,374,376,378,380,383,385,387],{"class":145,"line":358},11,[143,360,285],{"class":232},[143,362,236],{"class":264},[143,364,241],{"class":232},[143,366,236],{"class":264},[143,368,369],{"class":294},"0",[143,371,298],{"class":264},[143,373,301],{"class":160},[143,375,304],{"class":232},[143,377,236],{"class":264},[143,379,193],{"class":185},[143,381,382],{"class":189},"$0",[143,384,193],{"class":185},[143,386,316],{"class":264},[143,388,196],{"class":160},[143,390,392,394,396],{"class":145,"line":391},12,[143,393,324],{"class":160},[143,395,316],{"class":264},[143,397,196],{"class":160},[143,399,401],{"class":145,"line":400},13,[143,402,226],{"emptyLinePlaceholder":225},[143,404,406,408,410,412,415,417,419,421,423],{"class":145,"line":405},14,[143,407,261],{"class":232},[143,409,236],{"class":264},[143,411,193],{"class":185},[143,413,414],{"class":189},"should format decimal number",[143,416,193],{"class":185},[143,418,168],{"class":160},[143,420,248],{"class":160},[143,422,252],{"class":251},[143,424,255],{"class":160},[143,426,428,430,432,434,436,439,441,443,445,447,449,452,454,456],{"class":145,"line":427},15,[143,429,285],{"class":232},[143,431,236],{"class":264},[143,433,241],{"class":232},[143,435,236],{"class":264},[143,437,438],{"class":294},"1234.56",[143,440,298],{"class":264},[143,442,301],{"class":160},[143,444,304],{"class":232},[143,446,236],{"class":264},[143,448,193],{"class":185},[143,450,451],{"class":189},"$1,234.56",[143,453,193],{"class":185},[143,455,316],{"class":264},[143,457,196],{"class":160},[143,459,461,463,465],{"class":145,"line":460},16,[143,462,324],{"class":160},[143,464,316],{"class":264},[143,466,196],{"class":160},[143,468,470],{"class":145,"line":469},17,[143,471,226],{"emptyLinePlaceholder":225},[143,473,475,477,479,481,484,486,488,490,492],{"class":145,"line":474},18,[143,476,261],{"class":232},[143,478,236],{"class":264},[143,480,193],{"class":185},[143,482,483],{"class":189},"should handle negative number",[143,485,193],{"class":185},[143,487,168],{"class":160},[143,489,248],{"class":160},[143,491,252],{"class":251},[143,493,255],{"class":160},[143,495,497,499,501,503,505,509,512,514,516,518,520,522,525,527,529],{"class":145,"line":496},19,[143,498,285],{"class":232},[143,500,236],{"class":264},[143,502,241],{"class":232},[143,504,236],{"class":264},[143,506,508],{"class":507},"smGrS","-",[143,510,511],{"class":294},"500",[143,513,298],{"class":264},[143,515,301],{"class":160},[143,517,304],{"class":232},[143,519,236],{"class":264},[143,521,193],{"class":185},[143,523,524],{"class":189},"-$500",[143,526,193],{"class":185},[143,528,316],{"class":264},[143,530,196],{"class":160},[143,532,534,536,538],{"class":145,"line":533},20,[143,535,324],{"class":160},[143,537,316],{"class":264},[143,539,196],{"class":160},[143,541,543,546,548],{"class":145,"line":542},21,[143,544,545],{"class":160},"}",[143,547,316],{"class":164},[143,549,196],{"class":160},[15,551,552],{},"執行測試：",[110,554,558],{"className":555,"code":556,"language":557,"meta":118,"style":118},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","pnpm vitest run test\u002Funit\u002Futils\u002FformatPrice.test.ts\n# ❌ 失敗：formatPrice 還不存在\n","bash",[34,559,560,575],{"__ignoreMap":118},[143,561,562,566,569,572],{"class":145,"line":146},[143,563,565],{"class":564},"sbgvK","pnpm",[143,567,568],{"class":189}," vitest",[143,570,571],{"class":189}," run",[143,573,574],{"class":189}," test\u002Funit\u002Futils\u002FformatPrice.test.ts\n",[143,576,577],{"class":145,"line":153},[143,578,579],{"class":149},"# ❌ 失敗：formatPrice 還不存在\n",[15,581,582],{},[132,583,584],{},"Step 2: Green - 最小實作",[110,586,588],{"className":137,"code":587,"language":139,"meta":118,"style":118},"\u002F\u002F app\u002Futils\u002FformatPrice.ts\nexport function formatPrice(value: number): string {\n  const isNegative = value \u003C 0;\n  const absValue = Math.abs(value);\n  const formatted = absValue.toLocaleString(\"en-US\", {\n    minimumFractionDigits: 0,\n    maximumFractionDigits: 2,\n  });\n  return isNegative ? `-$${formatted}` : `$${formatted}`;\n}\n",[34,589,590,595,627,650,675,704,716,728,736,777],{"__ignoreMap":118},[143,591,592],{"class":145,"line":146},[143,593,594],{"class":149},"\u002F\u002F app\u002Futils\u002FformatPrice.ts\n",[143,596,597,600,603,605,607,611,614,618,620,622,625],{"class":145,"line":153},[143,598,599],{"class":156},"export",[143,601,602],{"class":251}," function",[143,604,206],{"class":232},[143,606,236],{"class":160},[143,608,610],{"class":609},"s99_P","value",[143,612,613],{"class":507},":",[143,615,617],{"class":616},"sZMiF"," number",[143,619,316],{"class":160},[143,621,613],{"class":507},[143,623,624],{"class":616}," string",[143,626,255],{"class":160},[143,628,629,632,636,639,642,645,648],{"class":145,"line":199},[143,630,631],{"class":251},"  const",[143,633,635],{"class":634},"s_hVV"," isNegative",[143,637,638],{"class":507}," =",[143,640,641],{"class":164}," value",[143,643,644],{"class":507}," \u003C",[143,646,647],{"class":294}," 0",[143,649,196],{"class":160},[143,651,652,654,657,659,662,664,667,669,671,673],{"class":145,"line":222},[143,653,631],{"class":251},[143,655,656],{"class":634}," absValue",[143,658,638],{"class":507},[143,660,661],{"class":164}," Math",[143,663,301],{"class":160},[143,665,666],{"class":232},"abs",[143,668,236],{"class":264},[143,670,610],{"class":164},[143,672,316],{"class":264},[143,674,196],{"class":160},[143,676,677,679,682,684,686,688,691,693,695,698,700,702],{"class":145,"line":229},[143,678,631],{"class":251},[143,680,681],{"class":634}," formatted",[143,683,638],{"class":507},[143,685,656],{"class":164},[143,687,301],{"class":160},[143,689,690],{"class":232},"toLocaleString",[143,692,236],{"class":264},[143,694,193],{"class":185},[143,696,697],{"class":189},"en-US",[143,699,193],{"class":185},[143,701,168],{"class":160},[143,703,255],{"class":160},[143,705,706,709,711,713],{"class":145,"line":258},[143,707,708],{"class":264},"    minimumFractionDigits",[143,710,613],{"class":160},[143,712,647],{"class":294},[143,714,715],{"class":160},",\n",[143,717,718,721,723,726],{"class":145,"line":282},[143,719,720],{"class":264},"    maximumFractionDigits",[143,722,613],{"class":160},[143,724,725],{"class":294}," 2",[143,727,715],{"class":160},[143,729,730,732,734],{"class":145,"line":321},[143,731,324],{"class":160},[143,733,316],{"class":264},[143,735,196],{"class":160},[143,737,738,741,743,746,749,752,755,758,761,764,766,769,771,773,775],{"class":145,"line":331},[143,739,740],{"class":156},"  return",[143,742,635],{"class":164},[143,744,745],{"class":507}," ?",[143,747,748],{"class":185}," `",[143,750,751],{"class":189},"-$",[143,753,754],{"class":185},"${",[143,756,757],{"class":164},"formatted",[143,759,760],{"class":185},"}`",[143,762,763],{"class":507}," :",[143,765,748],{"class":185},[143,767,768],{"class":189},"$",[143,770,754],{"class":185},[143,772,757],{"class":164},[143,774,760],{"class":185},[143,776,196],{"class":160},[143,778,779],{"class":145,"line":336},[143,780,781],{"class":160},"}\n",[15,783,552],{},[110,785,787],{"className":555,"code":786,"language":557,"meta":118,"style":118},"pnpm vitest run test\u002Funit\u002Futils\u002FformatPrice.test.ts\n# ✅ 通過\n",[34,788,789,799],{"__ignoreMap":118},[143,790,791,793,795,797],{"class":145,"line":146},[143,792,565],{"class":564},[143,794,568],{"class":189},[143,796,571],{"class":189},[143,798,574],{"class":189},[143,800,801],{"class":145,"line":153},[143,802,803],{"class":149},"# ✅ 通過\n",[15,805,806],{},[132,807,808],{},"Step 3: Refactor - 重構（如需要）",[15,810,811],{},"程式碼已經足夠簡潔，暫不需要重構。如果有更多需求，繼續循環。",[42,813],{},[11,815,816],{"id":816},"測試目錄結構",[110,818,821],{"className":819,"code":820,"language":115},[113],"test\u002F\n├── unit\u002F                   # 單元測試（Node 環境，快速）\n│   ├── utils\u002F\n│   │   └── formatPrice.test.ts\n│   ├── composables\u002F\n│   │   └── useCounter.test.ts\n│   └── services\u002F\n│       └── validator.test.ts\n├── nuxt\u002F                   # Nuxt 環境測試（完整 Nuxt 功能）\n│   └── components\u002F\n│       └── UserCard.nuxt.test.ts\n└── helpers\u002F                # 測試輔助函式\n    ├── mockUser.ts\n    └── mockSupabase.ts\n",[34,822,820],{"__ignoreMap":118},[49,824,826],{"id":825},"單元測試-vs-nuxt-環境測試","單元測試 vs Nuxt 環境測試",[54,828,829,848],{},[57,830,831],{},[60,832,833,836,839,842,845],{},[63,834,835],{},"類型",[63,837,838],{},"檔案命名",[63,840,841],{},"環境",[63,843,844],{},"速度",[63,846,847],{},"用途",[70,849,850,869],{},[60,851,852,855,860,863,866],{},[75,853,854],{},"單元測試",[75,856,857],{},[34,858,859],{},"*.test.ts",[75,861,862],{},"Node",[75,864,865],{},"快",[75,867,868],{},"純函式、utils、services",[60,870,871,874,879,882,885],{},[75,872,873],{},"Nuxt 測試",[75,875,876],{},[34,877,878],{},"*.nuxt.test.ts",[75,880,881],{},"Nuxt",[75,883,884],{},"慢",[75,886,887],{},"元件、composables、plugins",[49,889,890],{"id":890},"選擇原則",[110,892,895],{"className":893,"code":894,"language":115},[113],"需要測試的程式碼\n       │\n       ▼\n需要 Vue\u002FNuxt 功能嗎？\n（如 ref、computed、useRoute、useSupabaseClient）\n       │\n  ┌────┴────┐\n  │         │\n 是        否\n  │         │\n  ▼         ▼\nNuxt 測試   單元測試\n*.nuxt.test.ts  *.test.ts\n",[34,896,894],{"__ignoreMap":118},[42,898],{},[11,900,902],{"id":901},"vitest-設定","Vitest 設定",[49,904,905],{"id":905},"專案設定",[15,907,908,909,912],{},"Nuxt 4 整合了 Vitest，使用 ",[34,910,911],{},"@nuxt\u002Ftest-utils"," 提供完整的測試支援。",[110,914,916],{"className":137,"code":915,"language":139,"meta":118,"style":118},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: [\n    \"@nuxt\u002Ftest-utils\u002Fmodule\", \u002F\u002F 加入測試模組\n  ],\n});\n",[34,917,918,923,938,948,963,970],{"__ignoreMap":118},[143,919,920],{"class":145,"line":146},[143,921,922],{"class":149},"\u002F\u002F nuxt.config.ts\n",[143,924,925,927,930,933,935],{"class":145,"line":153},[143,926,599],{"class":156},[143,928,929],{"class":156}," default",[143,931,932],{"class":232}," defineNuxtConfig",[143,934,236],{"class":164},[143,936,937],{"class":160},"{\n",[143,939,940,943,945],{"class":145,"line":199},[143,941,942],{"class":264},"  modules",[143,944,613],{"class":160},[143,946,947],{"class":164}," [\n",[143,949,950,953,956,958,960],{"class":145,"line":222},[143,951,952],{"class":185},"    \"",[143,954,955],{"class":189},"@nuxt\u002Ftest-utils\u002Fmodule",[143,957,193],{"class":185},[143,959,168],{"class":160},[143,961,962],{"class":149}," \u002F\u002F 加入測試模組\n",[143,964,965,968],{"class":145,"line":229},[143,966,967],{"class":164},"  ]",[143,969,715],{"class":160},[143,971,972,974,976],{"class":145,"line":258},[143,973,545],{"class":160},[143,975,316],{"class":164},[143,977,196],{"class":160},[49,979,981],{"id":980},"packagejson-scripts","package.json scripts",[110,983,987],{"className":984,"code":985,"language":986,"meta":118,"style":118},"language-json shiki shiki-themes material-theme-lighter github-light github-dark","{\n  \"scripts\": {\n    \"test\": \"vitest run --coverage\",\n    \"test:unit\": \"vitest run test\u002Funit\",\n    \"test:watch\": \"vitest watch\"\n  }\n}\n","json",[34,988,989,993,1009,1029,1049,1068,1073],{"__ignoreMap":118},[143,990,991],{"class":145,"line":146},[143,992,937],{"class":160},[143,994,995,999,1003,1005,1007],{"class":145,"line":153},[143,996,998],{"class":997},"s39Yj","  \"",[143,1000,1002],{"class":1001},"sseR_","scripts",[143,1004,193],{"class":997},[143,1006,613],{"class":160},[143,1008,255],{"class":160},[143,1010,1011,1013,1016,1018,1020,1022,1025,1027],{"class":145,"line":199},[143,1012,952],{"class":997},[143,1014,1015],{"class":616},"test",[143,1017,193],{"class":997},[143,1019,613],{"class":160},[143,1021,186],{"class":185},[143,1023,1024],{"class":189},"vitest run --coverage",[143,1026,193],{"class":185},[143,1028,715],{"class":160},[143,1030,1031,1033,1036,1038,1040,1042,1045,1047],{"class":145,"line":222},[143,1032,952],{"class":997},[143,1034,1035],{"class":616},"test:unit",[143,1037,193],{"class":997},[143,1039,613],{"class":160},[143,1041,186],{"class":185},[143,1043,1044],{"class":189},"vitest run test\u002Funit",[143,1046,193],{"class":185},[143,1048,715],{"class":160},[143,1050,1051,1053,1056,1058,1060,1062,1065],{"class":145,"line":229},[143,1052,952],{"class":997},[143,1054,1055],{"class":616},"test:watch",[143,1057,193],{"class":997},[143,1059,613],{"class":160},[143,1061,186],{"class":185},[143,1063,1064],{"class":189},"vitest watch",[143,1066,1067],{"class":185},"\"\n",[143,1069,1070],{"class":145,"line":258},[143,1071,1072],{"class":160},"  }\n",[143,1074,1075],{"class":145,"line":282},[143,1076,781],{"class":160},[42,1078],{},[11,1080,1081],{"id":1081},"單元測試範例",[49,1083,1084],{"id":1084},"測試純函式",[110,1086,1088],{"className":137,"code":1087,"language":139,"meta":118,"style":118},"\u002F\u002F test\u002Funit\u002Futils\u002FparseDate.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { parseDate, formatDate } from \"~\u002Futils\u002Fdate\";\n\ndescribe(\"parseDate\", () => {\n  it(\"should parse ISO string\", () => {\n    const result = parseDate(\"2024-01-15T10:30:00Z\");\n    expect(result.getFullYear()).toBe(2024);\n    expect(result.getMonth()).toBe(0); \u002F\u002F 0-indexed\n    expect(result.getDate()).toBe(15);\n  });\n\n  it(\"should return null for invalid date\", () => {\n    expect(parseDate(\"invalid\")).toBeNull();\n  });\n});\n\ndescribe(\"formatDate\", () => {\n  it(\"should format date to YYYY-MM-DD\", () => {\n    const date = new Date(\"2024-01-15\");\n    expect(formatDate(date)).toBe(\"2024-01-15\");\n  });\n});\n",[34,1089,1090,1095,1123,1150,1154,1175,1196,1221,1251,1282,1310,1318,1322,1343,1372,1380,1388,1392,1413,1434,1462,1493,1502],{"__ignoreMap":118},[143,1091,1092],{"class":145,"line":146},[143,1093,1094],{"class":149},"\u002F\u002F test\u002Funit\u002Futils\u002FparseDate.test.ts\n",[143,1096,1097,1099,1101,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121],{"class":145,"line":153},[143,1098,157],{"class":156},[143,1100,161],{"class":160},[143,1102,165],{"class":164},[143,1104,168],{"class":160},[143,1106,171],{"class":164},[143,1108,168],{"class":160},[143,1110,176],{"class":164},[143,1112,179],{"class":160},[143,1114,182],{"class":156},[143,1116,186],{"class":185},[143,1118,190],{"class":189},[143,1120,193],{"class":185},[143,1122,196],{"class":160},[143,1124,1125,1127,1129,1132,1134,1137,1139,1141,1143,1146,1148],{"class":145,"line":199},[143,1126,157],{"class":156},[143,1128,161],{"class":160},[143,1130,1131],{"class":164}," parseDate",[143,1133,168],{"class":160},[143,1135,1136],{"class":164}," formatDate",[143,1138,179],{"class":160},[143,1140,182],{"class":156},[143,1142,186],{"class":185},[143,1144,1145],{"class":189},"~\u002Futils\u002Fdate",[143,1147,193],{"class":185},[143,1149,196],{"class":160},[143,1151,1152],{"class":145,"line":222},[143,1153,226],{"emptyLinePlaceholder":225},[143,1155,1156,1158,1160,1162,1165,1167,1169,1171,1173],{"class":145,"line":229},[143,1157,233],{"class":232},[143,1159,236],{"class":164},[143,1161,193],{"class":185},[143,1163,1164],{"class":189},"parseDate",[143,1166,193],{"class":185},[143,1168,168],{"class":160},[143,1170,248],{"class":160},[143,1172,252],{"class":251},[143,1174,255],{"class":160},[143,1176,1177,1179,1181,1183,1186,1188,1190,1192,1194],{"class":145,"line":258},[143,1178,261],{"class":232},[143,1180,236],{"class":264},[143,1182,193],{"class":185},[143,1184,1185],{"class":189},"should parse ISO string",[143,1187,193],{"class":185},[143,1189,168],{"class":160},[143,1191,248],{"class":160},[143,1193,252],{"class":251},[143,1195,255],{"class":160},[143,1197,1198,1201,1204,1206,1208,1210,1212,1215,1217,1219],{"class":145,"line":282},[143,1199,1200],{"class":251},"    const",[143,1202,1203],{"class":634}," result",[143,1205,638],{"class":507},[143,1207,1131],{"class":232},[143,1209,236],{"class":264},[143,1211,193],{"class":185},[143,1213,1214],{"class":189},"2024-01-15T10:30:00Z",[143,1216,193],{"class":185},[143,1218,316],{"class":264},[143,1220,196],{"class":160},[143,1222,1223,1225,1227,1230,1232,1235,1238,1240,1242,1244,1247,1249],{"class":145,"line":321},[143,1224,285],{"class":232},[143,1226,236],{"class":264},[143,1228,1229],{"class":164},"result",[143,1231,301],{"class":160},[143,1233,1234],{"class":232},"getFullYear",[143,1236,1237],{"class":264},"())",[143,1239,301],{"class":160},[143,1241,304],{"class":232},[143,1243,236],{"class":264},[143,1245,1246],{"class":294},"2024",[143,1248,316],{"class":264},[143,1250,196],{"class":160},[143,1252,1253,1255,1257,1259,1261,1264,1266,1268,1270,1272,1274,1276,1279],{"class":145,"line":331},[143,1254,285],{"class":232},[143,1256,236],{"class":264},[143,1258,1229],{"class":164},[143,1260,301],{"class":160},[143,1262,1263],{"class":232},"getMonth",[143,1265,1237],{"class":264},[143,1267,301],{"class":160},[143,1269,304],{"class":232},[143,1271,236],{"class":264},[143,1273,369],{"class":294},[143,1275,316],{"class":264},[143,1277,1278],{"class":160},";",[143,1280,1281],{"class":149}," \u002F\u002F 0-indexed\n",[143,1283,1284,1286,1288,1290,1292,1295,1297,1299,1301,1303,1306,1308],{"class":145,"line":336},[143,1285,285],{"class":232},[143,1287,236],{"class":264},[143,1289,1229],{"class":164},[143,1291,301],{"class":160},[143,1293,1294],{"class":232},"getDate",[143,1296,1237],{"class":264},[143,1298,301],{"class":160},[143,1300,304],{"class":232},[143,1302,236],{"class":264},[143,1304,1305],{"class":294},"15",[143,1307,316],{"class":264},[143,1309,196],{"class":160},[143,1311,1312,1314,1316],{"class":145,"line":358},[143,1313,324],{"class":160},[143,1315,316],{"class":264},[143,1317,196],{"class":160},[143,1319,1320],{"class":145,"line":391},[143,1321,226],{"emptyLinePlaceholder":225},[143,1323,1324,1326,1328,1330,1333,1335,1337,1339,1341],{"class":145,"line":400},[143,1325,261],{"class":232},[143,1327,236],{"class":264},[143,1329,193],{"class":185},[143,1331,1332],{"class":189},"should return null for invalid date",[143,1334,193],{"class":185},[143,1336,168],{"class":160},[143,1338,248],{"class":160},[143,1340,252],{"class":251},[143,1342,255],{"class":160},[143,1344,1345,1347,1349,1351,1353,1355,1358,1360,1362,1364,1367,1370],{"class":145,"line":405},[143,1346,285],{"class":232},[143,1348,236],{"class":264},[143,1350,1164],{"class":232},[143,1352,236],{"class":264},[143,1354,193],{"class":185},[143,1356,1357],{"class":189},"invalid",[143,1359,193],{"class":185},[143,1361,298],{"class":264},[143,1363,301],{"class":160},[143,1365,1366],{"class":232},"toBeNull",[143,1368,1369],{"class":264},"()",[143,1371,196],{"class":160},[143,1373,1374,1376,1378],{"class":145,"line":427},[143,1375,324],{"class":160},[143,1377,316],{"class":264},[143,1379,196],{"class":160},[143,1381,1382,1384,1386],{"class":145,"line":460},[143,1383,545],{"class":160},[143,1385,316],{"class":164},[143,1387,196],{"class":160},[143,1389,1390],{"class":145,"line":469},[143,1391,226],{"emptyLinePlaceholder":225},[143,1393,1394,1396,1398,1400,1403,1405,1407,1409,1411],{"class":145,"line":474},[143,1395,233],{"class":232},[143,1397,236],{"class":164},[143,1399,193],{"class":185},[143,1401,1402],{"class":189},"formatDate",[143,1404,193],{"class":185},[143,1406,168],{"class":160},[143,1408,248],{"class":160},[143,1410,252],{"class":251},[143,1412,255],{"class":160},[143,1414,1415,1417,1419,1421,1424,1426,1428,1430,1432],{"class":145,"line":496},[143,1416,261],{"class":232},[143,1418,236],{"class":264},[143,1420,193],{"class":185},[143,1422,1423],{"class":189},"should format date to YYYY-MM-DD",[143,1425,193],{"class":185},[143,1427,168],{"class":160},[143,1429,248],{"class":160},[143,1431,252],{"class":251},[143,1433,255],{"class":160},[143,1435,1436,1438,1441,1443,1446,1449,1451,1453,1456,1458,1460],{"class":145,"line":533},[143,1437,1200],{"class":251},[143,1439,1440],{"class":634}," date",[143,1442,638],{"class":507},[143,1444,1445],{"class":507}," new",[143,1447,1448],{"class":232}," Date",[143,1450,236],{"class":264},[143,1452,193],{"class":185},[143,1454,1455],{"class":189},"2024-01-15",[143,1457,193],{"class":185},[143,1459,316],{"class":264},[143,1461,196],{"class":160},[143,1463,1464,1466,1468,1470,1472,1475,1477,1479,1481,1483,1485,1487,1489,1491],{"class":145,"line":542},[143,1465,285],{"class":232},[143,1467,236],{"class":264},[143,1469,1402],{"class":232},[143,1471,236],{"class":264},[143,1473,1474],{"class":164},"date",[143,1476,298],{"class":264},[143,1478,301],{"class":160},[143,1480,304],{"class":232},[143,1482,236],{"class":264},[143,1484,193],{"class":185},[143,1486,1455],{"class":189},[143,1488,193],{"class":185},[143,1490,316],{"class":264},[143,1492,196],{"class":160},[143,1494,1496,1498,1500],{"class":145,"line":1495},22,[143,1497,324],{"class":160},[143,1499,316],{"class":264},[143,1501,196],{"class":160},[143,1503,1505,1507,1509],{"class":145,"line":1504},23,[143,1506,545],{"class":160},[143,1508,316],{"class":164},[143,1510,196],{"class":160},[49,1512,1514],{"id":1513},"測試-zod-schema","測試 Zod Schema",[110,1516,1518],{"className":137,"code":1517,"language":139,"meta":118,"style":118},"\u002F\u002F test\u002Funit\u002Fschemas\u002Fuser.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { createUserSchema } from \"~\u002Fschemas\u002Fuser\";\n\ndescribe(\"createUserSchema\", () => {\n  it(\"should validate valid input\", () => {\n    const input = {\n      name: \"John Doe\",\n      email: \"john@example.com\",\n      role: \"staff\",\n    };\n\n    const result = createUserSchema.safeParse(input);\n    expect(result.success).toBe(true);\n  });\n\n  it(\"should reject invalid email\", () => {\n    const input = {\n      name: \"John Doe\",\n      email: \"invalid-email\",\n      role: \"staff\",\n    };\n\n    const result = createUserSchema.safeParse(input);\n    expect(result.success).toBe(false);\n    if (!result.success) {\n      expect(result.error.issues[0].path).toContain(\"email\");\n    }\n  });\n\n  it(\"should reject empty name\", () => {\n    const input = {\n      name: \"\",\n      email: \"john@example.com\",\n      role: \"staff\",\n    };\n\n    const result = createUserSchema.safeParse(input);\n    expect(result.success).toBe(false);\n  });\n});\n",[34,1519,1520,1525,1553,1575,1579,1600,1621,1632,1648,1664,1680,1685,1689,1713,1742,1750,1754,1775,1785,1799,1814,1828,1832,1836,1859,1887,1910,1963,1969,1978,1983,2005,2016,2028,2043,2058,2063,2068,2091,2118,2127],{"__ignoreMap":118},[143,1521,1522],{"class":145,"line":146},[143,1523,1524],{"class":149},"\u002F\u002F test\u002Funit\u002Fschemas\u002Fuser.test.ts\n",[143,1526,1527,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547,1549,1551],{"class":145,"line":153},[143,1528,157],{"class":156},[143,1530,161],{"class":160},[143,1532,165],{"class":164},[143,1534,168],{"class":160},[143,1536,171],{"class":164},[143,1538,168],{"class":160},[143,1540,176],{"class":164},[143,1542,179],{"class":160},[143,1544,182],{"class":156},[143,1546,186],{"class":185},[143,1548,190],{"class":189},[143,1550,193],{"class":185},[143,1552,196],{"class":160},[143,1554,1555,1557,1559,1562,1564,1566,1568,1571,1573],{"class":145,"line":199},[143,1556,157],{"class":156},[143,1558,161],{"class":160},[143,1560,1561],{"class":164}," createUserSchema",[143,1563,179],{"class":160},[143,1565,182],{"class":156},[143,1567,186],{"class":185},[143,1569,1570],{"class":189},"~\u002Fschemas\u002Fuser",[143,1572,193],{"class":185},[143,1574,196],{"class":160},[143,1576,1577],{"class":145,"line":222},[143,1578,226],{"emptyLinePlaceholder":225},[143,1580,1581,1583,1585,1587,1590,1592,1594,1596,1598],{"class":145,"line":229},[143,1582,233],{"class":232},[143,1584,236],{"class":164},[143,1586,193],{"class":185},[143,1588,1589],{"class":189},"createUserSchema",[143,1591,193],{"class":185},[143,1593,168],{"class":160},[143,1595,248],{"class":160},[143,1597,252],{"class":251},[143,1599,255],{"class":160},[143,1601,1602,1604,1606,1608,1611,1613,1615,1617,1619],{"class":145,"line":258},[143,1603,261],{"class":232},[143,1605,236],{"class":264},[143,1607,193],{"class":185},[143,1609,1610],{"class":189},"should validate valid input",[143,1612,193],{"class":185},[143,1614,168],{"class":160},[143,1616,248],{"class":160},[143,1618,252],{"class":251},[143,1620,255],{"class":160},[143,1622,1623,1625,1628,1630],{"class":145,"line":282},[143,1624,1200],{"class":251},[143,1626,1627],{"class":634}," input",[143,1629,638],{"class":507},[143,1631,255],{"class":160},[143,1633,1634,1637,1639,1641,1644,1646],{"class":145,"line":321},[143,1635,1636],{"class":264},"      name",[143,1638,613],{"class":160},[143,1640,186],{"class":185},[143,1642,1643],{"class":189},"John Doe",[143,1645,193],{"class":185},[143,1647,715],{"class":160},[143,1649,1650,1653,1655,1657,1660,1662],{"class":145,"line":331},[143,1651,1652],{"class":264},"      email",[143,1654,613],{"class":160},[143,1656,186],{"class":185},[143,1658,1659],{"class":189},"john@example.com",[143,1661,193],{"class":185},[143,1663,715],{"class":160},[143,1665,1666,1669,1671,1673,1676,1678],{"class":145,"line":336},[143,1667,1668],{"class":264},"      role",[143,1670,613],{"class":160},[143,1672,186],{"class":185},[143,1674,1675],{"class":189},"staff",[143,1677,193],{"class":185},[143,1679,715],{"class":160},[143,1681,1682],{"class":145,"line":358},[143,1683,1684],{"class":160},"    };\n",[143,1686,1687],{"class":145,"line":391},[143,1688,226],{"emptyLinePlaceholder":225},[143,1690,1691,1693,1695,1697,1699,1701,1704,1706,1709,1711],{"class":145,"line":400},[143,1692,1200],{"class":251},[143,1694,1203],{"class":634},[143,1696,638],{"class":507},[143,1698,1561],{"class":164},[143,1700,301],{"class":160},[143,1702,1703],{"class":232},"safeParse",[143,1705,236],{"class":264},[143,1707,1708],{"class":164},"input",[143,1710,316],{"class":264},[143,1712,196],{"class":160},[143,1714,1715,1717,1719,1721,1723,1726,1728,1730,1732,1734,1738,1740],{"class":145,"line":405},[143,1716,285],{"class":232},[143,1718,236],{"class":264},[143,1720,1229],{"class":164},[143,1722,301],{"class":160},[143,1724,1725],{"class":164},"success",[143,1727,316],{"class":264},[143,1729,301],{"class":160},[143,1731,304],{"class":232},[143,1733,236],{"class":264},[143,1735,1737],{"class":1736},"syTEX","true",[143,1739,316],{"class":264},[143,1741,196],{"class":160},[143,1743,1744,1746,1748],{"class":145,"line":427},[143,1745,324],{"class":160},[143,1747,316],{"class":264},[143,1749,196],{"class":160},[143,1751,1752],{"class":145,"line":460},[143,1753,226],{"emptyLinePlaceholder":225},[143,1755,1756,1758,1760,1762,1765,1767,1769,1771,1773],{"class":145,"line":469},[143,1757,261],{"class":232},[143,1759,236],{"class":264},[143,1761,193],{"class":185},[143,1763,1764],{"class":189},"should reject invalid email",[143,1766,193],{"class":185},[143,1768,168],{"class":160},[143,1770,248],{"class":160},[143,1772,252],{"class":251},[143,1774,255],{"class":160},[143,1776,1777,1779,1781,1783],{"class":145,"line":474},[143,1778,1200],{"class":251},[143,1780,1627],{"class":634},[143,1782,638],{"class":507},[143,1784,255],{"class":160},[143,1786,1787,1789,1791,1793,1795,1797],{"class":145,"line":496},[143,1788,1636],{"class":264},[143,1790,613],{"class":160},[143,1792,186],{"class":185},[143,1794,1643],{"class":189},[143,1796,193],{"class":185},[143,1798,715],{"class":160},[143,1800,1801,1803,1805,1807,1810,1812],{"class":145,"line":533},[143,1802,1652],{"class":264},[143,1804,613],{"class":160},[143,1806,186],{"class":185},[143,1808,1809],{"class":189},"invalid-email",[143,1811,193],{"class":185},[143,1813,715],{"class":160},[143,1815,1816,1818,1820,1822,1824,1826],{"class":145,"line":542},[143,1817,1668],{"class":264},[143,1819,613],{"class":160},[143,1821,186],{"class":185},[143,1823,1675],{"class":189},[143,1825,193],{"class":185},[143,1827,715],{"class":160},[143,1829,1830],{"class":145,"line":1495},[143,1831,1684],{"class":160},[143,1833,1834],{"class":145,"line":1504},[143,1835,226],{"emptyLinePlaceholder":225},[143,1837,1839,1841,1843,1845,1847,1849,1851,1853,1855,1857],{"class":145,"line":1838},24,[143,1840,1200],{"class":251},[143,1842,1203],{"class":634},[143,1844,638],{"class":507},[143,1846,1561],{"class":164},[143,1848,301],{"class":160},[143,1850,1703],{"class":232},[143,1852,236],{"class":264},[143,1854,1708],{"class":164},[143,1856,316],{"class":264},[143,1858,196],{"class":160},[143,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1883,1885],{"class":145,"line":1861},25,[143,1863,285],{"class":232},[143,1865,236],{"class":264},[143,1867,1229],{"class":164},[143,1869,301],{"class":160},[143,1871,1725],{"class":164},[143,1873,316],{"class":264},[143,1875,301],{"class":160},[143,1877,304],{"class":232},[143,1879,236],{"class":264},[143,1881,1882],{"class":1736},"false",[143,1884,316],{"class":264},[143,1886,196],{"class":160},[143,1888,1890,1893,1896,1899,1901,1903,1905,1908],{"class":145,"line":1889},26,[143,1891,1892],{"class":156},"    if",[143,1894,1895],{"class":264}," (",[143,1897,1898],{"class":507},"!",[143,1900,1229],{"class":164},[143,1902,301],{"class":160},[143,1904,1725],{"class":164},[143,1906,1907],{"class":264},") ",[143,1909,937],{"class":160},[143,1911,1913,1916,1918,1920,1922,1925,1927,1930,1933,1935,1938,1940,1943,1945,1947,1950,1952,1954,1957,1959,1961],{"class":145,"line":1912},27,[143,1914,1915],{"class":232},"      expect",[143,1917,236],{"class":264},[143,1919,1229],{"class":164},[143,1921,301],{"class":160},[143,1923,1924],{"class":164},"error",[143,1926,301],{"class":160},[143,1928,1929],{"class":164},"issues",[143,1931,1932],{"class":264},"[",[143,1934,369],{"class":294},[143,1936,1937],{"class":264},"]",[143,1939,301],{"class":160},[143,1941,1942],{"class":164},"path",[143,1944,316],{"class":264},[143,1946,301],{"class":160},[143,1948,1949],{"class":232},"toContain",[143,1951,236],{"class":264},[143,1953,193],{"class":185},[143,1955,1956],{"class":189},"email",[143,1958,193],{"class":185},[143,1960,316],{"class":264},[143,1962,196],{"class":160},[143,1964,1966],{"class":145,"line":1965},28,[143,1967,1968],{"class":160},"    }\n",[143,1970,1972,1974,1976],{"class":145,"line":1971},29,[143,1973,324],{"class":160},[143,1975,316],{"class":264},[143,1977,196],{"class":160},[143,1979,1981],{"class":145,"line":1980},30,[143,1982,226],{"emptyLinePlaceholder":225},[143,1984,1986,1988,1990,1992,1995,1997,1999,2001,2003],{"class":145,"line":1985},31,[143,1987,261],{"class":232},[143,1989,236],{"class":264},[143,1991,193],{"class":185},[143,1993,1994],{"class":189},"should reject empty name",[143,1996,193],{"class":185},[143,1998,168],{"class":160},[143,2000,248],{"class":160},[143,2002,252],{"class":251},[143,2004,255],{"class":160},[143,2006,2008,2010,2012,2014],{"class":145,"line":2007},32,[143,2009,1200],{"class":251},[143,2011,1627],{"class":634},[143,2013,638],{"class":507},[143,2015,255],{"class":160},[143,2017,2019,2021,2023,2026],{"class":145,"line":2018},33,[143,2020,1636],{"class":264},[143,2022,613],{"class":160},[143,2024,2025],{"class":185}," \"\"",[143,2027,715],{"class":160},[143,2029,2031,2033,2035,2037,2039,2041],{"class":145,"line":2030},34,[143,2032,1652],{"class":264},[143,2034,613],{"class":160},[143,2036,186],{"class":185},[143,2038,1659],{"class":189},[143,2040,193],{"class":185},[143,2042,715],{"class":160},[143,2044,2046,2048,2050,2052,2054,2056],{"class":145,"line":2045},35,[143,2047,1668],{"class":264},[143,2049,613],{"class":160},[143,2051,186],{"class":185},[143,2053,1675],{"class":189},[143,2055,193],{"class":185},[143,2057,715],{"class":160},[143,2059,2061],{"class":145,"line":2060},36,[143,2062,1684],{"class":160},[143,2064,2066],{"class":145,"line":2065},37,[143,2067,226],{"emptyLinePlaceholder":225},[143,2069,2071,2073,2075,2077,2079,2081,2083,2085,2087,2089],{"class":145,"line":2070},38,[143,2072,1200],{"class":251},[143,2074,1203],{"class":634},[143,2076,638],{"class":507},[143,2078,1561],{"class":164},[143,2080,301],{"class":160},[143,2082,1703],{"class":232},[143,2084,236],{"class":264},[143,2086,1708],{"class":164},[143,2088,316],{"class":264},[143,2090,196],{"class":160},[143,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116],{"class":145,"line":2093},39,[143,2095,285],{"class":232},[143,2097,236],{"class":264},[143,2099,1229],{"class":164},[143,2101,301],{"class":160},[143,2103,1725],{"class":164},[143,2105,316],{"class":264},[143,2107,301],{"class":160},[143,2109,304],{"class":232},[143,2111,236],{"class":264},[143,2113,1882],{"class":1736},[143,2115,316],{"class":264},[143,2117,196],{"class":160},[143,2119,2121,2123,2125],{"class":145,"line":2120},40,[143,2122,324],{"class":160},[143,2124,316],{"class":264},[143,2126,196],{"class":160},[143,2128,2130,2132,2134],{"class":145,"line":2129},41,[143,2131,545],{"class":160},[143,2133,316],{"class":164},[143,2135,196],{"class":160},[42,2137],{},[11,2139,2141],{"id":2140},"nuxt-環境測試範例","Nuxt 環境測試範例",[49,2143,2145],{"id":2144},"測試-composable","測試 Composable",[110,2147,2149],{"className":137,"code":2148,"language":139,"meta":118,"style":118},"\u002F\u002F test\u002Fnuxt\u002Fcomposables\u002FuseCounter.nuxt.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { mountSuspended } from \"@nuxt\u002Ftest-utils\u002Fruntime\";\nimport { useCounter } from \"~\u002Fcomposables\u002FuseCounter\";\n\ndescribe(\"useCounter\", () => {\n  it(\"should initialize with default value\", async () => {\n    const wrapper = await mountSuspended({\n      setup() {\n        const { count } = useCounter();\n        return { count };\n      },\n      template: \"\u003Cdiv>{{ count }}\u003C\u002Fdiv>\",\n    });\n\n    expect(wrapper.text()).toBe(\"0\");\n  });\n\n  it(\"should increment count\", async () => {\n    const wrapper = await mountSuspended({\n      setup() {\n        const { count, increment } = useCounter();\n        return { count, increment };\n      },\n      template: '\u003Cbutton @click=\"increment\">{{ count }}\u003C\u002Fbutton>',\n    });\n\n    await wrapper.trigger(\"click\");\n    expect(wrapper.text()).toBe(\"1\");\n  });\n});\n",[34,2150,2151,2156,2184,2206,2228,2232,2253,2277,2295,2305,2325,2337,2342,2358,2367,2371,2402,2410,2414,2437,2453,2461,2484,2498,2502,2519,2527,2531,2556,2587,2595],{"__ignoreMap":118},[143,2152,2153],{"class":145,"line":146},[143,2154,2155],{"class":149},"\u002F\u002F test\u002Fnuxt\u002Fcomposables\u002FuseCounter.nuxt.test.ts\n",[143,2157,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182],{"class":145,"line":153},[143,2159,157],{"class":156},[143,2161,161],{"class":160},[143,2163,165],{"class":164},[143,2165,168],{"class":160},[143,2167,171],{"class":164},[143,2169,168],{"class":160},[143,2171,176],{"class":164},[143,2173,179],{"class":160},[143,2175,182],{"class":156},[143,2177,186],{"class":185},[143,2179,190],{"class":189},[143,2181,193],{"class":185},[143,2183,196],{"class":160},[143,2185,2186,2188,2190,2193,2195,2197,2199,2202,2204],{"class":145,"line":199},[143,2187,157],{"class":156},[143,2189,161],{"class":160},[143,2191,2192],{"class":164}," mountSuspended",[143,2194,179],{"class":160},[143,2196,182],{"class":156},[143,2198,186],{"class":185},[143,2200,2201],{"class":189},"@nuxt\u002Ftest-utils\u002Fruntime",[143,2203,193],{"class":185},[143,2205,196],{"class":160},[143,2207,2208,2210,2212,2215,2217,2219,2221,2224,2226],{"class":145,"line":222},[143,2209,157],{"class":156},[143,2211,161],{"class":160},[143,2213,2214],{"class":164}," useCounter",[143,2216,179],{"class":160},[143,2218,182],{"class":156},[143,2220,186],{"class":185},[143,2222,2223],{"class":189},"~\u002Fcomposables\u002FuseCounter",[143,2225,193],{"class":185},[143,2227,196],{"class":160},[143,2229,2230],{"class":145,"line":229},[143,2231,226],{"emptyLinePlaceholder":225},[143,2233,2234,2236,2238,2240,2243,2245,2247,2249,2251],{"class":145,"line":258},[143,2235,233],{"class":232},[143,2237,236],{"class":164},[143,2239,193],{"class":185},[143,2241,2242],{"class":189},"useCounter",[143,2244,193],{"class":185},[143,2246,168],{"class":160},[143,2248,248],{"class":160},[143,2250,252],{"class":251},[143,2252,255],{"class":160},[143,2254,2255,2257,2259,2261,2264,2266,2268,2271,2273,2275],{"class":145,"line":282},[143,2256,261],{"class":232},[143,2258,236],{"class":264},[143,2260,193],{"class":185},[143,2262,2263],{"class":189},"should initialize with default value",[143,2265,193],{"class":185},[143,2267,168],{"class":160},[143,2269,2270],{"class":251}," async",[143,2272,248],{"class":160},[143,2274,252],{"class":251},[143,2276,255],{"class":160},[143,2278,2279,2281,2284,2286,2289,2291,2293],{"class":145,"line":321},[143,2280,1200],{"class":251},[143,2282,2283],{"class":634}," wrapper",[143,2285,638],{"class":507},[143,2287,2288],{"class":156}," await",[143,2290,2192],{"class":232},[143,2292,236],{"class":264},[143,2294,937],{"class":160},[143,2296,2297,2301,2303],{"class":145,"line":331},[143,2298,2300],{"class":2299},"sVXei","      setup",[143,2302,1369],{"class":160},[143,2304,255],{"class":160},[143,2306,2307,2310,2312,2315,2317,2319,2321,2323],{"class":145,"line":336},[143,2308,2309],{"class":251},"        const",[143,2311,161],{"class":160},[143,2313,2314],{"class":634}," count",[143,2316,179],{"class":160},[143,2318,638],{"class":507},[143,2320,2214],{"class":232},[143,2322,1369],{"class":264},[143,2324,196],{"class":160},[143,2326,2327,2330,2332,2334],{"class":145,"line":358},[143,2328,2329],{"class":156},"        return",[143,2331,161],{"class":160},[143,2333,2314],{"class":164},[143,2335,2336],{"class":160}," };\n",[143,2338,2339],{"class":145,"line":391},[143,2340,2341],{"class":160},"      },\n",[143,2343,2344,2347,2349,2351,2354,2356],{"class":145,"line":400},[143,2345,2346],{"class":264},"      template",[143,2348,613],{"class":160},[143,2350,186],{"class":185},[143,2352,2353],{"class":189},"\u003Cdiv>{{ count }}\u003C\u002Fdiv>",[143,2355,193],{"class":185},[143,2357,715],{"class":160},[143,2359,2360,2363,2365],{"class":145,"line":405},[143,2361,2362],{"class":160},"    }",[143,2364,316],{"class":264},[143,2366,196],{"class":160},[143,2368,2369],{"class":145,"line":427},[143,2370,226],{"emptyLinePlaceholder":225},[143,2372,2373,2375,2377,2380,2382,2384,2386,2388,2390,2392,2394,2396,2398,2400],{"class":145,"line":460},[143,2374,285],{"class":232},[143,2376,236],{"class":264},[143,2378,2379],{"class":164},"wrapper",[143,2381,301],{"class":160},[143,2383,115],{"class":232},[143,2385,1237],{"class":264},[143,2387,301],{"class":160},[143,2389,304],{"class":232},[143,2391,236],{"class":264},[143,2393,193],{"class":185},[143,2395,369],{"class":189},[143,2397,193],{"class":185},[143,2399,316],{"class":264},[143,2401,196],{"class":160},[143,2403,2404,2406,2408],{"class":145,"line":469},[143,2405,324],{"class":160},[143,2407,316],{"class":264},[143,2409,196],{"class":160},[143,2411,2412],{"class":145,"line":474},[143,2413,226],{"emptyLinePlaceholder":225},[143,2415,2416,2418,2420,2422,2425,2427,2429,2431,2433,2435],{"class":145,"line":496},[143,2417,261],{"class":232},[143,2419,236],{"class":264},[143,2421,193],{"class":185},[143,2423,2424],{"class":189},"should increment count",[143,2426,193],{"class":185},[143,2428,168],{"class":160},[143,2430,2270],{"class":251},[143,2432,248],{"class":160},[143,2434,252],{"class":251},[143,2436,255],{"class":160},[143,2438,2439,2441,2443,2445,2447,2449,2451],{"class":145,"line":533},[143,2440,1200],{"class":251},[143,2442,2283],{"class":634},[143,2444,638],{"class":507},[143,2446,2288],{"class":156},[143,2448,2192],{"class":232},[143,2450,236],{"class":264},[143,2452,937],{"class":160},[143,2454,2455,2457,2459],{"class":145,"line":542},[143,2456,2300],{"class":2299},[143,2458,1369],{"class":160},[143,2460,255],{"class":160},[143,2462,2463,2465,2467,2469,2471,2474,2476,2478,2480,2482],{"class":145,"line":1495},[143,2464,2309],{"class":251},[143,2466,161],{"class":160},[143,2468,2314],{"class":634},[143,2470,168],{"class":160},[143,2472,2473],{"class":634}," increment",[143,2475,179],{"class":160},[143,2477,638],{"class":507},[143,2479,2214],{"class":232},[143,2481,1369],{"class":264},[143,2483,196],{"class":160},[143,2485,2486,2488,2490,2492,2494,2496],{"class":145,"line":1504},[143,2487,2329],{"class":156},[143,2489,161],{"class":160},[143,2491,2314],{"class":164},[143,2493,168],{"class":160},[143,2495,2473],{"class":164},[143,2497,2336],{"class":160},[143,2499,2500],{"class":145,"line":1838},[143,2501,2341],{"class":160},[143,2503,2504,2506,2508,2511,2514,2517],{"class":145,"line":1861},[143,2505,2346],{"class":264},[143,2507,613],{"class":160},[143,2509,2510],{"class":185}," '",[143,2512,2513],{"class":189},"\u003Cbutton @click=\"increment\">{{ count }}\u003C\u002Fbutton>",[143,2515,2516],{"class":185},"'",[143,2518,715],{"class":160},[143,2520,2521,2523,2525],{"class":145,"line":1889},[143,2522,2362],{"class":160},[143,2524,316],{"class":264},[143,2526,196],{"class":160},[143,2528,2529],{"class":145,"line":1912},[143,2530,226],{"emptyLinePlaceholder":225},[143,2532,2533,2536,2538,2540,2543,2545,2547,2550,2552,2554],{"class":145,"line":1965},[143,2534,2535],{"class":156},"    await",[143,2537,2283],{"class":164},[143,2539,301],{"class":160},[143,2541,2542],{"class":232},"trigger",[143,2544,236],{"class":264},[143,2546,193],{"class":185},[143,2548,2549],{"class":189},"click",[143,2551,193],{"class":185},[143,2553,316],{"class":264},[143,2555,196],{"class":160},[143,2557,2558,2560,2562,2564,2566,2568,2570,2572,2574,2576,2578,2581,2583,2585],{"class":145,"line":1971},[143,2559,285],{"class":232},[143,2561,236],{"class":264},[143,2563,2379],{"class":164},[143,2565,301],{"class":160},[143,2567,115],{"class":232},[143,2569,1237],{"class":264},[143,2571,301],{"class":160},[143,2573,304],{"class":232},[143,2575,236],{"class":264},[143,2577,193],{"class":185},[143,2579,2580],{"class":189},"1",[143,2582,193],{"class":185},[143,2584,316],{"class":264},[143,2586,196],{"class":160},[143,2588,2589,2591,2593],{"class":145,"line":1980},[143,2590,324],{"class":160},[143,2592,316],{"class":264},[143,2594,196],{"class":160},[143,2596,2597,2599,2601],{"class":145,"line":1985},[143,2598,545],{"class":160},[143,2600,316],{"class":164},[143,2602,196],{"class":160},[49,2604,2605],{"id":2605},"測試元件",[110,2607,2609],{"className":137,"code":2608,"language":139,"meta":118,"style":118},"\u002F\u002F test\u002Fnuxt\u002Fcomponents\u002FUserCard.nuxt.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { mountSuspended } from \"@nuxt\u002Ftest-utils\u002Fruntime\";\nimport UserCard from \"~\u002Fcomponents\u002FUserCard.vue\";\n\ndescribe(\"UserCard\", () => {\n  const mockUser = {\n    id: \"1\",\n    name: \"John Doe\",\n    email: \"john@example.com\",\n    role: \"admin\",\n  };\n\n  it(\"should render user name\", async () => {\n    const wrapper = await mountSuspended(UserCard, {\n      props: { user: mockUser },\n    });\n\n    expect(wrapper.text()).toContain(\"John Doe\");\n  });\n\n  it(\"should emit edit event on button click\", async () => {\n    const wrapper = await mountSuspended(UserCard, {\n      props: { user: mockUser },\n    });\n\n    await wrapper.find('[data-testid=\"edit-button\"]').trigger(\"click\");\n    expect(wrapper.emitted(\"edit\")).toBeTruthy();\n    expect(wrapper.emitted(\"edit\")![0]).toEqual([mockUser.id]);\n  });\n});\n",[34,2610,2611,2616,2644,2664,2683,2687,2708,2719,2734,2749,2764,2780,2785,2789,2812,2832,2851,2859,2863,2893,2901,2905,2928,2948,2964,2972,2976,3014,3047,3098,3106],{"__ignoreMap":118},[143,2612,2613],{"class":145,"line":146},[143,2614,2615],{"class":149},"\u002F\u002F test\u002Fnuxt\u002Fcomponents\u002FUserCard.nuxt.test.ts\n",[143,2617,2618,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2642],{"class":145,"line":153},[143,2619,157],{"class":156},[143,2621,161],{"class":160},[143,2623,165],{"class":164},[143,2625,168],{"class":160},[143,2627,171],{"class":164},[143,2629,168],{"class":160},[143,2631,176],{"class":164},[143,2633,179],{"class":160},[143,2635,182],{"class":156},[143,2637,186],{"class":185},[143,2639,190],{"class":189},[143,2641,193],{"class":185},[143,2643,196],{"class":160},[143,2645,2646,2648,2650,2652,2654,2656,2658,2660,2662],{"class":145,"line":199},[143,2647,157],{"class":156},[143,2649,161],{"class":160},[143,2651,2192],{"class":164},[143,2653,179],{"class":160},[143,2655,182],{"class":156},[143,2657,186],{"class":185},[143,2659,2201],{"class":189},[143,2661,193],{"class":185},[143,2663,196],{"class":160},[143,2665,2666,2668,2671,2674,2676,2679,2681],{"class":145,"line":222},[143,2667,157],{"class":156},[143,2669,2670],{"class":164}," UserCard ",[143,2672,2673],{"class":156},"from",[143,2675,186],{"class":185},[143,2677,2678],{"class":189},"~\u002Fcomponents\u002FUserCard.vue",[143,2680,193],{"class":185},[143,2682,196],{"class":160},[143,2684,2685],{"class":145,"line":229},[143,2686,226],{"emptyLinePlaceholder":225},[143,2688,2689,2691,2693,2695,2698,2700,2702,2704,2706],{"class":145,"line":258},[143,2690,233],{"class":232},[143,2692,236],{"class":164},[143,2694,193],{"class":185},[143,2696,2697],{"class":189},"UserCard",[143,2699,193],{"class":185},[143,2701,168],{"class":160},[143,2703,248],{"class":160},[143,2705,252],{"class":251},[143,2707,255],{"class":160},[143,2709,2710,2712,2715,2717],{"class":145,"line":282},[143,2711,631],{"class":251},[143,2713,2714],{"class":634}," mockUser",[143,2716,638],{"class":507},[143,2718,255],{"class":160},[143,2720,2721,2724,2726,2728,2730,2732],{"class":145,"line":321},[143,2722,2723],{"class":264},"    id",[143,2725,613],{"class":160},[143,2727,186],{"class":185},[143,2729,2580],{"class":189},[143,2731,193],{"class":185},[143,2733,715],{"class":160},[143,2735,2736,2739,2741,2743,2745,2747],{"class":145,"line":331},[143,2737,2738],{"class":264},"    name",[143,2740,613],{"class":160},[143,2742,186],{"class":185},[143,2744,1643],{"class":189},[143,2746,193],{"class":185},[143,2748,715],{"class":160},[143,2750,2751,2754,2756,2758,2760,2762],{"class":145,"line":336},[143,2752,2753],{"class":264},"    email",[143,2755,613],{"class":160},[143,2757,186],{"class":185},[143,2759,1659],{"class":189},[143,2761,193],{"class":185},[143,2763,715],{"class":160},[143,2765,2766,2769,2771,2773,2776,2778],{"class":145,"line":358},[143,2767,2768],{"class":264},"    role",[143,2770,613],{"class":160},[143,2772,186],{"class":185},[143,2774,2775],{"class":189},"admin",[143,2777,193],{"class":185},[143,2779,715],{"class":160},[143,2781,2782],{"class":145,"line":391},[143,2783,2784],{"class":160},"  };\n",[143,2786,2787],{"class":145,"line":400},[143,2788,226],{"emptyLinePlaceholder":225},[143,2790,2791,2793,2795,2797,2800,2802,2804,2806,2808,2810],{"class":145,"line":405},[143,2792,261],{"class":232},[143,2794,236],{"class":264},[143,2796,193],{"class":185},[143,2798,2799],{"class":189},"should render user name",[143,2801,193],{"class":185},[143,2803,168],{"class":160},[143,2805,2270],{"class":251},[143,2807,248],{"class":160},[143,2809,252],{"class":251},[143,2811,255],{"class":160},[143,2813,2814,2816,2818,2820,2822,2824,2826,2828,2830],{"class":145,"line":427},[143,2815,1200],{"class":251},[143,2817,2283],{"class":634},[143,2819,638],{"class":507},[143,2821,2288],{"class":156},[143,2823,2192],{"class":232},[143,2825,236],{"class":264},[143,2827,2697],{"class":164},[143,2829,168],{"class":160},[143,2831,255],{"class":160},[143,2833,2834,2837,2839,2841,2844,2846,2848],{"class":145,"line":460},[143,2835,2836],{"class":264},"      props",[143,2838,613],{"class":160},[143,2840,161],{"class":160},[143,2842,2843],{"class":264}," user",[143,2845,613],{"class":160},[143,2847,2714],{"class":164},[143,2849,2850],{"class":160}," },\n",[143,2852,2853,2855,2857],{"class":145,"line":469},[143,2854,2362],{"class":160},[143,2856,316],{"class":264},[143,2858,196],{"class":160},[143,2860,2861],{"class":145,"line":474},[143,2862,226],{"emptyLinePlaceholder":225},[143,2864,2865,2867,2869,2871,2873,2875,2877,2879,2881,2883,2885,2887,2889,2891],{"class":145,"line":496},[143,2866,285],{"class":232},[143,2868,236],{"class":264},[143,2870,2379],{"class":164},[143,2872,301],{"class":160},[143,2874,115],{"class":232},[143,2876,1237],{"class":264},[143,2878,301],{"class":160},[143,2880,1949],{"class":232},[143,2882,236],{"class":264},[143,2884,193],{"class":185},[143,2886,1643],{"class":189},[143,2888,193],{"class":185},[143,2890,316],{"class":264},[143,2892,196],{"class":160},[143,2894,2895,2897,2899],{"class":145,"line":533},[143,2896,324],{"class":160},[143,2898,316],{"class":264},[143,2900,196],{"class":160},[143,2902,2903],{"class":145,"line":542},[143,2904,226],{"emptyLinePlaceholder":225},[143,2906,2907,2909,2911,2913,2916,2918,2920,2922,2924,2926],{"class":145,"line":1495},[143,2908,261],{"class":232},[143,2910,236],{"class":264},[143,2912,193],{"class":185},[143,2914,2915],{"class":189},"should emit edit event on button click",[143,2917,193],{"class":185},[143,2919,168],{"class":160},[143,2921,2270],{"class":251},[143,2923,248],{"class":160},[143,2925,252],{"class":251},[143,2927,255],{"class":160},[143,2929,2930,2932,2934,2936,2938,2940,2942,2944,2946],{"class":145,"line":1504},[143,2931,1200],{"class":251},[143,2933,2283],{"class":634},[143,2935,638],{"class":507},[143,2937,2288],{"class":156},[143,2939,2192],{"class":232},[143,2941,236],{"class":264},[143,2943,2697],{"class":164},[143,2945,168],{"class":160},[143,2947,255],{"class":160},[143,2949,2950,2952,2954,2956,2958,2960,2962],{"class":145,"line":1838},[143,2951,2836],{"class":264},[143,2953,613],{"class":160},[143,2955,161],{"class":160},[143,2957,2843],{"class":264},[143,2959,613],{"class":160},[143,2961,2714],{"class":164},[143,2963,2850],{"class":160},[143,2965,2966,2968,2970],{"class":145,"line":1861},[143,2967,2362],{"class":160},[143,2969,316],{"class":264},[143,2971,196],{"class":160},[143,2973,2974],{"class":145,"line":1889},[143,2975,226],{"emptyLinePlaceholder":225},[143,2977,2978,2980,2982,2984,2987,2989,2991,2994,2996,2998,3000,3002,3004,3006,3008,3010,3012],{"class":145,"line":1912},[143,2979,2535],{"class":156},[143,2981,2283],{"class":164},[143,2983,301],{"class":160},[143,2985,2986],{"class":232},"find",[143,2988,236],{"class":264},[143,2990,2516],{"class":185},[143,2992,2993],{"class":189},"[data-testid=\"edit-button\"]",[143,2995,2516],{"class":185},[143,2997,316],{"class":264},[143,2999,301],{"class":160},[143,3001,2542],{"class":232},[143,3003,236],{"class":264},[143,3005,193],{"class":185},[143,3007,2549],{"class":189},[143,3009,193],{"class":185},[143,3011,316],{"class":264},[143,3013,196],{"class":160},[143,3015,3016,3018,3020,3022,3024,3027,3029,3031,3034,3036,3038,3040,3043,3045],{"class":145,"line":1965},[143,3017,285],{"class":232},[143,3019,236],{"class":264},[143,3021,2379],{"class":164},[143,3023,301],{"class":160},[143,3025,3026],{"class":232},"emitted",[143,3028,236],{"class":264},[143,3030,193],{"class":185},[143,3032,3033],{"class":189},"edit",[143,3035,193],{"class":185},[143,3037,298],{"class":264},[143,3039,301],{"class":160},[143,3041,3042],{"class":232},"toBeTruthy",[143,3044,1369],{"class":264},[143,3046,196],{"class":160},[143,3048,3049,3051,3053,3055,3057,3059,3061,3063,3065,3067,3069,3071,3073,3075,3078,3080,3083,3086,3089,3091,3094,3096],{"class":145,"line":1971},[143,3050,285],{"class":232},[143,3052,236],{"class":264},[143,3054,2379],{"class":164},[143,3056,301],{"class":160},[143,3058,3026],{"class":232},[143,3060,236],{"class":264},[143,3062,193],{"class":185},[143,3064,3033],{"class":189},[143,3066,193],{"class":185},[143,3068,316],{"class":264},[143,3070,1898],{"class":507},[143,3072,1932],{"class":264},[143,3074,369],{"class":294},[143,3076,3077],{"class":264},"])",[143,3079,301],{"class":160},[143,3081,3082],{"class":232},"toEqual",[143,3084,3085],{"class":264},"([",[143,3087,3088],{"class":164},"mockUser",[143,3090,301],{"class":160},[143,3092,3093],{"class":164},"id",[143,3095,3077],{"class":264},[143,3097,196],{"class":160},[143,3099,3100,3102,3104],{"class":145,"line":1980},[143,3101,324],{"class":160},[143,3103,316],{"class":264},[143,3105,196],{"class":160},[143,3107,3108,3110,3112],{"class":145,"line":1985},[143,3109,545],{"class":160},[143,3111,316],{"class":164},[143,3113,196],{"class":160},[42,3115],{},[11,3117,3119],{"id":3118},"mock-技巧","Mock 技巧",[49,3121,3123],{"id":3122},"mock-外部依賴","Mock 外部依賴",[110,3125,3127],{"className":137,"code":3126,"language":139,"meta":118,"style":118},"\u002F\u002F test\u002Funit\u002Fservices\u002Fapi.test.ts\nimport { describe, it, expect, vi, beforeEach } from \"vitest\";\n\n\u002F\u002F Mock $fetch\nvi.mock(\"#app\", () => ({\n  $fetch: vi.fn(),\n}));\n\nimport { $fetch } from \"#app\";\nimport { fetchUsers } from \"~\u002Fservices\u002Fapi\";\n\ndescribe(\"fetchUsers\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  it(\"should return users from API\", async () => {\n    const mockUsers = [{ id: \"1\", name: \"John\" }];\n    vi.mocked($fetch).mockResolvedValue({ data: mockUsers });\n\n    const result = await fetchUsers();\n    expect(result).toEqual(mockUsers);\n  });\n\n  it(\"should handle API error\", async () => {\n    vi.mocked($fetch).mockRejectedValue(new Error(\"Network error\"));\n\n    await expect(fetchUsers()).rejects.toThrow(\"Network error\");\n  });\n});\n",[34,3128,3129,3134,3172,3176,3181,3210,3228,3236,3240,3261,3283,3287,3308,3321,3335,3343,3347,3370,3416,3454,3458,3474,3497,3505,3509,3532,3572,3576,3610,3618],{"__ignoreMap":118},[143,3130,3131],{"class":145,"line":146},[143,3132,3133],{"class":149},"\u002F\u002F test\u002Funit\u002Fservices\u002Fapi.test.ts\n",[143,3135,3136,3138,3140,3142,3144,3146,3148,3150,3152,3155,3157,3160,3162,3164,3166,3168,3170],{"class":145,"line":153},[143,3137,157],{"class":156},[143,3139,161],{"class":160},[143,3141,165],{"class":164},[143,3143,168],{"class":160},[143,3145,171],{"class":164},[143,3147,168],{"class":160},[143,3149,176],{"class":164},[143,3151,168],{"class":160},[143,3153,3154],{"class":164}," vi",[143,3156,168],{"class":160},[143,3158,3159],{"class":164}," beforeEach",[143,3161,179],{"class":160},[143,3163,182],{"class":156},[143,3165,186],{"class":185},[143,3167,190],{"class":189},[143,3169,193],{"class":185},[143,3171,196],{"class":160},[143,3173,3174],{"class":145,"line":199},[143,3175,226],{"emptyLinePlaceholder":225},[143,3177,3178],{"class":145,"line":222},[143,3179,3180],{"class":149},"\u002F\u002F Mock $fetch\n",[143,3182,3183,3186,3188,3191,3193,3195,3198,3200,3202,3204,3206,3208],{"class":145,"line":229},[143,3184,3185],{"class":164},"vi",[143,3187,301],{"class":160},[143,3189,3190],{"class":232},"mock",[143,3192,236],{"class":164},[143,3194,193],{"class":185},[143,3196,3197],{"class":189},"#app",[143,3199,193],{"class":185},[143,3201,168],{"class":160},[143,3203,248],{"class":160},[143,3205,252],{"class":251},[143,3207,1895],{"class":164},[143,3209,937],{"class":160},[143,3211,3212,3215,3217,3219,3221,3224,3226],{"class":145,"line":258},[143,3213,3214],{"class":264},"  $fetch",[143,3216,613],{"class":160},[143,3218,3154],{"class":164},[143,3220,301],{"class":160},[143,3222,3223],{"class":232},"fn",[143,3225,1369],{"class":164},[143,3227,715],{"class":160},[143,3229,3230,3232,3234],{"class":145,"line":282},[143,3231,545],{"class":160},[143,3233,298],{"class":164},[143,3235,196],{"class":160},[143,3237,3238],{"class":145,"line":321},[143,3239,226],{"emptyLinePlaceholder":225},[143,3241,3242,3244,3246,3249,3251,3253,3255,3257,3259],{"class":145,"line":331},[143,3243,157],{"class":156},[143,3245,161],{"class":160},[143,3247,3248],{"class":164}," $fetch",[143,3250,179],{"class":160},[143,3252,182],{"class":156},[143,3254,186],{"class":185},[143,3256,3197],{"class":189},[143,3258,193],{"class":185},[143,3260,196],{"class":160},[143,3262,3263,3265,3267,3270,3272,3274,3276,3279,3281],{"class":145,"line":336},[143,3264,157],{"class":156},[143,3266,161],{"class":160},[143,3268,3269],{"class":164}," fetchUsers",[143,3271,179],{"class":160},[143,3273,182],{"class":156},[143,3275,186],{"class":185},[143,3277,3278],{"class":189},"~\u002Fservices\u002Fapi",[143,3280,193],{"class":185},[143,3282,196],{"class":160},[143,3284,3285],{"class":145,"line":358},[143,3286,226],{"emptyLinePlaceholder":225},[143,3288,3289,3291,3293,3295,3298,3300,3302,3304,3306],{"class":145,"line":391},[143,3290,233],{"class":232},[143,3292,236],{"class":164},[143,3294,193],{"class":185},[143,3296,3297],{"class":189},"fetchUsers",[143,3299,193],{"class":185},[143,3301,168],{"class":160},[143,3303,248],{"class":160},[143,3305,252],{"class":251},[143,3307,255],{"class":160},[143,3309,3310,3313,3315,3317,3319],{"class":145,"line":400},[143,3311,3312],{"class":232},"  beforeEach",[143,3314,236],{"class":264},[143,3316,1369],{"class":160},[143,3318,252],{"class":251},[143,3320,255],{"class":160},[143,3322,3323,3326,3328,3331,3333],{"class":145,"line":405},[143,3324,3325],{"class":164},"    vi",[143,3327,301],{"class":160},[143,3329,3330],{"class":232},"clearAllMocks",[143,3332,1369],{"class":264},[143,3334,196],{"class":160},[143,3336,3337,3339,3341],{"class":145,"line":427},[143,3338,324],{"class":160},[143,3340,316],{"class":264},[143,3342,196],{"class":160},[143,3344,3345],{"class":145,"line":460},[143,3346,226],{"emptyLinePlaceholder":225},[143,3348,3349,3351,3353,3355,3358,3360,3362,3364,3366,3368],{"class":145,"line":469},[143,3350,261],{"class":232},[143,3352,236],{"class":264},[143,3354,193],{"class":185},[143,3356,3357],{"class":189},"should return users from API",[143,3359,193],{"class":185},[143,3361,168],{"class":160},[143,3363,2270],{"class":251},[143,3365,248],{"class":160},[143,3367,252],{"class":251},[143,3369,255],{"class":160},[143,3371,3372,3374,3377,3379,3382,3385,3388,3390,3392,3394,3396,3398,3401,3403,3405,3408,3410,3412,3414],{"class":145,"line":474},[143,3373,1200],{"class":251},[143,3375,3376],{"class":634}," mockUsers",[143,3378,638],{"class":507},[143,3380,3381],{"class":264}," [",[143,3383,3384],{"class":160},"{",[143,3386,3387],{"class":264}," id",[143,3389,613],{"class":160},[143,3391,186],{"class":185},[143,3393,2580],{"class":189},[143,3395,193],{"class":185},[143,3397,168],{"class":160},[143,3399,3400],{"class":264}," name",[143,3402,613],{"class":160},[143,3404,186],{"class":185},[143,3406,3407],{"class":189},"John",[143,3409,193],{"class":185},[143,3411,179],{"class":160},[143,3413,1937],{"class":264},[143,3415,196],{"class":160},[143,3417,3418,3420,3422,3425,3427,3430,3432,3434,3437,3439,3441,3444,3446,3448,3450,3452],{"class":145,"line":496},[143,3419,3325],{"class":164},[143,3421,301],{"class":160},[143,3423,3424],{"class":232},"mocked",[143,3426,236],{"class":264},[143,3428,3429],{"class":164},"$fetch",[143,3431,316],{"class":264},[143,3433,301],{"class":160},[143,3435,3436],{"class":232},"mockResolvedValue",[143,3438,236],{"class":264},[143,3440,3384],{"class":160},[143,3442,3443],{"class":264}," data",[143,3445,613],{"class":160},[143,3447,3376],{"class":164},[143,3449,179],{"class":160},[143,3451,316],{"class":264},[143,3453,196],{"class":160},[143,3455,3456],{"class":145,"line":533},[143,3457,226],{"emptyLinePlaceholder":225},[143,3459,3460,3462,3464,3466,3468,3470,3472],{"class":145,"line":542},[143,3461,1200],{"class":251},[143,3463,1203],{"class":634},[143,3465,638],{"class":507},[143,3467,2288],{"class":156},[143,3469,3269],{"class":232},[143,3471,1369],{"class":264},[143,3473,196],{"class":160},[143,3475,3476,3478,3480,3482,3484,3486,3488,3490,3493,3495],{"class":145,"line":1495},[143,3477,285],{"class":232},[143,3479,236],{"class":264},[143,3481,1229],{"class":164},[143,3483,316],{"class":264},[143,3485,301],{"class":160},[143,3487,3082],{"class":232},[143,3489,236],{"class":264},[143,3491,3492],{"class":164},"mockUsers",[143,3494,316],{"class":264},[143,3496,196],{"class":160},[143,3498,3499,3501,3503],{"class":145,"line":1504},[143,3500,324],{"class":160},[143,3502,316],{"class":264},[143,3504,196],{"class":160},[143,3506,3507],{"class":145,"line":1838},[143,3508,226],{"emptyLinePlaceholder":225},[143,3510,3511,3513,3515,3517,3520,3522,3524,3526,3528,3530],{"class":145,"line":1861},[143,3512,261],{"class":232},[143,3514,236],{"class":264},[143,3516,193],{"class":185},[143,3518,3519],{"class":189},"should handle API error",[143,3521,193],{"class":185},[143,3523,168],{"class":160},[143,3525,2270],{"class":251},[143,3527,248],{"class":160},[143,3529,252],{"class":251},[143,3531,255],{"class":160},[143,3533,3534,3536,3538,3540,3542,3544,3546,3548,3551,3553,3556,3559,3561,3563,3566,3568,3570],{"class":145,"line":1889},[143,3535,3325],{"class":164},[143,3537,301],{"class":160},[143,3539,3424],{"class":232},[143,3541,236],{"class":264},[143,3543,3429],{"class":164},[143,3545,316],{"class":264},[143,3547,301],{"class":160},[143,3549,3550],{"class":232},"mockRejectedValue",[143,3552,236],{"class":264},[143,3554,3555],{"class":507},"new",[143,3557,3558],{"class":232}," Error",[143,3560,236],{"class":264},[143,3562,193],{"class":185},[143,3564,3565],{"class":189},"Network error",[143,3567,193],{"class":185},[143,3569,298],{"class":264},[143,3571,196],{"class":160},[143,3573,3574],{"class":145,"line":1912},[143,3575,226],{"emptyLinePlaceholder":225},[143,3577,3578,3580,3582,3584,3586,3588,3590,3593,3595,3598,3600,3602,3604,3606,3608],{"class":145,"line":1965},[143,3579,2535],{"class":156},[143,3581,176],{"class":232},[143,3583,236],{"class":264},[143,3585,3297],{"class":232},[143,3587,1237],{"class":264},[143,3589,301],{"class":160},[143,3591,3592],{"class":164},"rejects",[143,3594,301],{"class":160},[143,3596,3597],{"class":232},"toThrow",[143,3599,236],{"class":264},[143,3601,193],{"class":185},[143,3603,3565],{"class":189},[143,3605,193],{"class":185},[143,3607,316],{"class":264},[143,3609,196],{"class":160},[143,3611,3612,3614,3616],{"class":145,"line":1971},[143,3613,324],{"class":160},[143,3615,316],{"class":264},[143,3617,196],{"class":160},[143,3619,3620,3622,3624],{"class":145,"line":1980},[143,3621,545],{"class":160},[143,3623,316],{"class":164},[143,3625,196],{"class":160},[49,3627,3629],{"id":3628},"mock-supabase","Mock Supabase",[110,3631,3633],{"className":137,"code":3632,"language":139,"meta":118,"style":118},"\u002F\u002F test\u002Fhelpers\u002FmockSupabase.ts\nexport function createMockSupabase() {\n  return {\n    from: vi.fn().mockReturnThis(),\n    select: vi.fn().mockReturnThis(),\n    insert: vi.fn().mockReturnThis(),\n    update: vi.fn().mockReturnThis(),\n    delete: vi.fn().mockReturnThis(),\n    eq: vi.fn().mockReturnThis(),\n    single: vi.fn(),\n  };\n}\n\n\u002F\u002F 使用\nconst mockSupabase = createMockSupabase();\nmockSupabase.single.mockResolvedValue({ data: mockUser, error: null });\n",[34,3634,3635,3640,3653,3659,3683,3706,3729,3752,3775,3798,3815,3819,3823,3827,3832,3848],{"__ignoreMap":118},[143,3636,3637],{"class":145,"line":146},[143,3638,3639],{"class":149},"\u002F\u002F test\u002Fhelpers\u002FmockSupabase.ts\n",[143,3641,3642,3644,3646,3649,3651],{"class":145,"line":153},[143,3643,599],{"class":156},[143,3645,602],{"class":251},[143,3647,3648],{"class":232}," createMockSupabase",[143,3650,1369],{"class":160},[143,3652,255],{"class":160},[143,3654,3655,3657],{"class":145,"line":199},[143,3656,740],{"class":156},[143,3658,255],{"class":160},[143,3660,3661,3664,3666,3668,3670,3672,3674,3676,3679,3681],{"class":145,"line":222},[143,3662,3663],{"class":264},"    from",[143,3665,613],{"class":160},[143,3667,3154],{"class":164},[143,3669,301],{"class":160},[143,3671,3223],{"class":232},[143,3673,1369],{"class":264},[143,3675,301],{"class":160},[143,3677,3678],{"class":232},"mockReturnThis",[143,3680,1369],{"class":264},[143,3682,715],{"class":160},[143,3684,3685,3688,3690,3692,3694,3696,3698,3700,3702,3704],{"class":145,"line":229},[143,3686,3687],{"class":264},"    select",[143,3689,613],{"class":160},[143,3691,3154],{"class":164},[143,3693,301],{"class":160},[143,3695,3223],{"class":232},[143,3697,1369],{"class":264},[143,3699,301],{"class":160},[143,3701,3678],{"class":232},[143,3703,1369],{"class":264},[143,3705,715],{"class":160},[143,3707,3708,3711,3713,3715,3717,3719,3721,3723,3725,3727],{"class":145,"line":258},[143,3709,3710],{"class":264},"    insert",[143,3712,613],{"class":160},[143,3714,3154],{"class":164},[143,3716,301],{"class":160},[143,3718,3223],{"class":232},[143,3720,1369],{"class":264},[143,3722,301],{"class":160},[143,3724,3678],{"class":232},[143,3726,1369],{"class":264},[143,3728,715],{"class":160},[143,3730,3731,3734,3736,3738,3740,3742,3744,3746,3748,3750],{"class":145,"line":282},[143,3732,3733],{"class":264},"    update",[143,3735,613],{"class":160},[143,3737,3154],{"class":164},[143,3739,301],{"class":160},[143,3741,3223],{"class":232},[143,3743,1369],{"class":264},[143,3745,301],{"class":160},[143,3747,3678],{"class":232},[143,3749,1369],{"class":264},[143,3751,715],{"class":160},[143,3753,3754,3757,3759,3761,3763,3765,3767,3769,3771,3773],{"class":145,"line":321},[143,3755,3756],{"class":264},"    delete",[143,3758,613],{"class":160},[143,3760,3154],{"class":164},[143,3762,301],{"class":160},[143,3764,3223],{"class":232},[143,3766,1369],{"class":264},[143,3768,301],{"class":160},[143,3770,3678],{"class":232},[143,3772,1369],{"class":264},[143,3774,715],{"class":160},[143,3776,3777,3780,3782,3784,3786,3788,3790,3792,3794,3796],{"class":145,"line":331},[143,3778,3779],{"class":264},"    eq",[143,3781,613],{"class":160},[143,3783,3154],{"class":164},[143,3785,301],{"class":160},[143,3787,3223],{"class":232},[143,3789,1369],{"class":264},[143,3791,301],{"class":160},[143,3793,3678],{"class":232},[143,3795,1369],{"class":264},[143,3797,715],{"class":160},[143,3799,3800,3803,3805,3807,3809,3811,3813],{"class":145,"line":336},[143,3801,3802],{"class":264},"    single",[143,3804,613],{"class":160},[143,3806,3154],{"class":164},[143,3808,301],{"class":160},[143,3810,3223],{"class":232},[143,3812,1369],{"class":264},[143,3814,715],{"class":160},[143,3816,3817],{"class":145,"line":358},[143,3818,2784],{"class":160},[143,3820,3821],{"class":145,"line":391},[143,3822,781],{"class":160},[143,3824,3825],{"class":145,"line":400},[143,3826,226],{"emptyLinePlaceholder":225},[143,3828,3829],{"class":145,"line":405},[143,3830,3831],{"class":149},"\u002F\u002F 使用\n",[143,3833,3834,3837,3840,3842,3844,3846],{"class":145,"line":427},[143,3835,3836],{"class":251},"const",[143,3838,3839],{"class":634}," mockSupabase",[143,3841,638],{"class":507},[143,3843,3648],{"class":232},[143,3845,1369],{"class":164},[143,3847,196],{"class":160},[143,3849,3850,3853,3855,3858,3860,3862,3864,3866,3868,3870,3872,3874,3877,3879,3882,3884,3886],{"class":145,"line":460},[143,3851,3852],{"class":164},"mockSupabase",[143,3854,301],{"class":160},[143,3856,3857],{"class":164},"single",[143,3859,301],{"class":160},[143,3861,3436],{"class":232},[143,3863,236],{"class":164},[143,3865,3384],{"class":160},[143,3867,3443],{"class":264},[143,3869,613],{"class":160},[143,3871,2714],{"class":164},[143,3873,168],{"class":160},[143,3875,3876],{"class":264}," error",[143,3878,613],{"class":160},[143,3880,3881],{"class":997}," null",[143,3883,179],{"class":160},[143,3885,316],{"class":164},[143,3887,196],{"class":160},[42,3889],{},[11,3891,3893],{"id":3892},"pnpm-check-自動化流程","pnpm check 自動化流程",[49,3895,3896],{"id":3896},"完整檢查流程",[110,3898,3900],{"className":984,"code":3899,"language":986,"meta":118,"style":118},"{\n  \"scripts\": {\n    \"check\": \"pnpm format && pnpm lint && pnpm typecheck && pnpm test\",\n    \"format\": \"oxfmt .\",\n    \"lint\": \"oxlint --deny-warnings .\",\n    \"typecheck\": \"nuxt typecheck\",\n    \"test\": \"vitest run --coverage\"\n  }\n}\n",[34,3901,3902,3906,3918,3938,3958,3978,3998,4014,4018],{"__ignoreMap":118},[143,3903,3904],{"class":145,"line":146},[143,3905,937],{"class":160},[143,3907,3908,3910,3912,3914,3916],{"class":145,"line":153},[143,3909,998],{"class":997},[143,3911,1002],{"class":1001},[143,3913,193],{"class":997},[143,3915,613],{"class":160},[143,3917,255],{"class":160},[143,3919,3920,3922,3925,3927,3929,3931,3934,3936],{"class":145,"line":199},[143,3921,952],{"class":997},[143,3923,3924],{"class":616},"check",[143,3926,193],{"class":997},[143,3928,613],{"class":160},[143,3930,186],{"class":185},[143,3932,3933],{"class":189},"pnpm format && pnpm lint && pnpm typecheck && pnpm test",[143,3935,193],{"class":185},[143,3937,715],{"class":160},[143,3939,3940,3942,3945,3947,3949,3951,3954,3956],{"class":145,"line":222},[143,3941,952],{"class":997},[143,3943,3944],{"class":616},"format",[143,3946,193],{"class":997},[143,3948,613],{"class":160},[143,3950,186],{"class":185},[143,3952,3953],{"class":189},"oxfmt .",[143,3955,193],{"class":185},[143,3957,715],{"class":160},[143,3959,3960,3962,3965,3967,3969,3971,3974,3976],{"class":145,"line":229},[143,3961,952],{"class":997},[143,3963,3964],{"class":616},"lint",[143,3966,193],{"class":997},[143,3968,613],{"class":160},[143,3970,186],{"class":185},[143,3972,3973],{"class":189},"oxlint --deny-warnings .",[143,3975,193],{"class":185},[143,3977,715],{"class":160},[143,3979,3980,3982,3985,3987,3989,3991,3994,3996],{"class":145,"line":258},[143,3981,952],{"class":997},[143,3983,3984],{"class":616},"typecheck",[143,3986,193],{"class":997},[143,3988,613],{"class":160},[143,3990,186],{"class":185},[143,3992,3993],{"class":189},"nuxt typecheck",[143,3995,193],{"class":185},[143,3997,715],{"class":160},[143,3999,4000,4002,4004,4006,4008,4010,4012],{"class":145,"line":282},[143,4001,952],{"class":997},[143,4003,1015],{"class":616},[143,4005,193],{"class":997},[143,4007,613],{"class":160},[143,4009,186],{"class":185},[143,4011,1024],{"class":189},[143,4013,1067],{"class":185},[143,4015,4016],{"class":145,"line":321},[143,4017,1072],{"class":160},[143,4019,4020],{"class":145,"line":331},[143,4021,781],{"class":160},[49,4023,4024],{"id":4024},"流程圖",[110,4026,4029],{"className":4027,"code":4028,"language":115},[113],"pnpm check\n    │\n    ▼\n┌─────────────┐\n│ 1. format   │ → oxfmt：自動修復格式問題\n├─────────────┤\n│ 2. lint     │ → oxlint：檢查程式碼品質\n├─────────────┤\n│ 3. typecheck│ → nuxt typecheck：TypeScript 類型檢查\n├─────────────┤\n│ 4. test     │ → vitest：執行所有測試 + coverage\n└─────────────┘\n    │\n    ▼\n全部通過 → 可以 commit\n任一失敗 → 修復後重試\n",[34,4030,4028],{"__ignoreMap":118},[49,4032,4034],{"id":4033},"git-hooks-整合","Git Hooks 整合",[110,4036,4038],{"className":984,"code":4037,"language":986,"meta":118,"style":118},"\u002F\u002F package.json\n{\n  \"lint-staged\": {\n    \"*.{js,ts,vue}\": [\"oxlint --fix\", \"oxfmt\"]\n  }\n}\n",[34,4039,4040,4045,4049,4062,4094,4098],{"__ignoreMap":118},[143,4041,4042],{"class":145,"line":146},[143,4043,4044],{"class":149},"\u002F\u002F package.json\n",[143,4046,4047],{"class":145,"line":153},[143,4048,937],{"class":160},[143,4050,4051,4053,4056,4058,4060],{"class":145,"line":199},[143,4052,998],{"class":997},[143,4054,4055],{"class":1001},"lint-staged",[143,4057,193],{"class":997},[143,4059,613],{"class":160},[143,4061,255],{"class":160},[143,4063,4064,4066,4069,4071,4073,4075,4077,4080,4082,4084,4086,4089,4091],{"class":145,"line":222},[143,4065,952],{"class":997},[143,4067,4068],{"class":616},"*.{js,ts,vue}",[143,4070,193],{"class":997},[143,4072,613],{"class":160},[143,4074,3381],{"class":160},[143,4076,193],{"class":185},[143,4078,4079],{"class":189},"oxlint --fix",[143,4081,193],{"class":185},[143,4083,168],{"class":160},[143,4085,186],{"class":185},[143,4087,4088],{"class":189},"oxfmt",[143,4090,193],{"class":185},[143,4092,4093],{"class":160},"]\n",[143,4095,4096],{"class":145,"line":229},[143,4097,1072],{"class":160},[143,4099,4100],{"class":145,"line":258},[143,4101,781],{"class":160},[42,4103],{},[11,4105,4106],{"id":4106},"測試覆蓋率",[49,4108,4109],{"id":4109},"設定覆蓋率報告",[110,4111,4113],{"className":137,"code":4112,"language":139,"meta":118,"style":118},"\u002F\u002F vitest.config.ts（如果有的話）或 nuxt.config.ts\nexport default defineNuxtConfig({\n  \u002F\u002F ...\n  vitest: {\n    coverage: {\n      reporter: [\"text\", \"html\"],\n      include: [\"app\u002F**\u002F*.ts\", \"server\u002F**\u002F*.ts\"],\n      exclude: [\"**\u002F*.d.ts\", \"**\u002Ftypes\u002F**\"],\n    },\n  },\n});\n",[34,4114,4115,4120,4132,4137,4146,4155,4183,4212,4241,4246,4251],{"__ignoreMap":118},[143,4116,4117],{"class":145,"line":146},[143,4118,4119],{"class":149},"\u002F\u002F vitest.config.ts（如果有的話）或 nuxt.config.ts\n",[143,4121,4122,4124,4126,4128,4130],{"class":145,"line":153},[143,4123,599],{"class":156},[143,4125,929],{"class":156},[143,4127,932],{"class":232},[143,4129,236],{"class":164},[143,4131,937],{"class":160},[143,4133,4134],{"class":145,"line":199},[143,4135,4136],{"class":149},"  \u002F\u002F ...\n",[143,4138,4139,4142,4144],{"class":145,"line":222},[143,4140,4141],{"class":264},"  vitest",[143,4143,613],{"class":160},[143,4145,255],{"class":160},[143,4147,4148,4151,4153],{"class":145,"line":229},[143,4149,4150],{"class":264},"    coverage",[143,4152,613],{"class":160},[143,4154,255],{"class":160},[143,4156,4157,4160,4162,4164,4166,4168,4170,4172,4174,4177,4179,4181],{"class":145,"line":258},[143,4158,4159],{"class":264},"      reporter",[143,4161,613],{"class":160},[143,4163,3381],{"class":164},[143,4165,193],{"class":185},[143,4167,115],{"class":189},[143,4169,193],{"class":185},[143,4171,168],{"class":160},[143,4173,186],{"class":185},[143,4175,4176],{"class":189},"html",[143,4178,193],{"class":185},[143,4180,1937],{"class":164},[143,4182,715],{"class":160},[143,4184,4185,4188,4190,4192,4194,4197,4199,4201,4203,4206,4208,4210],{"class":145,"line":282},[143,4186,4187],{"class":264},"      include",[143,4189,613],{"class":160},[143,4191,3381],{"class":164},[143,4193,193],{"class":185},[143,4195,4196],{"class":189},"app\u002F**\u002F*.ts",[143,4198,193],{"class":185},[143,4200,168],{"class":160},[143,4202,186],{"class":185},[143,4204,4205],{"class":189},"server\u002F**\u002F*.ts",[143,4207,193],{"class":185},[143,4209,1937],{"class":164},[143,4211,715],{"class":160},[143,4213,4214,4217,4219,4221,4223,4226,4228,4230,4232,4235,4237,4239],{"class":145,"line":321},[143,4215,4216],{"class":264},"      exclude",[143,4218,613],{"class":160},[143,4220,3381],{"class":164},[143,4222,193],{"class":185},[143,4224,4225],{"class":189},"**\u002F*.d.ts",[143,4227,193],{"class":185},[143,4229,168],{"class":160},[143,4231,186],{"class":185},[143,4233,4234],{"class":189},"**\u002Ftypes\u002F**",[143,4236,193],{"class":185},[143,4238,1937],{"class":164},[143,4240,715],{"class":160},[143,4242,4243],{"class":145,"line":331},[143,4244,4245],{"class":160},"    },\n",[143,4247,4248],{"class":145,"line":336},[143,4249,4250],{"class":160},"  },\n",[143,4252,4253,4255,4257],{"class":145,"line":358},[143,4254,545],{"class":160},[143,4256,316],{"class":164},[143,4258,196],{"class":160},[49,4260,4261],{"id":4261},"覆蓋率目標",[54,4263,4264,4276],{},[57,4265,4266],{},[60,4267,4268,4270,4273],{},[63,4269,835],{},[63,4271,4272],{},"建議覆蓋率",[63,4274,4275],{},"說明",[70,4277,4278,4289,4300,4311],{},[60,4279,4280,4283,4286],{},[75,4281,4282],{},"Utils\u002FHelpers",[75,4284,4285],{},"90%+",[75,4287,4288],{},"純函式應該高覆蓋",[60,4290,4291,4294,4297],{},[75,4292,4293],{},"Services",[75,4295,4296],{},"80%+",[75,4298,4299],{},"業務邏輯核心",[60,4301,4302,4305,4308],{},[75,4303,4304],{},"Composables",[75,4306,4307],{},"70%+",[75,4309,4310],{},"視複雜度調整",[60,4312,4313,4316,4319],{},[75,4314,4315],{},"Components",[75,4317,4318],{},"60%+",[75,4320,4321],{},"重點測試互動邏輯",[42,4323],{},[11,4325,4326],{"id":4326},"踩坑經驗",[49,4328,4329],{"id":4329},"測試環境與生產環境行為不一致",[15,4331,4332,4335],{},[132,4333,4334],{},"問題","：測試通過但生產環境出錯。",[15,4337,4338,4341],{},[132,4339,4340],{},"原因","：",[4343,4344,4345,4348,4351],"ol",{},[22,4346,4347],{},"Mock 行為與實際不同",[22,4349,4350],{},"測試環境缺少某些全域設定",[22,4352,4353],{},"時區或語系差異",[15,4355,4356,4341],{},[132,4357,4358],{},"解決",[110,4360,4362],{"className":137,"code":4361,"language":139,"meta":118,"style":118},"\u002F\u002F 確保 Mock 行為與實際一致\nvi.mocked($fetch).mockImplementation(async (url, options) => {\n  \u002F\u002F 模擬真實的錯誤回應格式\n  if (url === \"\u002Fapi\u002Fv1\u002Fusers\") {\n    throw createError({\n      statusCode: 401,\n      message: \"未授權\",\n    });\n  }\n});\n",[34,4363,4364,4369,4406,4411,4434,4446,4458,4474,4482,4486],{"__ignoreMap":118},[143,4365,4366],{"class":145,"line":146},[143,4367,4368],{"class":149},"\u002F\u002F 確保 Mock 行為與實際一致\n",[143,4370,4371,4373,4375,4377,4380,4382,4385,4387,4390,4392,4395,4397,4400,4402,4404],{"class":145,"line":153},[143,4372,3185],{"class":164},[143,4374,301],{"class":160},[143,4376,3424],{"class":232},[143,4378,4379],{"class":164},"($fetch)",[143,4381,301],{"class":160},[143,4383,4384],{"class":232},"mockImplementation",[143,4386,236],{"class":164},[143,4388,4389],{"class":251},"async",[143,4391,1895],{"class":160},[143,4393,4394],{"class":609},"url",[143,4396,168],{"class":160},[143,4398,4399],{"class":609}," options",[143,4401,316],{"class":160},[143,4403,252],{"class":251},[143,4405,255],{"class":160},[143,4407,4408],{"class":145,"line":199},[143,4409,4410],{"class":149},"  \u002F\u002F 模擬真實的錯誤回應格式\n",[143,4412,4413,4416,4418,4420,4423,4425,4428,4430,4432],{"class":145,"line":222},[143,4414,4415],{"class":156},"  if",[143,4417,1895],{"class":264},[143,4419,4394],{"class":164},[143,4421,4422],{"class":507}," ===",[143,4424,186],{"class":185},[143,4426,4427],{"class":189},"\u002Fapi\u002Fv1\u002Fusers",[143,4429,193],{"class":185},[143,4431,1907],{"class":264},[143,4433,937],{"class":160},[143,4435,4436,4439,4442,4444],{"class":145,"line":229},[143,4437,4438],{"class":156},"    throw",[143,4440,4441],{"class":232}," createError",[143,4443,236],{"class":264},[143,4445,937],{"class":160},[143,4447,4448,4451,4453,4456],{"class":145,"line":258},[143,4449,4450],{"class":264},"      statusCode",[143,4452,613],{"class":160},[143,4454,4455],{"class":294}," 401",[143,4457,715],{"class":160},[143,4459,4460,4463,4465,4467,4470,4472],{"class":145,"line":282},[143,4461,4462],{"class":264},"      message",[143,4464,613],{"class":160},[143,4466,186],{"class":185},[143,4468,4469],{"class":189},"未授權",[143,4471,193],{"class":185},[143,4473,715],{"class":160},[143,4475,4476,4478,4480],{"class":145,"line":321},[143,4477,2362],{"class":160},[143,4479,316],{"class":264},[143,4481,196],{"class":160},[143,4483,4484],{"class":145,"line":331},[143,4485,1072],{"class":160},[143,4487,4488,4490,4492],{"class":145,"line":336},[143,4489,545],{"class":160},[143,4491,316],{"class":164},[143,4493,196],{"class":160},[49,4495,4496],{"id":4496},"非同步測試超時",[15,4498,4499,4501],{},[132,4500,4334],{},"：測試偶爾超時失敗。",[15,4503,4504,4341],{},[132,4505,4358],{},[110,4507,4509],{"className":137,"code":4508,"language":139,"meta":118,"style":118},"\u002F\u002F 設定適當的超時時間\nit(\n  \"should fetch data\",\n  async () => {\n    \u002F\u002F ...\n  },\n  { timeout: 10000 },\n); \u002F\u002F 10 秒超時\n\n\u002F\u002F 或全域設定\n\u002F\u002F vitest.config.ts\nexport default defineConfig({\n  test: {\n    testTimeout: 10000,\n  },\n});\n",[34,4510,4511,4516,4524,4535,4546,4551,4555,4570,4579,4583,4588,4593,4606,4615,4626,4630],{"__ignoreMap":118},[143,4512,4513],{"class":145,"line":146},[143,4514,4515],{"class":149},"\u002F\u002F 設定適當的超時時間\n",[143,4517,4518,4521],{"class":145,"line":153},[143,4519,4520],{"class":232},"it",[143,4522,4523],{"class":164},"(\n",[143,4525,4526,4528,4531,4533],{"class":145,"line":199},[143,4527,998],{"class":185},[143,4529,4530],{"class":189},"should fetch data",[143,4532,193],{"class":185},[143,4534,715],{"class":160},[143,4536,4537,4540,4542,4544],{"class":145,"line":222},[143,4538,4539],{"class":251},"  async",[143,4541,248],{"class":160},[143,4543,252],{"class":251},[143,4545,255],{"class":160},[143,4547,4548],{"class":145,"line":229},[143,4549,4550],{"class":149},"    \u002F\u002F ...\n",[143,4552,4553],{"class":145,"line":258},[143,4554,4250],{"class":160},[143,4556,4557,4560,4563,4565,4568],{"class":145,"line":282},[143,4558,4559],{"class":160},"  {",[143,4561,4562],{"class":264}," timeout",[143,4564,613],{"class":160},[143,4566,4567],{"class":294}," 10000",[143,4569,2850],{"class":160},[143,4571,4572,4574,4576],{"class":145,"line":321},[143,4573,316],{"class":164},[143,4575,1278],{"class":160},[143,4577,4578],{"class":149}," \u002F\u002F 10 秒超時\n",[143,4580,4581],{"class":145,"line":331},[143,4582,226],{"emptyLinePlaceholder":225},[143,4584,4585],{"class":145,"line":336},[143,4586,4587],{"class":149},"\u002F\u002F 或全域設定\n",[143,4589,4590],{"class":145,"line":358},[143,4591,4592],{"class":149},"\u002F\u002F vitest.config.ts\n",[143,4594,4595,4597,4599,4602,4604],{"class":145,"line":391},[143,4596,599],{"class":156},[143,4598,929],{"class":156},[143,4600,4601],{"class":232}," defineConfig",[143,4603,236],{"class":164},[143,4605,937],{"class":160},[143,4607,4608,4611,4613],{"class":145,"line":400},[143,4609,4610],{"class":264},"  test",[143,4612,613],{"class":160},[143,4614,255],{"class":160},[143,4616,4617,4620,4622,4624],{"class":145,"line":405},[143,4618,4619],{"class":264},"    testTimeout",[143,4621,613],{"class":160},[143,4623,4567],{"class":294},[143,4625,715],{"class":160},[143,4627,4628],{"class":145,"line":427},[143,4629,4250],{"class":160},[143,4631,4632,4634,4636],{"class":145,"line":460},[143,4633,545],{"class":160},[143,4635,316],{"class":164},[143,4637,196],{"class":160},[49,4639,4640],{"id":4640},"測試相互影響",[15,4642,4643,4645],{},[132,4644,4334],{},"：測試單獨跑通過，一起跑失敗。",[15,4647,4648,4650],{},[132,4649,4340],{},"：測試之間共享狀態。",[15,4652,4653,4341],{},[132,4654,4358],{},[110,4656,4658],{"className":137,"code":4657,"language":139,"meta":118,"style":118},"import { beforeEach, afterEach } from \"vitest\";\n\nbeforeEach(() => {\n  \u002F\u002F 每個測試前重置狀態\n  vi.clearAllMocks();\n});\n\nafterEach(() => {\n  \u002F\u002F 每個測試後清理\n  vi.restoreAllMocks();\n});\n",[34,4659,4660,4685,4689,4702,4707,4720,4728,4732,4745,4750,4763],{"__ignoreMap":118},[143,4661,4662,4664,4666,4668,4670,4673,4675,4677,4679,4681,4683],{"class":145,"line":146},[143,4663,157],{"class":156},[143,4665,161],{"class":160},[143,4667,3159],{"class":164},[143,4669,168],{"class":160},[143,4671,4672],{"class":164}," afterEach",[143,4674,179],{"class":160},[143,4676,182],{"class":156},[143,4678,186],{"class":185},[143,4680,190],{"class":189},[143,4682,193],{"class":185},[143,4684,196],{"class":160},[143,4686,4687],{"class":145,"line":153},[143,4688,226],{"emptyLinePlaceholder":225},[143,4690,4691,4694,4696,4698,4700],{"class":145,"line":199},[143,4692,4693],{"class":232},"beforeEach",[143,4695,236],{"class":164},[143,4697,1369],{"class":160},[143,4699,252],{"class":251},[143,4701,255],{"class":160},[143,4703,4704],{"class":145,"line":222},[143,4705,4706],{"class":149},"  \u002F\u002F 每個測試前重置狀態\n",[143,4708,4709,4712,4714,4716,4718],{"class":145,"line":229},[143,4710,4711],{"class":164},"  vi",[143,4713,301],{"class":160},[143,4715,3330],{"class":232},[143,4717,1369],{"class":264},[143,4719,196],{"class":160},[143,4721,4722,4724,4726],{"class":145,"line":258},[143,4723,545],{"class":160},[143,4725,316],{"class":164},[143,4727,196],{"class":160},[143,4729,4730],{"class":145,"line":282},[143,4731,226],{"emptyLinePlaceholder":225},[143,4733,4734,4737,4739,4741,4743],{"class":145,"line":321},[143,4735,4736],{"class":232},"afterEach",[143,4738,236],{"class":164},[143,4740,1369],{"class":160},[143,4742,252],{"class":251},[143,4744,255],{"class":160},[143,4746,4747],{"class":145,"line":331},[143,4748,4749],{"class":149},"  \u002F\u002F 每個測試後清理\n",[143,4751,4752,4754,4756,4759,4761],{"class":145,"line":336},[143,4753,4711],{"class":164},[143,4755,301],{"class":160},[143,4757,4758],{"class":232},"restoreAllMocks",[143,4760,1369],{"class":264},[143,4762,196],{"class":160},[143,4764,4765,4767,4769],{"class":145,"line":358},[143,4766,545],{"class":160},[143,4768,316],{"class":164},[143,4770,196],{"class":160},[42,4772],{},[11,4774,4776],{"id":4775},"測試-dos-and-donts","測試 Do's and Don'ts",[49,4778,4780],{"id":4779},"dos","✅ Do's",[110,4782,4784],{"className":137,"code":4783,"language":139,"meta":118,"style":118},"\u002F\u002F 1. 測試行為，而非實作細節\nit(\"should display error message when login fails\", () => {\n  \u002F\u002F 測試使用者看到的結果\n});\n\n\u002F\u002F 2. 使用有意義的測試名稱\nit(\"should return empty array when no users match filter\", () => {});\n\n\u002F\u002F 3. 每個測試只測試一件事\nit(\"should validate email format\", () => {\n  expect(validateEmail(\"invalid\")).toBe(false);\n});\n\nit(\"should accept valid email\", () => {\n  expect(validateEmail(\"test@example.com\")).toBe(true);\n});\n\n\u002F\u002F 4. 測試邊界案例\ndescribe(\"pagination\", () => {\n  it(\"should handle empty data\", () => {});\n  it(\"should handle single page\", () => {});\n  it(\"should handle last page\", () => {});\n});\n",[34,4785,4786,4791,4812,4817,4825,4829,4834,4860,4864,4869,4890,4922,4930,4934,4955,4986,4994,4998,5003,5024,5049,5074,5099],{"__ignoreMap":118},[143,4787,4788],{"class":145,"line":146},[143,4789,4790],{"class":149},"\u002F\u002F 1. 測試行為，而非實作細節\n",[143,4792,4793,4795,4797,4799,4802,4804,4806,4808,4810],{"class":145,"line":153},[143,4794,4520],{"class":232},[143,4796,236],{"class":164},[143,4798,193],{"class":185},[143,4800,4801],{"class":189},"should display error message when login fails",[143,4803,193],{"class":185},[143,4805,168],{"class":160},[143,4807,248],{"class":160},[143,4809,252],{"class":251},[143,4811,255],{"class":160},[143,4813,4814],{"class":145,"line":199},[143,4815,4816],{"class":149},"  \u002F\u002F 測試使用者看到的結果\n",[143,4818,4819,4821,4823],{"class":145,"line":222},[143,4820,545],{"class":160},[143,4822,316],{"class":164},[143,4824,196],{"class":160},[143,4826,4827],{"class":145,"line":229},[143,4828,226],{"emptyLinePlaceholder":225},[143,4830,4831],{"class":145,"line":258},[143,4832,4833],{"class":149},"\u002F\u002F 2. 使用有意義的測試名稱\n",[143,4835,4836,4838,4840,4842,4845,4847,4849,4851,4853,4856,4858],{"class":145,"line":282},[143,4837,4520],{"class":232},[143,4839,236],{"class":164},[143,4841,193],{"class":185},[143,4843,4844],{"class":189},"should return empty array when no users match filter",[143,4846,193],{"class":185},[143,4848,168],{"class":160},[143,4850,248],{"class":160},[143,4852,252],{"class":251},[143,4854,4855],{"class":160}," {}",[143,4857,316],{"class":164},[143,4859,196],{"class":160},[143,4861,4862],{"class":145,"line":321},[143,4863,226],{"emptyLinePlaceholder":225},[143,4865,4866],{"class":145,"line":331},[143,4867,4868],{"class":149},"\u002F\u002F 3. 每個測試只測試一件事\n",[143,4870,4871,4873,4875,4877,4880,4882,4884,4886,4888],{"class":145,"line":336},[143,4872,4520],{"class":232},[143,4874,236],{"class":164},[143,4876,193],{"class":185},[143,4878,4879],{"class":189},"should validate email format",[143,4881,193],{"class":185},[143,4883,168],{"class":160},[143,4885,248],{"class":160},[143,4887,252],{"class":251},[143,4889,255],{"class":160},[143,4891,4892,4895,4897,4900,4902,4904,4906,4908,4910,4912,4914,4916,4918,4920],{"class":145,"line":358},[143,4893,4894],{"class":232},"  expect",[143,4896,236],{"class":264},[143,4898,4899],{"class":232},"validateEmail",[143,4901,236],{"class":264},[143,4903,193],{"class":185},[143,4905,1357],{"class":189},[143,4907,193],{"class":185},[143,4909,298],{"class":264},[143,4911,301],{"class":160},[143,4913,304],{"class":232},[143,4915,236],{"class":264},[143,4917,1882],{"class":1736},[143,4919,316],{"class":264},[143,4921,196],{"class":160},[143,4923,4924,4926,4928],{"class":145,"line":391},[143,4925,545],{"class":160},[143,4927,316],{"class":164},[143,4929,196],{"class":160},[143,4931,4932],{"class":145,"line":400},[143,4933,226],{"emptyLinePlaceholder":225},[143,4935,4936,4938,4940,4942,4945,4947,4949,4951,4953],{"class":145,"line":405},[143,4937,4520],{"class":232},[143,4939,236],{"class":164},[143,4941,193],{"class":185},[143,4943,4944],{"class":189},"should accept valid email",[143,4946,193],{"class":185},[143,4948,168],{"class":160},[143,4950,248],{"class":160},[143,4952,252],{"class":251},[143,4954,255],{"class":160},[143,4956,4957,4959,4961,4963,4965,4967,4970,4972,4974,4976,4978,4980,4982,4984],{"class":145,"line":427},[143,4958,4894],{"class":232},[143,4960,236],{"class":264},[143,4962,4899],{"class":232},[143,4964,236],{"class":264},[143,4966,193],{"class":185},[143,4968,4969],{"class":189},"test@example.com",[143,4971,193],{"class":185},[143,4973,298],{"class":264},[143,4975,301],{"class":160},[143,4977,304],{"class":232},[143,4979,236],{"class":264},[143,4981,1737],{"class":1736},[143,4983,316],{"class":264},[143,4985,196],{"class":160},[143,4987,4988,4990,4992],{"class":145,"line":460},[143,4989,545],{"class":160},[143,4991,316],{"class":164},[143,4993,196],{"class":160},[143,4995,4996],{"class":145,"line":469},[143,4997,226],{"emptyLinePlaceholder":225},[143,4999,5000],{"class":145,"line":474},[143,5001,5002],{"class":149},"\u002F\u002F 4. 測試邊界案例\n",[143,5004,5005,5007,5009,5011,5014,5016,5018,5020,5022],{"class":145,"line":496},[143,5006,233],{"class":232},[143,5008,236],{"class":164},[143,5010,193],{"class":185},[143,5012,5013],{"class":189},"pagination",[143,5015,193],{"class":185},[143,5017,168],{"class":160},[143,5019,248],{"class":160},[143,5021,252],{"class":251},[143,5023,255],{"class":160},[143,5025,5026,5028,5030,5032,5035,5037,5039,5041,5043,5045,5047],{"class":145,"line":533},[143,5027,261],{"class":232},[143,5029,236],{"class":264},[143,5031,193],{"class":185},[143,5033,5034],{"class":189},"should handle empty data",[143,5036,193],{"class":185},[143,5038,168],{"class":160},[143,5040,248],{"class":160},[143,5042,252],{"class":251},[143,5044,4855],{"class":160},[143,5046,316],{"class":264},[143,5048,196],{"class":160},[143,5050,5051,5053,5055,5057,5060,5062,5064,5066,5068,5070,5072],{"class":145,"line":542},[143,5052,261],{"class":232},[143,5054,236],{"class":264},[143,5056,193],{"class":185},[143,5058,5059],{"class":189},"should handle single page",[143,5061,193],{"class":185},[143,5063,168],{"class":160},[143,5065,248],{"class":160},[143,5067,252],{"class":251},[143,5069,4855],{"class":160},[143,5071,316],{"class":264},[143,5073,196],{"class":160},[143,5075,5076,5078,5080,5082,5085,5087,5089,5091,5093,5095,5097],{"class":145,"line":1495},[143,5077,261],{"class":232},[143,5079,236],{"class":264},[143,5081,193],{"class":185},[143,5083,5084],{"class":189},"should handle last page",[143,5086,193],{"class":185},[143,5088,168],{"class":160},[143,5090,248],{"class":160},[143,5092,252],{"class":251},[143,5094,4855],{"class":160},[143,5096,316],{"class":264},[143,5098,196],{"class":160},[143,5100,5101,5103,5105],{"class":145,"line":1504},[143,5102,545],{"class":160},[143,5104,316],{"class":164},[143,5106,196],{"class":160},[49,5108,5110],{"id":5109},"donts","❌ Don'ts",[110,5112,5114],{"className":137,"code":5113,"language":139,"meta":118,"style":118},"\u002F\u002F 1. 不要測試框架本身\nit(\"should render component\", () => {\n  \u002F\u002F Vue 會渲染元件，不需要測試這個\n});\n\n\u002F\u002F 2. 不要測試私有方法\n\u002F\u002F 私有方法會透過公開方法被測試到\n\n\u002F\u002F 3. 不要過度 mock\n\u002F\u002F Mock 太多會讓測試失去意義\n\n\u002F\u002F 4. 不要忽略測試\nit.skip(\"should work\", () => {}); \u002F\u002F ❌ 禁止\n",[34,5115,5116,5121,5142,5147,5155,5159,5164,5169,5173,5178,5183,5187,5192],{"__ignoreMap":118},[143,5117,5118],{"class":145,"line":146},[143,5119,5120],{"class":149},"\u002F\u002F 1. 不要測試框架本身\n",[143,5122,5123,5125,5127,5129,5132,5134,5136,5138,5140],{"class":145,"line":153},[143,5124,4520],{"class":232},[143,5126,236],{"class":164},[143,5128,193],{"class":185},[143,5130,5131],{"class":189},"should render component",[143,5133,193],{"class":185},[143,5135,168],{"class":160},[143,5137,248],{"class":160},[143,5139,252],{"class":251},[143,5141,255],{"class":160},[143,5143,5144],{"class":145,"line":199},[143,5145,5146],{"class":149},"  \u002F\u002F Vue 會渲染元件，不需要測試這個\n",[143,5148,5149,5151,5153],{"class":145,"line":222},[143,5150,545],{"class":160},[143,5152,316],{"class":164},[143,5154,196],{"class":160},[143,5156,5157],{"class":145,"line":229},[143,5158,226],{"emptyLinePlaceholder":225},[143,5160,5161],{"class":145,"line":258},[143,5162,5163],{"class":149},"\u002F\u002F 2. 不要測試私有方法\n",[143,5165,5166],{"class":145,"line":282},[143,5167,5168],{"class":149},"\u002F\u002F 私有方法會透過公開方法被測試到\n",[143,5170,5171],{"class":145,"line":321},[143,5172,226],{"emptyLinePlaceholder":225},[143,5174,5175],{"class":145,"line":331},[143,5176,5177],{"class":149},"\u002F\u002F 3. 不要過度 mock\n",[143,5179,5180],{"class":145,"line":336},[143,5181,5182],{"class":149},"\u002F\u002F Mock 太多會讓測試失去意義\n",[143,5184,5185],{"class":145,"line":358},[143,5186,226],{"emptyLinePlaceholder":225},[143,5188,5189],{"class":145,"line":391},[143,5190,5191],{"class":149},"\u002F\u002F 4. 不要忽略測試\n",[143,5193,5194,5196,5198,5201,5203,5205,5208,5210,5212,5214,5216,5218,5220,5222],{"class":145,"line":400},[143,5195,4520],{"class":164},[143,5197,301],{"class":160},[143,5199,5200],{"class":232},"skip",[143,5202,236],{"class":164},[143,5204,193],{"class":185},[143,5206,5207],{"class":189},"should work",[143,5209,193],{"class":185},[143,5211,168],{"class":160},[143,5213,248],{"class":160},[143,5215,252],{"class":251},[143,5217,4855],{"class":160},[143,5219,316],{"class":164},[143,5221,1278],{"class":160},[143,5223,5224],{"class":149}," \u002F\u002F ❌ 禁止\n",[42,5226],{},[11,5228,5229],{"id":5229},"檢查清單",[15,5231,5232],{},"每次實作功能時確認：",[19,5234,5237,5245,5251,5257,5263,5269,5282],{"className":5235},[5236],"contains-task-list",[22,5238,5241,5244],{"className":5239},[5240],"task-list-item",[1708,5242],{"disabled":225,"type":5243},"checkbox"," 先寫測試（Red）",[22,5246,5248,5250],{"className":5247},[5240],[1708,5249],{"disabled":225,"type":5243}," 測試失敗訊息清楚",[22,5252,5254,5256],{"className":5253},[5240],[1708,5255],{"disabled":225,"type":5243}," 最小實作讓測試通過（Green）",[22,5258,5260,5262],{"className":5259},[5240],[1708,5261],{"disabled":225,"type":5243}," 重構後測試仍通過",[22,5264,5266,5268],{"className":5265},[5240],[1708,5267],{"disabled":225,"type":5243}," 涵蓋成功、失敗、邊界案例",[22,5270,5272,5274,5275,5278,5279],{"className":5271},[5240],[1708,5273],{"disabled":225,"type":5243}," 無 ",[34,5276,5277],{},"it.skip"," 或 ",[34,5280,5281],{},"describe.skip",[22,5283,5285,5287,5288,5290],{"className":5284},[5240],[1708,5286],{"disabled":225,"type":5243}," ",[34,5289,36],{}," 通過",[42,5292],{},[11,5294,5295],{"id":5295},"最佳實踐總結",[4343,5297,5298,5304,5310,5316,5324,5330],{},[22,5299,5300,5303],{},[132,5301,5302],{},"先寫測試","：TDD 不是「之後補測試」",[22,5305,5306,5309],{},[132,5307,5308],{},"測試關鍵邏輯","：不需要 100% coverage，但核心邏輯必須測試",[22,5311,5312,5315],{},[132,5313,5314],{},"快速回饋","：純函式用單元測試（Node 環境），速度更快",[22,5317,5318,4341,5321,5323],{},[132,5319,5320],{},"自動化檢查",[34,5322,36],{}," 整合 format、lint、typecheck、test",[22,5325,5326,5329],{},[132,5327,5328],{},"持續綠燈","：測試失敗時立即修復，不要累積",[22,5331,5332,4341,5335,5337],{},[132,5333,5334],{},"禁止跳過測試",[34,5336,5277],{}," 代表技術債",[42,5339],{},[11,5341,5342],{"id":5342},"延伸閱讀",[19,5344,5345,5354,5361,5368,5375],{},[22,5346,5347],{},[5348,5349,5353],"a",{"href":5350,"rel":5351},"https:\u002F\u002Fvitest.dev\u002F",[5352],"nofollow","Vitest 文件",[22,5355,5356],{},[5348,5357,5360],{"href":5358,"rel":5359},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Ftesting",[5352],"@nuxt\u002Ftest-utils 文件",[22,5362,5363],{},[5348,5364,5367],{"href":5365,"rel":5366},"https:\u002F\u002Ftest-utils.vuejs.org\u002F",[5352],"Vue Test Utils",[22,5369,5370,5371],{},"上一篇：",[5348,5372,5374],{"href":5373},"\u002Fblog\u002Fnuxt\u002Fpinia-colada-async-state","Pinia Colada 非同步狀態管理",[22,5376,5377,5378],{},"下一篇：",[5348,5379,5381],{"href":5380},"\u002Fblog\u002Fnuxt\u002Fai-assisted-claude-md","AI 輔助開發與 CLAUDE.md",[5383,5384,5385],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sseR_, html code.shiki .sseR_{--shiki-light:#9C3EDA;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":118,"searchDepth":199,"depth":199,"links":5387},[5388,5389,5393,5396,5400,5404,5408,5412,5416,5421,5425,5430,5434,5435,5436],{"id":13,"depth":153,"text":13},{"id":46,"depth":153,"text":47,"children":5390},[5391,5392],{"id":51,"depth":199,"text":52},{"id":107,"depth":199,"text":108},{"id":123,"depth":153,"text":124,"children":5394},[5395],{"id":127,"depth":199,"text":128},{"id":816,"depth":153,"text":816,"children":5397},[5398,5399],{"id":825,"depth":199,"text":826},{"id":890,"depth":199,"text":890},{"id":901,"depth":153,"text":902,"children":5401},[5402,5403],{"id":905,"depth":199,"text":905},{"id":980,"depth":199,"text":981},{"id":1081,"depth":153,"text":1081,"children":5405},[5406,5407],{"id":1084,"depth":199,"text":1084},{"id":1513,"depth":199,"text":1514},{"id":2140,"depth":153,"text":2141,"children":5409},[5410,5411],{"id":2144,"depth":199,"text":2145},{"id":2605,"depth":199,"text":2605},{"id":3118,"depth":153,"text":3119,"children":5413},[5414,5415],{"id":3122,"depth":199,"text":3123},{"id":3628,"depth":199,"text":3629},{"id":3892,"depth":153,"text":3893,"children":5417},[5418,5419,5420],{"id":3896,"depth":199,"text":3896},{"id":4024,"depth":199,"text":4024},{"id":4033,"depth":199,"text":4034},{"id":4106,"depth":153,"text":4106,"children":5422},[5423,5424],{"id":4109,"depth":199,"text":4109},{"id":4261,"depth":199,"text":4261},{"id":4326,"depth":153,"text":4326,"children":5426},[5427,5428,5429],{"id":4329,"depth":199,"text":4329},{"id":4496,"depth":199,"text":4496},{"id":4640,"depth":199,"text":4640},{"id":4775,"depth":153,"text":4776,"children":5431},[5432,5433],{"id":4779,"depth":199,"text":4780},{"id":5109,"depth":199,"text":5110},{"id":5229,"depth":153,"text":5229},{"id":5295,"depth":153,"text":5295},{"id":5342,"depth":153,"text":5342},"2026-01-22","在 Nuxt 4 專案中實踐 TDD 開發流程，涵蓋 Vitest 設定、單元測試與 Nuxt 環境測試。",false,"md",null,{},"\u002Fblog\u002Fnuxt\u002Ftdd-testing-workflow",{"title":5,"description":5438},"nuxt-fullstack","Nuxt 4 全棧實戰筆記","blog\u002Fnuxt\u002Ftdd-testing-workflow\u002Findex",[881,5449,5450],"TDD","Vitest","au-ZPWEVcXeAY76iL3AhI3IoRQKQq1M7zrAdTcbijMw",1780512499606]