[{"data":1,"prerenderedAt":1974},["ShallowReactive",2],{"page-\u002Freact\u002F24-testing":3},{"id":4,"title":5,"body":6,"description":1967,"extension":1968,"meta":1969,"navigation":129,"path":1970,"seo":1971,"stem":1972,"__hash__":1973},"content\u002Freact\u002F24-testing.md","24 — Testing",{"type":7,"value":8,"toc":1955},"minimark",[9,13,18,64,90,233,237,537,541,672,676,865,869,1231,1235,1436,1440,1548,1552,1641,1645,1649,1722,1733,1868,1872,1951],[10,11,5],"h1",{"id":12},"_24-testing",[14,15,17],"h2",{"id":16},"setup-vitest-react-testing-library","Setup: Vitest + React Testing Library",[19,20,23],"code-wrapper",{"filename":21,"language":22},"install.sh","bash",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-bash shiki shiki-themes github-light github-dark","npm install -D vitest @testing-library\u002Freact @testing-library\u002Fuser-event @testing-library\u002Fjest-dom jsdom\n","",[30,31,32],"code",{"__ignoreMap":28},[33,34,37,41,45,49,52,55,58,61],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sIsaT","npm",[33,42,44],{"class":43},"sJ6F3"," install",[33,46,48],{"class":47},"snvgF"," -D",[33,50,51],{"class":43}," vitest",[33,53,54],{"class":43}," @testing-library\u002Freact",[33,56,57],{"class":43}," @testing-library\u002Fuser-event",[33,59,60],{"class":43}," @testing-library\u002Fjest-dom",[33,62,63],{"class":43}," jsdom\n",[19,65,68],{"filename":66,"language":67},"vitest.setup.js","javascript",[24,69,72],{"className":70,"code":71,"language":67,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F vitest.setup.js — runs before all tests\nimport '@testing-library\u002Fjest-dom\u002Fvitest'\n",[30,73,74,80],{"__ignoreMap":28},[33,75,76],{"class":35,"line":36},[33,77,79],{"class":78},"sdCPZ","\u002F\u002F vitest.setup.js — runs before all tests\n",[33,81,83,87],{"class":35,"line":82},2,[33,84,86],{"class":85},"svdQ7","import",[33,88,89],{"class":43}," '@testing-library\u002Fjest-dom\u002Fvitest'\n",[19,91,93],{"filename":92,"language":67},"vitest.config.js",[24,94,96],{"className":70,"code":95,"language":67,"meta":28,"style":28},"import { defineConfig } from 'vitest\u002Fconfig'\nimport react from '@vitejs\u002Fplugin-react'\n\nexport default defineConfig({\n  plugins: [react()],\n  test: {\n    environment: 'jsdom',      \u002F\u002F DOM simulation\n    setupFiles: ['.\u002Fvitest.setup.js'],\n    globals: true,             \u002F\u002F expect, describe, it available globally\n    css: false,                \u002F\u002F don't process CSS in tests\n  },\n})\n",[30,97,98,112,124,131,146,158,164,179,191,206,221,227],{"__ignoreMap":28},[33,99,100,102,106,109],{"class":35,"line":36},[33,101,86],{"class":85},[33,103,105],{"class":104},"ssxIu"," { defineConfig } ",[33,107,108],{"class":85},"from",[33,110,111],{"class":43}," 'vitest\u002Fconfig'\n",[33,113,114,116,119,121],{"class":35,"line":82},[33,115,86],{"class":85},[33,117,118],{"class":104}," react ",[33,120,108],{"class":85},[33,122,123],{"class":43}," '@vitejs\u002Fplugin-react'\n",[33,125,127],{"class":35,"line":126},3,[33,128,130],{"emptyLinePlaceholder":129},true,"\n",[33,132,134,137,140,143],{"class":35,"line":133},4,[33,135,136],{"class":85},"export",[33,138,139],{"class":85}," default",[33,141,142],{"class":39}," defineConfig",[33,144,145],{"class":104},"({\n",[33,147,149,152,155],{"class":35,"line":148},5,[33,150,151],{"class":104},"  plugins: [",[33,153,154],{"class":39},"react",[33,156,157],{"class":104},"()],\n",[33,159,161],{"class":35,"line":160},6,[33,162,163],{"class":104},"  test: {\n",[33,165,167,170,173,176],{"class":35,"line":166},7,[33,168,169],{"class":104},"    environment: ",[33,171,172],{"class":43},"'jsdom'",[33,174,175],{"class":104},",      ",[33,177,178],{"class":78},"\u002F\u002F DOM simulation\n",[33,180,182,185,188],{"class":35,"line":181},8,[33,183,184],{"class":104},"    setupFiles: [",[33,186,187],{"class":43},"'.\u002Fvitest.setup.js'",[33,189,190],{"class":104},"],\n",[33,192,194,197,200,203],{"class":35,"line":193},9,[33,195,196],{"class":104},"    globals: ",[33,198,199],{"class":47},"true",[33,201,202],{"class":104},",             ",[33,204,205],{"class":78},"\u002F\u002F expect, describe, it available globally\n",[33,207,209,212,215,218],{"class":35,"line":208},10,[33,210,211],{"class":104},"    css: ",[33,213,214],{"class":47},"false",[33,216,217],{"class":104},",                ",[33,219,220],{"class":78},"\u002F\u002F don't process CSS in tests\n",[33,222,224],{"class":35,"line":223},11,[33,225,226],{"class":104},"  },\n",[33,228,230],{"class":35,"line":229},12,[33,231,232],{"class":104},"})\n",[14,234,236],{"id":235},"the-core-pattern-render-query-assert","The Core Pattern: render → query → assert",[19,238,240],{"filename":239,"language":67},"basic_test.js",[24,241,243],{"className":70,"code":242,"language":67,"meta":28,"style":28},"import { render, screen } from '@testing-library\u002Freact'\nimport { describe, it, expect } from 'vitest'\nimport Button from '.\u002FButton'\n\ndescribe('Button', () => {\n  it('renders the label', () => {\n    render(\u003CButton label=\"Click me\" \u002F>)\n    \u002F\u002F screen.getByRole queries the DOM by ARIA role — the most accessible query\n    expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument()\n  })\n\n  it('calls onClick when clicked', async () => {\n    const user = userEvent.setup()  \u002F\u002F simulates real browser interactions\n    const handleClick = vi.fn()     \u002F\u002F vitest mock function\n    render(\u003CButton label=\"Save\" onClick={handleClick} \u002F>)\n\n    await user.click(screen.getByRole('button', { name: 'Save' }))\n    expect(handleClick).toHaveBeenCalledOnce()\n  })\n})\n",[30,244,245,257,269,281,285,305,321,344,349,380,385,389,411,435,457,481,486,514,527,532],{"__ignoreMap":28},[33,246,247,249,252,254],{"class":35,"line":36},[33,248,86],{"class":85},[33,250,251],{"class":104}," { render, screen } ",[33,253,108],{"class":85},[33,255,256],{"class":43}," '@testing-library\u002Freact'\n",[33,258,259,261,264,266],{"class":35,"line":82},[33,260,86],{"class":85},[33,262,263],{"class":104}," { describe, it, expect } ",[33,265,108],{"class":85},[33,267,268],{"class":43}," 'vitest'\n",[33,270,271,273,276,278],{"class":35,"line":126},[33,272,86],{"class":85},[33,274,275],{"class":104}," Button ",[33,277,108],{"class":85},[33,279,280],{"class":43}," '.\u002FButton'\n",[33,282,283],{"class":35,"line":133},[33,284,130],{"emptyLinePlaceholder":129},[33,286,287,290,293,296,299,302],{"class":35,"line":148},[33,288,289],{"class":39},"describe",[33,291,292],{"class":104},"(",[33,294,295],{"class":43},"'Button'",[33,297,298],{"class":104},", () ",[33,300,301],{"class":85},"=>",[33,303,304],{"class":104}," {\n",[33,306,307,310,312,315,317,319],{"class":35,"line":160},[33,308,309],{"class":39},"  it",[33,311,292],{"class":104},[33,313,314],{"class":43},"'renders the label'",[33,316,298],{"class":104},[33,318,301],{"class":85},[33,320,304],{"class":104},[33,322,323,326,329,332,335,338,341],{"class":35,"line":166},[33,324,325],{"class":39},"    render",[33,327,328],{"class":104},"(\u003C",[33,330,331],{"class":47},"Button",[33,333,334],{"class":39}," label",[33,336,337],{"class":85},"=",[33,339,340],{"class":43},"\"Click me\"",[33,342,343],{"class":104}," \u002F>)\n",[33,345,346],{"class":35,"line":181},[33,347,348],{"class":78},"    \u002F\u002F screen.getByRole queries the DOM by ARIA role — the most accessible query\n",[33,350,351,354,357,360,362,365,368,371,374,377],{"class":35,"line":193},[33,352,353],{"class":39},"    expect",[33,355,356],{"class":104},"(screen.",[33,358,359],{"class":39},"getByRole",[33,361,292],{"class":104},[33,363,364],{"class":43},"'button'",[33,366,367],{"class":104},", { name: ",[33,369,370],{"class":43},"'Click me'",[33,372,373],{"class":104}," })).",[33,375,376],{"class":39},"toBeInTheDocument",[33,378,379],{"class":104},"()\n",[33,381,382],{"class":35,"line":208},[33,383,384],{"class":104},"  })\n",[33,386,387],{"class":35,"line":223},[33,388,130],{"emptyLinePlaceholder":129},[33,390,391,393,395,398,401,404,407,409],{"class":35,"line":229},[33,392,309],{"class":39},[33,394,292],{"class":104},[33,396,397],{"class":43},"'calls onClick when clicked'",[33,399,400],{"class":104},", ",[33,402,403],{"class":85},"async",[33,405,406],{"class":104}," () ",[33,408,301],{"class":85},[33,410,304],{"class":104},[33,412,414,417,420,423,426,429,432],{"class":35,"line":413},13,[33,415,416],{"class":85},"    const",[33,418,419],{"class":47}," user",[33,421,422],{"class":85}," =",[33,424,425],{"class":104}," userEvent.",[33,427,428],{"class":39},"setup",[33,430,431],{"class":104},"()  ",[33,433,434],{"class":78},"\u002F\u002F simulates real browser interactions\n",[33,436,438,440,443,445,448,451,454],{"class":35,"line":437},14,[33,439,416],{"class":85},[33,441,442],{"class":47}," handleClick",[33,444,422],{"class":85},[33,446,447],{"class":104}," vi.",[33,449,450],{"class":39},"fn",[33,452,453],{"class":104},"()     ",[33,455,456],{"class":78},"\u002F\u002F vitest mock function\n",[33,458,460,462,464,466,468,470,473,476,478],{"class":35,"line":459},15,[33,461,325],{"class":39},[33,463,328],{"class":104},[33,465,331],{"class":47},[33,467,334],{"class":39},[33,469,337],{"class":85},[33,471,472],{"class":43},"\"Save\"",[33,474,475],{"class":39}," onClick",[33,477,337],{"class":85},[33,479,480],{"class":104},"{handleClick} \u002F>)\n",[33,482,484],{"class":35,"line":483},16,[33,485,130],{"emptyLinePlaceholder":129},[33,487,489,492,495,498,500,502,504,506,508,511],{"class":35,"line":488},17,[33,490,491],{"class":85},"    await",[33,493,494],{"class":104}," user.",[33,496,497],{"class":39},"click",[33,499,356],{"class":104},[33,501,359],{"class":39},[33,503,292],{"class":104},[33,505,364],{"class":43},[33,507,367],{"class":104},[33,509,510],{"class":43},"'Save'",[33,512,513],{"class":104}," }))\n",[33,515,517,519,522,525],{"class":35,"line":516},18,[33,518,353],{"class":39},[33,520,521],{"class":104},"(handleClick).",[33,523,524],{"class":39},"toHaveBeenCalledOnce",[33,526,379],{"class":104},[33,528,530],{"class":35,"line":529},19,[33,531,384],{"class":104},[33,533,535],{"class":35,"line":534},20,[33,536,232],{"class":104},[14,538,540],{"id":539},"queries-which-to-use-and-why","Queries: Which to Use and Why",[19,542,544],{"filename":543,"language":67},"queries.js",[24,545,547],{"className":70,"code":546,"language":67,"meta":28,"style":28},"\u002F\u002F QUERY PRIORITY (from Testing Library docs — test as a user would interact):\n\u002F\u002F 1. getByRole — most accessible, mirrors how AT users find elements\n\u002F\u002F 2. getByLabelText — for form fields (labels are the user's way to find inputs)\n\u002F\u002F 3. getByPlaceholderText — fallback for inputs without labels\n\u002F\u002F 4. getByText — for non-interactive text content\n\u002F\u002F 5. getByDisplayValue — for inputs\u002Fselects by their current value\n\u002F\u002F 6. getByAltText — for images\n\u002F\u002F 7. getByTitle — for elements with title attributes\n\u002F\u002F 8. getByTestId — LAST resort: not accessible, couples to implementation\n\n\u002F\u002F VARIANTS:\n\u002F\u002F   getBy*     — returns 1 match, throws if 0 or >1 (use when you expect exactly 1)\n\u002F\u002F   queryBy*   — returns 1 match or null, throws if >1 (use for \"not in document\" asserts)\n\u002F\u002F   getAllBy*  — returns array, throws if 0 (use when multiple matches expected)\n\u002F\u002F   findBy*    — async: waits up to 1000ms for element to appear (use for async renders)\n\n\u002F\u002F ANTI-PATTERN: using getByTestId when a role query works\nscreen.getByTestId('submit-button')  \u002F\u002F ← couples to test ID, not accessible\nscreen.getByRole('button', { name: 'Submit' })  \u002F\u002F ← mirrors user experience, accessible\n",[30,548,549,554,559,564,569,574,579,584,589,594,598,603,608,613,618,623,627,632,651],{"__ignoreMap":28},[33,550,551],{"class":35,"line":36},[33,552,553],{"class":78},"\u002F\u002F QUERY PRIORITY (from Testing Library docs — test as a user would interact):\n",[33,555,556],{"class":35,"line":82},[33,557,558],{"class":78},"\u002F\u002F 1. getByRole — most accessible, mirrors how AT users find elements\n",[33,560,561],{"class":35,"line":126},[33,562,563],{"class":78},"\u002F\u002F 2. getByLabelText — for form fields (labels are the user's way to find inputs)\n",[33,565,566],{"class":35,"line":133},[33,567,568],{"class":78},"\u002F\u002F 3. getByPlaceholderText — fallback for inputs without labels\n",[33,570,571],{"class":35,"line":148},[33,572,573],{"class":78},"\u002F\u002F 4. getByText — for non-interactive text content\n",[33,575,576],{"class":35,"line":160},[33,577,578],{"class":78},"\u002F\u002F 5. getByDisplayValue — for inputs\u002Fselects by their current value\n",[33,580,581],{"class":35,"line":166},[33,582,583],{"class":78},"\u002F\u002F 6. getByAltText — for images\n",[33,585,586],{"class":35,"line":181},[33,587,588],{"class":78},"\u002F\u002F 7. getByTitle — for elements with title attributes\n",[33,590,591],{"class":35,"line":193},[33,592,593],{"class":78},"\u002F\u002F 8. getByTestId — LAST resort: not accessible, couples to implementation\n",[33,595,596],{"class":35,"line":208},[33,597,130],{"emptyLinePlaceholder":129},[33,599,600],{"class":35,"line":223},[33,601,602],{"class":78},"\u002F\u002F VARIANTS:\n",[33,604,605],{"class":35,"line":229},[33,606,607],{"class":78},"\u002F\u002F   getBy*     — returns 1 match, throws if 0 or >1 (use when you expect exactly 1)\n",[33,609,610],{"class":35,"line":413},[33,611,612],{"class":78},"\u002F\u002F   queryBy*   — returns 1 match or null, throws if >1 (use for \"not in document\" asserts)\n",[33,614,615],{"class":35,"line":437},[33,616,617],{"class":78},"\u002F\u002F   getAllBy*  — returns array, throws if 0 (use when multiple matches expected)\n",[33,619,620],{"class":35,"line":459},[33,621,622],{"class":78},"\u002F\u002F   findBy*    — async: waits up to 1000ms for element to appear (use for async renders)\n",[33,624,625],{"class":35,"line":483},[33,626,130],{"emptyLinePlaceholder":129},[33,628,629],{"class":35,"line":488},[33,630,631],{"class":78},"\u002F\u002F ANTI-PATTERN: using getByTestId when a role query works\n",[33,633,634,637,640,642,645,648],{"class":35,"line":516},[33,635,636],{"class":104},"screen.",[33,638,639],{"class":39},"getByTestId",[33,641,292],{"class":104},[33,643,644],{"class":43},"'submit-button'",[33,646,647],{"class":104},")  ",[33,649,650],{"class":78},"\u002F\u002F ← couples to test ID, not accessible\n",[33,652,653,655,657,659,661,663,666,669],{"class":35,"line":529},[33,654,636],{"class":104},[33,656,359],{"class":39},[33,658,292],{"class":104},[33,660,364],{"class":43},[33,662,367],{"class":104},[33,664,665],{"class":43},"'Submit'",[33,667,668],{"class":104}," })  ",[33,670,671],{"class":78},"\u002F\u002F ← mirrors user experience, accessible\n",[14,673,675],{"id":674},"userevent-over-fireevent","userEvent over fireEvent",[19,677,679],{"filename":678,"language":67},"user_event.js",[24,680,682],{"className":70,"code":681,"language":67,"meta":28,"style":28},"import userEvent from '@testing-library\u002Fuser-event'\n\n\u002F\u002F userEvent simulates REAL browser interactions: focus, blur, key events,\n\u002F\u002F input events, click events — all in the correct order, just like a real user.\n\u002F\u002F fireEvent only dispatches the specific event you name — missing intermediate events.\n\n\u002F\u002F ANTI-PATTERN: fireEvent for user interactions\nfireEvent.change(input, { target: { value: 'hello' } })\n\u002F\u002F Only dispatches 'change' — skips focus, keydown, input events.\n\u002F\u002F Components that depend on those intermediate events break.\n\n\u002F\u002F PRODUCTION: userEvent for all user interactions\nconst user = userEvent.setup()\nawait user.type(input, 'hello')     \u002F\u002F dispatches focus → keydown → input → keyup per char\nawait user.click(button)            \u002F\u002F dispatches mouseover → mousemove → mousedown → focus → mouseup → click\nawait user.tab()                    \u002F\u002F simulates tab key navigation\nawait user.keyboard('{Enter}')      \u002F\u002F simulates keydown + keyup for Enter\nawait user.selectOptions(select, 'option1')  \u002F\u002F simulates selecting from a \u003Cselect>\n",[30,683,684,696,700,705,710,715,719,724,741,746,751,755,760,775,796,810,825,845],{"__ignoreMap":28},[33,685,686,688,691,693],{"class":35,"line":36},[33,687,86],{"class":85},[33,689,690],{"class":104}," userEvent ",[33,692,108],{"class":85},[33,694,695],{"class":43}," '@testing-library\u002Fuser-event'\n",[33,697,698],{"class":35,"line":82},[33,699,130],{"emptyLinePlaceholder":129},[33,701,702],{"class":35,"line":126},[33,703,704],{"class":78},"\u002F\u002F userEvent simulates REAL browser interactions: focus, blur, key events,\n",[33,706,707],{"class":35,"line":133},[33,708,709],{"class":78},"\u002F\u002F input events, click events — all in the correct order, just like a real user.\n",[33,711,712],{"class":35,"line":148},[33,713,714],{"class":78},"\u002F\u002F fireEvent only dispatches the specific event you name — missing intermediate events.\n",[33,716,717],{"class":35,"line":160},[33,718,130],{"emptyLinePlaceholder":129},[33,720,721],{"class":35,"line":166},[33,722,723],{"class":78},"\u002F\u002F ANTI-PATTERN: fireEvent for user interactions\n",[33,725,726,729,732,735,738],{"class":35,"line":181},[33,727,728],{"class":104},"fireEvent.",[33,730,731],{"class":39},"change",[33,733,734],{"class":104},"(input, { target: { value: ",[33,736,737],{"class":43},"'hello'",[33,739,740],{"class":104}," } })\n",[33,742,743],{"class":35,"line":193},[33,744,745],{"class":78},"\u002F\u002F Only dispatches 'change' — skips focus, keydown, input events.\n",[33,747,748],{"class":35,"line":208},[33,749,750],{"class":78},"\u002F\u002F Components that depend on those intermediate events break.\n",[33,752,753],{"class":35,"line":223},[33,754,130],{"emptyLinePlaceholder":129},[33,756,757],{"class":35,"line":229},[33,758,759],{"class":78},"\u002F\u002F PRODUCTION: userEvent for all user interactions\n",[33,761,762,765,767,769,771,773],{"class":35,"line":413},[33,763,764],{"class":85},"const",[33,766,419],{"class":47},[33,768,422],{"class":85},[33,770,425],{"class":104},[33,772,428],{"class":39},[33,774,379],{"class":104},[33,776,777,780,782,785,788,790,793],{"class":35,"line":437},[33,778,779],{"class":85},"await",[33,781,494],{"class":104},[33,783,784],{"class":39},"type",[33,786,787],{"class":104},"(input, ",[33,789,737],{"class":43},[33,791,792],{"class":104},")     ",[33,794,795],{"class":78},"\u002F\u002F dispatches focus → keydown → input → keyup per char\n",[33,797,798,800,802,804,807],{"class":35,"line":459},[33,799,779],{"class":85},[33,801,494],{"class":104},[33,803,497],{"class":39},[33,805,806],{"class":104},"(button)            ",[33,808,809],{"class":78},"\u002F\u002F dispatches mouseover → mousemove → mousedown → focus → mouseup → click\n",[33,811,812,814,816,819,822],{"class":35,"line":483},[33,813,779],{"class":85},[33,815,494],{"class":104},[33,817,818],{"class":39},"tab",[33,820,821],{"class":104},"()                    ",[33,823,824],{"class":78},"\u002F\u002F simulates tab key navigation\n",[33,826,827,829,831,834,836,839,842],{"class":35,"line":488},[33,828,779],{"class":85},[33,830,494],{"class":104},[33,832,833],{"class":39},"keyboard",[33,835,292],{"class":104},[33,837,838],{"class":43},"'{Enter}'",[33,840,841],{"class":104},")      ",[33,843,844],{"class":78},"\u002F\u002F simulates keydown + keyup for Enter\n",[33,846,847,849,851,854,857,860,862],{"class":35,"line":516},[33,848,779],{"class":85},[33,850,494],{"class":104},[33,852,853],{"class":39},"selectOptions",[33,855,856],{"class":104},"(select, ",[33,858,859],{"class":43},"'option1'",[33,861,647],{"class":104},[33,863,864],{"class":78},"\u002F\u002F simulates selecting from a \u003Cselect>\n",[14,866,868],{"id":867},"testing-async-components","Testing Async Components",[19,870,872],{"filename":871,"language":67},"async_test.js",[24,873,875],{"className":70,"code":874,"language":67,"meta":28,"style":28},"import { render, screen, waitFor, within } from '@testing-library\u002Freact'\n\nit('shows data after loading', async () => {\n  \u002F\u002F Mock the fetch call\n  vi.spyOn(global, 'fetch').mockResolvedValue({\n    ok: true,\n    json: () => Promise.resolve({ name: 'Alice' }),\n  })\n\n  render(\u003CUserProfile userId={1} \u002F>)\n\n  \u002F\u002F findBy* waits up to 1000ms for the element to appear — for async renders\n  expect(await screen.findByText('Alice')).toBeInTheDocument()\n\n  \u002F\u002F waitFor: for non-DOM assertions that need to wait\n  await waitFor(() => {\n    expect(fetch).toHaveBeenCalledWith('\u002Fapi\u002Fusers\u002F1')\n  })\n\n  \u002F\u002F Loading state should be gone\n  expect(screen.queryByText('Loading…')).not.toBeInTheDocument()\n})\n\nit('shows error on failed fetch', async () => {\n  vi.spyOn(global, 'fetch').mockRejectedValue(new Error('Network error'))\n  render(\u003CUserProfile userId={1} \u002F>)\n  expect(await screen.findByText('Error: Network error')).toBeInTheDocument()\n})\n",[30,876,877,888,892,912,917,939,949,977,981,985,1009,1013,1018,1044,1048,1053,1068,1086,1090,1094,1099,1121,1126,1131,1151,1183,1202,1226],{"__ignoreMap":28},[33,878,879,881,884,886],{"class":35,"line":36},[33,880,86],{"class":85},[33,882,883],{"class":104}," { render, screen, waitFor, within } ",[33,885,108],{"class":85},[33,887,256],{"class":43},[33,889,890],{"class":35,"line":82},[33,891,130],{"emptyLinePlaceholder":129},[33,893,894,897,899,902,904,906,908,910],{"class":35,"line":126},[33,895,896],{"class":39},"it",[33,898,292],{"class":104},[33,900,901],{"class":43},"'shows data after loading'",[33,903,400],{"class":104},[33,905,403],{"class":85},[33,907,406],{"class":104},[33,909,301],{"class":85},[33,911,304],{"class":104},[33,913,914],{"class":35,"line":133},[33,915,916],{"class":78},"  \u002F\u002F Mock the fetch call\n",[33,918,919,922,925,928,931,934,937],{"class":35,"line":148},[33,920,921],{"class":104},"  vi.",[33,923,924],{"class":39},"spyOn",[33,926,927],{"class":104},"(global, ",[33,929,930],{"class":43},"'fetch'",[33,932,933],{"class":104},").",[33,935,936],{"class":39},"mockResolvedValue",[33,938,145],{"class":104},[33,940,941,944,946],{"class":35,"line":160},[33,942,943],{"class":104},"    ok: ",[33,945,199],{"class":47},[33,947,948],{"class":104},",\n",[33,950,951,954,957,959,962,965,968,971,974],{"class":35,"line":166},[33,952,953],{"class":39},"    json",[33,955,956],{"class":104},": () ",[33,958,301],{"class":85},[33,960,961],{"class":47}," Promise",[33,963,964],{"class":104},".",[33,966,967],{"class":39},"resolve",[33,969,970],{"class":104},"({ name: ",[33,972,973],{"class":43},"'Alice'",[33,975,976],{"class":104}," }),\n",[33,978,979],{"class":35,"line":181},[33,980,384],{"class":104},[33,982,983],{"class":35,"line":193},[33,984,130],{"emptyLinePlaceholder":129},[33,986,987,990,992,995,998,1000,1003,1006],{"class":35,"line":208},[33,988,989],{"class":39},"  render",[33,991,328],{"class":104},[33,993,994],{"class":47},"UserProfile",[33,996,997],{"class":39}," userId",[33,999,337],{"class":85},[33,1001,1002],{"class":104},"{",[33,1004,1005],{"class":47},"1",[33,1007,1008],{"class":104},"} \u002F>)\n",[33,1010,1011],{"class":35,"line":223},[33,1012,130],{"emptyLinePlaceholder":129},[33,1014,1015],{"class":35,"line":229},[33,1016,1017],{"class":78},"  \u002F\u002F findBy* waits up to 1000ms for the element to appear — for async renders\n",[33,1019,1020,1023,1025,1027,1030,1033,1035,1037,1040,1042],{"class":35,"line":413},[33,1021,1022],{"class":39},"  expect",[33,1024,292],{"class":104},[33,1026,779],{"class":85},[33,1028,1029],{"class":104}," screen.",[33,1031,1032],{"class":39},"findByText",[33,1034,292],{"class":104},[33,1036,973],{"class":43},[33,1038,1039],{"class":104},")).",[33,1041,376],{"class":39},[33,1043,379],{"class":104},[33,1045,1046],{"class":35,"line":437},[33,1047,130],{"emptyLinePlaceholder":129},[33,1049,1050],{"class":35,"line":459},[33,1051,1052],{"class":78},"  \u002F\u002F waitFor: for non-DOM assertions that need to wait\n",[33,1054,1055,1058,1061,1064,1066],{"class":35,"line":483},[33,1056,1057],{"class":85},"  await",[33,1059,1060],{"class":39}," waitFor",[33,1062,1063],{"class":104},"(() ",[33,1065,301],{"class":85},[33,1067,304],{"class":104},[33,1069,1070,1072,1075,1078,1080,1083],{"class":35,"line":488},[33,1071,353],{"class":39},[33,1073,1074],{"class":104},"(fetch).",[33,1076,1077],{"class":39},"toHaveBeenCalledWith",[33,1079,292],{"class":104},[33,1081,1082],{"class":43},"'\u002Fapi\u002Fusers\u002F1'",[33,1084,1085],{"class":104},")\n",[33,1087,1088],{"class":35,"line":516},[33,1089,384],{"class":104},[33,1091,1092],{"class":35,"line":529},[33,1093,130],{"emptyLinePlaceholder":129},[33,1095,1096],{"class":35,"line":534},[33,1097,1098],{"class":78},"  \u002F\u002F Loading state should be gone\n",[33,1100,1102,1104,1106,1109,1111,1114,1117,1119],{"class":35,"line":1101},21,[33,1103,1022],{"class":39},[33,1105,356],{"class":104},[33,1107,1108],{"class":39},"queryByText",[33,1110,292],{"class":104},[33,1112,1113],{"class":43},"'Loading…'",[33,1115,1116],{"class":104},")).not.",[33,1118,376],{"class":39},[33,1120,379],{"class":104},[33,1122,1124],{"class":35,"line":1123},22,[33,1125,232],{"class":104},[33,1127,1129],{"class":35,"line":1128},23,[33,1130,130],{"emptyLinePlaceholder":129},[33,1132,1134,1136,1138,1141,1143,1145,1147,1149],{"class":35,"line":1133},24,[33,1135,896],{"class":39},[33,1137,292],{"class":104},[33,1139,1140],{"class":43},"'shows error on failed fetch'",[33,1142,400],{"class":104},[33,1144,403],{"class":85},[33,1146,406],{"class":104},[33,1148,301],{"class":85},[33,1150,304],{"class":104},[33,1152,1154,1156,1158,1160,1162,1164,1167,1169,1172,1175,1177,1180],{"class":35,"line":1153},25,[33,1155,921],{"class":104},[33,1157,924],{"class":39},[33,1159,927],{"class":104},[33,1161,930],{"class":43},[33,1163,933],{"class":104},[33,1165,1166],{"class":39},"mockRejectedValue",[33,1168,292],{"class":104},[33,1170,1171],{"class":85},"new",[33,1173,1174],{"class":39}," Error",[33,1176,292],{"class":104},[33,1178,1179],{"class":43},"'Network error'",[33,1181,1182],{"class":104},"))\n",[33,1184,1186,1188,1190,1192,1194,1196,1198,1200],{"class":35,"line":1185},26,[33,1187,989],{"class":39},[33,1189,328],{"class":104},[33,1191,994],{"class":47},[33,1193,997],{"class":39},[33,1195,337],{"class":85},[33,1197,1002],{"class":104},[33,1199,1005],{"class":47},[33,1201,1008],{"class":104},[33,1203,1205,1207,1209,1211,1213,1215,1217,1220,1222,1224],{"class":35,"line":1204},27,[33,1206,1022],{"class":39},[33,1208,292],{"class":104},[33,1210,779],{"class":85},[33,1212,1029],{"class":104},[33,1214,1032],{"class":39},[33,1216,292],{"class":104},[33,1218,1219],{"class":43},"'Error: Network error'",[33,1221,1039],{"class":104},[33,1223,376],{"class":39},[33,1225,379],{"class":104},[33,1227,1229],{"class":35,"line":1228},28,[33,1230,232],{"class":104},[14,1232,1234],{"id":1233},"testing-context-providers","Testing Context Providers",[19,1236,1238],{"filename":1237,"language":67},"context_test.js",[24,1239,1241],{"className":70,"code":1240,"language":67,"meta":28,"style":28},"import { render, screen } from '@testing-library\u002Freact'\nimport { ThemeProvider } from '.\u002FThemeContext'\n\n\u002F\u002F Wrap the component under test in the provider — don't mock the context\n\u002F\u002F unless you specifically want to test behavior when the provider is absent.\nfunction renderWithTheme(ui, { theme = 'dark' } = {}) {\n  return render(\n    \u003CThemeProvider value={{ theme, toggle: vi.fn() }}>\n      {ui}\n    \u003C\u002FThemeProvider>\n  )\n}\n\nit('renders dark theme', () => {\n  renderWithTheme(\u003CThemedCard \u002F>, { theme: 'dark' })\n  expect(screen.getByRole('region')).toHaveClass('card-dark')\n})\n",[30,1242,1243,1253,1265,1269,1274,1279,1312,1323,1344,1349,1359,1364,1369,1373,1388,1407,1432],{"__ignoreMap":28},[33,1244,1245,1247,1249,1251],{"class":35,"line":36},[33,1246,86],{"class":85},[33,1248,251],{"class":104},[33,1250,108],{"class":85},[33,1252,256],{"class":43},[33,1254,1255,1257,1260,1262],{"class":35,"line":82},[33,1256,86],{"class":85},[33,1258,1259],{"class":104}," { ThemeProvider } ",[33,1261,108],{"class":85},[33,1263,1264],{"class":43}," '.\u002FThemeContext'\n",[33,1266,1267],{"class":35,"line":126},[33,1268,130],{"emptyLinePlaceholder":129},[33,1270,1271],{"class":35,"line":133},[33,1272,1273],{"class":78},"\u002F\u002F Wrap the component under test in the provider — don't mock the context\n",[33,1275,1276],{"class":35,"line":148},[33,1277,1278],{"class":78},"\u002F\u002F unless you specifically want to test behavior when the provider is absent.\n",[33,1280,1281,1284,1287,1289,1293,1296,1299,1301,1304,1307,1309],{"class":35,"line":160},[33,1282,1283],{"class":85},"function",[33,1285,1286],{"class":39}," renderWithTheme",[33,1288,292],{"class":104},[33,1290,1292],{"class":1291},"sCrzJ","ui",[33,1294,1295],{"class":104},", { ",[33,1297,1298],{"class":1291},"theme",[33,1300,422],{"class":85},[33,1302,1303],{"class":43}," 'dark'",[33,1305,1306],{"class":104}," } ",[33,1308,337],{"class":85},[33,1310,1311],{"class":104}," {}) {\n",[33,1313,1314,1317,1320],{"class":35,"line":166},[33,1315,1316],{"class":85},"  return",[33,1318,1319],{"class":39}," render",[33,1321,1322],{"class":104},"(\n",[33,1324,1325,1328,1331,1334,1336,1339,1341],{"class":35,"line":181},[33,1326,1327],{"class":104},"    \u003C",[33,1329,1330],{"class":47},"ThemeProvider",[33,1332,1333],{"class":39}," value",[33,1335,337],{"class":85},[33,1337,1338],{"class":104},"{{ theme, toggle: vi.",[33,1340,450],{"class":39},[33,1342,1343],{"class":104},"() }}>\n",[33,1345,1346],{"class":35,"line":193},[33,1347,1348],{"class":104},"      {ui}\n",[33,1350,1351,1354,1356],{"class":35,"line":208},[33,1352,1353],{"class":104},"    \u003C\u002F",[33,1355,1330],{"class":47},[33,1357,1358],{"class":104},">\n",[33,1360,1361],{"class":35,"line":223},[33,1362,1363],{"class":104},"  )\n",[33,1365,1366],{"class":35,"line":229},[33,1367,1368],{"class":104},"}\n",[33,1370,1371],{"class":35,"line":413},[33,1372,130],{"emptyLinePlaceholder":129},[33,1374,1375,1377,1379,1382,1384,1386],{"class":35,"line":437},[33,1376,896],{"class":39},[33,1378,292],{"class":104},[33,1380,1381],{"class":43},"'renders dark theme'",[33,1383,298],{"class":104},[33,1385,301],{"class":85},[33,1387,304],{"class":104},[33,1389,1390,1393,1395,1398,1401,1404],{"class":35,"line":459},[33,1391,1392],{"class":39},"  renderWithTheme",[33,1394,328],{"class":104},[33,1396,1397],{"class":47},"ThemedCard",[33,1399,1400],{"class":104}," \u002F>, { theme: ",[33,1402,1403],{"class":43},"'dark'",[33,1405,1406],{"class":104}," })\n",[33,1408,1409,1411,1413,1415,1417,1420,1422,1425,1427,1430],{"class":35,"line":483},[33,1410,1022],{"class":39},[33,1412,356],{"class":104},[33,1414,359],{"class":39},[33,1416,292],{"class":104},[33,1418,1419],{"class":43},"'region'",[33,1421,1039],{"class":104},[33,1423,1424],{"class":39},"toHaveClass",[33,1426,292],{"class":104},[33,1428,1429],{"class":43},"'card-dark'",[33,1431,1085],{"class":104},[33,1433,1434],{"class":35,"line":488},[33,1435,232],{"class":104},[14,1437,1439],{"id":1438},"tips-tricks","💡 Tips & Tricks",[19,1441,1443],{"filename":1442,"language":67},"tips.js",[24,1444,1446],{"className":70,"code":1445,"language":67,"meta":28,"style":28},"\u002F\u002F [Idiom] Test behavior, not implementation. Assert what the user sees (text,\n\u002F\u002F roles, visibility), not internal state or method calls. Tests that check\n\u002F\u002F \"setState was called\" break on refactoring without real bugs.\n\n\u002F\u002F [Debug] Use screen.debug() to print the current DOM when a query fails:\n\u002F\u002F   screen.debug()  \u002F\u002F prints the full rendered HTML\n\u002F\u002F   screen.debug(screen.getByRole('button'))  \u002F\u002F prints just the button\n\n\u002F\u002F [Idiom] Use within() to scope queries to a container:\n\u002F\u002F   const { getByRole } = within(screen.getByTestId('sidebar'))\n\u002F\u002F   getByRole('button', { name: 'Settings' })  \u002F\u002F only searches within sidebar\n\n\u002F\u002F [Performance] Use afterEach(() => { cleanup(); vi.restoreAllMocks() }) to\n\u002F\u002F reset between tests. Without cleanup, rendered components persist and can\n\u002F\u002F cause \"multiple elements\" errors in subsequent tests.\n\n\u002F\u002F [Idiom] For custom hooks that don't return JSX, use @testing-library\u002Freact-hooks\n\u002F\u002F (React 17) or render a test component that uses the hook (React 18+):\n\u002F\u002F   function TestComponent() { const result = useMyHook(); return \u003Cdiv>{result}\u003C\u002Fdiv> }\n",[30,1447,1448,1453,1458,1463,1467,1472,1480,1488,1492,1497,1502,1510,1514,1519,1524,1529,1533,1538,1543],{"__ignoreMap":28},[33,1449,1450],{"class":35,"line":36},[33,1451,1452],{"class":78},"\u002F\u002F [Idiom] Test behavior, not implementation. Assert what the user sees (text,\n",[33,1454,1455],{"class":35,"line":82},[33,1456,1457],{"class":78},"\u002F\u002F roles, visibility), not internal state or method calls. Tests that check\n",[33,1459,1460],{"class":35,"line":126},[33,1461,1462],{"class":78},"\u002F\u002F \"setState was called\" break on refactoring without real bugs.\n",[33,1464,1465],{"class":35,"line":133},[33,1466,130],{"emptyLinePlaceholder":129},[33,1468,1469],{"class":35,"line":148},[33,1470,1471],{"class":78},"\u002F\u002F [Debug] Use screen.debug() to print the current DOM when a query fails:\n",[33,1473,1474,1477],{"class":35,"line":160},[33,1475,1476],{"class":78},"\u002F\u002F   screen.debug()",[33,1478,1479],{"class":78},"  \u002F\u002F prints the full rendered HTML\n",[33,1481,1482,1485],{"class":35,"line":166},[33,1483,1484],{"class":78},"\u002F\u002F   screen.debug(screen.getByRole('button'))",[33,1486,1487],{"class":78},"  \u002F\u002F prints just the button\n",[33,1489,1490],{"class":35,"line":181},[33,1491,130],{"emptyLinePlaceholder":129},[33,1493,1494],{"class":35,"line":193},[33,1495,1496],{"class":78},"\u002F\u002F [Idiom] Use within() to scope queries to a container:\n",[33,1498,1499],{"class":35,"line":208},[33,1500,1501],{"class":78},"\u002F\u002F   const { getByRole } = within(screen.getByTestId('sidebar'))\n",[33,1503,1504,1507],{"class":35,"line":223},[33,1505,1506],{"class":78},"\u002F\u002F   getByRole('button', { name: 'Settings' })",[33,1508,1509],{"class":78},"  \u002F\u002F only searches within sidebar\n",[33,1511,1512],{"class":35,"line":229},[33,1513,130],{"emptyLinePlaceholder":129},[33,1515,1516],{"class":35,"line":413},[33,1517,1518],{"class":78},"\u002F\u002F [Performance] Use afterEach(() => { cleanup(); vi.restoreAllMocks() }) to\n",[33,1520,1521],{"class":35,"line":437},[33,1522,1523],{"class":78},"\u002F\u002F reset between tests. Without cleanup, rendered components persist and can\n",[33,1525,1526],{"class":35,"line":459},[33,1527,1528],{"class":78},"\u002F\u002F cause \"multiple elements\" errors in subsequent tests.\n",[33,1530,1531],{"class":35,"line":483},[33,1532,130],{"emptyLinePlaceholder":129},[33,1534,1535],{"class":35,"line":488},[33,1536,1537],{"class":78},"\u002F\u002F [Idiom] For custom hooks that don't return JSX, use @testing-library\u002Freact-hooks\n",[33,1539,1540],{"class":35,"line":516},[33,1541,1542],{"class":78},"\u002F\u002F (React 17) or render a test component that uses the hook (React 18+):\n",[33,1544,1545],{"class":35,"line":529},[33,1546,1547],{"class":78},"\u002F\u002F   function TestComponent() { const result = useMyHook(); return \u003Cdiv>{result}\u003C\u002Fdiv> }\n",[14,1549,1551],{"id":1550},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,1553,1555],{"filename":1554,"language":67},"edge_cases.js",[24,1556,1558],{"className":70,"code":1557,"language":67,"meta":28,"style":28},"\u002F\u002F [Gotcha] getBy* throws if 0 matches OR >1 matches. Use queryBy* for \"not present\"\n\u002F\u002F asserts (returns null, doesn't throw). Use getAllBy* for multiple matches.\n\n\u002F\u002F [Gotcha] findBy* has a 1000ms default timeout. For slow operations, pass a\n\u002F\u002F custom timeout: await screen.findByText('Loaded', {}, { timeout: 5000 })\n\n\u002F\u002F [Gotcha] Snapshot testing (toMatchSnapshot) is tempting but fragile — any\n\u002F\u002F minor DOM change (className, whitespace) breaks the snapshot, and developers\n\u002F\u002F reflexively update snapshots without reviewing. Prefer explicit assertions\n\u002F\u002F about behavior and content. If you must use snapshots, use toMatchInlineSnapshot.\n\n\u002F\u002F [Gotcha] Mocking modules with vi.mock() is hoisted — it runs BEFORE imports.\n\u002F\u002F The factory function can't reference variables defined in the test file\n\u002F\u002F without vi.hoisted(). Use vi.spyOn for method-level mocking instead.\n\n\u002F\u002F [Gotcha] userEvent.setup() must be called in the test body (not at describe\n\u002F\u002F level) to properly reset between tests. Each test gets a fresh user instance.\n",[30,1559,1560,1565,1570,1574,1579,1584,1588,1593,1598,1603,1608,1612,1617,1622,1627,1631,1636],{"__ignoreMap":28},[33,1561,1562],{"class":35,"line":36},[33,1563,1564],{"class":78},"\u002F\u002F [Gotcha] getBy* throws if 0 matches OR >1 matches. Use queryBy* for \"not present\"\n",[33,1566,1567],{"class":35,"line":82},[33,1568,1569],{"class":78},"\u002F\u002F asserts (returns null, doesn't throw). Use getAllBy* for multiple matches.\n",[33,1571,1572],{"class":35,"line":126},[33,1573,130],{"emptyLinePlaceholder":129},[33,1575,1576],{"class":35,"line":133},[33,1577,1578],{"class":78},"\u002F\u002F [Gotcha] findBy* has a 1000ms default timeout. For slow operations, pass a\n",[33,1580,1581],{"class":35,"line":148},[33,1582,1583],{"class":78},"\u002F\u002F custom timeout: await screen.findByText('Loaded', {}, { timeout: 5000 })\n",[33,1585,1586],{"class":35,"line":160},[33,1587,130],{"emptyLinePlaceholder":129},[33,1589,1590],{"class":35,"line":166},[33,1591,1592],{"class":78},"\u002F\u002F [Gotcha] Snapshot testing (toMatchSnapshot) is tempting but fragile — any\n",[33,1594,1595],{"class":35,"line":181},[33,1596,1597],{"class":78},"\u002F\u002F minor DOM change (className, whitespace) breaks the snapshot, and developers\n",[33,1599,1600],{"class":35,"line":193},[33,1601,1602],{"class":78},"\u002F\u002F reflexively update snapshots without reviewing. Prefer explicit assertions\n",[33,1604,1605],{"class":35,"line":208},[33,1606,1607],{"class":78},"\u002F\u002F about behavior and content. If you must use snapshots, use toMatchInlineSnapshot.\n",[33,1609,1610],{"class":35,"line":223},[33,1611,130],{"emptyLinePlaceholder":129},[33,1613,1614],{"class":35,"line":229},[33,1615,1616],{"class":78},"\u002F\u002F [Gotcha] Mocking modules with vi.mock() is hoisted — it runs BEFORE imports.\n",[33,1618,1619],{"class":35,"line":413},[33,1620,1621],{"class":78},"\u002F\u002F The factory function can't reference variables defined in the test file\n",[33,1623,1624],{"class":35,"line":437},[33,1625,1626],{"class":78},"\u002F\u002F without vi.hoisted(). Use vi.spyOn for method-level mocking instead.\n",[33,1628,1629],{"class":35,"line":459},[33,1630,130],{"emptyLinePlaceholder":129},[33,1632,1633],{"class":35,"line":483},[33,1634,1635],{"class":78},"\u002F\u002F [Gotcha] userEvent.setup() must be called in the test body (not at describe\n",[33,1637,1638],{"class":35,"line":488},[33,1639,1640],{"class":78},"\u002F\u002F level) to properly reset between tests. Each test gets a fresh user instance.\n",[14,1642,1644],{"id":1643},"spot-the-bug","🧠 Spot the Bug",[1646,1647,1648],"p",{},"A test for a modal component fails with \"Unable to find an element by role\":",[19,1650,1652],{"filename":1651,"language":67},"spot_the_bug.js",[24,1653,1655],{"className":70,"code":1654,"language":67,"meta":28,"style":28},"it('shows modal content', () => {\n  render(\u003CModal isOpen={true}>Delete this item?\u003C\u002FModal>)\n  expect(screen.getByText('Delete this item?')).toBeInTheDocument()\n})\n",[30,1656,1657,1672,1698,1718],{"__ignoreMap":28},[33,1658,1659,1661,1663,1666,1668,1670],{"class":35,"line":36},[33,1660,896],{"class":39},[33,1662,292],{"class":104},[33,1664,1665],{"class":43},"'shows modal content'",[33,1667,298],{"class":104},[33,1669,301],{"class":85},[33,1671,304],{"class":104},[33,1673,1674,1676,1678,1681,1684,1686,1688,1690,1693,1695],{"class":35,"line":82},[33,1675,989],{"class":39},[33,1677,328],{"class":104},[33,1679,1680],{"class":47},"Modal",[33,1682,1683],{"class":39}," isOpen",[33,1685,337],{"class":85},[33,1687,1002],{"class":104},[33,1689,199],{"class":47},[33,1691,1692],{"class":104},"}>Delete this item?\u003C\u002F",[33,1694,1680],{"class":47},[33,1696,1697],{"class":104},">)\n",[33,1699,1700,1702,1704,1707,1709,1712,1714,1716],{"class":35,"line":126},[33,1701,1022],{"class":39},[33,1703,356],{"class":104},[33,1705,1706],{"class":39},"getByText",[33,1708,292],{"class":104},[33,1710,1711],{"class":43},"'Delete this item?'",[33,1713,1039],{"class":104},[33,1715,376],{"class":39},[33,1717,379],{"class":104},[33,1719,1720],{"class":35,"line":133},[33,1721,232],{"class":104},[1646,1723,1724,1725,1728,1729,1732],{},"The modal uses a portal (",[30,1726,1727],{},"createPortal",") to render into ",[30,1730,1731],{},"document.body",". Why does the query fail?",[1734,1735,1736,1740,1756,1770,1848],"details",{},[1737,1738,1739],"summary",{},"Answer",[1646,1741,1742,1745,1746,1749,1750,1752,1753,1755],{},[30,1743,1744],{},"render()"," from Testing Library renders into a container div, and ",[30,1747,1748],{},"screen"," queries are scoped to that container by default. But ",[30,1751,1727],{}," renders into ",[30,1754,1731],{}," — outside the Testing Library container. The query doesn't find the portal content because it's looking in the wrong part of the DOM.",[1646,1757,1758,1762,1763,1766,1767,1769],{},[1759,1760,1761],"strong",{},"Fix",": use ",[30,1764,1765],{},"screen.getByText"," on the full ",[30,1768,1731],{},", or configure the base element:",[24,1771,1773],{"className":70,"code":1772,"language":67,"meta":28,"style":28},"import { render, screen } from '@testing-library\u002Freact'\n\nit('shows modal content', () => {\n  render(\u003CModal isOpen={true}>Delete this item?\u003C\u002FModal>, { container: document.body })\n  expect(screen.getByText('Delete this item?')).toBeInTheDocument()\n})\n",[30,1774,1775,1785,1789,1803,1826,1844],{"__ignoreMap":28},[33,1776,1777,1779,1781,1783],{"class":35,"line":36},[33,1778,86],{"class":85},[33,1780,251],{"class":104},[33,1782,108],{"class":85},[33,1784,256],{"class":43},[33,1786,1787],{"class":35,"line":82},[33,1788,130],{"emptyLinePlaceholder":129},[33,1790,1791,1793,1795,1797,1799,1801],{"class":35,"line":126},[33,1792,896],{"class":39},[33,1794,292],{"class":104},[33,1796,1665],{"class":43},[33,1798,298],{"class":104},[33,1800,301],{"class":85},[33,1802,304],{"class":104},[33,1804,1805,1807,1809,1811,1813,1815,1817,1819,1821,1823],{"class":35,"line":133},[33,1806,989],{"class":39},[33,1808,328],{"class":104},[33,1810,1680],{"class":47},[33,1812,1683],{"class":39},[33,1814,337],{"class":85},[33,1816,1002],{"class":104},[33,1818,199],{"class":47},[33,1820,1692],{"class":104},[33,1822,1680],{"class":47},[33,1824,1825],{"class":104},">, { container: document.body })\n",[33,1827,1828,1830,1832,1834,1836,1838,1840,1842],{"class":35,"line":148},[33,1829,1022],{"class":39},[33,1831,356],{"class":104},[33,1833,1706],{"class":39},[33,1835,292],{"class":104},[33,1837,1711],{"class":43},[33,1839,1039],{"class":104},[33,1841,376],{"class":39},[33,1843,379],{"class":104},[33,1845,1846],{"class":35,"line":160},[33,1847,232],{"class":104},[1646,1849,1850,1851,1854,1855,1857,1858,1860,1861,1864,1865,1867],{},"Or query the body directly: ",[30,1852,1853],{},"expect(screen.getByText('Delete this item?')).toBeInTheDocument()"," — ",[30,1856,1748],{}," actually queries ",[30,1859,1731],{}," by default in modern versions of Testing Library. If it still fails, check whether ",[30,1862,1863],{},"isOpen"," is actually ",[30,1866,199],{}," at render time (state may not have updated yet).",[14,1869,1871],{"id":1870},"key-takeaways","Key Takeaways",[19,1873,1875],{"filename":1874,"language":67},"key_takeaways.js",[24,1876,1878],{"className":70,"code":1877,"language":67,"meta":28,"style":28},"\u002F\u002F 1. Test BEHAVIOR, not implementation. Assert what the user sees (roles, text,\n\u002F\u002F    visibility), not internal state or method calls. Tests survive refactoring.\n\n\u002F\u002F 2. Query priority: getByRole > getByLabelText > getByText > getByTestId.\n\u002F\u002F    Use getByRole with { name: '...' } — it mirrors how users find elements.\n\n\u002F\u002F 3. Use userEvent (not fireEvent) for all user interactions — it simulates\n\u002F\u002F    the full event sequence (focus → keydown → input → keyup) like a real user.\n\n\u002F\u002F 4. Async: findBy* (waits for element to appear), waitFor (waits for assertion).\n\u002F\u002F    Default timeout 1000ms — increase with { timeout: 5000 } for slow ops.\n\n\u002F\u002F 5. Wrap components in their context providers in tests — don't mock the context\n\u002F\u002F    unless testing the \"no provider\" error case. Snapshot tests are fragile —\n\u002F\u002F    prefer explicit assertions about behavior and content.\n",[30,1879,1880,1885,1890,1894,1899,1904,1908,1913,1918,1922,1927,1932,1936,1941,1946],{"__ignoreMap":28},[33,1881,1882],{"class":35,"line":36},[33,1883,1884],{"class":78},"\u002F\u002F 1. Test BEHAVIOR, not implementation. Assert what the user sees (roles, text,\n",[33,1886,1887],{"class":35,"line":82},[33,1888,1889],{"class":78},"\u002F\u002F    visibility), not internal state or method calls. Tests survive refactoring.\n",[33,1891,1892],{"class":35,"line":126},[33,1893,130],{"emptyLinePlaceholder":129},[33,1895,1896],{"class":35,"line":133},[33,1897,1898],{"class":78},"\u002F\u002F 2. Query priority: getByRole > getByLabelText > getByText > getByTestId.\n",[33,1900,1901],{"class":35,"line":148},[33,1902,1903],{"class":78},"\u002F\u002F    Use getByRole with { name: '...' } — it mirrors how users find elements.\n",[33,1905,1906],{"class":35,"line":160},[33,1907,130],{"emptyLinePlaceholder":129},[33,1909,1910],{"class":35,"line":166},[33,1911,1912],{"class":78},"\u002F\u002F 3. Use userEvent (not fireEvent) for all user interactions — it simulates\n",[33,1914,1915],{"class":35,"line":181},[33,1916,1917],{"class":78},"\u002F\u002F    the full event sequence (focus → keydown → input → keyup) like a real user.\n",[33,1919,1920],{"class":35,"line":193},[33,1921,130],{"emptyLinePlaceholder":129},[33,1923,1924],{"class":35,"line":208},[33,1925,1926],{"class":78},"\u002F\u002F 4. Async: findBy* (waits for element to appear), waitFor (waits for assertion).\n",[33,1928,1929],{"class":35,"line":223},[33,1930,1931],{"class":78},"\u002F\u002F    Default timeout 1000ms — increase with { timeout: 5000 } for slow ops.\n",[33,1933,1934],{"class":35,"line":229},[33,1935,130],{"emptyLinePlaceholder":129},[33,1937,1938],{"class":35,"line":413},[33,1939,1940],{"class":78},"\u002F\u002F 5. Wrap components in their context providers in tests — don't mock the context\n",[33,1942,1943],{"class":35,"line":437},[33,1944,1945],{"class":78},"\u002F\u002F    unless testing the \"no provider\" error case. Snapshot tests are fragile —\n",[33,1947,1948],{"class":35,"line":459},[33,1949,1950],{"class":78},"\u002F\u002F    prefer explicit assertions about behavior and content.\n",[1952,1953,1954],"style",{},"html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":28,"searchDepth":82,"depth":82,"links":1956},[1957,1958,1959,1960,1961,1962,1963,1964,1965,1966],{"id":16,"depth":82,"text":17},{"id":235,"depth":82,"text":236},{"id":539,"depth":82,"text":540},{"id":674,"depth":82,"text":675},{"id":867,"depth":82,"text":868},{"id":1233,"depth":82,"text":1234},{"id":1438,"depth":82,"text":1439},{"id":1550,"depth":82,"text":1551},{"id":1643,"depth":82,"text":1644},{"id":1870,"depth":82,"text":1871},"React Testing Library patterns — queries, userEvent, async testing, hook testing, mocking, and why snapshot testing should be avoided. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F24-testing",{"title":5,"description":1967},"react\u002F24-testing","Ol4FgmHyg-frws869GNc2_5qNv_NY2IjITy2Urf49tM",1789924652539]