[{"data":1,"prerenderedAt":3710},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-component-interaction-testing-guide":3},{"id":4,"title":5,"body":6,"description":3695,"difficulty":3696,"extension":3697,"framework":3698,"frameworkSlug":3699,"meta":3700,"navigation":80,"order":77,"path":3701,"qaPath":3702,"seo":3703,"stem":3704,"subtopic":3705,"topic":3706,"topicSlug":3707,"updated":3708,"__hash__":3709},"blog\u002Fblog\u002Freact-component-interaction-testing-guide.md","React Component Interaction Testing — Complete Guide",{"type":7,"value":8,"toc":3673},"minimark",[9,14,23,31,46,198,204,208,211,349,352,377,381,384,552,563,567,570,787,792,799,875,878,911,915,1148,1152,1155,1310,1314,1324,1488,1491,1694,1698,1705,1911,1914,1918,1925,2355,2359,2369,2563,2569,2656,2660,2674,2885,2889,2896,3079,3083,3086,3356,3359,3381,3385,3388,3468,3471,3475,3610,3614,3617,3663,3669],[10,11,13],"h2",{"id":12},"from-rendering-to-interaction","From rendering to interaction",[15,16,17,18,22],"p",{},"Most RTL tutorials start with ",[19,20,21],"code",{},"render"," and stop at \"assert text is visible.\"\nReal-world components are interactive. Users click buttons, fill forms, open\nmodals, navigate routes, and trigger conditional rendering. This guide covers\nhow to test all of it.",[10,24,26,27,30],{"id":25},"the-userevent-setup-pattern","The ",[19,28,29],{},"userEvent"," setup pattern",[15,32,33,34,37,38,41,42,45],{},"Since ",[19,35,36],{},"@testing-library\u002Fuser-event"," v14, all interactions are async and require\na ",[19,39,40],{},"user"," instance created via ",[19,43,44],{},"userEvent.setup()",". Create it once per test:",[47,48,53],"pre",{"className":49,"code":50,"language":51,"meta":52,"style":52},"language-js shiki shiki-themes github-light github-dark","import userEvent from '@testing-library\u002Fuser-event'\n\ntest('some interaction', async () => {\n  const user = userEvent.setup()   \u002F\u002F one instance per test\n\n  render(\u003CMyComponent \u002F>)\n  await user.click(...)\n  await user.type(...)\n})\n","js","",[19,54,55,75,82,110,136,141,156,176,192],{"__ignoreMap":52},[56,57,60,64,68,71],"span",{"class":58,"line":59},"line",1,[56,61,63],{"class":62},"szBVR","import",[56,65,67],{"class":66},"sVt8B"," userEvent ",[56,69,70],{"class":62},"from",[56,72,74],{"class":73},"sZZnC"," '@testing-library\u002Fuser-event'\n",[56,76,78],{"class":58,"line":77},2,[56,79,81],{"emptyLinePlaceholder":80},true,"\n",[56,83,85,89,92,95,98,101,104,107],{"class":58,"line":84},3,[56,86,88],{"class":87},"sScJk","test",[56,90,91],{"class":66},"(",[56,93,94],{"class":73},"'some interaction'",[56,96,97],{"class":66},", ",[56,99,100],{"class":62},"async",[56,102,103],{"class":66}," () ",[56,105,106],{"class":62},"=>",[56,108,109],{"class":66}," {\n",[56,111,113,116,120,123,126,129,132],{"class":58,"line":112},4,[56,114,115],{"class":62},"  const",[56,117,119],{"class":118},"sj4cs"," user",[56,121,122],{"class":62}," =",[56,124,125],{"class":66}," userEvent.",[56,127,128],{"class":87},"setup",[56,130,131],{"class":66},"()   ",[56,133,135],{"class":134},"sJ8bj","\u002F\u002F one instance per test\n",[56,137,139],{"class":58,"line":138},5,[56,140,81],{"emptyLinePlaceholder":80},[56,142,144,147,150,153],{"class":58,"line":143},6,[56,145,146],{"class":87},"  render",[56,148,149],{"class":66},"(\u003C",[56,151,152],{"class":118},"MyComponent",[56,154,155],{"class":66}," \u002F>)\n",[56,157,159,162,165,168,170,173],{"class":58,"line":158},7,[56,160,161],{"class":62},"  await",[56,163,164],{"class":66}," user.",[56,166,167],{"class":87},"click",[56,169,91],{"class":66},[56,171,172],{"class":62},"...",[56,174,175],{"class":66},")\n",[56,177,179,181,183,186,188,190],{"class":58,"line":178},8,[56,180,161],{"class":62},[56,182,164],{"class":66},[56,184,185],{"class":87},"type",[56,187,91],{"class":66},[56,189,172],{"class":62},[56,191,175],{"class":66},[56,193,195],{"class":58,"line":194},9,[56,196,197],{"class":66},"})\n",[15,199,200,201,203],{},"Never create the instance inside a loop or call it before ",[19,202,21],{}," — fake\ntimers and the internal event queue depend on a stable instance.",[10,205,207],{"id":206},"testing-button-clicks-and-state-updates","Testing button clicks and state updates",[15,209,210],{},"The simplest interaction test: click a button, assert the resulting state\nchange in the DOM.",[47,212,214],{"className":49,"code":213,"language":51,"meta":52,"style":52},"test('counter increments on click', async () => {\n  const user = userEvent.setup()\n  render(\u003CCounter \u002F>)\n\n  expect(screen.getByText('Count: 0')).toBeInTheDocument()\n  await user.click(screen.getByRole('button', { name: \u002Fincrement\u002Fi }))\n  expect(screen.getByText('Count: 1')).toBeInTheDocument()\n})\n",[19,215,216,235,250,261,265,289,326,345],{"__ignoreMap":52},[56,217,218,220,222,225,227,229,231,233],{"class":58,"line":59},[56,219,88],{"class":87},[56,221,91],{"class":66},[56,223,224],{"class":73},"'counter increments on click'",[56,226,97],{"class":66},[56,228,100],{"class":62},[56,230,103],{"class":66},[56,232,106],{"class":62},[56,234,109],{"class":66},[56,236,237,239,241,243,245,247],{"class":58,"line":77},[56,238,115],{"class":62},[56,240,119],{"class":118},[56,242,122],{"class":62},[56,244,125],{"class":66},[56,246,128],{"class":87},[56,248,249],{"class":66},"()\n",[56,251,252,254,256,259],{"class":58,"line":84},[56,253,146],{"class":87},[56,255,149],{"class":66},[56,257,258],{"class":118},"Counter",[56,260,155],{"class":66},[56,262,263],{"class":58,"line":112},[56,264,81],{"emptyLinePlaceholder":80},[56,266,267,270,273,276,278,281,284,287],{"class":58,"line":138},[56,268,269],{"class":87},"  expect",[56,271,272],{"class":66},"(screen.",[56,274,275],{"class":87},"getByText",[56,277,91],{"class":66},[56,279,280],{"class":73},"'Count: 0'",[56,282,283],{"class":66},")).",[56,285,286],{"class":87},"toBeInTheDocument",[56,288,249],{"class":66},[56,290,291,293,295,297,299,302,304,307,310,313,317,320,323],{"class":58,"line":143},[56,292,161],{"class":62},[56,294,164],{"class":66},[56,296,167],{"class":87},[56,298,272],{"class":66},[56,300,301],{"class":87},"getByRole",[56,303,91],{"class":66},[56,305,306],{"class":73},"'button'",[56,308,309],{"class":66},", { name:",[56,311,312],{"class":73}," \u002F",[56,314,316],{"class":315},"sA_wV","increment",[56,318,319],{"class":73},"\u002F",[56,321,322],{"class":62},"i",[56,324,325],{"class":66}," }))\n",[56,327,328,330,332,334,336,339,341,343],{"class":58,"line":158},[56,329,269],{"class":87},[56,331,272],{"class":66},[56,333,275],{"class":87},[56,335,91],{"class":66},[56,337,338],{"class":73},"'Count: 1'",[56,340,283],{"class":66},[56,342,286],{"class":87},[56,344,249],{"class":66},[56,346,347],{"class":58,"line":178},[56,348,197],{"class":66},[15,350,351],{},"Three things to note:",[353,354,355,363,369],"ol",{},[356,357,358,359,362],"li",{},"Query by ",[19,360,361],{},"role + name"," — tests accessibility alongside behavior.",[356,364,365,368],{},[19,366,367],{},"await"," the click — userEvent v14 is async.",[356,370,371,372,376],{},"Assert the ",[373,374,375],"em",{},"DOM outcome",", not the component's internal state.",[10,378,380],{"id":379},"testing-callback-props","Testing callback props",[15,382,383],{},"When a component calls a callback prop in response to user action, verify\nboth that the callback was called and what arguments it received:",[47,385,387],{"className":49,"code":386,"language":51,"meta":52,"style":52},"test('calls onAddToCart with product id', async () => {\n  const onAddToCart = vi.fn()\n  const user = userEvent.setup()\n\n  render(\u003CProductCard id={42} name=\"Widget\" onAddToCart={onAddToCart} \u002F>)\n  await user.click(screen.getByRole('button', { name: \u002Fadd to cart\u002Fi }))\n\n  expect(onAddToCart).toHaveBeenCalledTimes(1)\n  expect(onAddToCart).toHaveBeenCalledWith(42)\n})\n",[19,388,389,408,425,439,443,482,511,515,532,547],{"__ignoreMap":52},[56,390,391,393,395,398,400,402,404,406],{"class":58,"line":59},[56,392,88],{"class":87},[56,394,91],{"class":66},[56,396,397],{"class":73},"'calls onAddToCart with product id'",[56,399,97],{"class":66},[56,401,100],{"class":62},[56,403,103],{"class":66},[56,405,106],{"class":62},[56,407,109],{"class":66},[56,409,410,412,415,417,420,423],{"class":58,"line":77},[56,411,115],{"class":62},[56,413,414],{"class":118}," onAddToCart",[56,416,122],{"class":62},[56,418,419],{"class":66}," vi.",[56,421,422],{"class":87},"fn",[56,424,249],{"class":66},[56,426,427,429,431,433,435,437],{"class":58,"line":84},[56,428,115],{"class":62},[56,430,119],{"class":118},[56,432,122],{"class":62},[56,434,125],{"class":66},[56,436,128],{"class":87},[56,438,249],{"class":66},[56,440,441],{"class":58,"line":112},[56,442,81],{"emptyLinePlaceholder":80},[56,444,445,447,449,452,455,458,461,464,467,470,472,475,477,479],{"class":58,"line":138},[56,446,146],{"class":87},[56,448,149],{"class":66},[56,450,451],{"class":118},"ProductCard",[56,453,454],{"class":87}," id",[56,456,457],{"class":62},"=",[56,459,460],{"class":66},"{",[56,462,463],{"class":118},"42",[56,465,466],{"class":66},"} ",[56,468,469],{"class":87},"name",[56,471,457],{"class":62},[56,473,474],{"class":73},"\"Widget\"",[56,476,414],{"class":87},[56,478,457],{"class":62},[56,480,481],{"class":66},"{onAddToCart} \u002F>)\n",[56,483,484,486,488,490,492,494,496,498,500,502,505,507,509],{"class":58,"line":143},[56,485,161],{"class":62},[56,487,164],{"class":66},[56,489,167],{"class":87},[56,491,272],{"class":66},[56,493,301],{"class":87},[56,495,91],{"class":66},[56,497,306],{"class":73},[56,499,309],{"class":66},[56,501,312],{"class":73},[56,503,504],{"class":315},"add to cart",[56,506,319],{"class":73},[56,508,322],{"class":62},[56,510,325],{"class":66},[56,512,513],{"class":58,"line":158},[56,514,81],{"emptyLinePlaceholder":80},[56,516,517,519,522,525,527,530],{"class":58,"line":178},[56,518,269],{"class":87},[56,520,521],{"class":66},"(onAddToCart).",[56,523,524],{"class":87},"toHaveBeenCalledTimes",[56,526,91],{"class":66},[56,528,529],{"class":118},"1",[56,531,175],{"class":66},[56,533,534,536,538,541,543,545],{"class":58,"line":194},[56,535,269],{"class":87},[56,537,521],{"class":66},[56,539,540],{"class":87},"toHaveBeenCalledWith",[56,542,91],{"class":66},[56,544,463],{"class":118},[56,546,175],{"class":66},[56,548,550],{"class":58,"line":549},10,[56,551,197],{"class":66},[15,553,554,555,558,559,562],{},"Use ",[19,556,557],{},"vi.fn()"," (Vitest) or ",[19,560,561],{},"jest.fn()"," for callback spies. Never mock internal\nimplementation functions — only the public contract.",[10,564,566],{"id":565},"form-testing","Form testing",[15,568,569],{},"Testing a form means filling every required field, submitting, and asserting\nthe outcome (callback called, navigation, success message).",[47,571,573],{"className":49,"code":572,"language":51,"meta":52,"style":52},"test('submits registration form with correct data', async () => {\n  const onSubmit = vi.fn()\n  const user = userEvent.setup()\n\n  render(\u003CRegistrationForm onSubmit={onSubmit} \u002F>)\n\n  await user.type(screen.getByLabelText(\u002Femail\u002Fi), 'alice@example.com')\n  await user.type(screen.getByLabelText(\u002Fpassword\u002Fi), 'secret123')\n  await user.click(screen.getByRole('button', { name: \u002Fregister\u002Fi }))\n\n  expect(onSubmit).toHaveBeenCalledWith({\n    email: 'alice@example.com',\n    password: 'secret123',\n  })\n})\n",[19,574,575,594,609,623,627,643,647,679,709,738,742,755,766,776,782],{"__ignoreMap":52},[56,576,577,579,581,584,586,588,590,592],{"class":58,"line":59},[56,578,88],{"class":87},[56,580,91],{"class":66},[56,582,583],{"class":73},"'submits registration form with correct data'",[56,585,97],{"class":66},[56,587,100],{"class":62},[56,589,103],{"class":66},[56,591,106],{"class":62},[56,593,109],{"class":66},[56,595,596,598,601,603,605,607],{"class":58,"line":77},[56,597,115],{"class":62},[56,599,600],{"class":118}," onSubmit",[56,602,122],{"class":62},[56,604,419],{"class":66},[56,606,422],{"class":87},[56,608,249],{"class":66},[56,610,611,613,615,617,619,621],{"class":58,"line":84},[56,612,115],{"class":62},[56,614,119],{"class":118},[56,616,122],{"class":62},[56,618,125],{"class":66},[56,620,128],{"class":87},[56,622,249],{"class":66},[56,624,625],{"class":58,"line":112},[56,626,81],{"emptyLinePlaceholder":80},[56,628,629,631,633,636,638,640],{"class":58,"line":138},[56,630,146],{"class":87},[56,632,149],{"class":66},[56,634,635],{"class":118},"RegistrationForm",[56,637,600],{"class":87},[56,639,457],{"class":62},[56,641,642],{"class":66},"{onSubmit} \u002F>)\n",[56,644,645],{"class":58,"line":143},[56,646,81],{"emptyLinePlaceholder":80},[56,648,649,651,653,655,657,660,662,664,667,669,671,674,677],{"class":58,"line":158},[56,650,161],{"class":62},[56,652,164],{"class":66},[56,654,185],{"class":87},[56,656,272],{"class":66},[56,658,659],{"class":87},"getByLabelText",[56,661,91],{"class":66},[56,663,319],{"class":73},[56,665,666],{"class":315},"email",[56,668,319],{"class":73},[56,670,322],{"class":62},[56,672,673],{"class":66},"), ",[56,675,676],{"class":73},"'alice@example.com'",[56,678,175],{"class":66},[56,680,681,683,685,687,689,691,693,695,698,700,702,704,707],{"class":58,"line":178},[56,682,161],{"class":62},[56,684,164],{"class":66},[56,686,185],{"class":87},[56,688,272],{"class":66},[56,690,659],{"class":87},[56,692,91],{"class":66},[56,694,319],{"class":73},[56,696,697],{"class":315},"password",[56,699,319],{"class":73},[56,701,322],{"class":62},[56,703,673],{"class":66},[56,705,706],{"class":73},"'secret123'",[56,708,175],{"class":66},[56,710,711,713,715,717,719,721,723,725,727,729,732,734,736],{"class":58,"line":194},[56,712,161],{"class":62},[56,714,164],{"class":66},[56,716,167],{"class":87},[56,718,272],{"class":66},[56,720,301],{"class":87},[56,722,91],{"class":66},[56,724,306],{"class":73},[56,726,309],{"class":66},[56,728,312],{"class":73},[56,730,731],{"class":315},"register",[56,733,319],{"class":73},[56,735,322],{"class":62},[56,737,325],{"class":66},[56,739,740],{"class":58,"line":549},[56,741,81],{"emptyLinePlaceholder":80},[56,743,745,747,750,752],{"class":58,"line":744},11,[56,746,269],{"class":87},[56,748,749],{"class":66},"(onSubmit).",[56,751,540],{"class":87},[56,753,754],{"class":66},"({\n",[56,756,758,761,763],{"class":58,"line":757},12,[56,759,760],{"class":66},"    email: ",[56,762,676],{"class":73},[56,764,765],{"class":66},",\n",[56,767,769,772,774],{"class":58,"line":768},13,[56,770,771],{"class":66},"    password: ",[56,773,706],{"class":73},[56,775,765],{"class":66},[56,777,779],{"class":58,"line":778},14,[56,780,781],{"class":66},"  })\n",[56,783,785],{"class":58,"line":784},15,[56,786,197],{"class":66},[788,789,791],"h3",{"id":790},"controlled-inputs","Controlled inputs",[15,793,794,795,798],{},"Controlled inputs update React state on every keystroke. ",[19,796,797],{},"user.type()"," fires\nthe correct keyboard event sequence, so controlled inputs work without any\nspecial setup:",[47,800,802],{"className":49,"code":801,"language":51,"meta":52,"style":52},"await user.type(screen.getByRole('textbox', { name: \u002Fsearch\u002Fi }), 'react hooks')\nexpect(screen.getByRole('textbox', { name: \u002Fsearch\u002Fi })).toHaveValue('react hooks')\n",[19,803,804,840],{"__ignoreMap":52},[56,805,806,808,810,812,814,816,818,821,823,825,828,830,832,835,838],{"class":58,"line":59},[56,807,367],{"class":62},[56,809,164],{"class":66},[56,811,185],{"class":87},[56,813,272],{"class":66},[56,815,301],{"class":87},[56,817,91],{"class":66},[56,819,820],{"class":73},"'textbox'",[56,822,309],{"class":66},[56,824,312],{"class":73},[56,826,827],{"class":315},"search",[56,829,319],{"class":73},[56,831,322],{"class":62},[56,833,834],{"class":66}," }), ",[56,836,837],{"class":73},"'react hooks'",[56,839,175],{"class":66},[56,841,842,845,847,849,851,853,855,857,859,861,863,866,869,871,873],{"class":58,"line":77},[56,843,844],{"class":87},"expect",[56,846,272],{"class":66},[56,848,301],{"class":87},[56,850,91],{"class":66},[56,852,820],{"class":73},[56,854,309],{"class":66},[56,856,312],{"class":73},[56,858,827],{"class":315},[56,860,319],{"class":73},[56,862,322],{"class":62},[56,864,865],{"class":66}," })).",[56,867,868],{"class":87},"toHaveValue",[56,870,91],{"class":66},[56,872,837],{"class":73},[56,874,175],{"class":66},[15,876,877],{},"To clear before typing:",[47,879,881],{"className":49,"code":880,"language":51,"meta":52,"style":52},"await user.clear(input)\nawait user.type(input, 'new value')\n",[19,882,883,895],{"__ignoreMap":52},[56,884,885,887,889,892],{"class":58,"line":59},[56,886,367],{"class":62},[56,888,164],{"class":66},[56,890,891],{"class":87},"clear",[56,893,894],{"class":66},"(input)\n",[56,896,897,899,901,903,906,909],{"class":58,"line":77},[56,898,367],{"class":62},[56,900,164],{"class":66},[56,902,185],{"class":87},[56,904,905],{"class":66},"(input, ",[56,907,908],{"class":73},"'new value'",[56,910,175],{"class":66},[788,912,914],{"id":913},"select-checkbox-and-radio","Select, checkbox, and radio",[47,916,918],{"className":49,"code":917,"language":51,"meta":52,"style":52},"\u002F\u002F Select\nawait user.selectOptions(screen.getByRole('combobox', { name: \u002Fcountry\u002Fi }), 'us')\nexpect(screen.getByRole('combobox')).toHaveDisplayValue('United States')\n\n\u002F\u002F Checkbox\nconst checkbox = screen.getByRole('checkbox', { name: \u002Faccept terms\u002Fi })\nawait user.click(checkbox)\nexpect(checkbox).toBeChecked()\n\n\u002F\u002F Radio\nawait user.click(screen.getByRole('radio', { name: \u002Fmonthly\u002Fi }))\nexpect(screen.getByRole('radio', { name: \u002Fmonthly\u002Fi })).toBeChecked()\nexpect(screen.getByRole('radio', { name: \u002Fannual\u002Fi })).not.toBeChecked()\n",[19,919,920,925,961,985,989,994,1028,1039,1051,1055,1060,1090,1118],{"__ignoreMap":52},[56,921,922],{"class":58,"line":59},[56,923,924],{"class":134},"\u002F\u002F Select\n",[56,926,927,929,931,934,936,938,940,943,945,947,950,952,954,956,959],{"class":58,"line":77},[56,928,367],{"class":62},[56,930,164],{"class":66},[56,932,933],{"class":87},"selectOptions",[56,935,272],{"class":66},[56,937,301],{"class":87},[56,939,91],{"class":66},[56,941,942],{"class":73},"'combobox'",[56,944,309],{"class":66},[56,946,312],{"class":73},[56,948,949],{"class":315},"country",[56,951,319],{"class":73},[56,953,322],{"class":62},[56,955,834],{"class":66},[56,957,958],{"class":73},"'us'",[56,960,175],{"class":66},[56,962,963,965,967,969,971,973,975,978,980,983],{"class":58,"line":84},[56,964,844],{"class":87},[56,966,272],{"class":66},[56,968,301],{"class":87},[56,970,91],{"class":66},[56,972,942],{"class":73},[56,974,283],{"class":66},[56,976,977],{"class":87},"toHaveDisplayValue",[56,979,91],{"class":66},[56,981,982],{"class":73},"'United States'",[56,984,175],{"class":66},[56,986,987],{"class":58,"line":112},[56,988,81],{"emptyLinePlaceholder":80},[56,990,991],{"class":58,"line":138},[56,992,993],{"class":134},"\u002F\u002F Checkbox\n",[56,995,996,999,1002,1004,1007,1009,1011,1014,1016,1018,1021,1023,1025],{"class":58,"line":143},[56,997,998],{"class":62},"const",[56,1000,1001],{"class":118}," checkbox",[56,1003,122],{"class":62},[56,1005,1006],{"class":66}," screen.",[56,1008,301],{"class":87},[56,1010,91],{"class":66},[56,1012,1013],{"class":73},"'checkbox'",[56,1015,309],{"class":66},[56,1017,312],{"class":73},[56,1019,1020],{"class":315},"accept terms",[56,1022,319],{"class":73},[56,1024,322],{"class":62},[56,1026,1027],{"class":66}," })\n",[56,1029,1030,1032,1034,1036],{"class":58,"line":158},[56,1031,367],{"class":62},[56,1033,164],{"class":66},[56,1035,167],{"class":87},[56,1037,1038],{"class":66},"(checkbox)\n",[56,1040,1041,1043,1046,1049],{"class":58,"line":178},[56,1042,844],{"class":87},[56,1044,1045],{"class":66},"(checkbox).",[56,1047,1048],{"class":87},"toBeChecked",[56,1050,249],{"class":66},[56,1052,1053],{"class":58,"line":194},[56,1054,81],{"emptyLinePlaceholder":80},[56,1056,1057],{"class":58,"line":549},[56,1058,1059],{"class":134},"\u002F\u002F Radio\n",[56,1061,1062,1064,1066,1068,1070,1072,1074,1077,1079,1081,1084,1086,1088],{"class":58,"line":744},[56,1063,367],{"class":62},[56,1065,164],{"class":66},[56,1067,167],{"class":87},[56,1069,272],{"class":66},[56,1071,301],{"class":87},[56,1073,91],{"class":66},[56,1075,1076],{"class":73},"'radio'",[56,1078,309],{"class":66},[56,1080,312],{"class":73},[56,1082,1083],{"class":315},"monthly",[56,1085,319],{"class":73},[56,1087,322],{"class":62},[56,1089,325],{"class":66},[56,1091,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116],{"class":58,"line":757},[56,1093,844],{"class":87},[56,1095,272],{"class":66},[56,1097,301],{"class":87},[56,1099,91],{"class":66},[56,1101,1076],{"class":73},[56,1103,309],{"class":66},[56,1105,312],{"class":73},[56,1107,1083],{"class":315},[56,1109,319],{"class":73},[56,1111,322],{"class":62},[56,1113,865],{"class":66},[56,1115,1048],{"class":87},[56,1117,249],{"class":66},[56,1119,1120,1122,1124,1126,1128,1130,1132,1134,1137,1139,1141,1144,1146],{"class":58,"line":768},[56,1121,844],{"class":87},[56,1123,272],{"class":66},[56,1125,301],{"class":87},[56,1127,91],{"class":66},[56,1129,1076],{"class":73},[56,1131,309],{"class":66},[56,1133,312],{"class":73},[56,1135,1136],{"class":315},"annual",[56,1138,319],{"class":73},[56,1140,322],{"class":62},[56,1142,1143],{"class":66}," })).not.",[56,1145,1048],{"class":87},[56,1147,249],{"class":66},[788,1149,1151],{"id":1150},"form-validation","Form validation",[15,1153,1154],{},"Test that the form shows validation errors when required fields are missing:",[47,1156,1158],{"className":49,"code":1157,"language":51,"meta":52,"style":52},"test('shows validation error for empty email', async () => {\n  const user = userEvent.setup()\n  render(\u003CLoginForm \u002F>)\n\n  \u002F\u002F Submit without filling email\n  await user.click(screen.getByRole('button', { name: \u002Fsign in\u002Fi }))\n\n  expect(screen.getByRole('alert')).toHaveTextContent(\u002Femail is required\u002Fi)\n  expect(screen.getByRole('button', { name: \u002Fsign in\u002Fi })).toBeDisabled()\n})\n",[19,1159,1160,1179,1193,1204,1208,1213,1242,1246,1277,1306],{"__ignoreMap":52},[56,1161,1162,1164,1166,1169,1171,1173,1175,1177],{"class":58,"line":59},[56,1163,88],{"class":87},[56,1165,91],{"class":66},[56,1167,1168],{"class":73},"'shows validation error for empty email'",[56,1170,97],{"class":66},[56,1172,100],{"class":62},[56,1174,103],{"class":66},[56,1176,106],{"class":62},[56,1178,109],{"class":66},[56,1180,1181,1183,1185,1187,1189,1191],{"class":58,"line":77},[56,1182,115],{"class":62},[56,1184,119],{"class":118},[56,1186,122],{"class":62},[56,1188,125],{"class":66},[56,1190,128],{"class":87},[56,1192,249],{"class":66},[56,1194,1195,1197,1199,1202],{"class":58,"line":84},[56,1196,146],{"class":87},[56,1198,149],{"class":66},[56,1200,1201],{"class":118},"LoginForm",[56,1203,155],{"class":66},[56,1205,1206],{"class":58,"line":112},[56,1207,81],{"emptyLinePlaceholder":80},[56,1209,1210],{"class":58,"line":138},[56,1211,1212],{"class":134},"  \u002F\u002F Submit without filling email\n",[56,1214,1215,1217,1219,1221,1223,1225,1227,1229,1231,1233,1236,1238,1240],{"class":58,"line":143},[56,1216,161],{"class":62},[56,1218,164],{"class":66},[56,1220,167],{"class":87},[56,1222,272],{"class":66},[56,1224,301],{"class":87},[56,1226,91],{"class":66},[56,1228,306],{"class":73},[56,1230,309],{"class":66},[56,1232,312],{"class":73},[56,1234,1235],{"class":315},"sign in",[56,1237,319],{"class":73},[56,1239,322],{"class":62},[56,1241,325],{"class":66},[56,1243,1244],{"class":58,"line":158},[56,1245,81],{"emptyLinePlaceholder":80},[56,1247,1248,1250,1252,1254,1256,1259,1261,1264,1266,1268,1271,1273,1275],{"class":58,"line":178},[56,1249,269],{"class":87},[56,1251,272],{"class":66},[56,1253,301],{"class":87},[56,1255,91],{"class":66},[56,1257,1258],{"class":73},"'alert'",[56,1260,283],{"class":66},[56,1262,1263],{"class":87},"toHaveTextContent",[56,1265,91],{"class":66},[56,1267,319],{"class":73},[56,1269,1270],{"class":315},"email is required",[56,1272,319],{"class":73},[56,1274,322],{"class":62},[56,1276,175],{"class":66},[56,1278,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297,1299,1301,1304],{"class":58,"line":194},[56,1280,269],{"class":87},[56,1282,272],{"class":66},[56,1284,301],{"class":87},[56,1286,91],{"class":66},[56,1288,306],{"class":73},[56,1290,309],{"class":66},[56,1292,312],{"class":73},[56,1294,1235],{"class":315},[56,1296,319],{"class":73},[56,1298,322],{"class":62},[56,1300,865],{"class":66},[56,1302,1303],{"class":87},"toBeDisabled",[56,1305,249],{"class":66},[56,1307,1308],{"class":58,"line":549},[56,1309,197],{"class":66},[10,1311,1313],{"id":1312},"testing-conditional-rendering","Testing conditional rendering",[15,1315,554,1316,1319,1320,1323],{},[19,1317,1318],{},"getBy*"," for elements that must be present, and ",[19,1321,1322],{},"queryBy*"," for elements\nthat might not be:",[47,1325,1327],{"className":49,"code":1326,"language":51,"meta":52,"style":52},"test('hides error when input is valid', async () => {\n  const user = userEvent.setup()\n  render(\u003CEmailInput \u002F>)\n\n  await user.type(screen.getByRole('textbox'), 'not-an-email')\n  expect(screen.getByRole('alert')).toBeInTheDocument()\n\n  await user.clear(screen.getByRole('textbox'))\n  await user.type(screen.getByRole('textbox'), 'valid@email.com')\n  expect(screen.queryByRole('alert')).not.toBeInTheDocument()\n})\n",[19,1328,1329,1348,1362,1373,1377,1400,1418,1422,1441,1464,1484],{"__ignoreMap":52},[56,1330,1331,1333,1335,1338,1340,1342,1344,1346],{"class":58,"line":59},[56,1332,88],{"class":87},[56,1334,91],{"class":66},[56,1336,1337],{"class":73},"'hides error when input is valid'",[56,1339,97],{"class":66},[56,1341,100],{"class":62},[56,1343,103],{"class":66},[56,1345,106],{"class":62},[56,1347,109],{"class":66},[56,1349,1350,1352,1354,1356,1358,1360],{"class":58,"line":77},[56,1351,115],{"class":62},[56,1353,119],{"class":118},[56,1355,122],{"class":62},[56,1357,125],{"class":66},[56,1359,128],{"class":87},[56,1361,249],{"class":66},[56,1363,1364,1366,1368,1371],{"class":58,"line":84},[56,1365,146],{"class":87},[56,1367,149],{"class":66},[56,1369,1370],{"class":118},"EmailInput",[56,1372,155],{"class":66},[56,1374,1375],{"class":58,"line":112},[56,1376,81],{"emptyLinePlaceholder":80},[56,1378,1379,1381,1383,1385,1387,1389,1391,1393,1395,1398],{"class":58,"line":138},[56,1380,161],{"class":62},[56,1382,164],{"class":66},[56,1384,185],{"class":87},[56,1386,272],{"class":66},[56,1388,301],{"class":87},[56,1390,91],{"class":66},[56,1392,820],{"class":73},[56,1394,673],{"class":66},[56,1396,1397],{"class":73},"'not-an-email'",[56,1399,175],{"class":66},[56,1401,1402,1404,1406,1408,1410,1412,1414,1416],{"class":58,"line":143},[56,1403,269],{"class":87},[56,1405,272],{"class":66},[56,1407,301],{"class":87},[56,1409,91],{"class":66},[56,1411,1258],{"class":73},[56,1413,283],{"class":66},[56,1415,286],{"class":87},[56,1417,249],{"class":66},[56,1419,1420],{"class":58,"line":158},[56,1421,81],{"emptyLinePlaceholder":80},[56,1423,1424,1426,1428,1430,1432,1434,1436,1438],{"class":58,"line":178},[56,1425,161],{"class":62},[56,1427,164],{"class":66},[56,1429,891],{"class":87},[56,1431,272],{"class":66},[56,1433,301],{"class":87},[56,1435,91],{"class":66},[56,1437,820],{"class":73},[56,1439,1440],{"class":66},"))\n",[56,1442,1443,1445,1447,1449,1451,1453,1455,1457,1459,1462],{"class":58,"line":194},[56,1444,161],{"class":62},[56,1446,164],{"class":66},[56,1448,185],{"class":87},[56,1450,272],{"class":66},[56,1452,301],{"class":87},[56,1454,91],{"class":66},[56,1456,820],{"class":73},[56,1458,673],{"class":66},[56,1460,1461],{"class":73},"'valid@email.com'",[56,1463,175],{"class":66},[56,1465,1466,1468,1470,1473,1475,1477,1480,1482],{"class":58,"line":549},[56,1467,269],{"class":87},[56,1469,272],{"class":66},[56,1471,1472],{"class":87},"queryByRole",[56,1474,91],{"class":66},[56,1476,1258],{"class":73},[56,1478,1479],{"class":66},")).not.",[56,1481,286],{"class":87},[56,1483,249],{"class":66},[56,1485,1486],{"class":58,"line":744},[56,1487,197],{"class":66},[15,1489,1490],{},"Testing a toggle:",[47,1492,1494],{"className":49,"code":1493,"language":51,"meta":52,"style":52},"test('accordion expands and collapses', async () => {\n  const user = userEvent.setup()\n  render(\u003CAccordion title=\"Details\">\u003Cp>Hidden content\u003C\u002Fp>\u003C\u002FAccordion>)\n\n  expect(screen.queryByText('Hidden content')).not.toBeInTheDocument()\n\n  await user.click(screen.getByRole('button', { name: \u002Fdetails\u002Fi }))\n  expect(screen.getByText('Hidden content')).toBeInTheDocument()\n\n  await user.click(screen.getByRole('button', { name: \u002Fdetails\u002Fi }))\n  expect(screen.queryByText('Hidden content')).not.toBeInTheDocument()\n})\n",[19,1495,1496,1515,1529,1565,1569,1589,1593,1622,1640,1644,1672,1690],{"__ignoreMap":52},[56,1497,1498,1500,1502,1505,1507,1509,1511,1513],{"class":58,"line":59},[56,1499,88],{"class":87},[56,1501,91],{"class":66},[56,1503,1504],{"class":73},"'accordion expands and collapses'",[56,1506,97],{"class":66},[56,1508,100],{"class":62},[56,1510,103],{"class":66},[56,1512,106],{"class":62},[56,1514,109],{"class":66},[56,1516,1517,1519,1521,1523,1525,1527],{"class":58,"line":77},[56,1518,115],{"class":62},[56,1520,119],{"class":118},[56,1522,122],{"class":62},[56,1524,125],{"class":66},[56,1526,128],{"class":87},[56,1528,249],{"class":66},[56,1530,1531,1533,1535,1538,1541,1543,1546,1549,1552,1555,1557,1560,1562],{"class":58,"line":84},[56,1532,146],{"class":87},[56,1534,149],{"class":66},[56,1536,1537],{"class":118},"Accordion",[56,1539,1540],{"class":87}," title",[56,1542,457],{"class":62},[56,1544,1545],{"class":73},"\"Details\"",[56,1547,1548],{"class":66},">\u003C",[56,1550,15],{"class":1551},"s9eBZ",[56,1553,1554],{"class":66},">Hidden content\u003C\u002F",[56,1556,15],{"class":1551},[56,1558,1559],{"class":66},">\u003C\u002F",[56,1561,1537],{"class":118},[56,1563,1564],{"class":66},">)\n",[56,1566,1567],{"class":58,"line":112},[56,1568,81],{"emptyLinePlaceholder":80},[56,1570,1571,1573,1575,1578,1580,1583,1585,1587],{"class":58,"line":138},[56,1572,269],{"class":87},[56,1574,272],{"class":66},[56,1576,1577],{"class":87},"queryByText",[56,1579,91],{"class":66},[56,1581,1582],{"class":73},"'Hidden content'",[56,1584,1479],{"class":66},[56,1586,286],{"class":87},[56,1588,249],{"class":66},[56,1590,1591],{"class":58,"line":143},[56,1592,81],{"emptyLinePlaceholder":80},[56,1594,1595,1597,1599,1601,1603,1605,1607,1609,1611,1613,1616,1618,1620],{"class":58,"line":158},[56,1596,161],{"class":62},[56,1598,164],{"class":66},[56,1600,167],{"class":87},[56,1602,272],{"class":66},[56,1604,301],{"class":87},[56,1606,91],{"class":66},[56,1608,306],{"class":73},[56,1610,309],{"class":66},[56,1612,312],{"class":73},[56,1614,1615],{"class":315},"details",[56,1617,319],{"class":73},[56,1619,322],{"class":62},[56,1621,325],{"class":66},[56,1623,1624,1626,1628,1630,1632,1634,1636,1638],{"class":58,"line":178},[56,1625,269],{"class":87},[56,1627,272],{"class":66},[56,1629,275],{"class":87},[56,1631,91],{"class":66},[56,1633,1582],{"class":73},[56,1635,283],{"class":66},[56,1637,286],{"class":87},[56,1639,249],{"class":66},[56,1641,1642],{"class":58,"line":194},[56,1643,81],{"emptyLinePlaceholder":80},[56,1645,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670],{"class":58,"line":549},[56,1647,161],{"class":62},[56,1649,164],{"class":66},[56,1651,167],{"class":87},[56,1653,272],{"class":66},[56,1655,301],{"class":87},[56,1657,91],{"class":66},[56,1659,306],{"class":73},[56,1661,309],{"class":66},[56,1663,312],{"class":73},[56,1665,1615],{"class":315},[56,1667,319],{"class":73},[56,1669,322],{"class":62},[56,1671,325],{"class":66},[56,1673,1674,1676,1678,1680,1682,1684,1686,1688],{"class":58,"line":744},[56,1675,269],{"class":87},[56,1677,272],{"class":66},[56,1679,1577],{"class":87},[56,1681,91],{"class":66},[56,1683,1582],{"class":73},[56,1685,1479],{"class":66},[56,1687,286],{"class":87},[56,1689,249],{"class":66},[56,1691,1692],{"class":58,"line":757},[56,1693,197],{"class":66},[10,1695,1697],{"id":1696},"testing-components-with-context","Testing components with context",[15,1699,1700,1701,1704],{},"Wrap the component in its required provider via the ",[19,1702,1703],{},"wrapper"," option, or\ncreate a custom render helper:",[47,1706,1708],{"className":49,"code":1707,"language":51,"meta":52,"style":52},"function renderWithTheme(ui, { theme = 'light' } = {}) {\n  return render(ui, {\n    wrapper: ({ children }) => (\n      \u003CThemeProvider initialTheme={theme}>{children}\u003C\u002FThemeProvider>\n    ),\n  })\n}\n\ntest('switches theme on toggle', async () => {\n  const user = userEvent.setup()\n  renderWithTheme(\u003CThemeToggle \u002F>)\n\n  await user.click(screen.getByRole('button', { name: \u002Fdark mode\u002Fi }))\n  expect(document.body).toHaveClass('dark')\n})\n",[19,1709,1710,1743,1754,1773,1794,1799,1803,1808,1812,1831,1845,1857,1861,1890,1907],{"__ignoreMap":52},[56,1711,1712,1715,1718,1720,1724,1727,1730,1732,1735,1738,1740],{"class":58,"line":59},[56,1713,1714],{"class":62},"function",[56,1716,1717],{"class":87}," renderWithTheme",[56,1719,91],{"class":66},[56,1721,1723],{"class":1722},"s4XuR","ui",[56,1725,1726],{"class":66},", { ",[56,1728,1729],{"class":1722},"theme",[56,1731,122],{"class":62},[56,1733,1734],{"class":73}," 'light'",[56,1736,1737],{"class":66}," } ",[56,1739,457],{"class":62},[56,1741,1742],{"class":66}," {}) {\n",[56,1744,1745,1748,1751],{"class":58,"line":77},[56,1746,1747],{"class":62},"  return",[56,1749,1750],{"class":87}," render",[56,1752,1753],{"class":66},"(ui, {\n",[56,1755,1756,1759,1762,1765,1768,1770],{"class":58,"line":84},[56,1757,1758],{"class":87},"    wrapper",[56,1760,1761],{"class":66},": ({ ",[56,1763,1764],{"class":1722},"children",[56,1766,1767],{"class":66}," }) ",[56,1769,106],{"class":62},[56,1771,1772],{"class":66}," (\n",[56,1774,1775,1778,1781,1784,1786,1789,1791],{"class":58,"line":112},[56,1776,1777],{"class":66},"      \u003C",[56,1779,1780],{"class":118},"ThemeProvider",[56,1782,1783],{"class":87}," initialTheme",[56,1785,457],{"class":62},[56,1787,1788],{"class":66},"{theme}>{children}\u003C\u002F",[56,1790,1780],{"class":118},[56,1792,1793],{"class":66},">\n",[56,1795,1796],{"class":58,"line":138},[56,1797,1798],{"class":66},"    ),\n",[56,1800,1801],{"class":58,"line":143},[56,1802,781],{"class":66},[56,1804,1805],{"class":58,"line":158},[56,1806,1807],{"class":66},"}\n",[56,1809,1810],{"class":58,"line":178},[56,1811,81],{"emptyLinePlaceholder":80},[56,1813,1814,1816,1818,1821,1823,1825,1827,1829],{"class":58,"line":194},[56,1815,88],{"class":87},[56,1817,91],{"class":66},[56,1819,1820],{"class":73},"'switches theme on toggle'",[56,1822,97],{"class":66},[56,1824,100],{"class":62},[56,1826,103],{"class":66},[56,1828,106],{"class":62},[56,1830,109],{"class":66},[56,1832,1833,1835,1837,1839,1841,1843],{"class":58,"line":549},[56,1834,115],{"class":62},[56,1836,119],{"class":118},[56,1838,122],{"class":62},[56,1840,125],{"class":66},[56,1842,128],{"class":87},[56,1844,249],{"class":66},[56,1846,1847,1850,1852,1855],{"class":58,"line":744},[56,1848,1849],{"class":87},"  renderWithTheme",[56,1851,149],{"class":66},[56,1853,1854],{"class":118},"ThemeToggle",[56,1856,155],{"class":66},[56,1858,1859],{"class":58,"line":757},[56,1860,81],{"emptyLinePlaceholder":80},[56,1862,1863,1865,1867,1869,1871,1873,1875,1877,1879,1881,1884,1886,1888],{"class":58,"line":768},[56,1864,161],{"class":62},[56,1866,164],{"class":66},[56,1868,167],{"class":87},[56,1870,272],{"class":66},[56,1872,301],{"class":87},[56,1874,91],{"class":66},[56,1876,306],{"class":73},[56,1878,309],{"class":66},[56,1880,312],{"class":73},[56,1882,1883],{"class":315},"dark mode",[56,1885,319],{"class":73},[56,1887,322],{"class":62},[56,1889,325],{"class":66},[56,1891,1892,1894,1897,1900,1902,1905],{"class":58,"line":778},[56,1893,269],{"class":87},[56,1895,1896],{"class":66},"(document.body).",[56,1898,1899],{"class":87},"toHaveClass",[56,1901,91],{"class":66},[56,1903,1904],{"class":73},"'dark'",[56,1906,175],{"class":66},[56,1908,1909],{"class":58,"line":784},[56,1910,197],{"class":66},[15,1912,1913],{},"Use the real provider — not a mocked context value — to catch integration\nbugs between the provider and consumer.",[10,1915,1917],{"id":1916},"testing-components-with-react-router","Testing components with React Router",[15,1919,1920,1921,1924],{},"Wrap with ",[19,1922,1923],{},"MemoryRouter"," to provide a router context without a real browser:",[47,1926,1928],{"className":49,"code":1927,"language":51,"meta":52,"style":52},"test('shows 404 for unknown route', () => {\n  render(\n    \u003CMemoryRouter initialEntries={['\u002Funknown']}>\n      \u003CRoutes>\n        \u003CRoute path=\"\u002F\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\"*\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002FMemoryRouter>\n  )\n  expect(screen.getByText('Page Not Found')).toBeInTheDocument()\n})\n\ntest('navigates to profile after login', async () => {\n  const user = userEvent.setup()\n  render(\n    \u003CMemoryRouter initialEntries={['\u002Flogin']}>\n      \u003CRoutes>\n        \u003CRoute path=\"\u002Flogin\" element={\u003CLoginPage \u002F>} \u002F>\n        \u003CRoute path=\"\u002Fprofile\" element={\u003CProfilePage \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002FMemoryRouter>\n  )\n\n  await user.type(screen.getByLabelText(\u002Femail\u002Fi), 'user@test.com')\n  await user.type(screen.getByLabelText(\u002Fpassword\u002Fi), 'pass123')\n  await user.click(screen.getByRole('button', { name: \u002Fsign in\u002Fi }))\n\n  expect(await screen.findByText('My Profile')).toBeInTheDocument()\n})\n",[19,1929,1930,1946,1953,1974,1983,2013,2037,2046,2055,2060,2079,2083,2087,2106,2120,2126,2144,2153,2178,2203,2212,2221,2226,2231,2261,2291,2320,2325,2350],{"__ignoreMap":52},[56,1931,1932,1934,1936,1939,1942,1944],{"class":58,"line":59},[56,1933,88],{"class":87},[56,1935,91],{"class":66},[56,1937,1938],{"class":73},"'shows 404 for unknown route'",[56,1940,1941],{"class":66},", () ",[56,1943,106],{"class":62},[56,1945,109],{"class":66},[56,1947,1948,1950],{"class":58,"line":77},[56,1949,146],{"class":87},[56,1951,1952],{"class":66},"(\n",[56,1954,1955,1958,1960,1963,1965,1968,1971],{"class":58,"line":84},[56,1956,1957],{"class":66},"    \u003C",[56,1959,1923],{"class":118},[56,1961,1962],{"class":87}," initialEntries",[56,1964,457],{"class":62},[56,1966,1967],{"class":66},"{[",[56,1969,1970],{"class":73},"'\u002Funknown'",[56,1972,1973],{"class":66},"]}>\n",[56,1975,1976,1978,1981],{"class":58,"line":112},[56,1977,1777],{"class":66},[56,1979,1980],{"class":118},"Routes",[56,1982,1793],{"class":66},[56,1984,1985,1988,1991,1994,1996,1999,2002,2004,2007,2010],{"class":58,"line":138},[56,1986,1987],{"class":66},"        \u003C",[56,1989,1990],{"class":118},"Route",[56,1992,1993],{"class":87}," path",[56,1995,457],{"class":62},[56,1997,1998],{"class":73},"\"\u002F\"",[56,2000,2001],{"class":87}," element",[56,2003,457],{"class":62},[56,2005,2006],{"class":66},"{\u003C",[56,2008,2009],{"class":118},"Home",[56,2011,2012],{"class":66}," \u002F>} \u002F>\n",[56,2014,2015,2017,2019,2021,2023,2026,2028,2030,2032,2035],{"class":58,"line":143},[56,2016,1987],{"class":66},[56,2018,1990],{"class":118},[56,2020,1993],{"class":87},[56,2022,457],{"class":62},[56,2024,2025],{"class":73},"\"*\"",[56,2027,2001],{"class":87},[56,2029,457],{"class":62},[56,2031,2006],{"class":66},[56,2033,2034],{"class":118},"NotFound",[56,2036,2012],{"class":66},[56,2038,2039,2042,2044],{"class":58,"line":158},[56,2040,2041],{"class":66},"      \u003C\u002F",[56,2043,1980],{"class":118},[56,2045,1793],{"class":66},[56,2047,2048,2051,2053],{"class":58,"line":178},[56,2049,2050],{"class":66},"    \u003C\u002F",[56,2052,1923],{"class":118},[56,2054,1793],{"class":66},[56,2056,2057],{"class":58,"line":194},[56,2058,2059],{"class":66},"  )\n",[56,2061,2062,2064,2066,2068,2070,2073,2075,2077],{"class":58,"line":549},[56,2063,269],{"class":87},[56,2065,272],{"class":66},[56,2067,275],{"class":87},[56,2069,91],{"class":66},[56,2071,2072],{"class":73},"'Page Not Found'",[56,2074,283],{"class":66},[56,2076,286],{"class":87},[56,2078,249],{"class":66},[56,2080,2081],{"class":58,"line":744},[56,2082,197],{"class":66},[56,2084,2085],{"class":58,"line":757},[56,2086,81],{"emptyLinePlaceholder":80},[56,2088,2089,2091,2093,2096,2098,2100,2102,2104],{"class":58,"line":768},[56,2090,88],{"class":87},[56,2092,91],{"class":66},[56,2094,2095],{"class":73},"'navigates to profile after login'",[56,2097,97],{"class":66},[56,2099,100],{"class":62},[56,2101,103],{"class":66},[56,2103,106],{"class":62},[56,2105,109],{"class":66},[56,2107,2108,2110,2112,2114,2116,2118],{"class":58,"line":778},[56,2109,115],{"class":62},[56,2111,119],{"class":118},[56,2113,122],{"class":62},[56,2115,125],{"class":66},[56,2117,128],{"class":87},[56,2119,249],{"class":66},[56,2121,2122,2124],{"class":58,"line":784},[56,2123,146],{"class":87},[56,2125,1952],{"class":66},[56,2127,2129,2131,2133,2135,2137,2139,2142],{"class":58,"line":2128},16,[56,2130,1957],{"class":66},[56,2132,1923],{"class":118},[56,2134,1962],{"class":87},[56,2136,457],{"class":62},[56,2138,1967],{"class":66},[56,2140,2141],{"class":73},"'\u002Flogin'",[56,2143,1973],{"class":66},[56,2145,2147,2149,2151],{"class":58,"line":2146},17,[56,2148,1777],{"class":66},[56,2150,1980],{"class":118},[56,2152,1793],{"class":66},[56,2154,2156,2158,2160,2162,2164,2167,2169,2171,2173,2176],{"class":58,"line":2155},18,[56,2157,1987],{"class":66},[56,2159,1990],{"class":118},[56,2161,1993],{"class":87},[56,2163,457],{"class":62},[56,2165,2166],{"class":73},"\"\u002Flogin\"",[56,2168,2001],{"class":87},[56,2170,457],{"class":62},[56,2172,2006],{"class":66},[56,2174,2175],{"class":118},"LoginPage",[56,2177,2012],{"class":66},[56,2179,2181,2183,2185,2187,2189,2192,2194,2196,2198,2201],{"class":58,"line":2180},19,[56,2182,1987],{"class":66},[56,2184,1990],{"class":118},[56,2186,1993],{"class":87},[56,2188,457],{"class":62},[56,2190,2191],{"class":73},"\"\u002Fprofile\"",[56,2193,2001],{"class":87},[56,2195,457],{"class":62},[56,2197,2006],{"class":66},[56,2199,2200],{"class":118},"ProfilePage",[56,2202,2012],{"class":66},[56,2204,2206,2208,2210],{"class":58,"line":2205},20,[56,2207,2041],{"class":66},[56,2209,1980],{"class":118},[56,2211,1793],{"class":66},[56,2213,2215,2217,2219],{"class":58,"line":2214},21,[56,2216,2050],{"class":66},[56,2218,1923],{"class":118},[56,2220,1793],{"class":66},[56,2222,2224],{"class":58,"line":2223},22,[56,2225,2059],{"class":66},[56,2227,2229],{"class":58,"line":2228},23,[56,2230,81],{"emptyLinePlaceholder":80},[56,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256,2259],{"class":58,"line":2233},24,[56,2235,161],{"class":62},[56,2237,164],{"class":66},[56,2239,185],{"class":87},[56,2241,272],{"class":66},[56,2243,659],{"class":87},[56,2245,91],{"class":66},[56,2247,319],{"class":73},[56,2249,666],{"class":315},[56,2251,319],{"class":73},[56,2253,322],{"class":62},[56,2255,673],{"class":66},[56,2257,2258],{"class":73},"'user@test.com'",[56,2260,175],{"class":66},[56,2262,2264,2266,2268,2270,2272,2274,2276,2278,2280,2282,2284,2286,2289],{"class":58,"line":2263},25,[56,2265,161],{"class":62},[56,2267,164],{"class":66},[56,2269,185],{"class":87},[56,2271,272],{"class":66},[56,2273,659],{"class":87},[56,2275,91],{"class":66},[56,2277,319],{"class":73},[56,2279,697],{"class":315},[56,2281,319],{"class":73},[56,2283,322],{"class":62},[56,2285,673],{"class":66},[56,2287,2288],{"class":73},"'pass123'",[56,2290,175],{"class":66},[56,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318],{"class":58,"line":2293},26,[56,2295,161],{"class":62},[56,2297,164],{"class":66},[56,2299,167],{"class":87},[56,2301,272],{"class":66},[56,2303,301],{"class":87},[56,2305,91],{"class":66},[56,2307,306],{"class":73},[56,2309,309],{"class":66},[56,2311,312],{"class":73},[56,2313,1235],{"class":315},[56,2315,319],{"class":73},[56,2317,322],{"class":62},[56,2319,325],{"class":66},[56,2321,2323],{"class":58,"line":2322},27,[56,2324,81],{"emptyLinePlaceholder":80},[56,2326,2328,2330,2332,2334,2336,2339,2341,2344,2346,2348],{"class":58,"line":2327},28,[56,2329,269],{"class":87},[56,2331,91],{"class":66},[56,2333,367],{"class":62},[56,2335,1006],{"class":66},[56,2337,2338],{"class":87},"findByText",[56,2340,91],{"class":66},[56,2342,2343],{"class":73},"'My Profile'",[56,2345,283],{"class":66},[56,2347,286],{"class":87},[56,2349,249],{"class":66},[56,2351,2353],{"class":58,"line":2352},29,[56,2354,197],{"class":66},[10,2356,2358],{"id":2357},"testing-modals-and-portals","Testing modals and portals",[15,2360,2361,2364,2365,2368],{},[19,2362,2363],{},"screen"," queries target ",[19,2366,2367],{},"document.body",", so portal content is automatically\nincluded — no special setup needed:",[47,2370,2372],{"className":49,"code":2371,"language":51,"meta":52,"style":52},"test('opens and closes a modal', async () => {\n  const user = userEvent.setup()\n  render(\u003CConfirmDeleteModal trigger={\u003Cbutton>Delete\u003C\u002Fbutton>} onConfirm={vi.fn()} \u002F>)\n\n  expect(screen.queryByRole('dialog')).not.toBeInTheDocument()\n\n  await user.click(screen.getByRole('button', { name: \u002Fdelete\u002Fi }))\n  expect(screen.getByRole('dialog')).toBeInTheDocument()\n\n  await user.keyboard('{Escape}')\n  expect(screen.queryByRole('dialog')).not.toBeInTheDocument()\n})\n",[19,2373,2374,2393,2407,2447,2451,2470,2474,2503,2521,2525,2541,2559],{"__ignoreMap":52},[56,2375,2376,2378,2380,2383,2385,2387,2389,2391],{"class":58,"line":59},[56,2377,88],{"class":87},[56,2379,91],{"class":66},[56,2381,2382],{"class":73},"'opens and closes a modal'",[56,2384,97],{"class":66},[56,2386,100],{"class":62},[56,2388,103],{"class":66},[56,2390,106],{"class":62},[56,2392,109],{"class":66},[56,2394,2395,2397,2399,2401,2403,2405],{"class":58,"line":77},[56,2396,115],{"class":62},[56,2398,119],{"class":118},[56,2400,122],{"class":62},[56,2402,125],{"class":66},[56,2404,128],{"class":87},[56,2406,249],{"class":66},[56,2408,2409,2411,2413,2416,2419,2421,2423,2426,2429,2431,2434,2437,2439,2442,2444],{"class":58,"line":84},[56,2410,146],{"class":87},[56,2412,149],{"class":66},[56,2414,2415],{"class":118},"ConfirmDeleteModal",[56,2417,2418],{"class":87}," trigger",[56,2420,457],{"class":62},[56,2422,2006],{"class":66},[56,2424,2425],{"class":1551},"button",[56,2427,2428],{"class":66},">Delete\u003C\u002F",[56,2430,2425],{"class":1551},[56,2432,2433],{"class":66},">} ",[56,2435,2436],{"class":87},"onConfirm",[56,2438,457],{"class":62},[56,2440,2441],{"class":66},"{vi.",[56,2443,422],{"class":87},[56,2445,2446],{"class":66},"()} \u002F>)\n",[56,2448,2449],{"class":58,"line":112},[56,2450,81],{"emptyLinePlaceholder":80},[56,2452,2453,2455,2457,2459,2461,2464,2466,2468],{"class":58,"line":138},[56,2454,269],{"class":87},[56,2456,272],{"class":66},[56,2458,1472],{"class":87},[56,2460,91],{"class":66},[56,2462,2463],{"class":73},"'dialog'",[56,2465,1479],{"class":66},[56,2467,286],{"class":87},[56,2469,249],{"class":66},[56,2471,2472],{"class":58,"line":143},[56,2473,81],{"emptyLinePlaceholder":80},[56,2475,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2497,2499,2501],{"class":58,"line":158},[56,2477,161],{"class":62},[56,2479,164],{"class":66},[56,2481,167],{"class":87},[56,2483,272],{"class":66},[56,2485,301],{"class":87},[56,2487,91],{"class":66},[56,2489,306],{"class":73},[56,2491,309],{"class":66},[56,2493,312],{"class":73},[56,2495,2496],{"class":315},"delete",[56,2498,319],{"class":73},[56,2500,322],{"class":62},[56,2502,325],{"class":66},[56,2504,2505,2507,2509,2511,2513,2515,2517,2519],{"class":58,"line":178},[56,2506,269],{"class":87},[56,2508,272],{"class":66},[56,2510,301],{"class":87},[56,2512,91],{"class":66},[56,2514,2463],{"class":73},[56,2516,283],{"class":66},[56,2518,286],{"class":87},[56,2520,249],{"class":66},[56,2522,2523],{"class":58,"line":194},[56,2524,81],{"emptyLinePlaceholder":80},[56,2526,2527,2529,2531,2534,2536,2539],{"class":58,"line":549},[56,2528,161],{"class":62},[56,2530,164],{"class":66},[56,2532,2533],{"class":87},"keyboard",[56,2535,91],{"class":66},[56,2537,2538],{"class":73},"'{Escape}'",[56,2540,175],{"class":66},[56,2542,2543,2545,2547,2549,2551,2553,2555,2557],{"class":58,"line":744},[56,2544,269],{"class":87},[56,2546,272],{"class":66},[56,2548,1472],{"class":87},[56,2550,91],{"class":66},[56,2552,2463],{"class":73},[56,2554,1479],{"class":66},[56,2556,286],{"class":87},[56,2558,249],{"class":66},[56,2560,2561],{"class":58,"line":757},[56,2562,197],{"class":66},[15,2564,554,2565,2568],{},[19,2566,2567],{},"within()"," to scope queries inside the modal:",[47,2570,2572],{"className":49,"code":2571,"language":51,"meta":52,"style":52},"const dialog = screen.getByRole('dialog')\nexpect(within(dialog).getByRole('heading')).toHaveTextContent('Confirm Deletion')\nawait user.click(within(dialog).getByRole('button', { name: \u002Fconfirm\u002Fi }))\n",[19,2573,2574,2593,2623],{"__ignoreMap":52},[56,2575,2576,2578,2581,2583,2585,2587,2589,2591],{"class":58,"line":59},[56,2577,998],{"class":62},[56,2579,2580],{"class":118}," dialog",[56,2582,122],{"class":62},[56,2584,1006],{"class":66},[56,2586,301],{"class":87},[56,2588,91],{"class":66},[56,2590,2463],{"class":73},[56,2592,175],{"class":66},[56,2594,2595,2597,2599,2602,2605,2607,2609,2612,2614,2616,2618,2621],{"class":58,"line":77},[56,2596,844],{"class":87},[56,2598,91],{"class":66},[56,2600,2601],{"class":87},"within",[56,2603,2604],{"class":66},"(dialog).",[56,2606,301],{"class":87},[56,2608,91],{"class":66},[56,2610,2611],{"class":73},"'heading'",[56,2613,283],{"class":66},[56,2615,1263],{"class":87},[56,2617,91],{"class":66},[56,2619,2620],{"class":73},"'Confirm Deletion'",[56,2622,175],{"class":66},[56,2624,2625,2627,2629,2631,2633,2635,2637,2639,2641,2643,2645,2647,2650,2652,2654],{"class":58,"line":84},[56,2626,367],{"class":62},[56,2628,164],{"class":66},[56,2630,167],{"class":87},[56,2632,91],{"class":66},[56,2634,2601],{"class":87},[56,2636,2604],{"class":66},[56,2638,301],{"class":87},[56,2640,91],{"class":66},[56,2642,306],{"class":73},[56,2644,309],{"class":66},[56,2646,312],{"class":73},[56,2648,2649],{"class":315},"confirm",[56,2651,319],{"class":73},[56,2653,322],{"class":62},[56,2655,325],{"class":66},[10,2657,2659],{"id":2658},"testing-keyboard-navigation","Testing keyboard navigation",[15,2661,2662,2665,2666,2669,2670,2673],{},[19,2663,2664],{},"user.tab()"," moves focus forward; ",[19,2667,2668],{},"user.keyboard('{Shift>}{Tab}{\u002FShift}')"," moves\nit backward. Combine with ",[19,2671,2672],{},"toHaveFocus()",":",[47,2675,2677],{"className":49,"code":2676,"language":51,"meta":52,"style":52},"test('tab cycles through interactive elements', async () => {\n  const user = userEvent.setup()\n  render(\u003CModal open>\u003Cinput aria-label=\"Name\" \u002F>\u003Cbutton>Save\u003C\u002Fbutton>\u003C\u002FModal>)\n\n  const input = screen.getByRole('textbox', { name: \u002Fname\u002Fi })\n  const saveBtn = screen.getByRole('button', { name: \u002Fsave\u002Fi })\n\n  input.focus()\n  await user.tab()\n  expect(saveBtn).toHaveFocus()\n\n  await user.tab()\n  expect(input).toHaveFocus()   \u002F\u002F focus trap wraps back\n})\n",[19,2678,2679,2698,2712,2753,2757,2786,2816,2820,2830,2841,2853,2857,2867,2881],{"__ignoreMap":52},[56,2680,2681,2683,2685,2688,2690,2692,2694,2696],{"class":58,"line":59},[56,2682,88],{"class":87},[56,2684,91],{"class":66},[56,2686,2687],{"class":73},"'tab cycles through interactive elements'",[56,2689,97],{"class":66},[56,2691,100],{"class":62},[56,2693,103],{"class":66},[56,2695,106],{"class":62},[56,2697,109],{"class":66},[56,2699,2700,2702,2704,2706,2708,2710],{"class":58,"line":77},[56,2701,115],{"class":62},[56,2703,119],{"class":118},[56,2705,122],{"class":62},[56,2707,125],{"class":66},[56,2709,128],{"class":87},[56,2711,249],{"class":66},[56,2713,2714,2716,2718,2721,2724,2726,2729,2732,2734,2737,2740,2742,2745,2747,2749,2751],{"class":58,"line":84},[56,2715,146],{"class":87},[56,2717,149],{"class":66},[56,2719,2720],{"class":118},"Modal",[56,2722,2723],{"class":87}," open",[56,2725,1548],{"class":66},[56,2727,2728],{"class":1551},"input",[56,2730,2731],{"class":87}," aria-label",[56,2733,457],{"class":62},[56,2735,2736],{"class":73},"\"Name\"",[56,2738,2739],{"class":66}," \u002F>\u003C",[56,2741,2425],{"class":1551},[56,2743,2744],{"class":66},">Save\u003C\u002F",[56,2746,2425],{"class":1551},[56,2748,1559],{"class":66},[56,2750,2720],{"class":118},[56,2752,1564],{"class":66},[56,2754,2755],{"class":58,"line":112},[56,2756,81],{"emptyLinePlaceholder":80},[56,2758,2759,2761,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784],{"class":58,"line":138},[56,2760,115],{"class":62},[56,2762,2763],{"class":118}," input",[56,2765,122],{"class":62},[56,2767,1006],{"class":66},[56,2769,301],{"class":87},[56,2771,91],{"class":66},[56,2773,820],{"class":73},[56,2775,309],{"class":66},[56,2777,312],{"class":73},[56,2779,469],{"class":315},[56,2781,319],{"class":73},[56,2783,322],{"class":62},[56,2785,1027],{"class":66},[56,2787,2788,2790,2793,2795,2797,2799,2801,2803,2805,2807,2810,2812,2814],{"class":58,"line":143},[56,2789,115],{"class":62},[56,2791,2792],{"class":118}," saveBtn",[56,2794,122],{"class":62},[56,2796,1006],{"class":66},[56,2798,301],{"class":87},[56,2800,91],{"class":66},[56,2802,306],{"class":73},[56,2804,309],{"class":66},[56,2806,312],{"class":73},[56,2808,2809],{"class":315},"save",[56,2811,319],{"class":73},[56,2813,322],{"class":62},[56,2815,1027],{"class":66},[56,2817,2818],{"class":58,"line":158},[56,2819,81],{"emptyLinePlaceholder":80},[56,2821,2822,2825,2828],{"class":58,"line":178},[56,2823,2824],{"class":66},"  input.",[56,2826,2827],{"class":87},"focus",[56,2829,249],{"class":66},[56,2831,2832,2834,2836,2839],{"class":58,"line":194},[56,2833,161],{"class":62},[56,2835,164],{"class":66},[56,2837,2838],{"class":87},"tab",[56,2840,249],{"class":66},[56,2842,2843,2845,2848,2851],{"class":58,"line":549},[56,2844,269],{"class":87},[56,2846,2847],{"class":66},"(saveBtn).",[56,2849,2850],{"class":87},"toHaveFocus",[56,2852,249],{"class":66},[56,2854,2855],{"class":58,"line":744},[56,2856,81],{"emptyLinePlaceholder":80},[56,2858,2859,2861,2863,2865],{"class":58,"line":757},[56,2860,161],{"class":62},[56,2862,164],{"class":66},[56,2864,2838],{"class":87},[56,2866,249],{"class":66},[56,2868,2869,2871,2874,2876,2878],{"class":58,"line":768},[56,2870,269],{"class":87},[56,2872,2873],{"class":66},"(input).",[56,2875,2850],{"class":87},[56,2877,131],{"class":66},[56,2879,2880],{"class":134},"\u002F\u002F focus trap wraps back\n",[56,2882,2883],{"class":58,"line":778},[56,2884,197],{"class":66},[10,2886,2888],{"id":2887},"testing-error-boundaries","Testing error boundaries",[15,2890,2891,2892,2895],{},"Error boundaries catch children that throw during render. Suppress\n",[19,2893,2894],{},"console.error"," to keep test output clean:",[47,2897,2899],{"className":49,"code":2898,"language":51,"meta":52,"style":52},"function ThrowOnMount() {\n  throw new Error('Render failure')\n}\n\ntest('renders fallback on child error', () => {\n  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})\n\n  render(\n    \u003CErrorBoundary fallback={\u003Cp>Something went wrong\u003C\u002Fp>}>\n      \u003CThrowOnMount \u002F>\n    \u003C\u002FErrorBoundary>\n  )\n\n  expect(screen.getByText('Something went wrong')).toBeInTheDocument()\n  consoleSpy.mockRestore()\n})\n",[19,2900,2901,2911,2929,2933,2937,2952,2986,2990,2996,3020,3030,3038,3042,3046,3065,3075],{"__ignoreMap":52},[56,2902,2903,2905,2908],{"class":58,"line":59},[56,2904,1714],{"class":62},[56,2906,2907],{"class":87}," ThrowOnMount",[56,2909,2910],{"class":66},"() {\n",[56,2912,2913,2916,2919,2922,2924,2927],{"class":58,"line":77},[56,2914,2915],{"class":62},"  throw",[56,2917,2918],{"class":62}," new",[56,2920,2921],{"class":87}," Error",[56,2923,91],{"class":66},[56,2925,2926],{"class":73},"'Render failure'",[56,2928,175],{"class":66},[56,2930,2931],{"class":58,"line":84},[56,2932,1807],{"class":66},[56,2934,2935],{"class":58,"line":112},[56,2936,81],{"emptyLinePlaceholder":80},[56,2938,2939,2941,2943,2946,2948,2950],{"class":58,"line":138},[56,2940,88],{"class":87},[56,2942,91],{"class":66},[56,2944,2945],{"class":73},"'renders fallback on child error'",[56,2947,1941],{"class":66},[56,2949,106],{"class":62},[56,2951,109],{"class":66},[56,2953,2954,2956,2959,2961,2963,2966,2969,2972,2975,2978,2981,2983],{"class":58,"line":143},[56,2955,115],{"class":62},[56,2957,2958],{"class":118}," consoleSpy",[56,2960,122],{"class":62},[56,2962,419],{"class":66},[56,2964,2965],{"class":87},"spyOn",[56,2967,2968],{"class":66},"(console, ",[56,2970,2971],{"class":73},"'error'",[56,2973,2974],{"class":66},").",[56,2976,2977],{"class":87},"mockImplementation",[56,2979,2980],{"class":66},"(() ",[56,2982,106],{"class":62},[56,2984,2985],{"class":66}," {})\n",[56,2987,2988],{"class":58,"line":158},[56,2989,81],{"emptyLinePlaceholder":80},[56,2991,2992,2994],{"class":58,"line":178},[56,2993,146],{"class":87},[56,2995,1952],{"class":66},[56,2997,2998,3000,3003,3006,3008,3010,3012,3015,3017],{"class":58,"line":194},[56,2999,1957],{"class":66},[56,3001,3002],{"class":118},"ErrorBoundary",[56,3004,3005],{"class":87}," fallback",[56,3007,457],{"class":62},[56,3009,2006],{"class":66},[56,3011,15],{"class":1551},[56,3013,3014],{"class":66},">Something went wrong\u003C\u002F",[56,3016,15],{"class":1551},[56,3018,3019],{"class":66},">}>\n",[56,3021,3022,3024,3027],{"class":58,"line":549},[56,3023,1777],{"class":66},[56,3025,3026],{"class":118},"ThrowOnMount",[56,3028,3029],{"class":66}," \u002F>\n",[56,3031,3032,3034,3036],{"class":58,"line":744},[56,3033,2050],{"class":66},[56,3035,3002],{"class":118},[56,3037,1793],{"class":66},[56,3039,3040],{"class":58,"line":757},[56,3041,2059],{"class":66},[56,3043,3044],{"class":58,"line":768},[56,3045,81],{"emptyLinePlaceholder":80},[56,3047,3048,3050,3052,3054,3056,3059,3061,3063],{"class":58,"line":778},[56,3049,269],{"class":87},[56,3051,272],{"class":66},[56,3053,275],{"class":87},[56,3055,91],{"class":66},[56,3057,3058],{"class":73},"'Something went wrong'",[56,3060,283],{"class":66},[56,3062,286],{"class":87},[56,3064,249],{"class":66},[56,3066,3067,3070,3073],{"class":58,"line":784},[56,3068,3069],{"class":66},"  consoleSpy.",[56,3071,3072],{"class":87},"mockRestore",[56,3074,249],{"class":66},[56,3076,3077],{"class":58,"line":2128},[56,3078,197],{"class":66},[10,3080,3082],{"id":3081},"integration-tests-for-full-user-flows","Integration tests for full user flows",[15,3084,3085],{},"Integration tests render a larger component tree and simulate a complete\nuser journey. They give high confidence but run slower.",[47,3087,3089],{"className":49,"code":3088,"language":51,"meta":52,"style":52},"test('login → dashboard flow', async () => {\n  const user = userEvent.setup()\n\n  server.use(\n    http.post('\u002Fapi\u002Flogin', () =>\n      HttpResponse.json({ token: 'tok', user: { name: 'Alice' } })\n    )\n  )\n\n  render(\n    \u003CMemoryRouter initialEntries={['\u002Flogin']}>\n      \u003CApp \u002F>\n    \u003C\u002FMemoryRouter>\n  )\n\n  await user.type(screen.getByLabelText(\u002Femail\u002Fi), 'alice@test.com')\n  await user.type(screen.getByLabelText(\u002Fpassword\u002Fi), 'secret')\n  await user.click(screen.getByRole('button', { name: \u002Fsign in\u002Fi }))\n\n  expect(await screen.findByText('Welcome, Alice')).toBeInTheDocument()\n})\n",[19,3090,3091,3110,3124,3128,3138,3156,3179,3184,3188,3192,3198,3214,3223,3231,3235,3239,3268,3297,3325,3329,3352],{"__ignoreMap":52},[56,3092,3093,3095,3097,3100,3102,3104,3106,3108],{"class":58,"line":59},[56,3094,88],{"class":87},[56,3096,91],{"class":66},[56,3098,3099],{"class":73},"'login → dashboard flow'",[56,3101,97],{"class":66},[56,3103,100],{"class":62},[56,3105,103],{"class":66},[56,3107,106],{"class":62},[56,3109,109],{"class":66},[56,3111,3112,3114,3116,3118,3120,3122],{"class":58,"line":77},[56,3113,115],{"class":62},[56,3115,119],{"class":118},[56,3117,122],{"class":62},[56,3119,125],{"class":66},[56,3121,128],{"class":87},[56,3123,249],{"class":66},[56,3125,3126],{"class":58,"line":84},[56,3127,81],{"emptyLinePlaceholder":80},[56,3129,3130,3133,3136],{"class":58,"line":112},[56,3131,3132],{"class":66},"  server.",[56,3134,3135],{"class":87},"use",[56,3137,1952],{"class":66},[56,3139,3140,3143,3146,3148,3151,3153],{"class":58,"line":138},[56,3141,3142],{"class":66},"    http.",[56,3144,3145],{"class":87},"post",[56,3147,91],{"class":66},[56,3149,3150],{"class":73},"'\u002Fapi\u002Flogin'",[56,3152,1941],{"class":66},[56,3154,3155],{"class":62},"=>\n",[56,3157,3158,3161,3164,3167,3170,3173,3176],{"class":58,"line":143},[56,3159,3160],{"class":66},"      HttpResponse.",[56,3162,3163],{"class":87},"json",[56,3165,3166],{"class":66},"({ token: ",[56,3168,3169],{"class":73},"'tok'",[56,3171,3172],{"class":66},", user: { name: ",[56,3174,3175],{"class":73},"'Alice'",[56,3177,3178],{"class":66}," } })\n",[56,3180,3181],{"class":58,"line":158},[56,3182,3183],{"class":66},"    )\n",[56,3185,3186],{"class":58,"line":178},[56,3187,2059],{"class":66},[56,3189,3190],{"class":58,"line":194},[56,3191,81],{"emptyLinePlaceholder":80},[56,3193,3194,3196],{"class":58,"line":549},[56,3195,146],{"class":87},[56,3197,1952],{"class":66},[56,3199,3200,3202,3204,3206,3208,3210,3212],{"class":58,"line":744},[56,3201,1957],{"class":66},[56,3203,1923],{"class":118},[56,3205,1962],{"class":87},[56,3207,457],{"class":62},[56,3209,1967],{"class":66},[56,3211,2141],{"class":73},[56,3213,1973],{"class":66},[56,3215,3216,3218,3221],{"class":58,"line":757},[56,3217,1777],{"class":66},[56,3219,3220],{"class":118},"App",[56,3222,3029],{"class":66},[56,3224,3225,3227,3229],{"class":58,"line":768},[56,3226,2050],{"class":66},[56,3228,1923],{"class":118},[56,3230,1793],{"class":66},[56,3232,3233],{"class":58,"line":778},[56,3234,2059],{"class":66},[56,3236,3237],{"class":58,"line":784},[56,3238,81],{"emptyLinePlaceholder":80},[56,3240,3241,3243,3245,3247,3249,3251,3253,3255,3257,3259,3261,3263,3266],{"class":58,"line":2128},[56,3242,161],{"class":62},[56,3244,164],{"class":66},[56,3246,185],{"class":87},[56,3248,272],{"class":66},[56,3250,659],{"class":87},[56,3252,91],{"class":66},[56,3254,319],{"class":73},[56,3256,666],{"class":315},[56,3258,319],{"class":73},[56,3260,322],{"class":62},[56,3262,673],{"class":66},[56,3264,3265],{"class":73},"'alice@test.com'",[56,3267,175],{"class":66},[56,3269,3270,3272,3274,3276,3278,3280,3282,3284,3286,3288,3290,3292,3295],{"class":58,"line":2146},[56,3271,161],{"class":62},[56,3273,164],{"class":66},[56,3275,185],{"class":87},[56,3277,272],{"class":66},[56,3279,659],{"class":87},[56,3281,91],{"class":66},[56,3283,319],{"class":73},[56,3285,697],{"class":315},[56,3287,319],{"class":73},[56,3289,322],{"class":62},[56,3291,673],{"class":66},[56,3293,3294],{"class":73},"'secret'",[56,3296,175],{"class":66},[56,3298,3299,3301,3303,3305,3307,3309,3311,3313,3315,3317,3319,3321,3323],{"class":58,"line":2155},[56,3300,161],{"class":62},[56,3302,164],{"class":66},[56,3304,167],{"class":87},[56,3306,272],{"class":66},[56,3308,301],{"class":87},[56,3310,91],{"class":66},[56,3312,306],{"class":73},[56,3314,309],{"class":66},[56,3316,312],{"class":73},[56,3318,1235],{"class":315},[56,3320,319],{"class":73},[56,3322,322],{"class":62},[56,3324,325],{"class":66},[56,3326,3327],{"class":58,"line":2180},[56,3328,81],{"emptyLinePlaceholder":80},[56,3330,3331,3333,3335,3337,3339,3341,3343,3346,3348,3350],{"class":58,"line":2205},[56,3332,269],{"class":87},[56,3334,91],{"class":66},[56,3336,367],{"class":62},[56,3338,1006],{"class":66},[56,3340,2338],{"class":87},[56,3342,91],{"class":66},[56,3344,3345],{"class":73},"'Welcome, Alice'",[56,3347,283],{"class":66},[56,3349,286],{"class":87},[56,3351,249],{"class":66},[56,3353,3354],{"class":58,"line":2214},[56,3355,197],{"class":66},[15,3357,3358],{},"A good testing strategy:",[3360,3361,3362,3369,3375],"ul",{},[356,3363,3364,3368],{},[3365,3366,3367],"strong",{},"Unit tests"," — pure logic (utils, reducers).",[356,3370,3371,3374],{},[3365,3372,3373],{},"Component tests"," — individual component behavior.",[356,3376,3377,3380],{},[3365,3378,3379],{},"Integration tests"," — critical multi-component flows (auth, checkout).",[10,3382,3384],{"id":3383},"snapshot-testing-use-sparingly","Snapshot testing: use sparingly",[15,3386,3387],{},"Snapshots catch unintentional markup changes but don't verify behavior. Prefer\nbehavioral assertions:",[47,3389,3391],{"className":49,"code":3390,"language":51,"meta":52,"style":52},"\u002F\u002F ❌ Snapshot — only catches \"something changed\", not \"it broke\"\nexpect(container.firstChild).toMatchSnapshot()\n\n\u002F\u002F ✅ Behavioral — verifies actual user-facing output\nexpect(screen.getByRole('heading')).toHaveTextContent('Dashboard')\nexpect(screen.getAllByRole('listitem')).toHaveLength(3)\n",[19,3392,3393,3398,3410,3414,3419,3442],{"__ignoreMap":52},[56,3394,3395],{"class":58,"line":59},[56,3396,3397],{"class":134},"\u002F\u002F ❌ Snapshot — only catches \"something changed\", not \"it broke\"\n",[56,3399,3400,3402,3405,3408],{"class":58,"line":77},[56,3401,844],{"class":87},[56,3403,3404],{"class":66},"(container.firstChild).",[56,3406,3407],{"class":87},"toMatchSnapshot",[56,3409,249],{"class":66},[56,3411,3412],{"class":58,"line":84},[56,3413,81],{"emptyLinePlaceholder":80},[56,3415,3416],{"class":58,"line":112},[56,3417,3418],{"class":134},"\u002F\u002F ✅ Behavioral — verifies actual user-facing output\n",[56,3420,3421,3423,3425,3427,3429,3431,3433,3435,3437,3440],{"class":58,"line":138},[56,3422,844],{"class":87},[56,3424,272],{"class":66},[56,3426,301],{"class":87},[56,3428,91],{"class":66},[56,3430,2611],{"class":73},[56,3432,283],{"class":66},[56,3434,1263],{"class":87},[56,3436,91],{"class":66},[56,3438,3439],{"class":73},"'Dashboard'",[56,3441,175],{"class":66},[56,3443,3444,3446,3448,3451,3453,3456,3458,3461,3463,3466],{"class":58,"line":143},[56,3445,844],{"class":87},[56,3447,272],{"class":66},[56,3449,3450],{"class":87},"getAllByRole",[56,3452,91],{"class":66},[56,3454,3455],{"class":73},"'listitem'",[56,3457,283],{"class":66},[56,3459,3460],{"class":87},"toHaveLength",[56,3462,91],{"class":66},[56,3464,3465],{"class":118},"3",[56,3467,175],{"class":66},[15,3469,3470],{},"Use inline snapshots for small, stable, purely presentational components.",[10,3472,3474],{"id":3473},"testing-disabled-states","Testing disabled states",[47,3476,3478],{"className":49,"code":3477,"language":51,"meta":52,"style":52},"test('disabled button does not fire callback', async () => {\n  const onClick = vi.fn()\n  const user = userEvent.setup()\n\n  render(\u003Cbutton disabled onClick={onClick}>Go\u003C\u002Fbutton>)\n  await user.click(screen.getByRole('button'))\n\n  expect(onClick).not.toHaveBeenCalled()\n  expect(screen.getByRole('button')).toBeDisabled()\n})\n",[19,3479,3480,3499,3514,3528,3532,3554,3572,3576,3588,3606],{"__ignoreMap":52},[56,3481,3482,3484,3486,3489,3491,3493,3495,3497],{"class":58,"line":59},[56,3483,88],{"class":87},[56,3485,91],{"class":66},[56,3487,3488],{"class":73},"'disabled button does not fire callback'",[56,3490,97],{"class":66},[56,3492,100],{"class":62},[56,3494,103],{"class":66},[56,3496,106],{"class":62},[56,3498,109],{"class":66},[56,3500,3501,3503,3506,3508,3510,3512],{"class":58,"line":77},[56,3502,115],{"class":62},[56,3504,3505],{"class":118}," onClick",[56,3507,122],{"class":62},[56,3509,419],{"class":66},[56,3511,422],{"class":87},[56,3513,249],{"class":66},[56,3515,3516,3518,3520,3522,3524,3526],{"class":58,"line":84},[56,3517,115],{"class":62},[56,3519,119],{"class":118},[56,3521,122],{"class":62},[56,3523,125],{"class":66},[56,3525,128],{"class":87},[56,3527,249],{"class":66},[56,3529,3530],{"class":58,"line":112},[56,3531,81],{"emptyLinePlaceholder":80},[56,3533,3534,3536,3538,3540,3543,3545,3547,3550,3552],{"class":58,"line":138},[56,3535,146],{"class":87},[56,3537,149],{"class":66},[56,3539,2425],{"class":1551},[56,3541,3542],{"class":87}," disabled",[56,3544,3505],{"class":87},[56,3546,457],{"class":62},[56,3548,3549],{"class":66},"{onClick}>Go\u003C\u002F",[56,3551,2425],{"class":1551},[56,3553,1564],{"class":66},[56,3555,3556,3558,3560,3562,3564,3566,3568,3570],{"class":58,"line":143},[56,3557,161],{"class":62},[56,3559,164],{"class":66},[56,3561,167],{"class":87},[56,3563,272],{"class":66},[56,3565,301],{"class":87},[56,3567,91],{"class":66},[56,3569,306],{"class":73},[56,3571,1440],{"class":66},[56,3573,3574],{"class":58,"line":158},[56,3575,81],{"emptyLinePlaceholder":80},[56,3577,3578,3580,3583,3586],{"class":58,"line":178},[56,3579,269],{"class":87},[56,3581,3582],{"class":66},"(onClick).not.",[56,3584,3585],{"class":87},"toHaveBeenCalled",[56,3587,249],{"class":66},[56,3589,3590,3592,3594,3596,3598,3600,3602,3604],{"class":58,"line":194},[56,3591,269],{"class":87},[56,3593,272],{"class":66},[56,3595,301],{"class":87},[56,3597,91],{"class":66},[56,3599,306],{"class":73},[56,3601,283],{"class":66},[56,3603,1303],{"class":87},[56,3605,249],{"class":66},[56,3607,3608],{"class":58,"line":549},[56,3609,197],{"class":66},[10,3611,3613],{"id":3612},"interview-checklist","Interview checklist",[15,3615,3616],{},"Before your next interview, make sure you can:",[3360,3618,3619,3622,3628,3636,3642,3647,3653,3660],{},[356,3620,3621],{},"Test clicks, typing, form submission, and select\u002Fcheckbox\u002Fradio.",[356,3623,3624,3625,3627],{},"Assert callback props are called with correct arguments using ",[19,3626,557],{},".",[356,3629,3630,3631,3633,3634,3627],{},"Test conditional rendering with ",[19,3632,1318],{}," and ",[19,3635,1322],{},[356,3637,3638,3639,3641],{},"Wrap components in context providers using the ",[19,3640,1703],{}," option.",[356,3643,3644,3645,3627],{},"Test routing behavior with ",[19,3646,1923],{},[356,3648,3649,3650,3652],{},"Test portals through ",[19,3651,2363],{}," without special setup.",[356,3654,3655,3656,3633,3658,3627],{},"Test keyboard focus with ",[19,3657,2664],{},[19,3659,2672],{},[356,3661,3662],{},"Write an integration test that covers a multi-step user flow.",[15,3664,3665,3668],{},[3365,3666,3667],{},"Rule of thumb:"," Each test should cover exactly one behavior — one user\naction and its observable consequence. Compound tests that click five things\nand check three outcomes are hard to debug when they fail.",[3670,3671,3672],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .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}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 .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":52,"searchDepth":77,"depth":77,"links":3674},[3675,3676,3678,3679,3680,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694],{"id":12,"depth":77,"text":13},{"id":25,"depth":77,"text":3677},"The userEvent setup pattern",{"id":206,"depth":77,"text":207},{"id":379,"depth":77,"text":380},{"id":565,"depth":77,"text":566,"children":3681},[3682,3683,3684],{"id":790,"depth":84,"text":791},{"id":913,"depth":84,"text":914},{"id":1150,"depth":84,"text":1151},{"id":1312,"depth":77,"text":1313},{"id":1696,"depth":77,"text":1697},{"id":1916,"depth":77,"text":1917},{"id":2357,"depth":77,"text":2358},{"id":2658,"depth":77,"text":2659},{"id":2887,"depth":77,"text":2888},{"id":3081,"depth":77,"text":3082},{"id":3383,"depth":77,"text":3384},{"id":3473,"depth":77,"text":3474},{"id":3612,"depth":77,"text":3613},"Master React component interaction testing — clicks, form submission, keyboard navigation, modals, portals, context, routing, and integration test patterns with React Testing Library.","medium","md","React","react",{},"\u002Fblog\u002Freact-component-interaction-testing-guide","\u002Freact\u002Ftesting\u002Fcomponent-interaction-testing",{"title":5,"description":3695},"blog\u002Freact-component-interaction-testing-guide","Component Interaction Testing","Testing","testing","2026-06-24","MM-1MTNSAoR5HLOY3iby8CUTOzs6mwa5kRmR1QpHSlc",1782244083220]