{"id":"l-tj-1-1","courseId":"testing-javascript","lang":"ar","kind":"intro","summary":"تشرح ما الذي تمنحه الاختبارات الآلية للفريق، وتسمّي الطبقات الأربع في كأس الاختبارات، وتختار الطبقة المناسبة لخطر معيّن في تطبيق صغير.","html":"<p>في الربيع الماضي غيّر زميل لي سطرًا واحدًا في دالة مساعدة لتقسيم الفواتير: صار <code dir=\"ltr\">Math.round</code> هو <code dir=\"ltr\">Math.floor</code>، كي “يصلح” حصّة بدت أعلى بسنت واحد. ظلّت كل الشاشات تعمل. وبعد ثلاثة أسابيع لاحظ مستخدم أن أرصدة مجموعته لم يعد مجموعها صفرًا، ولم يستطع أحد أن يحدّد أيّ واحد من 200 commit كان السبب. اختبار واحد من ثلاثة أسطر كان سيفشل خلال ثانية من ذلك التغيير.</p>\n<p>هذا بالضبط ما تمنحك إياه الاختبارات: <strong>الثقة لتغيير الكود بسرعة</strong>. بدونها تصبح كل إعادة هيكلة (refactoring) مقامرة، ويحتاج كل إصدار إلى شخص يتنقّل في التطبيق نقرةً نقرة. ومع اختبارات جيدة، تغيّر الكود، وتشغّل مجموعة الاختبارات (suite)، فتعرف النتيجة.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">ما هو الاختبار</h2>\n<p>انزع عنه كل أطر العمل، وسيبقى الاختبار كودًا يشغّل كودك و<strong>يرمي خطأً حين تكون النتيجة خاطئة</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" 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\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> splitEvenly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">totalCents</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> base</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">floor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> people);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> remainder</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> people;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Array.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ length: people }, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">i</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (i </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> remainder </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> base </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> base));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> shares</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> splitEvenly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1000</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sum</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> shares.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (sum </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!==</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`Expected shares to add up to 1000, got ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sum</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'PASS shares add up to the total'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, shares);</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>تضيف Vitest وJest وPlaywright الكثير فوق ذلك: رسائل فشل واضحة، ووضع المراقبة (watch mode)، والتشغيل المتوازي، والمتصفّحات. لكن في العمق، <code dir=\"ltr\">expect(sum).toBe(1000)</code> ليست سوى <code dir=\"ltr\">if</code> ترمي خطأً.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">للاختبارات تكلفة أيضًا</h2>\n<p>كل اختبار كود عليك صيانته. يأخذ وقتًا لكتابته، ودقائق من CI لتشغيله، وانتباهًا حين يفشل. والاختبار الذي يفشل دون أن يكون شيء معطّلًا (الـ <em>flaky test</em> أو الاختبار المتقلّب، أو الاختبار <em>الهشّ</em>) أسوأ من غيابه: فالناس يتعلّمون تجاهل البناء الأحمر. لذا فالسؤال ليس أبدًا “كم اختبارًا؟” بل <strong>كم من الثقة يشتري كل اختبار مقابل كل دقيقة من CI</strong>.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">كأس الاختبارات</h2>\n<p><em>كأس الاختبارات</em> (testing trophy) طريقة شائعة لتصوّر الطبقات ومقدار ما تكتبه من كل منها:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 640 330\" role=\"img\" aria-labelledby=\"l-tj-1-1-t1\"><defs><marker id=\"arrow-l-tj-1-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-tj-1-1-t1\">شكل كأس من أربع طبقات: التحليل الساكن في القاعدة، وطبقة unit رفيعة، وطبقة integration عريضة، وطبقة end-to-end صغيرة في القمة.</title>\n  <rect class=\"d-box-success\" x=\"250\" y=\"20\" width=\"140\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"320\" y=\"49\" text-anchor=\"middle\">End-to-end</text>\n  <rect class=\"d-box-primary\" x=\"170\" y=\"80\" width=\"300\" height=\"96\" rx=\"14\"/>\n  <text class=\"d-label-strong\" x=\"320\" y=\"122\" text-anchor=\"middle\">Integration</text>\n  <text class=\"d-label-muted\" x=\"320\" y=\"148\" text-anchor=\"middle\">مكوّنات ووحدات معًا</text>\n  <rect class=\"d-box-accent\" x=\"220\" y=\"188\" width=\"200\" height=\"52\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"320\" y=\"219\" text-anchor=\"middle\">Unit</text>\n  <rect class=\"d-box-warn\" x=\"190\" y=\"252\" width=\"260\" height=\"52\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"320\" y=\"283\" text-anchor=\"middle\">ساكن: أنواع + lint</text>\n  <path class=\"d-arrow\" d=\"M520 290 L520 40\" marker-end=\"url(#arrow-l-tj-1-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"532\" y=\"168\">أبطأ،</text>\n  <text class=\"d-label-muted\" x=\"532\" y=\"188\">أغلى،</text>\n  <text class=\"d-label-muted\" x=\"532\" y=\"208\">أقرب للواقع</text>\n</svg><figcaption>كأس الاختبارات: الـ integration tests تمنح أكبر ثقة مقابل تكلفتها</figcaption></figure>\n<ul>\n<li><strong>التحليل الساكن</strong> (TypeScript وESLint) يلتقط الأخطاء الإملائية والأنواع الخاطئة قبل أن يعمل أي شيء.</li>\n<li><strong>الـ unit tests (اختبارات الوحدة)</strong> تفحص دالة واحدة أو وحدة برمجية واحدة بمعزل عن غيرها. سريعة ودقيقة؛ ومثالية للمنطق الصافي مثل تقريب المبالغ المالية.</li>\n<li><strong>الـ integration tests (اختبارات التكامل)</strong> تفحص عدة وحدات تعمل معًا، مثل مكوّن نموذج يستدعي كود التحليل الحقيقي. تلتقط معظم الأخطاء الحقيقية بتكلفة معتدلة، ولهذا كان الكأس في أعرض حالاته عندها.</li>\n<li><strong>الـ end-to-end tests (اختبارات من الطرف إلى الطرف، أو e2e)</strong> تقود التطبيق الحقيقي في متصفّح حقيقي. هي الأقرب إلى الواقع والأغلى ثمنًا؛ فاحتفظ بها للمسارات التي لو انكسرت لتصدّرت الأخبار.</li>\n</ul>\n<p>القاعدة التي أستعملها كل يوم: <strong>اختر أرخص اختبار يمكن أن يفشل لو انكسر الشيء الذي يقلقك.</strong></p>\n<h2 id=\"sec-4\" tabindex=\"-1\">ما الذي ستبنيه في هذه الدورة</h2>\n<p>المشروع الذي يرافقك طوال الدورة هو <strong>Splitwise-lite</strong>، وحدة برمجية لتقسيم الفواتير في رحلة جماعية: تحليل المبالغ، وتقسيم الفواتير، وإضافة البقشيش، وحساب من يدين لمن، وتسوية الحسابات، إضافة إلى واجهة صغيرة وطبقة شبكة. في ساحة التجربة تحصل على كود يعمل وتكتب اختبارات له. ثم يشغّل المدقّق اختباراتك على نسخ معطوبة عمدًا (تقريب خاطئ، أو خطأ بفارق واحد، أو معاملات متبادلة الموقع)، وعلى اختباراتك أن تلتقط كل واحدة منها. النجاح على الكود السليم نصف المهمة؛ والفشل على الكود المعطوب هو النصف الآخر.</p>\n<p>قبل أن تثبّت أي شيء، ابنِ الفكرة الأساسية بنفسك في التمرين التالي: assertion (تأكيد) يرمي خطأً، ومشغّل اختبارات (test runner) يلتقطه.</p>\n","toc":[{"id":"sec-1","text":"ما هو الاختبار","level":2},{"id":"sec-2","text":"للاختبارات تكلفة أيضًا","level":2},{"id":"sec-3","text":"كأس الاختبارات","level":2},{"id":"sec-4","text":"ما الذي ستبنيه في هذه الدورة","level":2}],"takeaways":["الاختبار كود يشغّل كودك ويرمي خطأً حين تكون النتيجة خاطئة؛ وكل إطار عمل للاختبار مجرّد غلاف أجمل حول هذه الفكرة.","تمنحك الاختبارات الثقة لتغيير الكود بسرعة؛ أما الاختبار الذي لا يلتقط خطأً أبدًا ولا يوثّق شيئًا فتكلفته أكبر من فائدته.","لكأس الاختبارات (testing trophy) أربع طبقات (التحليل الساكن، والـ unit، والـ integration، والـ end-to-end)، وعادةً ما تمنحك الـ integration tests أكبر قدر من الثقة مقابل كل دقيقة من وقت CI.","اختر أرخص اختبار يمكن أن يفشل لو انكسر الشيء الذي يقلقك."],"further":[{"title":"Vitest — Why Vitest","url":"https://vitest.dev/guide/why"},{"title":"Playwright — Getting started","url":"https://playwright.dev/docs/intro"},{"title":"Testing Library — Guiding principles","url":"https://testing-library.com/docs/guiding-principles"}],"quiz":[{"q":"<p>يقرّب Splitwise-lite حصّة كل شخص إلى أقرب سنت كامل. أيّ اختبار يعطيك أسرع إشارة وأدقّها على أن التقريب صحيح؟</p>","options":[{"text":"اختبار end-to-end يضيف مصروفًا في المتصفّح ثم يقرأ الرصيد.","why":"سيلتقط الخطأ، لكنه بطيء، ويحتاج إلى تطبيق يعمل، وحين يفشل يشير إلى النظام كله لا إلى دالة التقريب."},{"text":"unit test يستدعي دالة التقسيم بـ 1000 سنت و3 أشخاص.","why":"صحيح. التقريب منطق صافٍ له حالات حدّية كثيرة، لذا فإن unit test يستغرق جزءًا من الثانية هو أرخص اختبار يفشل تمامًا حين ينكسر التقريب."},{"text":"فحص أنواع TypeScript على توقيع الدالة.","why":"تؤكّد الأنواع أن الدالة تستقبل أرقامًا وتعيد أرقامًا، لكنها لا تميّز 334 من 333؛ التقريب مسألة قيم لا مسألة أنواع."}],"answer":1},{"q":"<p>ماذا يفعل إطار الاختبار في جوهره حين يفشل assertion؟</p>","options":[{"text":"يعدّل الكود المصدري كي ينجح الـ assertion في المرة القادمة.","why":"لا يوجد إطار يغيّر كودك؛ الاختبار الفاشل معلومة لك أنت كي تتصرّف بناءً عليها."},{"text":"يسجّل تحذيرًا ويُبقي الاختبار أخضر.","why":"الاختبار الذي يبقى أخضر رغم فشل الـ assertion لا فائدة منه؛ الأطر تعلّم الاختبار بأنه فاشل."},{"text":"يرمي الـ assertion خطأً، فيلتقطه مشغّل الاختبارات، ويعلّم ذلك الاختبار بأنه فاشل، ثم ينتقل إلى التالي.","why":"صحيح. <code dir=\"ltr\">expect(...).toBe(...)</code> ترمي خطأً عند عدم التطابق؛ والمشغّل يلفّ كل اختبار بـ try/catch ويبلّغ عن النتيجة."}],"answer":2},{"q":"<p>تستغرق مجموعة اختبارات الـ end-to-end في فريقك 40 دقيقة، وتفشل عشوائيًا مرة في اليوم تقريبًا. أيّ تغيير يرفع الثقة مقابل كل دقيقة من CI أكثر من غيره؟</p>","options":[{"text":"انقل معظم فحوص المنطق إلى اختبارات integration وunit، واحتفظ ببضعة اختبارات end-to-end للمسارات الحرجة.","why":"صحيح. الطبقات الأدنى تفحص المنطق نفسه في أجزاء من الثانية دون تقلّب المتصفّح؛ وبضعة اختبارات end-to-end تبقى كافية لإثبات أن القطع متّصلة ببعضها."},{"text":"أضف إعادة محاولة تلقائية حتى ينجح كل اختبار.","why":"إعادة المحاولة تُخفي التقلّب بدل أن تصلحه، وتجعل المجموعة أبطأ؛ والأخطاء الحقيقية التي تفشل بشكل متقطّع تضيع هي الأخرى بين المحاولات."},{"text":"احذف مجموعة الـ end-to-end واعتمد على الـ unit tests وحدها.","why":"قد تنجح كل الـ unit tests والتطبيق معطّل، كأن لا يستدعي النموذج الدالة التي تختبرها أصلًا. ما زلت تحتاج إلى بضعة اختبارات end-to-end."}],"answer":0}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// An assertion: throw an Error when actual !== expected.\nfunction expectEqual(actual, expected) {\n  // your code\n}\n\n// A runner: run fn, log PASS or FAIL, never let one failure stop the others.\nfunction test(name, fn) {\n  // your code\n}\n\ntest(\"two plus two\", () => expectEqual(2 + 2, 4));\ntest(\"deliberately wrong\", () => expectEqual(0.1 + 0.2, 0.3));\n"},"solution":{"js":"function expectEqual(actual, expected) {\n  if (actual !== expected) {\n    throw new Error(`Expected ${expected} but received ${actual}`);\n  }\n}\n\nfunction test(name, fn) {\n  try {\n    fn();\n    console.log(`PASS ${name}`);\n  } catch (error) {\n    console.log(`FAIL ${name}: ${error.message}`);\n  }\n}\n\ntest(\"two plus two\", () => expectEqual(2 + 2, 4));\ntest(\"deliberately wrong\", () => expectEqual(0.1 + 0.2, 0.3));\n"},"checks":[{"id":"equal-passes","test":"try { expectEqual(5, 5); expectEqual(\"a\", \"a\"); return true; } catch { return false; }"},{"id":"unequal-throws","test":"try { expectEqual(333, 334); return false; } catch (e) { return e instanceof Error && String(e.message).includes(\"333\") && String(e.message).includes(\"334\"); }"},{"id":"logs-pass","test":"test(\"check-pass-case\", () => expectEqual(1, 1)); return logs.includes(\"PASS check-pass-case\");"},{"id":"logs-fail","test":"test(\"check-fail-case\", () => expectEqual(1, 2)); return logs.some((l) => l.startsWith(\"FAIL check-fail-case\") && l.includes(\"2\"));"},{"id":"keeps-going","test":"try { test(\"check-boom\", () => { throw new Error(\"boom\"); }); } catch { return false; } return logs.some((l) => l.startsWith(\"FAIL check-boom\"));"}],"title":"ابنِ مشغّل اختبارات من عشرة أسطر","prompt":"<p>اكتب الدالتين اللتين يقوم عليهما كل إطار اختبار.</p>\n<ul>\n<li><code dir=\"ltr\">expectEqual(actual, expected)</code> لا تفعل شيئًا حين تكون القيمتان متساويتين تمامًا (<code dir=\"ltr\">===</code>)، و<strong>ترمي <code dir=\"ltr\">Error</code></strong> في غير ذلك. يجب أن تتضمّن الرسالة القيمتين معًا، مثل <code dir=\"ltr\">Expected 334 but received 333</code>.</li>\n<li><code dir=\"ltr\">test(name, fn)</code> تستدعي <code dir=\"ltr\">fn</code>. إن انتهت بشكل طبيعي، اطبع <code dir=\"ltr\">PASS &lt;name&gt;</code>. وإن رمت خطأً، اطبع <code dir=\"ltr\">FAIL &lt;name&gt;: &lt;error message&gt;</code> و<strong>لا تُعِد رمي الخطأ</strong>، كي يعمل الاختبار التالي.</li>\n</ul>\n<p>شغّل الكود: الاختبار الثاني يفشل عمدًا، لأن <code dir=\"ltr\">0.1 + 0.2</code> لا يساوي <code dir=\"ltr\">0.3</code> تمامًا في JavaScript.</p>\n","hints":["تحتاج <code dir=\"ltr\">expectEqual</code> إلى <code dir=\"ltr\">if</code> واحدة و<code dir=\"ltr\">throw new Error(...)</code> واحدة. استخدم template literal لوضع القيمتين في الرسالة.","تحتاج <code dir=\"ltr\">test</code> إلى <code dir=\"ltr\">try { … } catch (error) { … }</code> حول استدعاء <code dir=\"ltr\">fn()</code>.","اطبع <code dir=\"ltr\">console.log(`PASS ${name}`)</code> بعد <code dir=\"ltr\">fn()</code> داخل <code dir=\"ltr\">try</code>، و<code dir=\"ltr\">console.log(`FAIL ${name}: ${error.message}`)</code> داخل <code dir=\"ltr\">catch</code>."],"explanation":"<p>هذا هو قلب Vitest وJest وكل مشغّل اختبارات آخر: الـ assertion عبارة <code dir=\"ltr\">if</code> ترمي خطأً، والاختبار دالة يستدعيها المشغّل داخل <code dir=\"ltr\">try/catch</code>. التقاط الخطأ هو ما يسمح بالإبلاغ عن اختبار فاشل واحد دون إيقاف بقية المجموعة. تضيف الأطر الحقيقية رسائل أفضل (مقارنة الفروق بين الكائنات)، ودعم الكود غير المتزامن، والـ hooks، ووضع المراقبة، والتشغيل المتوازي، لكن العقد هو نفسه: <strong>ينجح الاختبار حين تنتهي دالته دون أن ترمي خطأً</strong>. ولاحظ أيضًا الفشل المقصود: <code dir=\"ltr\">0.1 + 0.2</code> يساوي <code dir=\"ltr\">0.30000000000000004</code>، وهذا بالضبط سبب تخزين Splitwise-lite للمبالغ كسنتات صحيحة.</p>\n","checkLabels":{"equal-passes":"لا ترمي expectEqual خطأً حين تتساوى القيمتان","unequal-throws":"ترمي expectEqual خطأً من نوع Error يذكر القيمتين","logs-pass":"تطبع test السطر PASS <name> للاختبار الناجح","logs-fail":"تطبع test السطر FAIL <name>: <message> للاختبار الفاشل","keeps-going":"تلتقط test الخطأ بدل إعادة رميه"}},"words":459,"readMinutes":3}