{"checkpoints":{"s-tj-1":[{"q":"<p>يبلّغ مستخدم أن تقسيم عشاء بقيمة $100.00 بين ثلاثة أشخاص يترك الأرصدة منحرفة بسنت واحد. أيّ اختبار تكتبه أولًا؟</p>","options":[{"text":"اختبار Playwright يضيف العشاء في المتصفّح ويقرأ الأرصدة.","why":"سيعيد إنتاج الخطأ، لكن ببطء ومع تدخّل النظام كله؛ وحين يفشل ما زال عليك أن تجد كود التقريب."},{"text":"اختبار لقطة شاشة لشاشة الأرصدة.","why":"قد تُظهر لقطة الشاشة الرقم الخاطئ لكنها لا تقول شيئًا عن الحساب الذي أنتجه، وتنكسر مع أي تغيير بصري."},{"text":"نوع TypeScript أكثر صرامة لمعامل المبلغ.","why":"القيم أرقام أصلًا؛ ولا يستطيع النوع التمييز بين 3333 و3334 سنتًا."},{"text":"unit test يقسّم 10000 سنت على 3 أشخاص ويتحقّق من أن مجموع الحصص يساوي 10000.","why":"صحيح. الخطأ في منطق صافٍ، فأرخص اختبار يفشل تمامًا حين يكون خاطئًا هو unit test يستغرق جزءًا من الثانية مع ثابت المجموع."}],"answer":3},{"q":"<p>بافتراض أن الاستيرادات موجودة، ماذا يحدث حين يعمل هذا الاختبار؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">test</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'formats cents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1234</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toEqual</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'$12.34'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'$0.05'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n","options":[{"text":"ينجح إن كانت <code dir=\"ltr\">formatMoney</code> صحيحة: فـ <code dir=\"ltr\">toEqual</code> يقارن القيم البدائية بقيمتها، تمامًا مثل <code dir=\"ltr\">toBe</code>.","why":"صحيح. الفرق بين هذين الـ matchers لا يظهر إلا مع الكائنات والمصفوفات."},{"text":"يفشل، لأن <code dir=\"ltr\">toEqual</code> لا يُستخدم إلا مع الكائنات.","why":"يعمل <code dir=\"ltr\">toEqual</code> مع أي قيمة؛ ومع النصوص يتصرّف مثل <code dir=\"ltr\">toBe</code>."},{"text":"يفشل، لأن الاختبار لا يجوز أن يحتوي إلا على <code dir=\"ltr\">expect</code> واحد.","why":"لا توجد قاعدة كهذه؛ سلوك واحد لكل اختبار هو التوجيه، والسطران هنا يفحصان التنسيق."},{"text":"لا يعمل إلا حين يكون <code dir=\"ltr\">globals: true</code> مضبوطًا في الإعدادات.","why":"مع الاستيراد الصريح من <code dir=\"ltr\">vitest</code>، لا حاجة إلى الـ globals."}],"answer":0},{"q":"<p>في <code dir=\"ltr\">package.json</code> لديك <code dir=\"ltr\">&quot;test&quot;: &quot;vitest&quot;</code>، ومهمّة GitHub Actions تشغّل <code dir=\"ltr\">npm test</code>. ماذا يحدث بعد انتهاء الاختبارات في CI؟</p>","options":[{"text":"تعلق المهمّة في وضع المراقبة حتى تصل إلى مهلة الخادم.","why":"هذا ما كان سيحدث لولا اكتشاف CI؛ فـ Vitest تفحص متغيّر البيئة <code dir=\"ltr\">CI</code>."},{"text":"تخرج Vitest بخطأ تطلب فيه الخيار <code dir=\"ltr\">--run</code>.","why":"لا تشترط Vitest هذا الخيار في CI؛ بل تختار وضع التشغيل الواحد بنفسها."},{"text":"تلاحظ Vitest متغيّر البيئة <code dir=\"ltr\">CI</code>، فتعمل مرة واحدة وتخرج بالنتيجة.","why":"صحيح. وضع المراقبة لا يكون افتراضيًا إلا في طرفية محلية تفاعلية."},{"text":"تتخطّى Vitest الاختبارات لأنه لا توجد طرفية متّصلة.","why":"تعمل الاختبارات بشكل طبيعي في CI؛ والمراقبة وحدها هي التي تُطفأ."}],"answer":2},{"q":"<p>يفشل اختبار بهذه المخرجات. ما الخطأ الأرجح؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>FAIL  src/tip.test.js &gt; withTip &gt; adds a 15% tip\nAssertionError: expected { tipCents: 300, totalCents: 2000 }\n  to deeply equal { tipCents: 300, totalCents: 2300 }</code></pre></div>\n","options":[{"text":"البقشيش يُقرَّب في الاتجاه الخطأ.","why":"قيمة <code dir=\"ltr\">tipCents</code> المستلمة 300، كما هو متوقّع تمامًا، فحساب البقشيش سليم."},{"text":"يجب أن يستخدم الاختبار <code dir=\"ltr\">toBe</code> بدل <code dir=\"ltr\">toEqual</code>.","why":"سيفشل <code dir=\"ltr\">toBe</code> حتى مع نتيجة صحيحة، لأن الدالة تعيد كائنًا جديدًا."},{"text":"النسبة تُقسم على 1000 بدل 100.","why":"عندها سيكون البقشيش 30 لا 300؛ والبقشيش في الكائن المستلم صحيح."},{"text":"المجموع يُعاد دون إضافة البقشيش.","why":"صحيح. <code dir=\"ltr\">totalCents</code> يساوي المجموع الفرعي (2000)، فقد نسيت الدالة إضافة <code dir=\"ltr\">tipCents</code>."}],"answer":3},{"q":"<p>في مشروعك 300 اختبار لمنطق مالي صافٍ و5 ملفات اختبار تعرض DOM. كيف تضبط البيئة؟</p>","options":[{"text":"اضبط <code dir=\"ltr\">environment: 'jsdom'</code> على مستوى المشروع كي يستطيع كل اختبار استخدام الـ DOM.","why":"عندها تدفع الاختبارات الصافية الـ 300 كلها ثمن بناء متصفّح مزيّف، ويمرّ الاستخدام غير المقصود للـ DOM في الكود الصافي دون أن يُلاحظ."},{"text":"أبقِ بيئة Node الافتراضية وأضف <code dir=\"ltr\">// @vitest-environment jsdom</code> في أعلى ملفات اختبار الـ DOM الخمسة.","why":"صحيح. فعّل الـ DOM لكل ملف على حدة حيث يُحتاج إليه، وأبقِ البقية سريعة وصادقة."},{"text":"استبدل <code dir=\"ltr\">document</code> بكائن عادي في ملف إعداد.","why":"البديل المصنوع يدويًا يفوته معظم سلوك الـ DOM؛ وjsdom أو happy-dom موجودان بالضبط لتوفيره."},{"text":"انقل اختبارات الـ DOM الخمسة إلى Playwright.","why":"اختبارات الـ end-to-end أبطأ بكثير؛ وسلوك الـ DOM مثل عرض قائمة يخدمه jsdom جيدًا."}],"answer":1}],"s-tj-2":[{"q":"<p>يمنح Splitwise-lite اشتراكًا مميّزًا مجانيًا للمجموعات التي تنفق $50.00 (5000 سنت) أو أكثر. أيّ مدخلين يختبران القاعدة على أفضل وجه؟</p>","options":[{"text":"4999 و5000","why":"صحيح. يقعان على جهتي الحدّ، وهما الزوج الوحيد الذي يميّز <code dir=\"ltr\">&gt;=</code> من <code dir=\"ltr\">&gt;</code>."},{"text":"1000 و10000","why":"كلاهما بعيد عن الحدّ، فالخطأ بفارق واحد في المقارنة ينجح فيهما معًا."},{"text":"0 و5000","why":"الصفر حالة إضافية مفيدة، لكن بدون 4999 لا تستطيع معرفة إن كان الحدّ في مكانه الصحيح."},{"text":"5000 و5001","why":"كلاهما يجب أن يستحقّ الاشتراك، فالخطأ الذي يقبل 4999 أيضًا لن يُلتقط."}],"answer":0},{"q":"<p>ما الذي يجب تغييره في هذا الاختبار؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">test</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'rejects a negative amount'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">saveExpense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ description: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Taxi'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amountCents: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }, api))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .rejects.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toThrow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'positive'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n","options":[{"text":"لا شيء؛ إنه صحيح كما هو مكتوب.","why":"الـ assertion من نوع <code dir=\"ltr\">rejects</code> لا يُنتظَر أبدًا، فلا ينتظره الاختبار؛ وتعلّم Vitest 4 هذه الاختبارات بأنها فاشلة."},{"text":"استبدل <code dir=\"ltr\">toThrow</code> بـ <code dir=\"ltr\">toThrowError</code>.","why":"هما اسمان للشيء نفسه؛ المشكلة في <code dir=\"ltr\">await</code> المفقودة."},{"text":"لفّ الاستدعاء في <code dir=\"ltr\">try/catch</code> وأكّد داخل <code dir=\"ltr\">catch</code>.","why":"هذا لا ينجح إلا مع <code dir=\"ltr\">expect.assertions(1)</code>، وهو أثقل من انتظار <code dir=\"ltr\">rejects</code>."},{"text":"ضع <code dir=\"ltr\">await</code> قبل <code dir=\"ltr\">expect(...)</code> كي ينتظر الاختبار الرفض.","why":"صحيح. كل assertion من نوع <code dir=\"ltr\">resolves</code> أو <code dir=\"ltr\">rejects</code> يجب أن يُنتظَر بـ await."}],"answer":3},{"q":"<p>أثناء اختبار <code dir=\"ltr\">computeBalances</code>، يستبدل اختبارٌ <code dir=\"ltr\">splitEvenly</code> بـ <code dir=\"ltr\">vi.fn().mockReturnValue([500, 500])</code>. لاحقًا يظهر خطأ تقريب في <code dir=\"ltr\">splitEvenly</code>. ماذا يحدث لذلك الاختبار؟</p>","options":[{"text":"يفشل، لأن <code dir=\"ltr\">computeBalances</code> تحصل الآن على حصص خاطئة.","why":"لا تستدعي <code dir=\"ltr\">computeBalances</code> الدالة <code dir=\"ltr\">splitEvenly</code> الحقيقية أبدًا في ذلك الاختبار، فلا ترى الخطأ أبدًا."},{"text":"يبقى أخضر، لأن <code dir=\"ltr\">splitEvenly</code> الحقيقية لا تعمل فيه أبدًا.","why":"صحيح. استخدام الـ mocks مكان كودك الصافي يُلغي بالضبط التكامل الذي كان سيلتقط هذا."},{"text":"تحذّر Vitest من أن الـ mock لم يعد يطابق التطبيق الحقيقي.","why":"لا تقارن Vitest الـ mocks بالكود الحقيقي؛ فالـ mock هو ما تقول له أن يكونه."},{"text":"يتعطّل الاختبار لأن الـ mocks للوحدات الداخلية غير مسموح بها.","why":"تسمح بها Vitest، ولهذا يبقى القرار لك."}],"answer":1},{"q":"<p>يفشل الصف الثاني في هذا الجدول. ما اسم الاختبار الذي تبلّغ عنه Vitest؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">test.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">each</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, people: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, first: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">334</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1001</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, people: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, first: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">251</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'splits $total between $people'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, ({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">total</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">people</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">first</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">splitEvenly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(total, people)[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(first);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">splits $total between $people</code>","why":"العناصر النائبة <code dir=\"ltr\">$field</code> تُملأ من كائن كل صف."},{"text":"<code dir=\"ltr\">splits 1001 between 4 (row 2)</code>","why":"لا تضيف Vitest رقم الصف ما لم تستخدم <code dir=\"ltr\">%#</code> في الاسم."},{"text":"<code dir=\"ltr\">splits 1001 between 4</code>","why":"صحيح. يصبح كل صف اختبارًا مستقلًّا مع استبدال القيم، فيسمّي الفشل المدخل."},{"text":"<code dir=\"ltr\">splits [object Object] between undefined</code>","why":"يقرأ <code dir=\"ltr\">$total</code> و<code dir=\"ltr\">$people</code> هاتين الخاصيتين من كائن كل صف، فتظهر القيم الحقيقية في الاسم."}],"answer":2},{"q":"<p>تذكير مجدول بـ <code dir=\"ltr\">setTimeout</code> ليُرسَل بعد 24 ساعة من إضافة مصروف. مع تفعيل <code dir=\"ltr\">vi.useFakeTimers()</code>، أيّ سطر يجعل التذكير يُطلق في الاختبار؟</p>","options":[{"text":"<code dir=\"ltr\">await new Promise((r) =&gt; setTimeout(r, 86_400_000))</code>","why":"مع المؤقّتات المزيّفة يكون <code dir=\"ltr\">setTimeout</code> هذا مزيّفًا أيضًا، فلا يُطلق أبدًا وتنتهي مهلة الاختبار."},{"text":"<code dir=\"ltr\">vi.useRealTimers()</code>","why":"العودة إلى المؤقّتات الحقيقية تعني انتظار يوم حقيقي."},{"text":"<code dir=\"ltr\">vi.setSystemTime(tomorrow)</code>","why":"يحرّك الساعة التي يقرؤها <code dir=\"ltr\">Date</code>، لكنه لا يشغّل المؤقّتات التي حان موعدها."},{"text":"<code dir=\"ltr\">vi.advanceTimersByTime(24 * 60 * 60 * 1000)</code>","why":"صحيح. تقديم الساعة المزيّفة يشغّل فورًا كل مؤقّت يحين موعده خلال تلك الفترة."}],"answer":3}],"s-tj-3":[{"q":"<p>يعرض النموذج أخطاء التحقّق في <code dir=\"ltr\">&lt;p role=&quot;alert&quot;&gt;Amount must be greater than zero&lt;/p&gt;</code>. أيّ query هو الأنسب؟</p>","options":[{"text":"<code dir=\"ltr\">screen.getByRole('alert')</code>","why":"صحيح. الدور هو ما تعلنه قارئات الشاشة، فيفشل الاختبار إن توقّف إعلان الرسالة."},{"text":"<code dir=\"ltr\">container.querySelector('.error-text')</code>","why":"الصنف تنسيق؛ وإعادة تسميته تكسر الاختبار دون أن تكسر شيئًا يراه المستخدمون."},{"text":"<code dir=\"ltr\">screen.getByTestId('amount-error')</code>","why":"الـ test ids هي الملاذ الأخير، ولا تثبت شيئًا عن طريقة وصول الرسالة إلى المستخدمين."},{"text":"<code dir=\"ltr\">screen.getByDisplayValue('Amount must be greater than zero')</code>","why":"استعلامات display value تطابق القيمة الحالية لحقول النماذج، لا نص الفقرات."}],"answer":0},{"q":"<p>يُحلّ <code dir=\"ltr\">load</code> بالقيمة <code dir=\"ltr\">{ Ben: -500 }</code>. يفشل هذا الاختبار بالرسالة “Unable to find an element with the text”. ما الإصلاح؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">test</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shows balances'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">BalancePanel</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> loadBalances</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">load</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> />);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(screen.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getByText</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Ben owes $5.00'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBeInTheDocument</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n","options":[{"text":"لفّ <code dir=\"ltr\">render</code> داخل <code dir=\"ltr\">waitFor</code>.","why":"يعيد <code dir=\"ltr\">waitFor</code> محاولة الـ assertions؛ وتكرار الـ render داخله لا يفيد ويسبّب آثارًا جانبية."},{"text":"ارفع مهلة الاختبار في Vitest.","why":"يفشل الاختبار فورًا، لا بعد انتهاء مهلة: يفحص <code dir=\"ltr\">getByText</code> مرة واحدة، قبل أن يُحلّ الـ promise."},{"text":"اجعل الاختبار <code dir=\"ltr\">async</code> واستخدم <code dir=\"ltr\">expect(await screen.findByText('Ben owes $5.00')).toBeInTheDocument()</code>.","why":"صحيح. ينتظر <code dir=\"ltr\">findByText</code> ظهور النص بعد تحميل البيانات."},{"text":"استخدم <code dir=\"ltr\">screen.queryByText('Ben owes $5.00')</code> بدلًا من ذلك.","why":"يفحص <code dir=\"ltr\">queryBy</code> أيضًا مرة واحدة فقط؛ سيعيد <code dir=\"ltr\">null</code> ويظلّ الـ assertion يفشل."}],"answer":2},{"q":"<p>تستخدم اختبارات المكوّنات MSW مع <code dir=\"ltr\">server.listen({ onUnhandledRequest: 'error' })</code>. إعادة هيكلة تغيّر عن غير قصد الـ URL في الطلب من <code dir=\"ltr\">/groups/:id</code> إلى <code dir=\"ltr\">/group/:id</code>. ماذا يحدث؟</p>","options":[{"text":"تنجح الاختبارات، لأن MSW يجيب عن كل طلب بـ 200 فارغ.","why":"لا يجيب MSW إلا عن الطلبات التي تطابق معالِجًا؛ وليس لديه استجابة نجاح شاملة."},{"text":"تفشل الاختبارات، لأن الطلب لا يطابق أي معالِج، والطلبات غير المعالَجة أخطاء.","why":"صحيح. هذا الخيار يحوّل الـ URL الخاطئ إلى فشل صاخب بدل تعليق صامت أو استدعاء شبكة حقيقي."},{"text":"يمرّر MSW الطلب بصمت إلى الشبكة الحقيقية.","why":"مع <code dir=\"ltr\">onUnhandledRequest: 'error'</code>، يُبلَّغ عن الطلبات غير المطابقة كأخطاء، ولا تُمرَّر بهدوء."},{"text":"تتخطّى Vitest الاختبارات المتأثّرة.","why":"لا شيء يُتخطّى؛ الطلب الفاشل يظهر على شكل فشل في الاختبار."}],"answer":1},{"q":"<p>أيّ استخدام لـ <code dir=\"ltr\">waitFor</code> مناسب؟</p>","options":[{"text":"<code dir=\"ltr\">await waitFor(() =&gt; user.click(screen.getByRole('button', { name: 'Save' })))</code>","why":"النقر داخل دالة يُعاد تشغيلها قد يحدث عدة مرات؛ والتفاعلات مكانها خارج <code dir=\"ltr\">waitFor</code>."},{"text":"<code dir=\"ltr\">await waitFor(() =&gt; screen.findByText('Saved'))</code>","why":"ينتظر <code dir=\"ltr\">findBy</code> أصلًا؛ ولفّه داخل <code dir=\"ltr\">waitFor</code> يضاعف الانتظار ويُربك المهلة."},{"text":"<code dir=\"ltr\">await waitFor(() =&gt; {})</code>","why":"الدالة الفارغة تنجح فورًا ولا تنتظر شيئًا بعينه."},{"text":"<code dir=\"ltr\">await waitFor(() =&gt; expect(onSave).toHaveBeenCalledTimes(1))</code>","why":"صحيح. assertion لا يتعلّق بظهور عنصر، يُعاد حتى يتحقّق."}],"answer":3},{"q":"<p>في <code dir=\"ltr\">ExpenseList</code> مربّع اختيار “Hide settled”. ما أفضل اختبار له؟</p>","options":[{"text":"انقر مربّع الاختيار بـ user-event وأكّد أيّ عناصر القائمة ما زالت على الشاشة.","why":"صحيح. يختبر ما يراه المستخدم، ويصمد أمام أي تغيير في طريقة تخزين الـ state."},{"text":"أكّد أن <code dir=\"ltr\">useState</code> استُدعي بالقيمة <code dir=\"ltr\">false</code> في البداية.","why":"هذا يفحص الأجزاء الداخلية لـ React، لا السلوك؛ وقد تظلّ القائمة تُعرض بشكل خاطئ."},{"text":"التقط snapshot قبل النقر وبعده.","why":"الـ snapshots تسجّل كل شيء ولا تشرح شيئًا؛ ولا يستطيع المراجعون معرفة ما الذي كان يجب أن يتغيّر."},{"text":"استدعِ المكوّن كدالة وافحص الـ JSX المُعاد.","why":"استدعاء المكوّنات مباشرة يتخطّى الـ render في React وقواعد الـ hooks؛ اعرضه بالـ render بدلًا من ذلك."}],"answer":0}],"s-tj-4":[{"q":"<p>حقل المبلغ هو <code dir=\"ltr\">&lt;label for=&quot;amount&quot;&gt;Amount&lt;/label&gt;&lt;input id=&quot;amount&quot; name=&quot;amount&quot; placeholder=&quot;0.00&quot;&gt;</code>. أيّ locator في Playwright هو الأفضل؟</p>","options":[{"text":"<code dir=\"ltr\">page.locator('input[name=amount]')</code>","why":"يعمل، لكنه يعتمد على خاصية لا يراها المستخدمون أبدًا ولا يفحص شيئًا عن التسمية."},{"text":"<code dir=\"ltr\">page.getByPlaceholder('0.00')</code>","why":"النصوص النائبة إشارة أضعف من التسميات، وكثيرًا ما تتغيّر أو تختفي."},{"text":"<code dir=\"ltr\">page.getByLabel('Amount')</code>","why":"صحيح. يجد الحقل بتسميته، بالطريقة التي يجده بها المستخدمون والتقنيات المساعدة."},{"text":"<code dir=\"ltr\">page.locator('#amount')</code>","why":"المعرّفات تفاصيل داخلية قد تتغيّر في إعادة هيكلة."}],"answer":2},{"q":"<p>يستغرق تسجيل الدخول 3 ثوانٍ. أربعون اختبارًا تحتاج إلى مستخدم مسجّل الدخول لكنها لا تغيّر الحساب أبدًا. ما أفضل نهج؟</p>","options":[{"text":"سجّل الدخول عبر الواجهة داخل <code dir=\"ltr\">beforeEach</code>.","why":"هذا يضيف دقيقتين من تسجيلات الدخول ويُرهق خدمة المصادقة دون أي ثقة إضافية."},{"text":"دع اختبارًا واحدًا يسجّل الدخول وشغّل البقية بعده في الوضع التسلسلي.","why":"الاختبارات المتسلسلة لا يمكن تشغيلها بالتوازي ولا منفردة، وفشل واحد يتتالى."},{"text":"شارك كائن <code dir=\"ltr\">page</code> واحدًا بين كل الاختبارات في متغيّر على مستوى الوحدة.","why":"مشاركة الصفحة تكسر العزل؛ فقد يترك اختبار حالة تغيّر اختبارًا آخر."},{"text":"سجّل الدخول مرة واحدة في setup project، واحفظ حالة التخزين، ودع الاختبارات تعيد استخدامها.","why":"صحيح. ما زال كل اختبار يحصل على سياقه الخاص، مصادَقًا عليه مسبقًا."}],"answer":3},{"q":"<p>في trace لاختبار فاشل، نجح النقر على “Settle up”، ولقطة الـ DOM بعده مباشرة تُظهر رسالة منبثقة “Session expired, please sign in”. ما الذي تحقّق فيه أولًا؟</p>","options":[{"text":"إعداد المصادقة في الاختبار، مثل حالة تخزين محفوظة منتهية الصلاحية.","why":"صحيح. استجابت الصفحة فعلًا؛ لكنها رفضت الجلسة، فالمشكلة في طريقة تسجيل الاختبار للدخول."},{"text":"إضافة <code dir=\"ltr\">page.waitForTimeout(2000)</code> قبل الـ assertion.","why":"الانتظار أطول لن يحيي جلسة منتهية."},{"text":"تغيير الـ locator الخاص بـ Settle up.","why":"يُظهر الـ trace أن النقر أصاب الزر الصحيح."},{"text":"ترقية Playwright، لأن هذا يبدو خطأً في الأداة.","why":"تُظهر اللقطة أن التطبيق يتصرّف بشكل صحيح مع جلسة منتهية؛ والمشتبه به هو إعداد الاختبار."}],"answer":0},{"q":"<p>تريد اختبار أن صفحة المجموعة تعرض “Could not load this group” حين يجيب الـ API بـ 503. ماذا تفعل؟</p>","options":[{"text":"أوقف خادم الـ backend في CI قبل تشغيل ذلك الاختبار.","why":"هذا يكسر كل اختبار آخر يعمل بالتوازي على الخادم نفسه."},{"text":"استدعِ <code dir=\"ltr\">page.route('**/api/groups/*', (route) =&gt; route.fulfill({ status: 503 }))</code> قبل <code dir=\"ltr\">page.goto</code>.","why":"صحيح. يُنتج التوجيه الاستجابة النادرة لهذا الاختبار وحده دون لمس الخادم الحقيقي."},{"text":"استبدل <code dir=\"ltr\">fetch</code> بـ <code dir=\"ltr\">vi.fn()</code> داخل اختبار Playwright.","why":"<code dir=\"ltr\">vi</code> تابع لـ Vitest؛ واختبارات Playwright تعمل في Node بينما تعمل الصفحة في متصفّح حقيقي."},{"text":"ارمِ خطأً داخل hook من نوع <code dir=\"ltr\">beforeEach</code>.","why":"هذا يُفشل الاختبار قبل أن يبدأ بدل أن يحاكي استجابة الخادم."}],"answer":1},{"q":"<p>ينجح اختبار دائمًا مع <code dir=\"ltr\">--workers=1</code> ويفشل أحيانًا مع <code dir=\"ltr\">--workers=4</code>. ما المشتبه به الأول؟</p>","options":[{"text":"مهل أقصر من اللازم للتشغيل المتوازي.","why":"ممكن، لكن السبب الكلاسيكي للحساسية تجاه الترتيب والتوازي هو الحالة المشتركة."},{"text":"عدم تطابق إصدارات المتصفّح بين العمّال.","why":"يستخدم كل العمّال المتصفّحات المثبّتة نفسها."},{"text":"اختبارات تتشارك البيانات على الخادم، فتغيّر الاختبارات المتوازية ما يراه غيرها.","why":"صحيح. أعطِ كل اختبار بياناته الخاصة، مثلًا عبر fixture بأسماء فريدة."},{"text":"assertions كثيرة جدًا في اختبار واحد.","why":"عدد الـ assertions لا يتفاعل مع عدد العمّال."}],"answer":2}],"s-tj-5":[{"q":"<p>بكتابة الاختبار أولًا، لديك <code dir=\"ltr\">parseAmount('12')</code> ينجح مع <code dir=\"ltr\">return Number(input) * 100;</code>. أيّ اختبار تالٍ يفرض أصغر خطوة مفيدة؟</p>","options":[{"text":"جدول من 500 صف بكل صيغة تخطر لك.","why":"كتابة اختبارات كثيرة دفعة واحدة تفرض قفزة كبيرة؛ والـ TDD يتقدّم باختبار فاشل صغير واحد في كل مرة."},{"text":"<code dir=\"ltr\">expect(() =&gt; parseAmount('abc')).toThrow()</code>","why":"صحيح. يفشل اليوم (فالكود يعيد <code dir=\"ltr\">NaN</code>) ويحتاج إلى تغيير صغير واحد: رفض ما ليس رقمًا."},{"text":"<code dir=\"ltr\">expect(parseAmount('12')).toBe(1200)</code> باسم مختلف.","why":"ينجح أصلًا، فلا يفرض شيئًا جديدًا."},{"text":"اختبار Playwright لنموذج المصروفات كاملًا.","why":"خطوة أكبر بكثير من اللازم، وتغذية راجعة بطيئة لقاعدة تحليل."}],"answer":1},{"q":"<p>الملف <code dir=\"ltr\">src/money/settle-up.js</code> بلا اختبارات، لكنه لا يظهر في تقرير الـ coverage في Vitest أصلًا. لماذا؟</p>","options":[{"text":"لا يستطيع المزوّد v8 قياس ذلك الملف.","why":"يقيس v8 أي JavaScript تحمّله الاختبارات؛ وهذا الملف لم يُحمَّل قط."},{"text":"الملف مغطّى بالكامل، فهو مخفي.","why":"الملفات المغطّاة تُسرد أيضًا؛ ولا شيء يُخفيها."},{"text":"الحدود الدنيا للـ coverage تستبعد الملفات التي دون الحدّ.","why":"الحدود الدنيا تُفشل التشغيل؛ ولا تزيل الملفات من التقرير."},{"text":"<code dir=\"ltr\">coverage.include</code> غير مضبوط، فلا يُبلَّغ إلا عن الملفات التي حمّلتها الاختبارات.","why":"صحيح. اضبط <code dir=\"ltr\">include</code> (مثلًا <code dir=\"ltr\">src/**/*.js</code>) كي تظهر الملفات غير المختبَرة بنسبة 0%."}],"answer":3},{"q":"<p>يبلّغ Stryker عن <code dir=\"ltr\">EqualityOperator: amountCents &lt;= 0 → amountCents &lt; 0</code> بأنه Survived في التحقّق داخل <code dir=\"ltr\">saveExpense</code>. أيّ اختبار يقتله؟</p>","options":[{"text":"حفظ مصروف بقيمة <code dir=\"ltr\">amountCents: 0</code> يجب أن يُرفض.","why":"صحيح. الصفر هو القيمة الوحيدة التي يختلف عندها <code dir=\"ltr\">&lt;= 0</code> عن <code dir=\"ltr\">&lt; 0</code>."},{"text":"حفظ مصروف بقيمة <code dir=\"ltr\">amountCents: -100</code> يجب أن يُرفض.","why":"كلتا النسختين ترفضان -100، فينجو الـ mutant."},{"text":"حفظ مصروف بقيمة <code dir=\"ltr\">amountCents: 4500</code> يجب أن يُحلّ بنجاح.","why":"كلتا النسختين تقبلان 4500، فينجو الـ mutant من هذا الاختبار."},{"text":"حفظ مصروف بقيمة <code dir=\"ltr\">amountCents: 12.5</code> يجب أن يُرفض.","why":"فحص العدد الصحيح يرفض 12.5 في النسختين."}],"answer":0},{"q":"<p>في workflow GitHub Actions، يختفي artifact تقرير Playwright في كل مرة تفشل فيها الاختبارات. ما الذي ينقص خطوة الرفع؟</p>","options":[{"text":"<code dir=\"ltr\">needs: e2e</code>","why":"يرتّب <code dir=\"ltr\">needs</code> المهامّ؛ والمشكلة في خطوة تُتخطّى داخل المهمّة نفسها."},{"text":"<code dir=\"ltr\">retention-days: 30</code>","why":"مدة الاحتفاظ تتحكّم في المدة التي يُحفظ فيها الـ artifact، لا في رفعه من عدمه."},{"text":"<code dir=\"ltr\">if: ${{ !cancelled() }}</code>، كي تعمل حتى بعد فشل خطوة الاختبار.","why":"صحيح. تُتخطّى الخطوات بعد الفشل افتراضيًا؛ والشرط يجعل الرفع يعمل رغم ذلك."},{"text":"<code dir=\"ltr\">--shard=1/1</code> في أمر الاختبار.","why":"الـ sharding يقسّم المجموعة؛ ولا علاقة له برفع التقارير."}],"answer":2},{"q":"<p>أيّ من هذه الاختبارات يستحق أن يُكتب؟</p>","options":[{"text":"أن <code dir=\"ltr\">sortBy</code> في lodash يرتّب المصروفات حسب التاريخ.","why":"هذا يختبر المكتبة، وهو ما يفعله القائمون على صيانتها أصلًا."},{"text":"أن رسالة الخطأ تحمل الصنف <code dir=\"ltr\">text-red-600</code>.","why":"تفاصيل التنسيق تجعل الاختبارات هشّة دون أن تلتقط أخطاء السلوك."},{"text":"أن دالة مساعدة خاصة تُستدعى مرتين بالضبط.","why":"يجمّد التفاصيل الداخلية وينجح مع الأخطاء الحقيقية."},{"text":"أن السنت المتبقّي الواحد يذهب إلى أول شخص في التقسيم.","why":"صحيح. إنه سلوك مالي موثّق قد يكسره خطأ معقول."}],"answer":3}]},"final":[{"q":"<p>نموذج يحلّل المبلغ ويرسله عبر عميل API. أيّ اختبار يعطي أكبر ثقة مقابل كل دقيقة من CI لذلك النموذج؟</p>","options":[{"text":"اختبار مكوّن يعرض النموذج مع المحلّل الحقيقي وشبكة مزيّفة.","why":"صحيح. يشغّل النموذج والتحليل والطلب معًا، في أجزاء من الثانية، دون متصفّح أو خادم."},{"text":"unit test لعميل الـ API وحده.","why":"مفيد، لكنه لا يقول شيئًا عمّا إذا كان النموذج يستخدم المحلّل والعميل بشكل صحيح."},{"text":"اختبار end-to-end لكل رسالة تحقّق.","why":"فحص كل رسالة أرخص وأدقّ على مستوى المكوّن؛ واحتفظ بالـ end-to-end للمسار الرئيسي."},{"text":"أنواع TypeScript أكثر صرامة على الـ props الخاصة بالنموذج.","why":"تلتقط الأنواع أخطاء الشكل، لا إن كانت 12.5 تصبح 1250 سنتًا."}],"answer":0},{"q":"<p>ما المشكلة الأساسية في هذا الاختبار؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">test</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'withTip'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">withTip</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">15</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).tipCents).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">300</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> withTip</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toThrow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">withTip</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).totalCents).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n","options":[{"text":"لا يستطيع <code dir=\"ltr\">toBe</code> مقارنة الأرقام بشكل موثوق.","why":"مع أعداد صحيحة كهذه، يكون <code dir=\"ltr\">toBe</code> هو الصحيح تمامًا."},{"text":"يفحص ثلاثة سلوكيات، ففشل الأول يُخفي البقية، والاسم لا يقول ما الذي انكسر.","why":"صحيح. قسّمه إلى ثلاثة اختبارات تحمل أسماء القواعد التي تفحصها."},{"text":"لا يجوز أن يحتوي الاختبار إلا على استدعاء <code dir=\"ltr\">expect</code> واحد.","why":"عدة assertions عن سلوك واحد مقبولة؛ المشكلة في السلوكيات الثلاثة."},{"text":"يجب انتظار <code dir=\"ltr\">withTip</code> بـ await.","why":"<code dir=\"ltr\">withTip</code> متزامنة؛ ولا يوجد ما يُنتظر."}],"answer":1},{"q":"<p>يجب أن يكون عدد أعضاء المجموعة بين 2 و12. أيّ المدخلات تختبر القاعدة على أفضل وجه؟</p>","options":[{"text":"5 و6 و7","why":"كلها في منتصف النطاق الصالح؛ وأخطاء الحدود تنجح فيها جميعًا."},{"text":"2 و12","why":"تختبر الحافّتين الصالحتين لكنها لا تختبر الجهة غير الصالحة من أيّ حدّ."},{"text":"0 و100","why":"بعيدة عن الحدّين، فلن يُلاحظ خطأ بفارق واحد."},{"text":"1 و2 و12 و13","why":"صحيح. خارج كل حدّ مباشرة، وعليه."}],"answer":3},{"q":"<p>ينجح <code dir=\"ltr\">await expect(loadGroup('x', fetchMock)).rejects.toThrow()</code> مع الكود الصحيح ومع نسخة تنهار بخطأ <code dir=\"ltr\">TypeError</code> قبل أن تفحص رمز الحالة. كيف تجعل الاختبار ذا معنى؟</p>","options":[{"text":"استخدم <code dir=\"ltr\">resolves</code> بدل <code dir=\"ltr\">rejects</code>.","why":"يجب أن يُرفض الـ promise؛ والانتقال إلى <code dir=\"ltr\">resolves</code> سيجعل الكود الصحيح يفشل."},{"text":"أضف مزيدًا من كلمات <code dir=\"ltr\">await</code> حول الاستدعاء.","why":"الـ assertion منتظَر أصلًا؛ وما ينقص هو الدقّة."},{"text":"طابق الرسالة المتوقّعة، مثلًا <code dir=\"ltr\">.rejects.toThrow('HTTP 404')</code>.","why":"صحيح. لا تُنتج تلك الرسالة إلا النسخة التي تفحص رمز الحالة فعلًا."},{"text":"استبدل <code dir=\"ltr\">toThrow</code> بـ <code dir=\"ltr\">toThrowError</code>.","why":"هما اسمان لسلوك متطابق."}],"answer":2},{"q":"<p>تنجح الاختبارات واحدًا واحدًا لكنها تفشل في التشغيل الكامل. اختبار سابق ينفّذ <code dir=\"ltr\">vi.spyOn(Math, 'random').mockReturnValue(0)</code>. ما الإصلاح؟</p>","options":[{"text":"استعِد الـ mocks بعد كل اختبار، بـ <code dir=\"ltr\">vi.restoreAllMocks()</code> داخل <code dir=\"ltr\">afterEach</code> أو بـ <code dir=\"ltr\">restoreMocks: true</code> في الإعدادات.","why":"صحيح. استبدل الـ spy دالة على كائن مشترك وتسرّب إلى الاختبارات اللاحقة."},{"text":"شغّل ملفات الاختبار واحدًا تلو الآخر.","why":"هذا يُخفي التسرّب بدل أن يزيله، ويجعل المجموعة أبطأ."},{"text":"ارفع مهلة الاختبار في Vitest.","why":"حالات الفشل تتعلّق بحالة متسرّبة، لا بالوقت."},{"text":"توقّف عن استخدام <code dir=\"ltr\">Math.random</code> في كود الإنتاج.","why":"كود الإنتاج سليم؛ الاختبار هو الذي نسي أن ينظّف بعد نفسه."}],"answer":0},{"q":"<p>زرّ أيقونة فقط يُعرض هكذا: <code dir=\"ltr\">&lt;button aria-label=&quot;Delete expense&quot;&gt;&lt;svg&gt;…&lt;/svg&gt;&lt;/button&gt;</code>. أيّ query يجب أن يستخدمه اختبار Testing Library؟</p>","options":[{"text":"<code dir=\"ltr\">screen.getByText('Delete expense')</code>","why":"لا يوجد نص ظاهر؛ فالاسم يأتي من <code dir=\"ltr\">aria-label</code>، ولا تقرؤه استعلامات النص."},{"text":"<code dir=\"ltr\">screen.getByRole('button', { name: 'Delete expense' })</code>","why":"صحيح. الاسم القابل للوصول يأتي من <code dir=\"ltr\">aria-label</code>، فيجده استعلام الدور كما يجده قارئ الشاشة."},{"text":"<code dir=\"ltr\">screen.getByTestId('delete')</code>","why":"هناك اسم قابل للوصول جيد تمامًا؛ والـ test id سيكون خطوة إلى الوراء."},{"text":"<code dir=\"ltr\">container.querySelector('svg').closest('button')</code>","why":"يربط الاختبار ببنية الأيقونة ويتجاهل الاسم القابل للوصول."}],"answer":1},{"q":"<p>تظهر رسالة منبثقة “Saved” بعد الحفظ وتختفي بعد لحظة. كيف تؤكّد أنها تختفي؟</p>","options":[{"text":"<code dir=\"ltr\">expect(screen.getByText('Saved')).toBeNull()</code>","why":"يرمي <code dir=\"ltr\">getByText</code> خطأً ما دامت الرسالة موجودة، ولا يعيد <code dir=\"ltr\">null</code> أبدًا."},{"text":"<code dir=\"ltr\">expect(await screen.findByText('Saved')).toBeNull()</code>","why":"يُحلّ <code dir=\"ltr\">findByText</code> بالعنصر حين يظهر؛ ولا يُحلّ أبدًا بـ <code dir=\"ltr\">null</code>."},{"text":"انتظر 5 ثوانٍ، ثم <code dir=\"ltr\">expect(screen.queryByText('Saved')).toBeNull()</code>","why":"الانتظار الثابت بطيء ويظلّ يتسابق على جهاز مشغول."},{"text":"<code dir=\"ltr\">await waitForElementToBeRemoved(() =&gt; screen.queryByText('Saved'))</code>","why":"صحيح. ينتظر بالضبط حتى تختفي الرسالة، أو يفشل بعد انتهاء المهلة."}],"answer":3},{"q":"<p>يفشل هذا الفحص في Playwright بين حين وآخر في CI. ما الإصلاح؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> rows</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> page.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getByRole</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'listitem'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">all</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(rows.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n","options":[{"text":"أضف <code dir=\"ltr\">await page.waitForLoadState('networkidle')</code> قبله.","why":"هدوء الشبكة مؤشّر ضعيف على عرض القائمة، والـ assertion ما زال لا يعيد المحاولة."},{"text":"استخدم <code dir=\"ltr\">await page.getByRole('listitem').count()</code> بدل <code dir=\"ltr\">.all()</code>.","why":"ما زالت قراءة لمرة واحدة يتبعها assertion لا يعيد المحاولة."},{"text":"<code dir=\"ltr\">await expect(page.getByRole('listitem')).toHaveCount(3)</code>","why":"صحيح. يعيد الـ assertion الموجّه للويب العدّ حتى تصبح العناصر ثلاثة أو تنتهي المهلة."},{"text":"اضبط <code dir=\"ltr\">retries: 3</code> في الإعدادات.","why":"إعادة المحاولة تُخفي السباق بدل أن تصلحه."}],"answer":2},{"q":"<p>تتشارك اختبارات الـ end-to-end مجموعة واحدة معبّأة مسبقًا، وأحدها يحذف منها مصروفًا. تفشل اختبارات أخرى حسب الترتيب. ما الإصلاح الأفضل؟</p>","options":[{"text":"fixture تنشئ مجموعة باسم فريد لكل اختبار عبر الـ API وتحذفها بعده.","why":"صحيح. كل اختبار يملك بياناته، فيتوقّف الترتيب والتوازي عن أن يكونا مهمّين."},{"text":"شغّل المجموعة تسلسليًا بترتيب ثابت.","why":"يُخفي الاعتماد، ويبطّئ المجموعة، ويدع فشلًا واحدًا يتتالى."},{"text":"أعد تعبئة قاعدة البيانات مرة واحدة قبل المجموعة كلها.","why":"ما زالت الاختبارات تتشارك البيانات نفسها وتغيّرها أثناء التشغيل."},{"text":"فعّل ثلاث محاولات إعادة لتلك الاختبارات.","why":"إعادة المحاولة تحجب التصادم دون أن تزيله."}],"answer":0},{"q":"<p>أيّ أداة تخبرك أن لا اختبار سيلاحظ لو أصبح <code dir=\"ltr\">&lt;</code> في كودك <code dir=\"ltr\">&lt;=</code>؟</p>","options":[{"text":"تغطية الأسطر","why":"لا تُظهر تغطية الأسطر إلا أن السطر عمل."},{"text":"تغطية الفروع","why":"تُظهر تغطية الفروع أن جهتي الشرط عملتا، لا أن اختبارًا سيفشل لو تحرّك الحدّ."},{"text":"مترجم TypeScript","why":"لـ <code dir=\"ltr\">&lt;</code> و<code dir=\"ltr\">&lt;=</code> الأنواع نفسها؛ ولا يستطيع المترجم التمييز بينهما."},{"text":"الـ mutation testing، مثل StrykerJS","why":"صحيح. يُجري هذا التغيير بالضبط ويبلّغ إن فشل أيّ اختبار."}],"answer":3},{"q":"<p>ما زالت الـ pull requests تُدمج بينما مهمّة الـ end-to-end حمراء. ما الإصلاح الصحيح؟</p>","options":[{"text":"أعد تشغيل المهمّة حتى تخضرّ، ثم ادمج.","why":"هذا يدرّب الناس على تجاهل حالات الفشل، بما فيها الحقيقية."},{"text":"اجعل مهامّ الاختبار فحوص حالة إلزامية على الفرع الرئيسي.","why":"صحيح. الفحص الإلزامي الأحمر يمنع الـ merge، فيصبح الـ pipeline بوابة لا اقتراحًا."},{"text":"أزل مهمّة الـ end-to-end كي يبقى الـ pipeline أخضر.","why":"هذا يرمي الفحص الوحيد الذي يتحقّق من أن القطع تعمل معًا."},{"text":"شغّل مهمّة الـ end-to-end بعد الـ merge فقط.","why":"عندها يصل الكود المعطوب إلى main قبل أن يعلم به أحد."}],"answer":1},{"q":"<p>اختبار مكوّن يستخدم mock مكان <code dir=\"ltr\">formatMoney</code> الصافية الخاصة بك بحيث تعيد دائمًا <code dir=\"ltr\">'X'</code>. ما التغيير الأفضل؟</p>","options":[{"text":"أبقِ الـ mock وأضف snapshot للمخرجات.","why":"سيسجّل الـ snapshot القيمة <code dir=\"ltr\">X</code> ولن يختبر التنسيق الحقيقي أبدًا."},{"text":"استبدل الـ mock بـ <code dir=\"ltr\">vi.mock</code> على الوحدة.","why":"الفكرة نفسها بأداة مختلفة؛ والدالة الحقيقية ما زالت لا تعمل أبدًا."},{"text":"استخدم <code dir=\"ltr\">formatMoney</code> الحقيقية: إنها كود صافٍ سريع تملكه، والاختبار عندها يفحص المخرجات الحقيقية.","why":"صحيح. استخدم الـ mocks عند الحدود؛ وشغّل منطقك الصافي على حقيقته."},{"text":"احذف اختبار المكوّن.","why":"ما زال المكوّن يحتاج إلى اختبار؛ وكل ما عليه هو التوقّف عن تزييف دالة التنسيق."}],"answer":2}],"glossary":[{"id":"testing-trophy","term":"كأس الاختبارات (Testing trophy)","definition":"نموذج لمجموعة الاختبارات من أربع طبقات: التحليل الساكن، واختبارات unit وintegration وend-to-end. يكون في أعرض حالاته عند اختبارات الـ integration، التي تمنح عادةً أكبر ثقة مقابل تكلفتها.","lesson":"l-tj-1-1"},{"id":"unit-test","term":"Unit test","definition":"اختبار يفحص دالة واحدة أو وحدة برمجية واحدة بمعزل عن غيرها، وعادةً في أجزاء من الثانية. مثالي للمنطق الصافي ذي الحالات الحدّية الكثيرة، مثل تقريب المبالغ المالية.","lesson":"l-tj-1-1"},{"id":"integration-test","term":"Integration test","definition":"اختبار يفحص عدة وحدات تعمل معًا، مثل مكوّن نموذج يستخدم كود التحليل الحقيقي مع شبكة مزيّفة. يلتقط أخطاء الربط التي تفوت الـ unit tests.","lesson":"l-tj-1-1"},{"id":"end-to-end-test","term":"End-to-end test","definition":"اختبار يقود التطبيق الحقيقي في متصفّح حقيقي، عبر الواجهة والـ API وطبقة البيانات. وهو أقرب أنواع الاختبارات إلى الواقع وأغلاها ثمنًا.","lesson":"l-tj-4-1"},{"id":"watch-mode","term":"وضع المراقبة (Watch mode)","definition":"الوضع الافتراضي لـ Vitest محليًا: بعد التشغيل ينتظر، وحين تحفظ ملفًا يعيد تشغيل الاختبارات المتأثّرة بذلك التغيير فقط، معتمدًا على مخطّط الوحدات في Vite.","lesson":"l-tj-1-2"},{"id":"aaa","term":"Arrange, Act, Assert","definition":"شكل ثلاثي للاختبارات: ابنِ المدخلات، ونفّذ الفعل الوحيد الخاضع للاختبار، ثم اذكر ما يجب أن يكون صحيحًا الآن. يجعل الاختبارات سهلة التصفّح بالعين.","lesson":"l-tj-1-3"},{"id":"assertion","term":"Assertion","definition":"فحص داخل الاختبار يرمي خطأً حين لا تكون القيمة كما توقّعت، مثل <code dir=\"ltr\">expect(total).toBe(1000)</code>. ويفشل الاختبار حين يرمي أحد الـ assertions خطأً.","lesson":"l-tj-1-3"},{"id":"implementation-detail","term":"التفاصيل الداخلية (Implementation detail)","definition":"الطريقة التي ينتج بها الكود نتيجته (دوال مساعدة خاصة، وحالة داخلية، وترتيب الاستدعاءات)، في مقابل السلوك الذي يلاحظه المستدعون. الاختبارات المرتبطة بها تنكسر مع إعادات هيكلة غير ضارّة.","lesson":"l-tj-2-1"},{"id":"invariant","term":"الثابت (Invariant)","definition":"خاصية تصحّ مع كل مدخل صالح، مثل “مجموع الحصص يساوي الإجمالي”. وتأكيد ثابت ما عبر عدة مدخلات يلتقط عائلات كاملة من الأخطاء.","lesson":"l-tj-2-1"},{"id":"boundary-value","term":"تحليل القيم الحدّية (Boundary value analysis)","definition":"اختبار المدخلات الواقعة تحت كل حدّ في قاعدة ما مباشرة، وعليه، وفوقه مباشرة، مثل 0 و0.01، أو منزلتين عشريتين وثلاث، لأن الأخطاء تتجمّع عند هذه الحواف.","lesson":"l-tj-2-2"},{"id":"table-driven-test","term":"الاختبار المعتمد على الجداول (Table-driven test)","definition":"اختبار يُكتب مرة واحدة ويُشغَّل لكل صف في جدول من المدخلات والمخرجات المتوقّعة. في Vitest، ينشئ <code dir=\"ltr\">test.each</code> اختبارًا مسمّى لكل صف.","lesson":"l-tj-2-2"},{"id":"stub","term":"Stub","definition":"متعاون بديل يعيد إجابات جاهزة كي يتحكّم الاختبار في اعتمادية ما، مثل كائن API تُحلّ دالة <code dir=\"ltr\">post</code> فيه دائمًا بمعرّف.","lesson":"l-tj-2-3"},{"id":"test-double","term":"بديل الاختبار (Test double)","definition":"أي شيء يقوم مقام متعاون حقيقي في الاختبار: الـ stubs تعيد إجابات جاهزة، والـ spies تسجّل الاستدعاءات، والـ mocks هي spies تؤكّد عليها، والـ fakes نسخ أبسط تعمل فعلًا.","lesson":"l-tj-2-4"},{"id":"spy","term":"Spy","definition":"غلاف حول دالة يسجّل كيف استُدعيت. يراقب <code dir=\"ltr\">vi.spyOn(object, method)</code> دالة حقيقية، ويستطيع أيضًا استبدالها إلى أن تُستعاد.","lesson":"l-tj-2-4"},{"id":"fake-timers","term":"المؤقّتات المزيّفة (Fake timers)","definition":"ساعة خاصة بالاختبار تستبدل <code dir=\"ltr\">setTimeout</code> و<code dir=\"ltr\">setInterval</code> و<code dir=\"ltr\">Date</code>، فتستطيع الاختبارات القفز إلى الأمام باستدعاءات مثل <code dir=\"ltr\">vi.advanceTimersByTime(2000)</code> بدل الانتظار بالوقت الحقيقي.","lesson":"l-tj-2-4"},{"id":"accessible-name","term":"الاسم القابل للوصول (Accessible name)","definition":"الاسم الذي تعلنه التقنيات المساعدة لعنصر ما، ويُحسب من <code dir=\"ltr\">aria-labelledby</code> أو <code dir=\"ltr\">aria-label</code> أو تسمية مرتبطة أو نص العنصر. وتطابق استعلامات الدور عليه.","lesson":"l-tj-3-1"},{"id":"query-priority","term":"أولوية الاستعلامات (Query priority)","definition":"الترتيب الذي توصي به Testing Library لإيجاد العناصر: بالدور، أو التسمية، أو النص النائب، أو النص، أو القيمة المعروضة أولًا؛ ثم النص البديل والعنوان؛ والـ test ids في الأخير.","lesson":"l-tj-3-1"},{"id":"user-event","term":"user-event","definition":"حزمة من Testing Library تحاكي تفاعلات المستخدم كاملة، بما فيها أحداث المؤشّر والتركيز ولوحة المفاتيح، بدل إطلاق أحداث منفردة كما يفعل <code dir=\"ltr\">fireEvent</code>.","lesson":"l-tj-3-2"},{"id":"findby","term":"استعلام findBy (findBy query)","definition":"استعلام غير متزامن في Testing Library يعيد المحاولة حتى يظهر عنصر مطابق، فيُحلّ به، أو يُرفض بعد انتهاء المهلة (ثانية واحدة افتراضيًا).","lesson":"l-tj-3-3"},{"id":"msw","term":"MSW (Mock Service Worker)","definition":"مكتبة تعترض طلبات الشبكة في Node والمتصفّح وتجيب عنها بمعالِجات تعرّفها أنت، فيظلّ الـ fetch الحقيقي وكود البيانات لديك يعملان في الاختبارات.","lesson":"l-tj-3-4"},{"id":"locator","term":"Locator","definition":"في Playwright، وصف كسول وصارم لطريقة إيجاد عنصر، مثل <code dir=\"ltr\">page.getByRole('button', { name: 'Save' })</code>. ويُعاد حلّه مع كل فعل.","lesson":"l-tj-4-1"},{"id":"web-first-assertion","term":"Assertion موجّه للويب (Web-first assertion)","definition":"assertion في Playwright على locator، مثل <code dir=\"ltr\">toHaveText</code>، يعيد فحص الصفحة حتى ينجح أو تنتهي مهلته، بدل قراءة قيمة مرة واحدة.","lesson":"l-tj-4-1"},{"id":"fixture","term":"Fixture","definition":"في Playwright، قيمة يطلبها الاختبار باسمها، مثل <code dir=\"ltr\">page</code> أو <code dir=\"ltr\">group</code> مخصّصة، تُهيّأ قبل <code dir=\"ltr\">use()</code> وتُهدم بعده، ولا تُنشأ إلا للاختبارات التي تحتاجها.","lesson":"l-tj-4-2"},{"id":"trace","term":"Trace","definition":"تسجيل من Playwright لتشغيل اختبار، فيه كل فعل، ولقطات DOM قبله وبعده، ومخرجات الـ console، واستدعاءات الشبكة، ويُفتح في عارض الـ trace.","lesson":"l-tj-4-3"},{"id":"flaky-test","term":"Flaky test","definition":"اختبار متقلّب ينجح أحيانًا ويفشل أحيانًا دون أي تغيير في الكود، عادةً بسبب التوقيت أو البيانات المشتركة أو الوقت غير المتحكَّم فيه. ويعلّم الفرق تجاهل حالات الفشل.","lesson":"l-tj-4-3"},{"id":"tdd","term":"التطوير الموجّه بالاختبارات (TDD)","definition":"كتابة اختبار فاشل واحد، ثم جعله ينجح بأبسط كود، ثم إعادة الهيكلة والاختبارات خضراء، في دورات قصيرة متكرّرة تُعرف بـ red وgreen وrefactor.","lesson":"l-tj-5-1"},{"id":"code-coverage","term":"Code coverage","definition":"نسبة العبارات والفروع والدوال والأسطر التي عملت أثناء الاختبارات. تُظهر الكود غير المختبَر، لكنها لا تُظهر إن كان الكود المنفَّذ قد فُحص فعلًا.","lesson":"l-tj-5-2"},{"id":"mutation-testing","term":"اختبار الطفرات (Mutation testing)","definition":"تقييم مجموعة الاختبارات بصنع أخطاء صغيرة متعمَّدة (mutants) في الكود وإعادة تشغيل الاختبارات؛ الـ mutants المقتولة التُقطت، والناجية تكشف اختبارات ضعيفة.","lesson":"l-tj-5-3"},{"id":"equivalent-mutant","term":"Mutant متكافئ (Equivalent mutant)","definition":"mutant لا يغيّر تعديله أي سلوك يستطيع المستدعي ملاحظته، فلا يستطيع أي اختبار معقول قتله. يجب تجاهله، لا مطاردته.","lesson":"l-tj-5-3"},{"id":"required-check","term":"فحص الحالة الإلزامي (Required status check)","definition":"مهمّة CI يجب أن تنجح قبل أن يُدمج pull request في فرع محمي، فتحوّل pipeline الاختبارات من اقتراح إلى بوابة.","lesson":"l-tj-5-4"}]}