[{"data":1,"prerenderedAt":1807},["ShallowReactive",2],{"note:code\u002Fvue\u002F10-testing-debugging":3},{"id":4,"title":5,"body":6,"description":16,"extension":1801,"meta":1802,"navigation":350,"path":1803,"seo":1804,"stem":1805,"__hash__":1806},"notes\u002Fsource\u002Fcode\u002Fvue\u002F10-testing-debugging.md","10 测试、调试和质量保障",{"type":7,"value":8,"toc":1781},"minimark",[9,13,17,20,25,28,39,42,65,71,74,80,87,102,111,114,125,163,167,170,227,230,234,237,240,260,263,274,281,285,288,294,399,402,542,545,549,552,607,610,662,665,749,753,756,1020,1026,1030,1033,1207,1217,1221,1224,1404,1407,1465,1468,1479,1483,1486,1489,1503,1506,1517,1521,1524,1545,1548,1568,1571,1575,1579,1582,1601,1604,1606,1626,1629,1631,1646,1649,1651,1684,1690,1694,1697,1714,1717,1726,1729,1743,1746,1777],[10,11,5],"h1",{"id":12},"_10-测试调试和质量保障",[14,15,16],"p",{},"测试不是为了追求形式，而是为了让你修改代码时知道有没有把旧功能弄坏。调试也不是靠猜，而是有步骤地定位问题。",[14,18,19],{},"本章目标：你能读懂常见报错，能用 Vue DevTools 和控制台观察状态，能用 Vitest 测试组合式函数和组件事件。",[21,22,24],"h2",{"id":23},"_1-先学会读报错","1. 先学会读报错",[14,26,27],{},"报错一般包含几类信息：",[29,30,36],"pre",{"className":31,"code":33,"language":34,"meta":35},[32],"language-text","ReferenceError: count is not defined\n    at Counter.vue:12:5\n","text","",[37,38,33],"code",{"__ignoreMap":35},[14,40,41],{},"你要看：",[43,44,45,53,59],"ul",{},[46,47,48,49,52],"li",{},"错误类型：",[37,50,51],{},"ReferenceError","。",[46,54,55,56,52],{},"错误内容：",[37,57,58],{},"count is not defined",[46,60,61,62,52],{},"文件和行号：",[37,63,64],{},"Counter.vue:12:5",[14,66,67,68,52],{},"这句话意思是：第 12 行用了一个不存在的变量 ",[37,69,70],{},"count",[14,72,73],{},"常见错误：",[29,75,78],{"className":76,"code":77,"language":34,"meta":35},[32],"Cannot read properties of undefined\n",[37,79,77],{"__ignoreMap":35},[14,81,82,83,86],{},"意思是你在 ",[37,84,85],{},"undefined"," 上读属性，例如：",[29,88,92],{"className":89,"code":90,"language":91,"meta":35,"style":35},"language-js shiki shiki-themes github-light github-dark","user.name\n","js",[37,93,94],{"__ignoreMap":35},[95,96,99],"span",{"class":97,"line":98},"line",1,[95,100,90],{"class":101},"sVt8B",[14,103,104,105,108,109,52],{},"但 ",[37,106,107],{},"user"," 是 ",[37,110,85],{},[14,112,113],{},"修复思路：",[43,115,116,119,122],{},[46,117,118],{},"检查数据是否还没加载。",[46,120,121],{},"给初始值。",[46,123,124],{},"用条件渲染。",[29,126,130],{"className":127,"code":128,"language":129,"meta":35,"style":35},"language-vue shiki shiki-themes github-light github-dark","\u003Cp v-if=\"user\">{{ user.name }}\u003C\u002Fp>\n","vue",[37,131,132],{"__ignoreMap":35},[95,133,134,137,140,144,147,151,153,155,158,160],{"class":97,"line":98},[95,135,136],{"class":101},"\u003C",[95,138,14],{"class":139},"s9eBZ",[95,141,143],{"class":142},"szBVR"," v-if",[95,145,146],{"class":101},"=",[95,148,150],{"class":149},"sZZnC","\"",[95,152,107],{"class":101},[95,154,150],{"class":149},[95,156,157],{"class":101},">{{ user.name }}\u003C\u002F",[95,159,14],{"class":139},[95,161,162],{"class":101},">\n",[21,164,166],{"id":165},"_2-调试的基本流程","2. 调试的基本流程",[14,168,169],{},"遇到问题不要马上乱改。按步骤：",[171,172,173,180,186,192,198,204,221],"ol",{},[46,174,175,179],{},[176,177,178],"strong",{},"复现问题","：能稳定触发吗？",[46,181,182,185],{},[176,183,184],{},"缩小范围","：是页面没进入、数据没来、事件没触发，还是渲染条件错？",[46,187,188,191],{},[176,189,190],{},"观察状态","：用 Vue DevTools 看组件 state、props、Pinia。",[46,193,194,197],{},[176,195,196],{},"检查事件","：按钮点击函数有没有执行？",[46,199,200,203],{},[176,201,202],{},"检查数据变化","：状态有没有真的改变？",[46,205,206,209,210,213,214,213,217,220],{},[176,207,208],{},"检查模板条件","：",[37,211,212],{},"v-if","、",[37,215,216],{},"v-for",[37,218,219],{},"key"," 有没有写错？",[46,222,223,226],{},[176,224,225],{},"修复后验证","：重新跑同样操作。",[14,228,229],{},"调试不是“试试这个、试试那个”，而是不断缩小问题范围。",[21,231,233],{"id":232},"_3-vue-devtools-能看什么","3. Vue DevTools 能看什么",[14,235,236],{},"推荐安装 Vue 官方 DevTools 浏览器扩展。",[14,238,239],{},"它能看：",[43,241,242,245,248,251,254,257],{},[46,243,244],{},"组件树。",[46,246,247],{},"当前组件 props。",[46,249,250],{},"当前组件 state。",[46,252,253],{},"computed 值。",[46,255,256],{},"Pinia store。",[46,258,259],{},"事件。",[14,261,262],{},"例如子组件没有显示数据，先看：",[43,264,265,268,271],{},[46,266,267],{},"父组件是否传了 prop？",[46,269,270],{},"子组件 props 面板里有没有收到？",[46,272,273],{},"prop 名是否拼错？",[14,275,276,277,280],{},"这比到处 ",[37,278,279],{},"console.log"," 更清楚。",[21,282,284],{"id":283},"_4-什么是测试","4. 什么是测试",[14,286,287],{},"测试就是用代码验证另一个代码的行为。",[14,289,290,291,209],{},"例如你写了 ",[37,292,293],{},"useCounter",[29,295,297],{"className":89,"code":296,"language":91,"meta":35,"style":35},"export function useCounter(initialValue = 0) {\n  const count = ref(initialValue)\n\n  function increase() {\n    count.value++\n  }\n\n  return { count, increase }\n}\n",[37,298,299,328,345,352,364,373,379,384,393],{"__ignoreMap":35},[95,300,301,304,307,311,314,318,321,325],{"class":97,"line":98},[95,302,303],{"class":142},"export",[95,305,306],{"class":142}," function",[95,308,310],{"class":309},"sScJk"," useCounter",[95,312,313],{"class":101},"(",[95,315,317],{"class":316},"s4XuR","initialValue",[95,319,320],{"class":142}," =",[95,322,324],{"class":323},"sj4cs"," 0",[95,326,327],{"class":101},") {\n",[95,329,331,334,337,339,342],{"class":97,"line":330},2,[95,332,333],{"class":142},"  const",[95,335,336],{"class":323}," count",[95,338,320],{"class":142},[95,340,341],{"class":309}," ref",[95,343,344],{"class":101},"(initialValue)\n",[95,346,348],{"class":97,"line":347},3,[95,349,351],{"emptyLinePlaceholder":350},true,"\n",[95,353,355,358,361],{"class":97,"line":354},4,[95,356,357],{"class":142},"  function",[95,359,360],{"class":309}," increase",[95,362,363],{"class":101},"() {\n",[95,365,367,370],{"class":97,"line":366},5,[95,368,369],{"class":101},"    count.value",[95,371,372],{"class":142},"++\n",[95,374,376],{"class":97,"line":375},6,[95,377,378],{"class":101},"  }\n",[95,380,382],{"class":97,"line":381},7,[95,383,351],{"emptyLinePlaceholder":350},[95,385,387,390],{"class":97,"line":386},8,[95,388,389],{"class":142},"  return",[95,391,392],{"class":101}," { count, increase }\n",[95,394,396],{"class":97,"line":395},9,[95,397,398],{"class":101},"}\n",[14,400,401],{},"测试：",[29,403,405],{"className":89,"code":404,"language":91,"meta":35,"style":35},"import { describe, expect, it } from 'vitest'\nimport { useCounter } from '..\u002Fcomposables\u002FuseCounter'\n\ndescribe('useCounter', () => {\n  it('increases count', () => {\n    const counter = useCounter(1)\n\n    counter.increase()\n\n    expect(counter.count.value).toBe(2)\n  })\n})\n",[37,406,407,421,433,437,456,472,492,496,507,511,530,536],{"__ignoreMap":35},[95,408,409,412,415,418],{"class":97,"line":98},[95,410,411],{"class":142},"import",[95,413,414],{"class":101}," { describe, expect, it } ",[95,416,417],{"class":142},"from",[95,419,420],{"class":149}," 'vitest'\n",[95,422,423,425,428,430],{"class":97,"line":330},[95,424,411],{"class":142},[95,426,427],{"class":101}," { useCounter } ",[95,429,417],{"class":142},[95,431,432],{"class":149}," '..\u002Fcomposables\u002FuseCounter'\n",[95,434,435],{"class":97,"line":347},[95,436,351],{"emptyLinePlaceholder":350},[95,438,439,442,444,447,450,453],{"class":97,"line":354},[95,440,441],{"class":309},"describe",[95,443,313],{"class":101},[95,445,446],{"class":149},"'useCounter'",[95,448,449],{"class":101},", () ",[95,451,452],{"class":142},"=>",[95,454,455],{"class":101}," {\n",[95,457,458,461,463,466,468,470],{"class":97,"line":366},[95,459,460],{"class":309},"  it",[95,462,313],{"class":101},[95,464,465],{"class":149},"'increases count'",[95,467,449],{"class":101},[95,469,452],{"class":142},[95,471,455],{"class":101},[95,473,474,477,480,482,484,486,489],{"class":97,"line":375},[95,475,476],{"class":142},"    const",[95,478,479],{"class":323}," counter",[95,481,320],{"class":142},[95,483,310],{"class":309},[95,485,313],{"class":101},[95,487,488],{"class":323},"1",[95,490,491],{"class":101},")\n",[95,493,494],{"class":97,"line":381},[95,495,351],{"emptyLinePlaceholder":350},[95,497,498,501,504],{"class":97,"line":386},[95,499,500],{"class":101},"    counter.",[95,502,503],{"class":309},"increase",[95,505,506],{"class":101},"()\n",[95,508,509],{"class":97,"line":395},[95,510,351],{"emptyLinePlaceholder":350},[95,512,514,517,520,523,525,528],{"class":97,"line":513},10,[95,515,516],{"class":309},"    expect",[95,518,519],{"class":101},"(counter.count.value).",[95,521,522],{"class":309},"toBe",[95,524,313],{"class":101},[95,526,527],{"class":323},"2",[95,529,491],{"class":101},[95,531,533],{"class":97,"line":532},11,[95,534,535],{"class":101},"  })\n",[95,537,539],{"class":97,"line":538},12,[95,540,541],{"class":101},"})\n",[14,543,544],{},"这段测试表达的需求是：从 1 开始，调用 increase 后应该变成 2。",[21,546,548],{"id":547},"_5-vitest-基础","5. Vitest 基础",[14,550,551],{},"常见结构：",[29,553,555],{"className":89,"code":554,"language":91,"meta":35,"style":35},"describe('一组测试', () => {\n  it('描述一个行为', () => {\n    expect(实际结果).toBe(期望结果)\n  })\n})\n",[37,556,557,572,587,599,603],{"__ignoreMap":35},[95,558,559,561,563,566,568,570],{"class":97,"line":98},[95,560,441],{"class":309},[95,562,313],{"class":101},[95,564,565],{"class":149},"'一组测试'",[95,567,449],{"class":101},[95,569,452],{"class":142},[95,571,455],{"class":101},[95,573,574,576,578,581,583,585],{"class":97,"line":330},[95,575,460],{"class":309},[95,577,313],{"class":101},[95,579,580],{"class":149},"'描述一个行为'",[95,582,449],{"class":101},[95,584,452],{"class":142},[95,586,455],{"class":101},[95,588,589,591,594,596],{"class":97,"line":347},[95,590,516],{"class":309},[95,592,593],{"class":101},"(实际结果).",[95,595,522],{"class":309},[95,597,598],{"class":101},"(期望结果)\n",[95,600,601],{"class":97,"line":354},[95,602,535],{"class":101},[95,604,605],{"class":97,"line":366},[95,606,541],{"class":101},[14,608,609],{},"例如：",[29,611,613],{"className":89,"code":612,"language":91,"meta":35,"style":35},"it('adds two numbers', () => {\n  expect(1 + 2).toBe(3)\n})\n",[37,614,615,631,658],{"__ignoreMap":35},[95,616,617,620,622,625,627,629],{"class":97,"line":98},[95,618,619],{"class":309},"it",[95,621,313],{"class":101},[95,623,624],{"class":149},"'adds two numbers'",[95,626,449],{"class":101},[95,628,452],{"class":142},[95,630,455],{"class":101},[95,632,633,636,638,640,643,646,649,651,653,656],{"class":97,"line":330},[95,634,635],{"class":309},"  expect",[95,637,313],{"class":101},[95,639,488],{"class":323},[95,641,642],{"class":142}," +",[95,644,645],{"class":323}," 2",[95,647,648],{"class":101},").",[95,650,522],{"class":309},[95,652,313],{"class":101},[95,654,655],{"class":323},"3",[95,657,491],{"class":101},[95,659,660],{"class":97,"line":347},[95,661,541],{"class":101},[14,663,664],{},"常见断言：",[29,666,668],{"className":89,"code":667,"language":91,"meta":35,"style":35},"expect(value).toBe(1)              \u002F\u002F 基本值相等\nexpect(object).toEqual({ a: 1 })   \u002F\u002F 对象或数组内容相等\nexpect(text).toContain('Vue')      \u002F\u002F 包含文本\nexpect(fn).toThrow()               \u002F\u002F 函数会抛错\n",[37,669,670,691,712,733],{"__ignoreMap":35},[95,671,672,675,678,680,682,684,687],{"class":97,"line":98},[95,673,674],{"class":309},"expect",[95,676,677],{"class":101},"(value).",[95,679,522],{"class":309},[95,681,313],{"class":101},[95,683,488],{"class":323},[95,685,686],{"class":101},")              ",[95,688,690],{"class":689},"sJ8bj","\u002F\u002F 基本值相等\n",[95,692,693,695,698,701,704,706,709],{"class":97,"line":330},[95,694,674],{"class":309},[95,696,697],{"class":101},"(object).",[95,699,700],{"class":309},"toEqual",[95,702,703],{"class":101},"({ a: ",[95,705,488],{"class":323},[95,707,708],{"class":101}," })   ",[95,710,711],{"class":689},"\u002F\u002F 对象或数组内容相等\n",[95,713,714,716,719,722,724,727,730],{"class":97,"line":347},[95,715,674],{"class":309},[95,717,718],{"class":101},"(text).",[95,720,721],{"class":309},"toContain",[95,723,313],{"class":101},[95,725,726],{"class":149},"'Vue'",[95,728,729],{"class":101},")      ",[95,731,732],{"class":689},"\u002F\u002F 包含文本\n",[95,734,735,737,740,743,746],{"class":97,"line":354},[95,736,674],{"class":309},[95,738,739],{"class":101},"(fn).",[95,741,742],{"class":309},"toThrow",[95,744,745],{"class":101},"()               ",[95,747,748],{"class":689},"\u002F\u002F 函数会抛错\n",[21,750,752],{"id":751},"_6-测试组合式函数","6. 测试组合式函数",[14,754,755],{},"组合式函数通常没有 DOM，最适合初学者练测试。",[29,757,759],{"className":89,"code":758,"language":91,"meta":35,"style":35},"import { describe, expect, it } from 'vitest'\nimport { useCounter } from '..\u002Fcomposables\u002FuseCounter'\n\ndescribe('useCounter', () => {\n  it('starts from initial value', () => {\n    const counter = useCounter(5)\n\n    expect(counter.count.value).toBe(5)\n  })\n\n  it('increases by one', () => {\n    const counter = useCounter(0)\n\n    counter.increase()\n\n    expect(counter.count.value).toBe(1)\n  })\n\n  it('resets to initial value', () => {\n    const counter = useCounter(3)\n\n    counter.increase()\n    counter.reset()\n\n    expect(counter.count.value).toBe(3)\n  })\n})\n",[37,760,761,771,781,785,799,814,831,835,849,853,857,872,889,894,903,908,923,928,933,949,966,971,980,990,995,1010,1015],{"__ignoreMap":35},[95,762,763,765,767,769],{"class":97,"line":98},[95,764,411],{"class":142},[95,766,414],{"class":101},[95,768,417],{"class":142},[95,770,420],{"class":149},[95,772,773,775,777,779],{"class":97,"line":330},[95,774,411],{"class":142},[95,776,427],{"class":101},[95,778,417],{"class":142},[95,780,432],{"class":149},[95,782,783],{"class":97,"line":347},[95,784,351],{"emptyLinePlaceholder":350},[95,786,787,789,791,793,795,797],{"class":97,"line":354},[95,788,441],{"class":309},[95,790,313],{"class":101},[95,792,446],{"class":149},[95,794,449],{"class":101},[95,796,452],{"class":142},[95,798,455],{"class":101},[95,800,801,803,805,808,810,812],{"class":97,"line":366},[95,802,460],{"class":309},[95,804,313],{"class":101},[95,806,807],{"class":149},"'starts from initial value'",[95,809,449],{"class":101},[95,811,452],{"class":142},[95,813,455],{"class":101},[95,815,816,818,820,822,824,826,829],{"class":97,"line":375},[95,817,476],{"class":142},[95,819,479],{"class":323},[95,821,320],{"class":142},[95,823,310],{"class":309},[95,825,313],{"class":101},[95,827,828],{"class":323},"5",[95,830,491],{"class":101},[95,832,833],{"class":97,"line":381},[95,834,351],{"emptyLinePlaceholder":350},[95,836,837,839,841,843,845,847],{"class":97,"line":386},[95,838,516],{"class":309},[95,840,519],{"class":101},[95,842,522],{"class":309},[95,844,313],{"class":101},[95,846,828],{"class":323},[95,848,491],{"class":101},[95,850,851],{"class":97,"line":395},[95,852,535],{"class":101},[95,854,855],{"class":97,"line":513},[95,856,351],{"emptyLinePlaceholder":350},[95,858,859,861,863,866,868,870],{"class":97,"line":532},[95,860,460],{"class":309},[95,862,313],{"class":101},[95,864,865],{"class":149},"'increases by one'",[95,867,449],{"class":101},[95,869,452],{"class":142},[95,871,455],{"class":101},[95,873,874,876,878,880,882,884,887],{"class":97,"line":538},[95,875,476],{"class":142},[95,877,479],{"class":323},[95,879,320],{"class":142},[95,881,310],{"class":309},[95,883,313],{"class":101},[95,885,886],{"class":323},"0",[95,888,491],{"class":101},[95,890,892],{"class":97,"line":891},13,[95,893,351],{"emptyLinePlaceholder":350},[95,895,897,899,901],{"class":97,"line":896},14,[95,898,500],{"class":101},[95,900,503],{"class":309},[95,902,506],{"class":101},[95,904,906],{"class":97,"line":905},15,[95,907,351],{"emptyLinePlaceholder":350},[95,909,911,913,915,917,919,921],{"class":97,"line":910},16,[95,912,516],{"class":309},[95,914,519],{"class":101},[95,916,522],{"class":309},[95,918,313],{"class":101},[95,920,488],{"class":323},[95,922,491],{"class":101},[95,924,926],{"class":97,"line":925},17,[95,927,535],{"class":101},[95,929,931],{"class":97,"line":930},18,[95,932,351],{"emptyLinePlaceholder":350},[95,934,936,938,940,943,945,947],{"class":97,"line":935},19,[95,937,460],{"class":309},[95,939,313],{"class":101},[95,941,942],{"class":149},"'resets to initial value'",[95,944,449],{"class":101},[95,946,452],{"class":142},[95,948,455],{"class":101},[95,950,952,954,956,958,960,962,964],{"class":97,"line":951},20,[95,953,476],{"class":142},[95,955,479],{"class":323},[95,957,320],{"class":142},[95,959,310],{"class":309},[95,961,313],{"class":101},[95,963,655],{"class":323},[95,965,491],{"class":101},[95,967,969],{"class":97,"line":968},21,[95,970,351],{"emptyLinePlaceholder":350},[95,972,974,976,978],{"class":97,"line":973},22,[95,975,500],{"class":101},[95,977,503],{"class":309},[95,979,506],{"class":101},[95,981,983,985,988],{"class":97,"line":982},23,[95,984,500],{"class":101},[95,986,987],{"class":309},"reset",[95,989,506],{"class":101},[95,991,993],{"class":97,"line":992},24,[95,994,351],{"emptyLinePlaceholder":350},[95,996,998,1000,1002,1004,1006,1008],{"class":97,"line":997},25,[95,999,516],{"class":309},[95,1001,519],{"class":101},[95,1003,522],{"class":309},[95,1005,313],{"class":101},[95,1007,655],{"class":323},[95,1009,491],{"class":101},[95,1011,1013],{"class":97,"line":1012},26,[95,1014,535],{"class":101},[95,1016,1018],{"class":97,"line":1017},27,[95,1019,541],{"class":101},[14,1021,1022,1023,52],{},"注意：测试里访问 ref 仍然要 ",[37,1024,1025],{},".value",[21,1027,1029],{"id":1028},"_7-组件测试","7. 组件测试",[14,1031,1032],{},"组件测试需要把组件挂载起来。",[29,1034,1036],{"className":89,"code":1035,"language":91,"meta":35,"style":35},"import { mount } from '@vue\u002Ftest-utils'\nimport { describe, expect, it } from 'vitest'\nimport TodoItem from '..\u002Fcomponents\u002FTodoItem.vue'\n\ndescribe('TodoItem', () => {\n  it('renders title', () => {\n    const wrapper = mount(TodoItem, {\n      props: {\n        todo: {\n          id: '1',\n          title: '学习组件测试',\n          done: false\n        }\n      }\n    })\n\n    expect(wrapper.text()).toContain('学习组件测试')\n  })\n})\n",[37,1037,1038,1050,1060,1072,1076,1091,1106,1121,1126,1131,1142,1152,1160,1165,1170,1175,1179,1199,1203],{"__ignoreMap":35},[95,1039,1040,1042,1045,1047],{"class":97,"line":98},[95,1041,411],{"class":142},[95,1043,1044],{"class":101}," { mount } ",[95,1046,417],{"class":142},[95,1048,1049],{"class":149}," '@vue\u002Ftest-utils'\n",[95,1051,1052,1054,1056,1058],{"class":97,"line":330},[95,1053,411],{"class":142},[95,1055,414],{"class":101},[95,1057,417],{"class":142},[95,1059,420],{"class":149},[95,1061,1062,1064,1067,1069],{"class":97,"line":347},[95,1063,411],{"class":142},[95,1065,1066],{"class":101}," TodoItem ",[95,1068,417],{"class":142},[95,1070,1071],{"class":149}," '..\u002Fcomponents\u002FTodoItem.vue'\n",[95,1073,1074],{"class":97,"line":354},[95,1075,351],{"emptyLinePlaceholder":350},[95,1077,1078,1080,1082,1085,1087,1089],{"class":97,"line":366},[95,1079,441],{"class":309},[95,1081,313],{"class":101},[95,1083,1084],{"class":149},"'TodoItem'",[95,1086,449],{"class":101},[95,1088,452],{"class":142},[95,1090,455],{"class":101},[95,1092,1093,1095,1097,1100,1102,1104],{"class":97,"line":375},[95,1094,460],{"class":309},[95,1096,313],{"class":101},[95,1098,1099],{"class":149},"'renders title'",[95,1101,449],{"class":101},[95,1103,452],{"class":142},[95,1105,455],{"class":101},[95,1107,1108,1110,1113,1115,1118],{"class":97,"line":381},[95,1109,476],{"class":142},[95,1111,1112],{"class":323}," wrapper",[95,1114,320],{"class":142},[95,1116,1117],{"class":309}," mount",[95,1119,1120],{"class":101},"(TodoItem, {\n",[95,1122,1123],{"class":97,"line":386},[95,1124,1125],{"class":101},"      props: {\n",[95,1127,1128],{"class":97,"line":395},[95,1129,1130],{"class":101},"        todo: {\n",[95,1132,1133,1136,1139],{"class":97,"line":513},[95,1134,1135],{"class":101},"          id: ",[95,1137,1138],{"class":149},"'1'",[95,1140,1141],{"class":101},",\n",[95,1143,1144,1147,1150],{"class":97,"line":532},[95,1145,1146],{"class":101},"          title: ",[95,1148,1149],{"class":149},"'学习组件测试'",[95,1151,1141],{"class":101},[95,1153,1154,1157],{"class":97,"line":538},[95,1155,1156],{"class":101},"          done: ",[95,1158,1159],{"class":323},"false\n",[95,1161,1162],{"class":97,"line":891},[95,1163,1164],{"class":101},"        }\n",[95,1166,1167],{"class":97,"line":896},[95,1168,1169],{"class":101},"      }\n",[95,1171,1172],{"class":97,"line":905},[95,1173,1174],{"class":101},"    })\n",[95,1176,1177],{"class":97,"line":910},[95,1178,351],{"emptyLinePlaceholder":350},[95,1180,1181,1183,1186,1188,1191,1193,1195,1197],{"class":97,"line":925},[95,1182,516],{"class":309},[95,1184,1185],{"class":101},"(wrapper.",[95,1187,34],{"class":309},[95,1189,1190],{"class":101},"()).",[95,1192,721],{"class":309},[95,1194,313],{"class":101},[95,1196,1149],{"class":149},[95,1198,491],{"class":101},[95,1200,1201],{"class":97,"line":930},[95,1202,535],{"class":101},[95,1204,1205],{"class":97,"line":935},[95,1206,541],{"class":101},[14,1208,1209,1212,1213,1216],{},[37,1210,1211],{},"mount"," 会创建组件实例。",[37,1214,1215],{},"wrapper"," 是测试工具提供的包装对象，可以读取文本、查找按钮、触发事件。",[21,1218,1220],{"id":1219},"_8-测试-emit","8. 测试 emit",[14,1222,1223],{},"子组件点击后应该发事件：",[29,1225,1227],{"className":89,"code":1226,"language":91,"meta":35,"style":35},"it('emits toggle event', async () => {\n  const wrapper = mount(TodoItem, {\n    props: {\n      todo: {\n        id: '1',\n        title: '学习测试',\n        done: false\n      }\n    }\n  })\n\n  await wrapper.get('[data-test=\"toggle\"]').trigger('click')\n\n  expect(wrapper.emitted('toggle')).toBeTruthy()\n  expect(wrapper.emitted('toggle')[0]).toEqual(['1'])\n})\n",[37,1228,1229,1251,1263,1268,1273,1282,1292,1299,1303,1308,1312,1316,1344,1348,1370,1400],{"__ignoreMap":35},[95,1230,1231,1233,1235,1238,1241,1244,1247,1249],{"class":97,"line":98},[95,1232,619],{"class":309},[95,1234,313],{"class":101},[95,1236,1237],{"class":149},"'emits toggle event'",[95,1239,1240],{"class":101},", ",[95,1242,1243],{"class":142},"async",[95,1245,1246],{"class":101}," () ",[95,1248,452],{"class":142},[95,1250,455],{"class":101},[95,1252,1253,1255,1257,1259,1261],{"class":97,"line":330},[95,1254,333],{"class":142},[95,1256,1112],{"class":323},[95,1258,320],{"class":142},[95,1260,1117],{"class":309},[95,1262,1120],{"class":101},[95,1264,1265],{"class":97,"line":347},[95,1266,1267],{"class":101},"    props: {\n",[95,1269,1270],{"class":97,"line":354},[95,1271,1272],{"class":101},"      todo: {\n",[95,1274,1275,1278,1280],{"class":97,"line":366},[95,1276,1277],{"class":101},"        id: ",[95,1279,1138],{"class":149},[95,1281,1141],{"class":101},[95,1283,1284,1287,1290],{"class":97,"line":375},[95,1285,1286],{"class":101},"        title: ",[95,1288,1289],{"class":149},"'学习测试'",[95,1291,1141],{"class":101},[95,1293,1294,1297],{"class":97,"line":381},[95,1295,1296],{"class":101},"        done: ",[95,1298,1159],{"class":323},[95,1300,1301],{"class":97,"line":386},[95,1302,1169],{"class":101},[95,1304,1305],{"class":97,"line":395},[95,1306,1307],{"class":101},"    }\n",[95,1309,1310],{"class":97,"line":513},[95,1311,535],{"class":101},[95,1313,1314],{"class":97,"line":532},[95,1315,351],{"emptyLinePlaceholder":350},[95,1317,1318,1321,1324,1327,1329,1332,1334,1337,1339,1342],{"class":97,"line":538},[95,1319,1320],{"class":142},"  await",[95,1322,1323],{"class":101}," wrapper.",[95,1325,1326],{"class":309},"get",[95,1328,313],{"class":101},[95,1330,1331],{"class":149},"'[data-test=\"toggle\"]'",[95,1333,648],{"class":101},[95,1335,1336],{"class":309},"trigger",[95,1338,313],{"class":101},[95,1340,1341],{"class":149},"'click'",[95,1343,491],{"class":101},[95,1345,1346],{"class":97,"line":891},[95,1347,351],{"emptyLinePlaceholder":350},[95,1349,1350,1352,1354,1357,1359,1362,1365,1368],{"class":97,"line":896},[95,1351,635],{"class":309},[95,1353,1185],{"class":101},[95,1355,1356],{"class":309},"emitted",[95,1358,313],{"class":101},[95,1360,1361],{"class":149},"'toggle'",[95,1363,1364],{"class":101},")).",[95,1366,1367],{"class":309},"toBeTruthy",[95,1369,506],{"class":101},[95,1371,1372,1374,1376,1378,1380,1382,1385,1387,1390,1392,1395,1397],{"class":97,"line":905},[95,1373,635],{"class":309},[95,1375,1185],{"class":101},[95,1377,1356],{"class":309},[95,1379,313],{"class":101},[95,1381,1361],{"class":149},[95,1383,1384],{"class":101},")[",[95,1386,886],{"class":323},[95,1388,1389],{"class":101},"]).",[95,1391,700],{"class":309},[95,1393,1394],{"class":101},"([",[95,1396,1138],{"class":149},[95,1398,1399],{"class":101},"])\n",[95,1401,1402],{"class":97,"line":910},[95,1403,541],{"class":101},[14,1405,1406],{},"模板中建议给测试钩子：",[29,1408,1410],{"className":127,"code":1409,"language":129,"meta":35,"style":35},"\u003Cbutton data-test=\"toggle\" @click=\"emit('toggle', todo.id)\">\n  切换\n\u003C\u002Fbutton>\n",[37,1411,1412,1451,1456],{"__ignoreMap":35},[95,1413,1414,1416,1419,1422,1424,1427,1430,1433,1435,1437,1440,1442,1444,1447,1449],{"class":97,"line":98},[95,1415,136],{"class":101},[95,1417,1418],{"class":139},"button",[95,1420,1421],{"class":309}," data-test",[95,1423,146],{"class":101},[95,1425,1426],{"class":149},"\"toggle\"",[95,1428,1429],{"class":101}," @",[95,1431,1432],{"class":309},"click",[95,1434,146],{"class":101},[95,1436,150],{"class":149},[95,1438,1439],{"class":309},"emit",[95,1441,313],{"class":101},[95,1443,1361],{"class":149},[95,1445,1446],{"class":101},", todo.id)",[95,1448,150],{"class":149},[95,1450,162],{"class":101},[95,1452,1453],{"class":97,"line":330},[95,1454,1455],{"class":101},"  切换\n",[95,1457,1458,1461,1463],{"class":97,"line":347},[95,1459,1460],{"class":101},"\u003C\u002F",[95,1462,1418],{"class":139},[95,1464,162],{"class":101},[14,1466,1467],{},"为什么不用 class？",[43,1469,1470,1473],{},[46,1471,1472],{},"class 主要服务样式，可能会改。",[46,1474,1475,1478],{},[37,1476,1477],{},"data-test"," 明确表示给测试使用。",[21,1480,1482],{"id":1481},"_9-测试应该关注什么","9. 测试应该关注什么",[14,1484,1485],{},"好测试关注行为，不关注实现细节。",[14,1487,1488],{},"推荐测试：",[43,1490,1491,1494,1497,1500],{},[46,1492,1493],{},"给定 props，组件显示正确文本。",[46,1495,1496],{},"用户点击按钮，组件 emit 正确事件。",[46,1498,1499],{},"组合式函数调用方法后状态改变。",[46,1501,1502],{},"store action 修改状态。",[14,1504,1505],{},"不推荐过度测试：",[43,1507,1508,1511,1514],{},[46,1509,1510],{},"某个内部变量名字。",[46,1512,1513],{},"某个 class 的存在，除非它代表重要状态。",[46,1515,1516],{},"完全重复 Vue 自己已经保证的行为。",[21,1518,1520],{"id":1519},"_10-测试命名","10. 测试命名",[14,1522,1523],{},"好的测试名像需求：",[29,1525,1527],{"className":89,"code":1526,"language":91,"meta":35,"style":35},"it('emits remove event with todo id when remove button is clicked', () => {})\n",[37,1528,1529],{"__ignoreMap":35},[95,1530,1531,1533,1535,1538,1540,1542],{"class":97,"line":98},[95,1532,619],{"class":309},[95,1534,313],{"class":101},[95,1536,1537],{"class":149},"'emits remove event with todo id when remove button is clicked'",[95,1539,449],{"class":101},[95,1541,452],{"class":142},[95,1543,1544],{"class":101}," {})\n",[14,1546,1547],{},"不好的测试名：",[29,1549,1551],{"className":89,"code":1550,"language":91,"meta":35,"style":35},"it('test 1', () => {})\n",[37,1552,1553],{"__ignoreMap":35},[95,1554,1555,1557,1559,1562,1564,1566],{"class":97,"line":98},[95,1556,619],{"class":309},[95,1558,313],{"class":101},[95,1560,1561],{"class":149},"'test 1'",[95,1563,449],{"class":101},[95,1565,452],{"class":142},[95,1567,1544],{"class":101},[14,1569,1570],{},"读测试的人应该不看实现也知道这条测试验证什么。",[21,1572,1574],{"id":1573},"_11-常见-vue-调试场景","11. 常见 Vue 调试场景",[1576,1577,1578],"h3",{"id":1578},"页面空白",[14,1580,1581],{},"检查：",[43,1583,1584,1587,1590,1593,1598],{},[46,1585,1586],{},"控制台是否有报错。",[46,1588,1589],{},"根组件是否挂载。",[46,1591,1592],{},"Router 是否有匹配页面。",[46,1594,1595,1597],{},[37,1596,212],{}," 条件是否一直为 false。",[46,1599,1600],{},"请求是否一直 loading。",[1576,1602,1603],{"id":1603},"点击按钮没反应",[14,1605,1581],{},[43,1607,1608,1611,1617,1620,1623],{},[46,1609,1610],{},"按钮是否真的可点击，是否 disabled。",[46,1612,1613,1616],{},[37,1614,1615],{},"@click"," 函数名是否拼错。",[46,1618,1619],{},"函数里是否改了响应式状态。",[46,1621,1622],{},"子组件是否 emit。",[46,1624,1625],{},"父组件监听事件名是否一致。",[1576,1627,1628],{"id":1628},"列表状态错位",[14,1630,1581],{},[43,1632,1633,1640,1643],{},[46,1634,1635,1637,1638,52],{},[37,1636,216],{}," 是否有稳定 ",[37,1639,219],{},[46,1641,1642],{},"是否用 index 当 key。",[46,1644,1645],{},"是否对数组做了增删排序。",[1576,1647,1648],{"id":1648},"表单提交刷新页面",[14,1650,1581],{},[29,1652,1654],{"className":127,"code":1653,"language":129,"meta":35,"style":35},"\u003Cform @submit.prevent=\"submit\">\n",[37,1655,1656],{"__ignoreMap":35},[95,1657,1658,1660,1663,1665,1668,1671,1674,1676,1678,1680,1682],{"class":97,"line":98},[95,1659,136],{"class":101},[95,1661,1662],{"class":139},"form",[95,1664,1429],{"class":101},[95,1666,1667],{"class":309},"submit",[95,1669,1670],{"class":101},".",[95,1672,1673],{"class":309},"prevent",[95,1675,146],{"class":101},[95,1677,150],{"class":149},[95,1679,1667],{"class":101},[95,1681,150],{"class":149},[95,1683,162],{"class":101},[14,1685,1686,1687,52],{},"是否忘记 ",[37,1688,1689],{},".prevent",[21,1691,1693],{"id":1692},"_12-质量保障清单","12. 质量保障清单",[14,1695,1696],{},"提交前至少运行：",[29,1698,1702],{"className":1699,"code":1700,"language":1701,"meta":35,"style":35},"language-powershell shiki shiki-themes github-light github-dark","npm run test\nnpm run build\n","powershell",[37,1703,1704,1709],{"__ignoreMap":35},[95,1705,1706],{"class":97,"line":98},[95,1707,1708],{},"npm run test\n",[95,1710,1711],{"class":97,"line":330},[95,1712,1713],{},"npm run build\n",[14,1715,1716],{},"如果有 lint：",[29,1718,1720],{"className":1699,"code":1719,"language":1701,"meta":35,"style":35},"npm run lint\n",[37,1721,1722],{"__ignoreMap":35},[95,1723,1724],{"class":97,"line":98},[95,1725,1719],{},[14,1727,1728],{},"如果改了 UI，最好浏览器实际点一遍：",[43,1730,1731,1734,1737,1740],{},[46,1732,1733],{},"首页是否能打开。",[46,1735,1736],{},"关键按钮是否有反应。",[46,1738,1739],{},"表单错误是否显示。",[46,1741,1742],{},"移动端是否明显错位。",[21,1744,1745],{"id":1745},"本章练习",[171,1747,1748,1754,1760,1768,1771,1774],{},[46,1749,1750,1751,1753],{},"为 ",[37,1752,293],{}," 写 3 个测试：初始值、增加、重置。",[46,1755,1750,1756,1759],{},[37,1757,1758],{},"TodoItem"," 写测试：能显示标题。",[46,1761,1750,1762,1764,1765,52],{},[37,1763,1758],{}," 写测试：点击切换按钮 emit ",[37,1766,1767],{},"toggle",[46,1769,1770],{},"故意把事件名改错，确认测试失败。",[46,1772,1773],{},"修复事件名，确认测试通过。",[46,1775,1776],{},"写一份你自己的调试清单。",[1778,1779,1780],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":35,"searchDepth":330,"depth":330,"links":1782},[1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1799,1800],{"id":23,"depth":330,"text":24},{"id":165,"depth":330,"text":166},{"id":232,"depth":330,"text":233},{"id":283,"depth":330,"text":284},{"id":547,"depth":330,"text":548},{"id":751,"depth":330,"text":752},{"id":1028,"depth":330,"text":1029},{"id":1219,"depth":330,"text":1220},{"id":1481,"depth":330,"text":1482},{"id":1519,"depth":330,"text":1520},{"id":1573,"depth":330,"text":1574,"children":1794},[1795,1796,1797,1798],{"id":1578,"depth":347,"text":1578},{"id":1603,"depth":347,"text":1603},{"id":1628,"depth":347,"text":1628},{"id":1648,"depth":347,"text":1648},{"id":1692,"depth":330,"text":1693},{"id":1745,"depth":330,"text":1745},"md",{"order":513},"\u002Fsource\u002Fcode\u002Fvue\u002F10-testing-debugging",{"title":5,"description":16},"source\u002Fcode\u002Fvue\u002F10-testing-debugging","Bf9uJ-uJRAOO7iH0RGCOfQ2D7UrudiEhxBY2t9l-qkk",1784032889391]