[{"data":1,"prerenderedAt":2383},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-rtl-basics-guide":3},{"id":4,"title":5,"body":6,"description":2369,"difficulty":2370,"extension":2371,"framework":2372,"frameworkSlug":223,"meta":2373,"navigation":198,"order":110,"path":2374,"qaPath":2375,"seo":2376,"stem":2377,"subtopic":2378,"topic":2379,"topicSlug":2380,"updated":2381,"__hash__":2382},"blog\u002Fblog\u002Freact-rtl-basics-guide.md","React Testing Library — Complete Interview Guide",{"type":7,"value":8,"toc":2348},"minimark",[9,14,18,21,33,36,40,43,52,55,93,97,152,159,269,276,288,291,301,319,441,446,464,482,551,555,558,632,747,763,767,770,851,854,929,939,945,952,990,997,1065,1074,1081,1126,1133,1136,1144,1171,1178,1220,1225,1330,1337,1344,1393,1398,1439,1445,1632,1636,1639,1703,1706,1710,1716,1806,1812,1939,1946,1953,2019,2023,2026,2286,2290,2293,2338,2344],[10,11,13],"h2",{"id":12},"why-react-testing-library-replaced-enzyme","Why React Testing Library replaced Enzyme",[15,16,17],"p",{},"For years, Enzyme was the go-to tool for React testing. It gave you direct\naccess to component internals: state, instance methods, lifecycle hooks,\nshallow rendering that skipped child components entirely. Tests were fast and\nsurgical.",[15,19,20],{},"They were also brittle. Rename a piece of internal state and six tests break.\nExtract a child component and shallow-rendered tests stop reaching the logic\nyou care about. Move from class to function components and the entire API\nsurface changes.",[15,22,23,27,28,32],{},[24,25,26],"strong",{},"React Testing Library"," (RTL) made a different bet: test components the way\nusers use them. Users can't see ",[29,30,31],"code",{},"state.isOpen",". They can see a button, they can\nclick it, and they can see a menu appear. Tests that describe this behavior\nsurvive refactors because they're coupled to the observable output — the DOM —\nnot the implementation.",[15,34,35],{},"This guide walks through every RTL concept that appears in interviews.",[10,37,39],{"id":38},"the-core-philosophy","The core philosophy",[15,41,42],{},"Kent C. Dodds (RTL's creator) distilled the philosophy into one line:",[44,45,46],"blockquote",{},[15,47,48],{},[49,50,51],"em",{},"\"The more your tests resemble the way your software is used, the more\nconfidence they can give you.\"",[15,53,54],{},"Three practical consequences:",[56,57,58,77,83],"ol",{},[59,60,61,64,65,68,69,72,73,76],"li",{},[24,62,63],{},"No access to internals."," RTL deliberately omits methods like\n",[29,66,67],{},"instance()",", ",[29,70,71],{},"state()",", or ",[29,74,75],{},"find('.internal-class')",".",[59,78,79,82],{},[24,80,81],{},"Queries mirror what users see."," Find elements by role, label, or text —\nthe same signals a screen reader or a real user uses.",[59,84,85,88,89,92],{},[24,86,87],{},"Interactions fire real events."," ",[29,90,91],{},"userEvent.click()"," triggers the full\npointer\u002Fmouse\u002Ffocus sequence, not just a synthetic click handler.",[10,94,96],{"id":95},"setting-up-rtl-with-vitest","Setting up RTL with Vitest",[98,99,104],"pre",{"className":100,"code":101,"language":102,"meta":103,"style":103},"language-bash shiki shiki-themes github-light github-dark","npm install -D vitest @vitest\u002Fui jsdom \\\n  @testing-library\u002Freact @testing-library\u002Fuser-event \\\n  @testing-library\u002Fjest-dom\n","bash","",[29,105,106,135,146],{"__ignoreMap":103},[107,108,111,115,119,123,126,129,132],"span",{"class":109,"line":110},"line",1,[107,112,114],{"class":113},"sScJk","npm",[107,116,118],{"class":117},"sZZnC"," install",[107,120,122],{"class":121},"sj4cs"," -D",[107,124,125],{"class":117}," vitest",[107,127,128],{"class":117}," @vitest\u002Fui",[107,130,131],{"class":117}," jsdom",[107,133,134],{"class":121}," \\\n",[107,136,138,141,144],{"class":109,"line":137},2,[107,139,140],{"class":117},"  @testing-library\u002Freact",[107,142,143],{"class":117}," @testing-library\u002Fuser-event",[107,145,134],{"class":121},[107,147,149],{"class":109,"line":148},3,[107,150,151],{"class":117},"  @testing-library\u002Fjest-dom\n",[15,153,154],{},[24,155,156],{},[29,157,158],{},"vitest.config.ts",[98,160,164],{"className":161,"code":162,"language":163,"meta":103,"style":103},"language-ts shiki shiki-themes github-light github-dark","import { defineConfig } from 'vitest\u002Fconfig'\nimport react from '@vitejs\u002Fplugin-react'\n\nexport default defineConfig({\n  plugins: [react()],\n  test: {\n    environment: 'jsdom',\n    setupFiles: ['.\u002Fsrc\u002Ftest\u002Fsetup.ts'],\n  },\n})\n","ts",[29,165,166,182,194,200,215,227,233,245,257,263],{"__ignoreMap":103},[107,167,168,172,176,179],{"class":109,"line":110},[107,169,171],{"class":170},"szBVR","import",[107,173,175],{"class":174},"sVt8B"," { defineConfig } ",[107,177,178],{"class":170},"from",[107,180,181],{"class":117}," 'vitest\u002Fconfig'\n",[107,183,184,186,189,191],{"class":109,"line":137},[107,185,171],{"class":170},[107,187,188],{"class":174}," react ",[107,190,178],{"class":170},[107,192,193],{"class":117}," '@vitejs\u002Fplugin-react'\n",[107,195,196],{"class":109,"line":148},[107,197,199],{"emptyLinePlaceholder":198},true,"\n",[107,201,203,206,209,212],{"class":109,"line":202},4,[107,204,205],{"class":170},"export",[107,207,208],{"class":170}," default",[107,210,211],{"class":113}," defineConfig",[107,213,214],{"class":174},"({\n",[107,216,218,221,224],{"class":109,"line":217},5,[107,219,220],{"class":174},"  plugins: [",[107,222,223],{"class":113},"react",[107,225,226],{"class":174},"()],\n",[107,228,230],{"class":109,"line":229},6,[107,231,232],{"class":174},"  test: {\n",[107,234,236,239,242],{"class":109,"line":235},7,[107,237,238],{"class":174},"    environment: ",[107,240,241],{"class":117},"'jsdom'",[107,243,244],{"class":174},",\n",[107,246,248,251,254],{"class":109,"line":247},8,[107,249,250],{"class":174},"    setupFiles: [",[107,252,253],{"class":117},"'.\u002Fsrc\u002Ftest\u002Fsetup.ts'",[107,255,256],{"class":174},"],\n",[107,258,260],{"class":109,"line":259},9,[107,261,262],{"class":174},"  },\n",[107,264,266],{"class":109,"line":265},10,[107,267,268],{"class":174},"})\n",[15,270,271],{},[24,272,273],{},[29,274,275],{},"src\u002Ftest\u002Fsetup.ts",[98,277,279],{"className":161,"code":278,"language":163,"meta":103,"style":103},"import '@testing-library\u002Fjest-dom\u002Fvitest'\n",[29,280,281],{"__ignoreMap":103},[107,282,283,285],{"class":109,"line":110},[107,284,171],{"class":170},[107,286,287],{"class":117}," '@testing-library\u002Fjest-dom\u002Fvitest'\n",[15,289,290],{},"That's the entire setup. Everything else is imported on a per-test basis.",[10,292,294,297,298],{"id":293},"render-and-screen",[29,295,296],{},"render()"," and ",[29,299,300],{},"screen",[15,302,303,305,306,309,310,313,314,318],{},[29,304,296],{}," mounts a component into a ",[29,307,308],{},"\u003Cdiv>"," attached to ",[29,311,312],{},"document.body"," and\nreturns a bag of query helpers. But in practice you almost never use those\nreturn-value helpers directly — you use ",[24,315,316],{},[29,317,300],{}," instead.",[98,320,324],{"className":321,"code":322,"language":323,"meta":103,"style":103},"language-js shiki shiki-themes github-light github-dark","import { render, screen } from '@testing-library\u002Freact'\nimport Greeting from '.\u002FGreeting'\n\ntest('renders a greeting', () => {\n  render(\u003CGreeting name=\"Alice\" \u002F>)\n\n  \u002F\u002F screen queries target document.body — works for portals too\n  expect(screen.getByRole('heading')).toHaveTextContent('Hello, Alice')\n})\n","js",[29,325,326,338,350,354,374,397,401,407,437],{"__ignoreMap":103},[107,327,328,330,333,335],{"class":109,"line":110},[107,329,171],{"class":170},[107,331,332],{"class":174}," { render, screen } ",[107,334,178],{"class":170},[107,336,337],{"class":117}," '@testing-library\u002Freact'\n",[107,339,340,342,345,347],{"class":109,"line":137},[107,341,171],{"class":170},[107,343,344],{"class":174}," Greeting ",[107,346,178],{"class":170},[107,348,349],{"class":117}," '.\u002FGreeting'\n",[107,351,352],{"class":109,"line":148},[107,353,199],{"emptyLinePlaceholder":198},[107,355,356,359,362,365,368,371],{"class":109,"line":202},[107,357,358],{"class":113},"test",[107,360,361],{"class":174},"(",[107,363,364],{"class":117},"'renders a greeting'",[107,366,367],{"class":174},", () ",[107,369,370],{"class":170},"=>",[107,372,373],{"class":174}," {\n",[107,375,376,379,382,385,388,391,394],{"class":109,"line":217},[107,377,378],{"class":113},"  render",[107,380,381],{"class":174},"(\u003C",[107,383,384],{"class":121},"Greeting",[107,386,387],{"class":113}," name",[107,389,390],{"class":170},"=",[107,392,393],{"class":117},"\"Alice\"",[107,395,396],{"class":174}," \u002F>)\n",[107,398,399],{"class":109,"line":229},[107,400,199],{"emptyLinePlaceholder":198},[107,402,403],{"class":109,"line":235},[107,404,406],{"class":405},"sJ8bj","  \u002F\u002F screen queries target document.body — works for portals too\n",[107,408,409,412,415,418,420,423,426,429,431,434],{"class":109,"line":247},[107,410,411],{"class":113},"  expect",[107,413,414],{"class":174},"(screen.",[107,416,417],{"class":113},"getByRole",[107,419,361],{"class":174},[107,421,422],{"class":117},"'heading'",[107,424,425],{"class":174},")).",[107,427,428],{"class":113},"toHaveTextContent",[107,430,361],{"class":174},[107,432,433],{"class":117},"'Hello, Alice'",[107,435,436],{"class":174},")\n",[107,438,439],{"class":109,"line":259},[107,440,268],{"class":174},[15,442,443,445],{},[29,444,300],{}," has three advantages over the return-value helpers:",[447,448,449,452,458],"ul",{},[59,450,451],{},"No destructuring required — one import, always available.",[59,453,454,455,457],{},"Finds elements anywhere in ",[29,456,312],{},", including portals.",[59,459,460,463],{},[29,461,462],{},"screen.debug()"," dumps the current DOM to the console without needing the\nreturn value.",[15,465,466,467,470,471,68,474,477,478,481],{},"When you ",[49,468,469],{},"do"," need the return value, it's for ",[29,472,473],{},"rerender",[29,475,476],{},"unmount",", or\n",[29,479,480],{},"container",":",[98,483,485],{"className":321,"code":484,"language":323,"meta":103,"style":103},"const { rerender, unmount, container } = render(\u003CCounter value={0} \u002F>)\nrerender(\u003CCounter value={5} \u002F>)\n",[29,486,487,532],{"__ignoreMap":103},[107,488,489,492,495,497,499,501,503,505,508,510,513,515,518,521,523,526,529],{"class":109,"line":110},[107,490,491],{"class":170},"const",[107,493,494],{"class":174}," { ",[107,496,473],{"class":121},[107,498,68],{"class":174},[107,500,476],{"class":121},[107,502,68],{"class":174},[107,504,480],{"class":121},[107,506,507],{"class":174}," } ",[107,509,390],{"class":170},[107,511,512],{"class":113}," render",[107,514,381],{"class":174},[107,516,517],{"class":121},"Counter",[107,519,520],{"class":113}," value",[107,522,390],{"class":170},[107,524,525],{"class":174},"{",[107,527,528],{"class":121},"0",[107,530,531],{"class":174},"} \u002F>)\n",[107,533,534,536,538,540,542,544,546,549],{"class":109,"line":137},[107,535,473],{"class":113},[107,537,381],{"class":174},[107,539,517],{"class":121},[107,541,520],{"class":113},[107,543,390],{"class":170},[107,545,525],{"class":174},[107,547,548],{"class":121},"5",[107,550,531],{"class":174},[10,552,554],{"id":553},"the-three-query-variants","The three query variants",[15,556,557],{},"Every RTL query comes in three flavors:",[559,560,561,580],"table",{},[562,563,564],"thead",{},[565,566,567,571,574,577],"tr",{},[568,569,570],"th",{},"Variant",[568,572,573],{},"Not found",[568,575,576],{},"Multiple",[568,578,579],{},"Async",[581,582,583,601,617],"tbody",{},[565,584,585,591,596,598],{},[586,587,588],"td",{},[29,589,590],{},"getBy",[586,592,593],{},[24,594,595],{},"throws",[586,597,595],{},[586,599,600],{},"No",[565,602,603,608,613,615],{},[586,604,605],{},[29,606,607],{},"queryBy",[586,609,610],{},[29,611,612],{},"null",[586,614,595],{},[586,616,600],{},[565,618,619,624,627,629],{},[586,620,621],{},[29,622,623],{},"findBy",[586,625,626],{},"rejects",[586,628,626],{},[586,630,631],{},"Yes — polls until timeout",[98,633,635],{"className":321,"code":634,"language":323,"meta":103,"style":103},"\u002F\u002F getBy — element MUST be present right now\nconst btn = screen.getByRole('button', { name: \u002Fsubmit\u002Fi })\n\n\u002F\u002F queryBy — element MIGHT not exist (use with not.toBeInTheDocument)\nexpect(screen.queryByText('Error')).not.toBeInTheDocument()\n\n\u002F\u002F findBy — element will appear after async work (returns Promise)\nconst item = await screen.findByText('Data loaded')\n",[29,636,637,642,681,685,690,714,718,723],{"__ignoreMap":103},[107,638,639],{"class":109,"line":110},[107,640,641],{"class":405},"\u002F\u002F getBy — element MUST be present right now\n",[107,643,644,646,649,652,655,657,659,662,665,668,672,675,678],{"class":109,"line":137},[107,645,491],{"class":170},[107,647,648],{"class":121}," btn",[107,650,651],{"class":170}," =",[107,653,654],{"class":174}," screen.",[107,656,417],{"class":113},[107,658,361],{"class":174},[107,660,661],{"class":117},"'button'",[107,663,664],{"class":174},", { name:",[107,666,667],{"class":117}," \u002F",[107,669,671],{"class":670},"sA_wV","submit",[107,673,674],{"class":117},"\u002F",[107,676,677],{"class":170},"i",[107,679,680],{"class":174}," })\n",[107,682,683],{"class":109,"line":148},[107,684,199],{"emptyLinePlaceholder":198},[107,686,687],{"class":109,"line":202},[107,688,689],{"class":405},"\u002F\u002F queryBy — element MIGHT not exist (use with not.toBeInTheDocument)\n",[107,691,692,695,697,700,702,705,708,711],{"class":109,"line":217},[107,693,694],{"class":113},"expect",[107,696,414],{"class":174},[107,698,699],{"class":113},"queryByText",[107,701,361],{"class":174},[107,703,704],{"class":117},"'Error'",[107,706,707],{"class":174},")).not.",[107,709,710],{"class":113},"toBeInTheDocument",[107,712,713],{"class":174},"()\n",[107,715,716],{"class":109,"line":229},[107,717,199],{"emptyLinePlaceholder":198},[107,719,720],{"class":109,"line":235},[107,721,722],{"class":405},"\u002F\u002F findBy — element will appear after async work (returns Promise)\n",[107,724,725,727,730,732,735,737,740,742,745],{"class":109,"line":247},[107,726,491],{"class":170},[107,728,729],{"class":121}," item",[107,731,651],{"class":170},[107,733,734],{"class":170}," await",[107,736,654],{"class":174},[107,738,739],{"class":113},"findByText",[107,741,361],{"class":174},[107,743,744],{"class":117},"'Data loaded'",[107,746,436],{"class":174},[15,748,749,750,753,754,68,757,244,760,76],{},"All three have ",[29,751,752],{},"All"," variants that return arrays: ",[29,755,756],{},"getAllBy",[29,758,759],{},"queryAllBy",[29,761,762],{},"findAllBy",[10,764,766],{"id":765},"query-priority-which-query-to-reach-for-first","Query priority — which query to reach for first",[15,768,769],{},"RTL's official priority order, from most to least preferred:",[56,771,772,783,795,803,811,819,827,839],{},[59,773,774,778,779,782],{},[24,775,776],{},[29,777,417],{}," — most inclusive; covers buttons, headings, links, inputs,\ndialogs, checkboxes, and more. Add ",[29,780,781],{},"{ name: \u002Ftext\u002Fi }"," to filter by\naccessible name.",[59,784,785,790,791,794],{},[24,786,787],{},[29,788,789],{},"getByLabelText"," — for form fields with ",[29,792,793],{},"\u003Clabel>"," elements.",[59,796,797,802],{},[24,798,799],{},[29,800,801],{},"getByPlaceholderText"," — fallback when no visible label exists.",[59,804,805,810],{},[24,806,807],{},[29,808,809],{},"getByText"," — paragraphs, list items, non-interactive text.",[59,812,813,818],{},[24,814,815],{},[29,816,817],{},"getByDisplayValue"," — current value of a select or input.",[59,820,821,826],{},[24,822,823],{},[29,824,825],{},"getByAltText"," — image alt text.",[59,828,829,834,835,838],{},[24,830,831],{},[29,832,833],{},"getByTitle"," — ",[29,836,837],{},"title"," attribute.",[59,840,841,846,847,850],{},[24,842,843],{},[29,844,845],{},"getByTestId"," — last resort; requires ",[29,848,849],{},"data-testid"," attribute changes.",[15,852,853],{},"The ranking reflects accessibility: queries at the top are the same signals\nscreen readers use. Tests that pass at the top levels also verify that your\nmarkup is accessible.",[98,855,857],{"className":321,"code":856,"language":323,"meta":103,"style":103},"\u002F\u002F ✅ Best — tests accessibility too\nscreen.getByRole('button', { name: \u002Fsign in\u002Fi })\nscreen.getByLabelText(\u002Femail address\u002Fi)\n\n\u002F\u002F ⚠️ Last resort\nscreen.getByTestId('submit-btn')\n",[29,858,859,864,888,907,911,916],{"__ignoreMap":103},[107,860,861],{"class":109,"line":110},[107,862,863],{"class":405},"\u002F\u002F ✅ Best — tests accessibility too\n",[107,865,866,869,871,873,875,877,879,882,884,886],{"class":109,"line":137},[107,867,868],{"class":174},"screen.",[107,870,417],{"class":113},[107,872,361],{"class":174},[107,874,661],{"class":117},[107,876,664],{"class":174},[107,878,667],{"class":117},[107,880,881],{"class":670},"sign in",[107,883,674],{"class":117},[107,885,677],{"class":170},[107,887,680],{"class":174},[107,889,890,892,894,896,898,901,903,905],{"class":109,"line":148},[107,891,868],{"class":174},[107,893,789],{"class":113},[107,895,361],{"class":174},[107,897,674],{"class":117},[107,899,900],{"class":670},"email address",[107,902,674],{"class":117},[107,904,677],{"class":170},[107,906,436],{"class":174},[107,908,909],{"class":109,"line":202},[107,910,199],{"emptyLinePlaceholder":198},[107,912,913],{"class":109,"line":217},[107,914,915],{"class":405},"\u002F\u002F ⚠️ Last resort\n",[107,917,918,920,922,924,927],{"class":109,"line":229},[107,919,868],{"class":174},[107,921,845],{"class":113},[107,923,361],{"class":174},[107,925,926],{"class":117},"'submit-btn'",[107,928,436],{"class":174},[10,930,932,935,936],{"id":931},"userevent-vs-fireevent",[29,933,934],{},"userEvent"," vs ",[29,937,938],{},"fireEvent",[15,940,941,942,944],{},"Both simulate user interactions, but ",[29,943,934],{}," is almost always what you\nwant.",[15,946,947,951],{},[24,948,949],{},[29,950,938],{}," dispatches a single synthetic DOM event synchronously:",[98,953,955],{"className":321,"code":954,"language":323,"meta":103,"style":103},"fireEvent.click(button)         \u002F\u002F one click event, nothing else\nfireEvent.change(input, { target: { value: 'hello' } })  \u002F\u002F one change event\n",[29,956,957,971],{"__ignoreMap":103},[107,958,959,962,965,968],{"class":109,"line":110},[107,960,961],{"class":174},"fireEvent.",[107,963,964],{"class":113},"click",[107,966,967],{"class":174},"(button)         ",[107,969,970],{"class":405},"\u002F\u002F one click event, nothing else\n",[107,972,973,975,978,981,984,987],{"class":109,"line":137},[107,974,961],{"class":174},[107,976,977],{"class":113},"change",[107,979,980],{"class":174},"(input, { target: { value: ",[107,982,983],{"class":117},"'hello'",[107,985,986],{"class":174}," } })  ",[107,988,989],{"class":405},"\u002F\u002F one change event\n",[15,991,992,996],{},[24,993,994],{},[29,995,934],{}," simulates the real browser sequence of events:",[98,998,1000],{"className":321,"code":999,"language":323,"meta":103,"style":103},"const user = userEvent.setup()\nawait user.click(button)   \u002F\u002F pointerover, pointerenter, mouseover, mouseenter,\n                           \u002F\u002F pointermove, mousemove, pointerdown, mousedown,\n                           \u002F\u002F focus, pointerup, mouseup, click\nawait user.type(input, 'hello')  \u002F\u002F one keydown+keypress+input+keyup cycle per char\n",[29,1001,1002,1019,1035,1040,1045],{"__ignoreMap":103},[107,1003,1004,1006,1009,1011,1014,1017],{"class":109,"line":110},[107,1005,491],{"class":170},[107,1007,1008],{"class":121}," user",[107,1010,651],{"class":170},[107,1012,1013],{"class":174}," userEvent.",[107,1015,1016],{"class":113},"setup",[107,1018,713],{"class":174},[107,1020,1021,1024,1027,1029,1032],{"class":109,"line":137},[107,1022,1023],{"class":170},"await",[107,1025,1026],{"class":174}," user.",[107,1028,964],{"class":113},[107,1030,1031],{"class":174},"(button)   ",[107,1033,1034],{"class":405},"\u002F\u002F pointerover, pointerenter, mouseover, mouseenter,\n",[107,1036,1037],{"class":109,"line":148},[107,1038,1039],{"class":405},"                           \u002F\u002F pointermove, mousemove, pointerdown, mousedown,\n",[107,1041,1042],{"class":109,"line":202},[107,1043,1044],{"class":405},"                           \u002F\u002F focus, pointerup, mouseup, click\n",[107,1046,1047,1049,1051,1054,1057,1059,1062],{"class":109,"line":217},[107,1048,1023],{"class":170},[107,1050,1026],{"class":174},[107,1052,1053],{"class":113},"type",[107,1055,1056],{"class":174},"(input, ",[107,1058,983],{"class":117},[107,1060,1061],{"class":174},")  ",[107,1063,1064],{"class":405},"\u002F\u002F one keydown+keypress+input+keyup cycle per char\n",[15,1066,1067,1068,1070,1071,1073],{},"Since v14, all ",[29,1069,934],{}," methods are async — always ",[29,1072,1023],{}," them.",[15,1075,1076,1077,1080],{},"Create the ",[29,1078,1079],{},"user"," object once per test:",[98,1082,1084],{"className":321,"code":1083,"language":323,"meta":103,"style":103},"const user = userEvent.setup()\nawait user.type(input, 'react')\nawait user.click(submitButton)\n",[29,1085,1086,1100,1115],{"__ignoreMap":103},[107,1087,1088,1090,1092,1094,1096,1098],{"class":109,"line":110},[107,1089,491],{"class":170},[107,1091,1008],{"class":121},[107,1093,651],{"class":170},[107,1095,1013],{"class":174},[107,1097,1016],{"class":113},[107,1099,713],{"class":174},[107,1101,1102,1104,1106,1108,1110,1113],{"class":109,"line":137},[107,1103,1023],{"class":170},[107,1105,1026],{"class":174},[107,1107,1053],{"class":113},[107,1109,1056],{"class":174},[107,1111,1112],{"class":117},"'react'",[107,1114,436],{"class":174},[107,1116,1117,1119,1121,1123],{"class":109,"line":148},[107,1118,1023],{"class":170},[107,1120,1026],{"class":174},[107,1122,964],{"class":113},[107,1124,1125],{"class":174},"(submitButton)\n",[10,1127,1129,1132],{"id":1128},"waitfor-and-async-assertions",[29,1130,1131],{},"waitFor"," and async assertions",[15,1134,1135],{},"When state updates happen asynchronously (after a fetch, a timer, or a\ndebounce), you need to wait for the DOM to reflect the new state.",[15,1137,1138,1143],{},[24,1139,1140],{},[29,1141,1142],{},"findBy*"," is the cleanest solution for single-element waits:",[98,1145,1147],{"className":321,"code":1146,"language":323,"meta":103,"style":103},"const successMsg = await screen.findByText('Saved successfully')\n",[29,1148,1149],{"__ignoreMap":103},[107,1150,1151,1153,1156,1158,1160,1162,1164,1166,1169],{"class":109,"line":110},[107,1152,491],{"class":170},[107,1154,1155],{"class":121}," successMsg",[107,1157,651],{"class":170},[107,1159,734],{"class":170},[107,1161,654],{"class":174},[107,1163,739],{"class":113},[107,1165,361],{"class":174},[107,1167,1168],{"class":117},"'Saved successfully'",[107,1170,436],{"class":174},[15,1172,1173,1177],{},[24,1174,1175],{},[29,1176,1131],{}," is for more complex multi-assertion async scenarios:",[98,1179,1181],{"className":321,"code":1180,"language":323,"meta":103,"style":103},"await waitFor(() => {\n  expect(screen.getByText('Alice')).toBeInTheDocument()\n})\n",[29,1182,1183,1197,1216],{"__ignoreMap":103},[107,1184,1185,1187,1190,1193,1195],{"class":109,"line":110},[107,1186,1023],{"class":170},[107,1188,1189],{"class":113}," waitFor",[107,1191,1192],{"class":174},"(() ",[107,1194,370],{"class":170},[107,1196,373],{"class":174},[107,1198,1199,1201,1203,1205,1207,1210,1212,1214],{"class":109,"line":137},[107,1200,411],{"class":113},[107,1202,414],{"class":174},[107,1204,809],{"class":113},[107,1206,361],{"class":174},[107,1208,1209],{"class":117},"'Alice'",[107,1211,425],{"class":174},[107,1213,710],{"class":113},[107,1215,713],{"class":174},[107,1217,1218],{"class":109,"line":148},[107,1219,268],{"class":174},[15,1221,1222,1223,481],{},"Common mistake — putting multiple assertions in one ",[29,1224,1131],{},[98,1226,1228],{"className":321,"code":1227,"language":323,"meta":103,"style":103},"\u002F\u002F ❌ Problematic: if first passes but second fails, it retries the whole block\nawait waitFor(() => {\n  expect(screen.getByText('A')).toBeInTheDocument()\n  expect(screen.getByText('B')).toBeInTheDocument()\n})\n\n\u002F\u002F ✅ Better: wait for the trigger, then assert synchronously\nawait screen.findByText('A')\nexpect(screen.getByText('B')).toBeInTheDocument()\n",[29,1229,1230,1235,1247,1266,1285,1289,1293,1298,1312],{"__ignoreMap":103},[107,1231,1232],{"class":109,"line":110},[107,1233,1234],{"class":405},"\u002F\u002F ❌ Problematic: if first passes but second fails, it retries the whole block\n",[107,1236,1237,1239,1241,1243,1245],{"class":109,"line":137},[107,1238,1023],{"class":170},[107,1240,1189],{"class":113},[107,1242,1192],{"class":174},[107,1244,370],{"class":170},[107,1246,373],{"class":174},[107,1248,1249,1251,1253,1255,1257,1260,1262,1264],{"class":109,"line":148},[107,1250,411],{"class":113},[107,1252,414],{"class":174},[107,1254,809],{"class":113},[107,1256,361],{"class":174},[107,1258,1259],{"class":117},"'A'",[107,1261,425],{"class":174},[107,1263,710],{"class":113},[107,1265,713],{"class":174},[107,1267,1268,1270,1272,1274,1276,1279,1281,1283],{"class":109,"line":202},[107,1269,411],{"class":113},[107,1271,414],{"class":174},[107,1273,809],{"class":113},[107,1275,361],{"class":174},[107,1277,1278],{"class":117},"'B'",[107,1280,425],{"class":174},[107,1282,710],{"class":113},[107,1284,713],{"class":174},[107,1286,1287],{"class":109,"line":217},[107,1288,268],{"class":174},[107,1290,1291],{"class":109,"line":229},[107,1292,199],{"emptyLinePlaceholder":198},[107,1294,1295],{"class":109,"line":235},[107,1296,1297],{"class":405},"\u002F\u002F ✅ Better: wait for the trigger, then assert synchronously\n",[107,1299,1300,1302,1304,1306,1308,1310],{"class":109,"line":247},[107,1301,1023],{"class":170},[107,1303,654],{"class":174},[107,1305,739],{"class":113},[107,1307,361],{"class":174},[107,1309,1259],{"class":117},[107,1311,436],{"class":174},[107,1313,1314,1316,1318,1320,1322,1324,1326,1328],{"class":109,"line":259},[107,1315,694],{"class":113},[107,1317,414],{"class":174},[107,1319,809],{"class":113},[107,1321,361],{"class":174},[107,1323,1278],{"class":117},[107,1325,425],{"class":174},[107,1327,710],{"class":113},[107,1329,713],{"class":174},[10,1331,1333,1336],{"id":1332},"testing-libraryjest-dom-matchers",[29,1334,1335],{},"@testing-library\u002Fjest-dom"," matchers",[15,1338,1339,1340,1343],{},"Without ",[29,1341,1342],{},"jest-dom"," you'd write:",[98,1345,1347],{"className":321,"code":1346,"language":323,"meta":103,"style":103},"expect(element).not.toBeNull()\nexpect(element.textContent).toBe('hello')\nexpect(element.disabled).toBe(true)\n",[29,1348,1349,1361,1377],{"__ignoreMap":103},[107,1350,1351,1353,1356,1359],{"class":109,"line":110},[107,1352,694],{"class":113},[107,1354,1355],{"class":174},"(element).not.",[107,1357,1358],{"class":113},"toBeNull",[107,1360,713],{"class":174},[107,1362,1363,1365,1368,1371,1373,1375],{"class":109,"line":137},[107,1364,694],{"class":113},[107,1366,1367],{"class":174},"(element.textContent).",[107,1369,1370],{"class":113},"toBe",[107,1372,361],{"class":174},[107,1374,983],{"class":117},[107,1376,436],{"class":174},[107,1378,1379,1381,1384,1386,1388,1391],{"class":109,"line":148},[107,1380,694],{"class":113},[107,1382,1383],{"class":174},"(element.disabled).",[107,1385,1370],{"class":113},[107,1387,361],{"class":174},[107,1389,1390],{"class":121},"true",[107,1392,436],{"class":174},[15,1394,1395,1396,481],{},"With ",[29,1397,1342],{},[98,1399,1401],{"className":321,"code":1400,"language":323,"meta":103,"style":103},"expect(element).toBeInTheDocument()\nexpect(element).toHaveTextContent('hello')\nexpect(element).toBeDisabled()\n",[29,1402,1403,1414,1428],{"__ignoreMap":103},[107,1404,1405,1407,1410,1412],{"class":109,"line":110},[107,1406,694],{"class":113},[107,1408,1409],{"class":174},"(element).",[107,1411,710],{"class":113},[107,1413,713],{"class":174},[107,1415,1416,1418,1420,1422,1424,1426],{"class":109,"line":137},[107,1417,694],{"class":113},[107,1419,1409],{"class":174},[107,1421,428],{"class":113},[107,1423,361],{"class":174},[107,1425,983],{"class":117},[107,1427,436],{"class":174},[107,1429,1430,1432,1434,1437],{"class":109,"line":148},[107,1431,694],{"class":113},[107,1433,1409],{"class":174},[107,1435,1436],{"class":113},"toBeDisabled",[107,1438,713],{"class":174},[15,1440,1441,1442,1444],{},"The ",[29,1443,1342],{}," versions produce far better failure messages. Essential matchers:",[98,1446,1448],{"className":321,"code":1447,"language":323,"meta":103,"style":103},"\u002F\u002F Presence \u002F visibility\ntoBeInTheDocument()\ntoBeVisible()\ntoBeEnabled() \u002F toBeDisabled()\n\n\u002F\u002F Content\ntoHaveTextContent('text')\ntoHaveTextContent(\u002Fregex\u002Fi)\n\n\u002F\u002F Form state\ntoBeChecked()\ntoHaveValue('input value')\ntoHaveDisplayValue('Option A')\n\n\u002F\u002F DOM properties\ntoHaveAttribute('href', '\u002Fhome')\ntoHaveClass('active', 'selected')\ntoHaveFocus()\ntoHaveStyle({ display: 'flex' })\n",[29,1449,1450,1455,1461,1468,1483,1487,1492,1503,1520,1524,1529,1537,1550,1563,1568,1574,1592,1610,1618],{"__ignoreMap":103},[107,1451,1452],{"class":109,"line":110},[107,1453,1454],{"class":405},"\u002F\u002F Presence \u002F visibility\n",[107,1456,1457,1459],{"class":109,"line":137},[107,1458,710],{"class":113},[107,1460,713],{"class":174},[107,1462,1463,1466],{"class":109,"line":148},[107,1464,1465],{"class":113},"toBeVisible",[107,1467,713],{"class":174},[107,1469,1470,1473,1476,1478,1481],{"class":109,"line":202},[107,1471,1472],{"class":113},"toBeEnabled",[107,1474,1475],{"class":174},"() ",[107,1477,674],{"class":170},[107,1479,1480],{"class":113}," toBeDisabled",[107,1482,713],{"class":174},[107,1484,1485],{"class":109,"line":217},[107,1486,199],{"emptyLinePlaceholder":198},[107,1488,1489],{"class":109,"line":229},[107,1490,1491],{"class":405},"\u002F\u002F Content\n",[107,1493,1494,1496,1498,1501],{"class":109,"line":235},[107,1495,428],{"class":113},[107,1497,361],{"class":174},[107,1499,1500],{"class":117},"'text'",[107,1502,436],{"class":174},[107,1504,1505,1507,1509,1511,1514,1516,1518],{"class":109,"line":247},[107,1506,428],{"class":113},[107,1508,361],{"class":174},[107,1510,674],{"class":117},[107,1512,1513],{"class":670},"regex",[107,1515,674],{"class":117},[107,1517,677],{"class":170},[107,1519,436],{"class":174},[107,1521,1522],{"class":109,"line":259},[107,1523,199],{"emptyLinePlaceholder":198},[107,1525,1526],{"class":109,"line":265},[107,1527,1528],{"class":405},"\u002F\u002F Form state\n",[107,1530,1532,1535],{"class":109,"line":1531},11,[107,1533,1534],{"class":113},"toBeChecked",[107,1536,713],{"class":174},[107,1538,1540,1543,1545,1548],{"class":109,"line":1539},12,[107,1541,1542],{"class":113},"toHaveValue",[107,1544,361],{"class":174},[107,1546,1547],{"class":117},"'input value'",[107,1549,436],{"class":174},[107,1551,1553,1556,1558,1561],{"class":109,"line":1552},13,[107,1554,1555],{"class":113},"toHaveDisplayValue",[107,1557,361],{"class":174},[107,1559,1560],{"class":117},"'Option A'",[107,1562,436],{"class":174},[107,1564,1566],{"class":109,"line":1565},14,[107,1567,199],{"emptyLinePlaceholder":198},[107,1569,1571],{"class":109,"line":1570},15,[107,1572,1573],{"class":405},"\u002F\u002F DOM properties\n",[107,1575,1577,1580,1582,1585,1587,1590],{"class":109,"line":1576},16,[107,1578,1579],{"class":113},"toHaveAttribute",[107,1581,361],{"class":174},[107,1583,1584],{"class":117},"'href'",[107,1586,68],{"class":174},[107,1588,1589],{"class":117},"'\u002Fhome'",[107,1591,436],{"class":174},[107,1593,1595,1598,1600,1603,1605,1608],{"class":109,"line":1594},17,[107,1596,1597],{"class":113},"toHaveClass",[107,1599,361],{"class":174},[107,1601,1602],{"class":117},"'active'",[107,1604,68],{"class":174},[107,1606,1607],{"class":117},"'selected'",[107,1609,436],{"class":174},[107,1611,1613,1616],{"class":109,"line":1612},18,[107,1614,1615],{"class":113},"toHaveFocus",[107,1617,713],{"class":174},[107,1619,1621,1624,1627,1630],{"class":109,"line":1620},19,[107,1622,1623],{"class":113},"toHaveStyle",[107,1625,1626],{"class":174},"({ display: ",[107,1628,1629],{"class":117},"'flex'",[107,1631,680],{"class":174},[10,1633,1635],{"id":1634},"debugging-failing-tests","Debugging failing tests",[15,1637,1638],{},"When a test fails with \"Unable to find element\":",[98,1640,1642],{"className":321,"code":1641,"language":323,"meta":103,"style":103},"\u002F\u002F Print the full DOM\nscreen.debug()\n\n\u002F\u002F Print a specific subtree\nscreen.debug(screen.getByRole('form'))\n\n\u002F\u002F Open Testing Playground with current DOM pre-loaded\nscreen.logTestingPlaygroundURL()\n",[29,1643,1644,1649,1658,1662,1667,1685,1689,1694],{"__ignoreMap":103},[107,1645,1646],{"class":109,"line":110},[107,1647,1648],{"class":405},"\u002F\u002F Print the full DOM\n",[107,1650,1651,1653,1656],{"class":109,"line":137},[107,1652,868],{"class":174},[107,1654,1655],{"class":113},"debug",[107,1657,713],{"class":174},[107,1659,1660],{"class":109,"line":148},[107,1661,199],{"emptyLinePlaceholder":198},[107,1663,1664],{"class":109,"line":202},[107,1665,1666],{"class":405},"\u002F\u002F Print a specific subtree\n",[107,1668,1669,1671,1673,1675,1677,1679,1682],{"class":109,"line":217},[107,1670,868],{"class":174},[107,1672,1655],{"class":113},[107,1674,414],{"class":174},[107,1676,417],{"class":113},[107,1678,361],{"class":174},[107,1680,1681],{"class":117},"'form'",[107,1683,1684],{"class":174},"))\n",[107,1686,1687],{"class":109,"line":229},[107,1688,199],{"emptyLinePlaceholder":198},[107,1690,1691],{"class":109,"line":235},[107,1692,1693],{"class":405},"\u002F\u002F Open Testing Playground with current DOM pre-loaded\n",[107,1695,1696,1698,1701],{"class":109,"line":247},[107,1697,868],{"class":174},[107,1699,1700],{"class":113},"logTestingPlaygroundURL",[107,1702,713],{"class":174},[15,1704,1705],{},"RTL v13+ also prints a role breakdown in error messages, showing you which\nelements matched and suggesting alternative queries.",[10,1707,1709],{"id":1708},"wrapping-providers","Wrapping providers",[15,1711,1712,1713,481],{},"Components that need router or context providers require a ",[29,1714,1715],{},"wrapper",[98,1717,1719],{"className":321,"code":1718,"language":323,"meta":103,"style":103},"render(\u003CNavBar \u002F>, {\n  wrapper: ({ children }) => (\n    \u003CMemoryRouter>\n      \u003CThemeProvider theme=\"dark\">{children}\u003C\u002FThemeProvider>\n    \u003C\u002FMemoryRouter>\n  ),\n})\n",[29,1720,1721,1734,1754,1765,1788,1797,1802],{"__ignoreMap":103},[107,1722,1723,1726,1728,1731],{"class":109,"line":110},[107,1724,1725],{"class":113},"render",[107,1727,381],{"class":174},[107,1729,1730],{"class":121},"NavBar",[107,1732,1733],{"class":174}," \u002F>, {\n",[107,1735,1736,1739,1742,1746,1749,1751],{"class":109,"line":137},[107,1737,1738],{"class":113},"  wrapper",[107,1740,1741],{"class":174},": ({ ",[107,1743,1745],{"class":1744},"s4XuR","children",[107,1747,1748],{"class":174}," }) ",[107,1750,370],{"class":170},[107,1752,1753],{"class":174}," (\n",[107,1755,1756,1759,1762],{"class":109,"line":148},[107,1757,1758],{"class":174},"    \u003C",[107,1760,1761],{"class":121},"MemoryRouter",[107,1763,1764],{"class":174},">\n",[107,1766,1767,1770,1773,1776,1778,1781,1784,1786],{"class":109,"line":202},[107,1768,1769],{"class":174},"      \u003C",[107,1771,1772],{"class":121},"ThemeProvider",[107,1774,1775],{"class":113}," theme",[107,1777,390],{"class":170},[107,1779,1780],{"class":117},"\"dark\"",[107,1782,1783],{"class":174},">{children}\u003C\u002F",[107,1785,1772],{"class":121},[107,1787,1764],{"class":174},[107,1789,1790,1793,1795],{"class":109,"line":217},[107,1791,1792],{"class":174},"    \u003C\u002F",[107,1794,1761],{"class":121},[107,1796,1764],{"class":174},[107,1798,1799],{"class":109,"line":229},[107,1800,1801],{"class":174},"  ),\n",[107,1803,1804],{"class":109,"line":235},[107,1805,268],{"class":174},[15,1807,1808,1809,1811],{},"For larger projects, create a custom ",[29,1810,1725],{}," helper so you don't repeat this:",[98,1813,1815],{"className":321,"code":1814,"language":323,"meta":103,"style":103},"\u002F\u002F test\u002Futils.tsx\nimport { render } from '@testing-library\u002Freact'\nimport { MemoryRouter } from 'react-router-dom'\n\nconst customRender = (ui, options) =>\n  render(ui, { wrapper: ({ children }) => \u003CMemoryRouter>{children}\u003C\u002FMemoryRouter>, ...options })\n\nexport * from '@testing-library\u002Freact'\nexport { customRender as render }\n",[29,1816,1817,1822,1833,1845,1849,1875,1910,1914,1926],{"__ignoreMap":103},[107,1818,1819],{"class":109,"line":110},[107,1820,1821],{"class":405},"\u002F\u002F test\u002Futils.tsx\n",[107,1823,1824,1826,1829,1831],{"class":109,"line":137},[107,1825,171],{"class":170},[107,1827,1828],{"class":174}," { render } ",[107,1830,178],{"class":170},[107,1832,337],{"class":117},[107,1834,1835,1837,1840,1842],{"class":109,"line":148},[107,1836,171],{"class":170},[107,1838,1839],{"class":174}," { MemoryRouter } ",[107,1841,178],{"class":170},[107,1843,1844],{"class":117}," 'react-router-dom'\n",[107,1846,1847],{"class":109,"line":202},[107,1848,199],{"emptyLinePlaceholder":198},[107,1850,1851,1853,1856,1858,1861,1864,1866,1869,1872],{"class":109,"line":217},[107,1852,491],{"class":170},[107,1854,1855],{"class":113}," customRender",[107,1857,651],{"class":170},[107,1859,1860],{"class":174}," (",[107,1862,1863],{"class":1744},"ui",[107,1865,68],{"class":174},[107,1867,1868],{"class":1744},"options",[107,1870,1871],{"class":174},") ",[107,1873,1874],{"class":170},"=>\n",[107,1876,1877,1879,1882,1884,1886,1888,1890,1892,1895,1897,1899,1901,1904,1907],{"class":109,"line":229},[107,1878,378],{"class":113},[107,1880,1881],{"class":174},"(ui, { ",[107,1883,1715],{"class":113},[107,1885,1741],{"class":174},[107,1887,1745],{"class":1744},[107,1889,1748],{"class":174},[107,1891,370],{"class":170},[107,1893,1894],{"class":174}," \u003C",[107,1896,1761],{"class":121},[107,1898,1783],{"class":174},[107,1900,1761],{"class":121},[107,1902,1903],{"class":174},">, ",[107,1905,1906],{"class":170},"...",[107,1908,1909],{"class":174},"options })\n",[107,1911,1912],{"class":109,"line":235},[107,1913,199],{"emptyLinePlaceholder":198},[107,1915,1916,1918,1921,1924],{"class":109,"line":247},[107,1917,205],{"class":170},[107,1919,1920],{"class":121}," *",[107,1922,1923],{"class":170}," from",[107,1925,337],{"class":117},[107,1927,1928,1930,1933,1936],{"class":109,"line":259},[107,1929,205],{"class":170},[107,1931,1932],{"class":174}," { customRender ",[107,1934,1935],{"class":170},"as",[107,1937,1938],{"class":174}," render }\n",[10,1940,1942,1945],{"id":1941},"within-for-scoped-queries",[29,1943,1944],{},"within()"," for scoped queries",[15,1947,1948,1949,1952],{},"When a page has multiple similar elements (multiple \"Delete\" buttons, for\nexample), use ",[29,1950,1951],{},"within"," to scope queries to a specific container:",[98,1954,1956],{"className":321,"code":1955,"language":323,"meta":103,"style":103},"const aliceRow = screen.getByText('Alice').closest('tr')\nwithin(aliceRow).getByRole('button', { name: \u002Fdelete\u002Fi }).click()\n",[29,1957,1958,1988],{"__ignoreMap":103},[107,1959,1960,1962,1965,1967,1969,1971,1973,1975,1978,1981,1983,1986],{"class":109,"line":110},[107,1961,491],{"class":170},[107,1963,1964],{"class":121}," aliceRow",[107,1966,651],{"class":170},[107,1968,654],{"class":174},[107,1970,809],{"class":113},[107,1972,361],{"class":174},[107,1974,1209],{"class":117},[107,1976,1977],{"class":174},").",[107,1979,1980],{"class":113},"closest",[107,1982,361],{"class":174},[107,1984,1985],{"class":117},"'tr'",[107,1987,436],{"class":174},[107,1989,1990,1992,1995,1997,1999,2001,2003,2005,2008,2010,2012,2015,2017],{"class":109,"line":137},[107,1991,1951],{"class":113},[107,1993,1994],{"class":174},"(aliceRow).",[107,1996,417],{"class":113},[107,1998,361],{"class":174},[107,2000,661],{"class":117},[107,2002,664],{"class":174},[107,2004,667],{"class":117},[107,2006,2007],{"class":670},"delete",[107,2009,674],{"class":117},[107,2011,677],{"class":170},[107,2013,2014],{"class":174}," }).",[107,2016,964],{"class":113},[107,2018,713],{"class":174},[10,2020,2022],{"id":2021},"testing-async-data-fetching","Testing async data fetching",[15,2024,2025],{},"The full pattern with MSW:",[98,2027,2029],{"className":321,"code":2028,"language":323,"meta":103,"style":103},"import { setupServer } from 'msw\u002Fnode'\nimport { http, HttpResponse } from 'msw'\n\nconst server = setupServer(\n  http.get('\u002Fapi\u002Fusers', () => HttpResponse.json([{ id: 1, name: 'Alice' }]))\n)\n\nbeforeAll(() => server.listen())\nafterEach(() => server.resetHandlers())\nafterAll(() => server.close())\n\ntest('loads and displays users', async () => {\n  render(\u003CUserList \u002F>)\n\n  expect(screen.getByRole('status')).toBeInTheDocument()   \u002F\u002F loading spinner\n\n  expect(await screen.findByText('Alice')).toBeInTheDocument()\n\n  expect(screen.queryByRole('status')).not.toBeInTheDocument()\n})\n",[29,2030,2031,2043,2055,2059,2074,2111,2115,2119,2137,2153,2169,2173,2194,2205,2209,2232,2236,2258,2262,2281],{"__ignoreMap":103},[107,2032,2033,2035,2038,2040],{"class":109,"line":110},[107,2034,171],{"class":170},[107,2036,2037],{"class":174}," { setupServer } ",[107,2039,178],{"class":170},[107,2041,2042],{"class":117}," 'msw\u002Fnode'\n",[107,2044,2045,2047,2050,2052],{"class":109,"line":137},[107,2046,171],{"class":170},[107,2048,2049],{"class":174}," { http, HttpResponse } ",[107,2051,178],{"class":170},[107,2053,2054],{"class":117}," 'msw'\n",[107,2056,2057],{"class":109,"line":148},[107,2058,199],{"emptyLinePlaceholder":198},[107,2060,2061,2063,2066,2068,2071],{"class":109,"line":202},[107,2062,491],{"class":170},[107,2064,2065],{"class":121}," server",[107,2067,651],{"class":170},[107,2069,2070],{"class":113}," setupServer",[107,2072,2073],{"class":174},"(\n",[107,2075,2076,2079,2082,2084,2087,2089,2091,2094,2097,2100,2103,2106,2108],{"class":109,"line":217},[107,2077,2078],{"class":174},"  http.",[107,2080,2081],{"class":113},"get",[107,2083,361],{"class":174},[107,2085,2086],{"class":117},"'\u002Fapi\u002Fusers'",[107,2088,367],{"class":174},[107,2090,370],{"class":170},[107,2092,2093],{"class":174}," HttpResponse.",[107,2095,2096],{"class":113},"json",[107,2098,2099],{"class":174},"([{ id: ",[107,2101,2102],{"class":121},"1",[107,2104,2105],{"class":174},", name: ",[107,2107,1209],{"class":117},[107,2109,2110],{"class":174}," }]))\n",[107,2112,2113],{"class":109,"line":229},[107,2114,436],{"class":174},[107,2116,2117],{"class":109,"line":235},[107,2118,199],{"emptyLinePlaceholder":198},[107,2120,2121,2124,2126,2128,2131,2134],{"class":109,"line":247},[107,2122,2123],{"class":113},"beforeAll",[107,2125,1192],{"class":174},[107,2127,370],{"class":170},[107,2129,2130],{"class":174}," server.",[107,2132,2133],{"class":113},"listen",[107,2135,2136],{"class":174},"())\n",[107,2138,2139,2142,2144,2146,2148,2151],{"class":109,"line":259},[107,2140,2141],{"class":113},"afterEach",[107,2143,1192],{"class":174},[107,2145,370],{"class":170},[107,2147,2130],{"class":174},[107,2149,2150],{"class":113},"resetHandlers",[107,2152,2136],{"class":174},[107,2154,2155,2158,2160,2162,2164,2167],{"class":109,"line":265},[107,2156,2157],{"class":113},"afterAll",[107,2159,1192],{"class":174},[107,2161,370],{"class":170},[107,2163,2130],{"class":174},[107,2165,2166],{"class":113},"close",[107,2168,2136],{"class":174},[107,2170,2171],{"class":109,"line":1531},[107,2172,199],{"emptyLinePlaceholder":198},[107,2174,2175,2177,2179,2182,2184,2187,2190,2192],{"class":109,"line":1539},[107,2176,358],{"class":113},[107,2178,361],{"class":174},[107,2180,2181],{"class":117},"'loads and displays users'",[107,2183,68],{"class":174},[107,2185,2186],{"class":170},"async",[107,2188,2189],{"class":174}," () ",[107,2191,370],{"class":170},[107,2193,373],{"class":174},[107,2195,2196,2198,2200,2203],{"class":109,"line":1552},[107,2197,378],{"class":113},[107,2199,381],{"class":174},[107,2201,2202],{"class":121},"UserList",[107,2204,396],{"class":174},[107,2206,2207],{"class":109,"line":1565},[107,2208,199],{"emptyLinePlaceholder":198},[107,2210,2211,2213,2215,2217,2219,2222,2224,2226,2229],{"class":109,"line":1570},[107,2212,411],{"class":113},[107,2214,414],{"class":174},[107,2216,417],{"class":113},[107,2218,361],{"class":174},[107,2220,2221],{"class":117},"'status'",[107,2223,425],{"class":174},[107,2225,710],{"class":113},[107,2227,2228],{"class":174},"()   ",[107,2230,2231],{"class":405},"\u002F\u002F loading spinner\n",[107,2233,2234],{"class":109,"line":1576},[107,2235,199],{"emptyLinePlaceholder":198},[107,2237,2238,2240,2242,2244,2246,2248,2250,2252,2254,2256],{"class":109,"line":1594},[107,2239,411],{"class":113},[107,2241,361],{"class":174},[107,2243,1023],{"class":170},[107,2245,654],{"class":174},[107,2247,739],{"class":113},[107,2249,361],{"class":174},[107,2251,1209],{"class":117},[107,2253,425],{"class":174},[107,2255,710],{"class":113},[107,2257,713],{"class":174},[107,2259,2260],{"class":109,"line":1612},[107,2261,199],{"emptyLinePlaceholder":198},[107,2263,2264,2266,2268,2271,2273,2275,2277,2279],{"class":109,"line":1620},[107,2265,411],{"class":113},[107,2267,414],{"class":174},[107,2269,2270],{"class":113},"queryByRole",[107,2272,361],{"class":174},[107,2274,2221],{"class":117},[107,2276,707],{"class":174},[107,2278,710],{"class":113},[107,2280,713],{"class":174},[107,2282,2284],{"class":109,"line":2283},20,[107,2285,268],{"class":174},[10,2287,2289],{"id":2288},"interview-checklist","Interview checklist",[15,2291,2292],{},"Before your next React testing interview, make sure you can:",[447,2294,2295,2298,2301,2309,2316,2325,2328,2333],{},[59,2296,2297],{},"Explain RTL's philosophy and how it differs from Enzyme.",[59,2299,2300],{},"Choose the right query variant (get\u002Fquery\u002Ffind) for any scenario.",[59,2302,2303,2304,2306,2307,76],{},"Apply query priority and explain why ",[29,2305,417],{}," beats ",[29,2308,845],{},[59,2310,2311,2312,297,2314,76],{},"Write async tests with ",[29,2313,623],{},[29,2315,1131],{},[59,2317,2318,2319,297,2322,2324],{},"Use ",[29,2320,2321],{},"userEvent.setup()",[29,2323,1023],{}," every interaction.",[59,2326,2327],{},"Set up MSW handlers for happy path, 4xx, and 5xx.",[59,2329,2330,2331,76],{},"Scope queries with ",[29,2332,1944],{},[59,2334,2335,2336,76],{},"Debug a failing test with ",[29,2337,462],{},[15,2339,2340,2343],{},[24,2341,2342],{},"Rule of thumb:"," If your test reads like a user story — \"user clicks the\nbutton, sees the success message\" — it's a good RTL test. If it reads like\nan implementation audit — \"state.isSubmitting is true\" — rewrite it.",[2345,2346,2347],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":103,"searchDepth":137,"depth":137,"links":2349},[2350,2351,2352,2353,2355,2356,2357,2359,2361,2363,2364,2365,2367,2368],{"id":12,"depth":137,"text":13},{"id":38,"depth":137,"text":39},{"id":95,"depth":137,"text":96},{"id":293,"depth":137,"text":2354},"render() and screen",{"id":553,"depth":137,"text":554},{"id":765,"depth":137,"text":766},{"id":931,"depth":137,"text":2358},"userEvent vs fireEvent",{"id":1128,"depth":137,"text":2360},"waitFor and async assertions",{"id":1332,"depth":137,"text":2362},"@testing-library\u002Fjest-dom matchers",{"id":1634,"depth":137,"text":1635},{"id":1708,"depth":137,"text":1709},{"id":1941,"depth":137,"text":2366},"within() for scoped queries",{"id":2021,"depth":137,"text":2022},{"id":2288,"depth":137,"text":2289},"Learn React Testing Library from first principles — the testing philosophy, queries, screen, userEvent, waitFor, jest-dom matchers, and async testing patterns that interviewers ask about.","medium","md","React",{},"\u002Fblog\u002Freact-rtl-basics-guide","\u002Freact\u002Ftesting\u002Frtl-basics",{"title":5,"description":2369},"blog\u002Freact-rtl-basics-guide","RTL Basics","Testing","testing","2026-06-24","SYoNWbMyVekYzfCuiTvgaOoVeS-MJl8AfjHBVhbvRFA",1782244083220]