انتقل إلى المحتوى

وحدة:ar-verb/مختبر

من ويكاموس، القاموس الحر
-- Unit tests for [[Module:ar-verb]]. Click talk page to run tests.
-- اختبارات الوحدة. انقر على صفحة النقاش لعرض النتائج
-- 🚧 Work In Progress !!
local tests = require("Module:UnitTests")
local m_ar_verb = require("Module:ar-verb")


-- ═══════════════════════════════════════════════════════════
-- Utilities required for some tests
-- ═══════════════════════════════════════════════════════════
local m_string_utilities = require("Module:string utilities")
local u = m_string_utilities.char
-- ═══════════════════════════════════════════════════════════

function tests:test_conj_form_I()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "كتب", ["وزن"] = "فعَل يفعُل"}, "ar-conj")
    self:equals("كتب، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "كَتَبَ")
    self:equals("كتب، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَكْتُبُ")
    self:equals("كتب، فعَل يفعُل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "اُكْتُبْ")
    self:equals("كتب، فعَل يفعُل، اسم الفاعل", result.forms.ap[1].form, "كَاتِب")
    self:equals("كتب، فعَل يفعُل، اسم المفعول", result.forms.pp[1].form, "مَكْتُوب")
    self:equals("كتب، فعَل يفعُل، له معلوم", result.has_active, true)
    self:equals("كتب، فعَل يفعُل، له مجهول", result.has_passive, true)
    self:equals("كتب، فعَل يفعُل، الوزن I", result.verb_forms[1], "I")
end

function tests:test_conj_form_II()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "درس", ["وزن"] = "فعّل"}, "ar-conj")
    self:equals("درس، فعّل، هو، الماضي", result.forms.past_3ms[1].form, "دَرَّسَ")
    self:equals("درس، فعّل، هو، المضارع", result.forms.ind_3ms[1].form, "يُدَرِّسُ")
    self:equals("درس، فعّل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "دَرِّسْ")
    self:equals("درس، فعّل، اسم الفاعل", result.forms.ap[1].form, "مُدَرِّس")
    self:equals("درس، فعّل، اسم المفعول", result.forms.pp[1].form, "مُدَرَّس")
    self:equals("درس، فعّل، المصدر", result.forms.vn[1].form, "تَدْرِيس")
end

function tests:test_conj_form_III()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "قتل", ["وزن"] = "فاعل"}, "ar-conj")
    self:equals("قتل، فاعل، هو، الماضي", result.forms.past_3ms[1].form, "قَاتَلَ")
    self:equals("قتل، فاعل، هو، المضارع", result.forms.ind_3ms[1].form, "يُقَاتِلُ")
    self:equals("قتل، فاعل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "قَاتِلْ")
    self:equals("قتل، فاعل، اسم الفاعل", result.forms.ap[1].form, "مُقَاتِل")
    self:equals("قتل، فاعل، اسم المفعول", result.forms.pp[1].form, "مُقَاتَل")
end

function tests:test_conj_form_IV()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "رسل", ["وزن"] = "أفعل"}, "ar-conj")
    self:equals("رسل، أفعل، هو، الماضي", result.forms.past_3ms[1].form, "أَرْسَلَ")
    self:equals("رسل، أفعل، هو، المضارع", result.forms.ind_3ms[1].form, "يُرْسِلُ")
    self:equals("رسل، أفعل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "أَرْسِلْ")
    self:equals("رسل، أفعل، اسم الفاعل", result.forms.ap[1].form, "مُرْسِل")
    self:equals("رسل، أفعل، اسم المفعول", result.forms.pp[1].form, "مُرْسَل")
end

-- وزن «أفعل» من الأجوف: تُعَلّ العين ألفًا (أَفَالَ) في الواوي واليائي معًا،
-- تبعًا لإعلالها في الثلاثي المجرد (معجم متن اللغة).
function tests:test_conj_form_IV_hollow()
    -- أجوف واوي
    local qwl = m_ar_verb.do_generate_forms({["جذر"] = "قول", ["وزن"] = "أفعل"}, "ar-conj")
    self:equals("قول، أفعل، هو، الماضي", qwl.forms.past_3ms[1].form, "أَقَالَ")

    -- أجوف يائي: يُعَلّ كنظيره الواوي
    local yaa_cases = {
        {"تيس", "أَتَاسَ"},
        {"طيب", "أَطَابَ"},
        {"بين", "أَبَانَ"},
        {"ضيع", "أَضَاعَ"},
        {"طيح", "أَطَاحَ"},
    }
    for _, case in ipairs(yaa_cases) do
        local root, expected = case[1], case[2]
        local result = m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = "أفعل"}, "ar-conj")
        self:equals(root .. "، أفعل، هو، الماضي", result.forms.past_3ms[1].form, expected)
    end

    -- الاستثناء يخصّ «أفعل» وحده؛ الأوزان الأخرى من الأجوف اليائي تبقى معلّة
    local tys_x = m_ar_verb.do_generate_forms({["جذر"] = "تيس", ["وزن"] = "استفعل"}, "ar-conj")
    self:equals("تيس، استفعل، هو، الماضي", tys_x.forms.past_3ms[1].form, "اِسْتَتَاسَ")
end

-- 7 جذور يائية شاذّة يبقى وزن «أفعل» منها صحيحًا (أَفْيَلَ) — أفعال مسموعة موثّقة،
-- لا الصفة المشبهة (مثل أَخْيَف/خَيْفاء). انظر SOUND_FORM_IV_ROOTS في الوحدة.
function tests:test_conj_form_IV_sound_exceptions()
    local cases = {
        {"خيل", "أَخْيَلَ"}, -- أخيلت السماء — المعجم الوسيط 1/266
        {"غيل", "أَغْيَلَ"}, -- أغيلته سقته الغَيْل — لسان العرب 11/111
        {"حيج", "أَحْيَجَ"}, -- أحيجت الأرض — تاج العروس 5/500
        {"حين", "أَحْيَنَ"}, -- أحين القوم — المعجم الوسيط 1/212
        {"خيف", "أَخْيَفَ"}, -- أخيف الحاج — كتاب الأفعال لابن القوطية 35
        {"ريف", "أَرْيَفَ"}, -- أريفت الأرض — المحيط في اللغة 10/256
        {"زين", "أَزْيَنَ"}, -- أزينت الأرض — مقاييس اللغة 3/41
    }
    for _, case in ipairs(cases) do
        local root, expected = case[1], case[2]
        local result = m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = "أفعل"}, "ar-conj")
        self:equals(root .. "، أفعل، هو، الماضي (شاذّ صحيح)", result.forms.past_3ms[1].form, expected)
    end
end

function tests:test_conj_weak()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "قول", ["وزن"] = "فعَل يفعُل"}, "ar-conj")
    self:equals("قول، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "قَالَ")
    self:equals("قول، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَقُولُ")
    self:equals("قول، فعَل يفعُل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "قُلْ")

    local result = m_ar_verb.do_generate_forms({["جذر"] = "رمي", ["وزن"] = "فعَل يفعِل"}, "ar-conj")
    self:equals("رمي، فعَل يفعِل، هو، الماضي", result.forms.past_3ms[1].form, "رَمَى")
    self:equals("رمي، فعَل يفعِل، هو، المضارع", result.forms.ind_3ms[1].form, "يَرْمِي")
    self:equals("رمي، فعَل يفعِل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "اِرْمِ")
    self:equals("رمي، فعَل يفعِل، اسم الفاعل", result.forms.ap[1].form, "رَامٍ")

    local result = m_ar_verb.do_generate_forms({["جذر"] = "م_د_د", ["وزن"] = "فعَل يفعُل"}, "ar-conj")
    self:equals("مدد، فعَل يفعُل، مضعَّف، هو، الماضي", result.forms.past_3ms[1].form, "مَدَّ")

    local result = m_ar_verb.do_generate_forms({["جذر"] = "وصل", ["وزن"] = "فعَل يفعِل"}, "ar-conj")
    self:equals("وصل، فعَل يفعِل، مثال، هو، الماضي", result.forms.past_3ms[1].form, "وَصَلَ")
    self:equals("وصل، فعَل يفعِل، مثال، هو، المضارع", result.forms.ind_3ms[1].form, "يَصِلُ")
    self:equals("وصل، فعَل يفعِل، مثال، أنت، الأمر", result.forms.imp_2ms[1].form, "صِلْ")
end

function tests:test_conj_with_underscores()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "ك_ت_ب", ["وزن"] = "فعَل يفعُل"}, "ar-conj")
    self:equals("كتب، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "كَتَبَ")
    self:equals("كتب، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَكْتُبُ")
end

function tests:test_conj_with_spaces()
    local result = m_ar_verb.do_generate_forms({["جذر"] = "ك ت ب", ["وزن"] = "فعَل يفعُل"}, "ar-conj")
    self:equals("كتب، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "كَتَبَ")
    self:equals("كتب، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَكْتُبُ")
end

function tests:test_conj_passive_types()
    local result_pass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.pass>"}, "ar-conj")
    self:equals("كتب، مبني للمجهول فقط", result_pass.passive, "pass")

    local result_ipass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.ipass>"}, "ar-conj")
    self:equals("كتب، مجهول غير شخصي", result_ipass.passive, "ipass")

    local result_nopass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.nopass>"}, "ar-conj")
    self:equals("كتب، بدون مجهول", result_nopass.passive, "nopass")
    self:equals("كتب، بدون مجهول، has_passive خطأ", result_nopass.has_passive, false)
end

function tests:test_conj_overrides()
    local result = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.vn:كِتَابَة>"}, "ar-conj")
    self:equals("كتب، تجاوز المصدر", result.forms.vn[1].form, "كِتَابَة")
end

function tests:test_infer_radicals()
    local result = m_ar_verb.infer_radicals({
        headword = "كتب",
        vform = "I"
    })
    self:equals("كتب، نوع الضعف: صحيح", result.weakness, "sound")
    self:equals("كتب، حرف الجذر الأول: ك", result.rad1, "ك")
    self:equals("كتب، حرف الجذر الثاني: ت", result.rad2, "ت")
    self:equals("كتب، حرف الجذر الثالث: ب", result.rad3, "ب")

    local U  = u(0x064F) -- ḍamma
    local result = m_ar_verb.infer_radicals({
        headword = "قال",
        vform = "I",
        nonpast_vowel = U
    })
    self:equals("قال، نوع الضعف: أجوف", result.weakness, "hollow")
    self:equals("قال، حرف الجذر الأول: ق", result.rad1, "ق")
    self:equals("قال، حرف الجذر الثاني: و (مستنتج)", result.rad2, "و")
    self:equals("قال، حرف الجذر الثالث: ل", result.rad3, "ل")

    local result = m_ar_verb.infer_radicals({
        headword = "رمى",
        vform = "I"
    })
    self:equals("رمى، نوع الضعف: ناقص", result.weakness, "final-weak")
    self:equals("رمى، حرف الجذر الأول: ر", result.rad1, "ر")
    self:equals("رمى، حرف الجذر الثاني: م", result.rad2, "م")
    self:equals("رمى، حرف الجذر الثالث: ي", result.rad3, "ي")
end

function tests:test_infer_participle_vocalization()
    local result = m_ar_verb.infer_participle_vocalization("كاتب", "I", "sound", true)
    self:equals("كاتب، تشكيل اسم الفاعل", result, "كَاتِب")
    local result = m_ar_verb.infer_participle_vocalization("مكتوب", "I", "sound", false)
    self:equals("مكتوب، تشكيل اسم المفعول", result, "مَكْتُوب")
    local result = m_ar_verb.infer_participle_vocalization("مدرس", "II", "sound", true)
    self:equals("مدرس، فعّل، تشكيل اسم الفاعل", result, "مُدَرِّس")
end

-- ═══════════════════════════════════════════════════════════
-- Tests for classify_triliteral_verb function
-- ═══════════════════════════════════════════════════════════

-- الصحيح
function tests:test_verb_classify_sound()
    self:equals("كتب", m_ar_verb.classify_triliteral_verb("ك", "ت", "ب"), "صحيح سالم")
    self:equals("ذهب", m_ar_verb.classify_triliteral_verb("ذ", "ه", "ب"), "صحيح سالم")
    self:equals("فعل", m_ar_verb.classify_triliteral_verb("ف", "ع", "ل"), "صحيح سالم")

    self:equals("مدّ", m_ar_verb.classify_triliteral_verb("م", "د", "د"), "صحيح مُضعَّف")
    self:equals("شدّ", m_ar_verb.classify_triliteral_verb("ش", "د", "د"), "صحيح مُضعَّف")
    self:equals("عدّ", m_ar_verb.classify_triliteral_verb("ع", "د", "د"), "صحيح مُضعَّف")

    self:equals("أخذ", m_ar_verb.classify_triliteral_verb("أ", "خ", "ذ"), "صحيح مهموز الفاء")
    self:equals("أكل", m_ar_verb.classify_triliteral_verb("أ", "ك", "ل"), "صحيح مهموز الفاء")
    self:equals("سأل", m_ar_verb.classify_triliteral_verb("س", "أ", "ل"), "صحيح مهموز العين")
    self:equals("قرأ", m_ar_verb.classify_triliteral_verb("ق", "ر", "أ"), "صحيح مهموز اللام")
    self:equals("أبأ", m_ar_verb.classify_triliteral_verb("أ", "ب", "أ"), "صحيح مهموز الفاء واللام")
end

-- المعتل
function tests:test_verb_classify_weak()
    self:equals("وجد", m_ar_verb.classify_triliteral_verb("و", "ج", "د"), "معتل مثال واوي")
    self:equals("وقف", m_ar_verb.classify_triliteral_verb("و", "ق", "ف"), "معتل مثال واوي")
    self:equals("وضأ", m_ar_verb.classify_triliteral_verb("و", "ض", "أ"), "معتل مثال واوي مهموز اللام")
    self:equals("يسر", m_ar_verb.classify_triliteral_verb("ي", "س", "ر"), "معتل مثال يائي")
    self:equals("يئس", m_ar_verb.classify_triliteral_verb("ي", "أ", "س"), "معتل مثال يائي مهموز العين")

    self:equals("قال", m_ar_verb.classify_triliteral_verb("ق", "و", "ل"), "معتل أجوف واوي")
    self:equals("صام", m_ar_verb.classify_triliteral_verb("ص", "و", "م"), "معتل أجوف واوي")
    self:equals("باع", m_ar_verb.classify_triliteral_verb("ب", "ي", "ع"), "معتل أجوف يائي")
    self:equals("نام", m_ar_verb.classify_triliteral_verb("ن", "و", "م"), "معتل أجوف واوي")

    self:equals("دعا", m_ar_verb.classify_triliteral_verb("د", "ع", "و"), "معتل ناقص واوي")
    self:equals("سما", m_ar_verb.classify_triliteral_verb("س", "م", "و"), "معتل ناقص واوي")
    self:equals("رمى", m_ar_verb.classify_triliteral_verb("ر", "م", "ي"), "معتل ناقص يائي")
    self:equals("بكى", m_ar_verb.classify_triliteral_verb("ب", "ك", "ي"), "معتل ناقص يائي")

    -- لفيف مقرون (adjacent weak letters)
    self:equals("ويّ", m_ar_verb.classify_triliteral_verb("و", "ي", "ي"), "معتل لفيف مقرون")
    self:equals("أوى", m_ar_verb.classify_triliteral_verb("أ", "و", "ي"), "معتل لفيف مقرون مهموز الفاء")
    -- لفيف مفروق (separated weak letters)
    self:equals("وقى", m_ar_verb.classify_triliteral_verb("و", "ق", "ي"), "معتل لفيف مفروق")
    self:equals("وأى", m_ar_verb.classify_triliteral_verb("و", "أ", "ي"), "معتل لفيف مفروق مهموز العين")

    -- Combined weaknesses
    self:equals("أول", m_ar_verb.classify_triliteral_verb("أ", "و", "ل"), "معتل أجوف واوي مهموز الفاء")
    self:equals("قوأ", m_ar_verb.classify_triliteral_verb("ق", "و", "أ"), "معتل أجوف واوي مهموز اللام")
    self:equals("أيب", m_ar_verb.classify_triliteral_verb("أ", "ي", "ب"), "معتل أجوف يائي مهموز الفاء")
    self:equals("جيأ", m_ar_verb.classify_triliteral_verb("ج", "ي", "أ"), "معتل أجوف يائي مهموز اللام")
    self:equals("رأو", m_ar_verb.classify_triliteral_verb("ر", "أ", "و"), "معتل ناقص واوي مهموز العين")
    self:equals("شأي", m_ar_verb.classify_triliteral_verb("ش", "أ", "ي"), "معتل ناقص يائي مهموز العين")
    self:equals("ودد", m_ar_verb.classify_triliteral_verb("و", "د", "د"), "معتل مثال واوي مُضعَّف")
    self:equals("ينن", m_ar_verb.classify_triliteral_verb("ي", "ن", "ن"), "معتل مثال يائي مُضعَّف")
    self:equals("وأب", m_ar_verb.classify_triliteral_verb("و", "أ", "ب"), "معتل مثال واوي مهموز العين")
    self:equals("أبب", m_ar_verb.classify_triliteral_verb("أ", "ب", "ب"), "صحيح مُضعَّف مهموز الفاء")

    self:equals("أتى", m_ar_verb.classify_triliteral_verb("أ", "ت", "ي"), "معتل ناقص يائي مهموز الفاء")
    self:equals("قلا", m_ar_verb.classify_triliteral_verb("ق", "ل", "و"), "معتل ناقص واوي")
    self:equals("نما (و)", m_ar_verb.classify_triliteral_verb("ن", "م", "و"), "معتل ناقص واوي")
    self:equals("نمى (ي)", m_ar_verb.classify_triliteral_verb("ن", "م", "ي"), "معتل ناقص يائي")
    self:equals("يئس", m_ar_verb.classify_triliteral_verb("ي", "أ", "س"), "معتل مثال يائي مهموز العين")
end

-- ═══════════════════════════════════════════════════════════
-- تغطية كل الأوزان — فعل شاهد لكل وزن
-- ═══════════════════════════════════════════════════════════

-- مختصر: يولّد التصريف ويعيد صيغة الخانة المطلوبة
local function conj(root, pattern)
    return m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = pattern}, "ar-conj")
end
local function slot(result, name)
    local forms = result.forms[name]
    if not forms then
        return nil
    end
    local out = {}
    for i, f in ipairs(forms) do
        out[i] = f.form
    end
    return table.concat(out, "/")
end

-- الثلاثي المجرد: الأبواب الستة
function tests:test_conj_form_I_all_vowel_patterns()
    local cases = {
        -- جذر، وزن، الماضي، المضارع، الأمر
        {"كتب", "فعَل يفعُل", "كَتَبَ", "يَكْتُبُ", "اُكْتُبْ"},
        {"ضرب", "فعَل يفعِل", "ضَرَبَ", "يَضْرِبُ", "اِضْرِبْ"},
        {"فتح", "فعَل يفعَل", "فَتَحَ", "يَفْتَحُ", "اِفْتَحْ"},
        {"علم", "فعِل يفعَل", "عَلِمَ", "يَعْلَمُ", "اِعْلَمْ"},
        {"كرم", "فعُل يفعُل", "كَرُمَ", "يَكْرُمُ", "اُكْرُمْ"},
        {"حسب", "فعِل يفعِل", "حَسِبَ", "يَحْسِبُ", "اِحْسِبْ"},
    }
    for _, c in ipairs(cases) do
        local root, pattern, past, pres, imp = c[1], c[2], c[3], c[4], c[5]
        local r = conj(root, pattern)
        self:equals(root .. "، " .. pattern .. "، الماضي", slot(r, "past_3ms"), past)
        self:equals(root .. "، " .. pattern .. "، المضارع", slot(r, "ind_3ms"), pres)
        self:equals(root .. "، " .. pattern .. "، الأمر", slot(r, "imp_2ms"), imp)
    end
end

-- المزيد: الأوزان V–XIII
function tests:test_conj_derived_forms()
    local cases = {
        -- جذر، وزن، الماضي، المضارع، المصدر
        {"علم", "تفعّل",   "تَعَلَّمَ",   "يَتَعَلَّمُ",   "تَعَلُّم"},
        {"عون", "تفاعل",   "تَعَاوَنَ",   "يَتَعَاوَنُ",   "تَعَاوُن"},
        {"كسر", "انفعل",   "اِنْكَسَرَ",  "يَنْكَسِرُ",   "اِنْكِسَار"},
        {"جمع", "افتعل",   "اِجْتَمَعَ",  "يَجْتَمِعُ",   "اِجْتِمَاع"},
        {"حمر", "افعلّ",   "اِحْمَرَّ",   "يَحْمَرُّ",     "اِحْمِرَار"},
        {"خرج", "استفعل", "اِسْتَخْرَجَ", "يَسْتَخْرِجُ", "اِسْتِخْرَاج"},
        {"حمر", "افعالّ", "اِحْمَارَّ",  "يَحْمَارُّ",    "اِحْمِيرَار"},
        {"عشب", "افعوعل", "اِعْشَوْشَبَ", "يَعْشَوْشِبُ", "اِعْشِيشَاب"},
        {"جلذ", "افعوّل", "اِجْلَوَّذَ", "يَجْلَوِّذُ",  "اِجْلِوَّاذ"},
    }
    for _, c in ipairs(cases) do
        local root, pattern, past, pres, vn = c[1], c[2], c[3], c[4], c[5]
        local r = conj(root, pattern)
        self:equals(root .. "، " .. pattern .. "، الماضي", slot(r, "past_3ms"), past)
        self:equals(root .. "، " .. pattern .. "، المضارع", slot(r, "ind_3ms"), pres)
        self:equals(root .. "، " .. pattern .. "، المصدر", slot(r, "vn"), vn)
    end
end

-- الرباعي: مجرده ومزيده
function tests:test_conj_quadriliteral()
    local cases = {
        {"دحرج", "فعلل",   "دَحْرَجَ",     "يُدَحْرِجُ",     "دَحْرَجَة"},
        {"دحرج", "تفعلل", "تَدَحْرَجَ",   "يَتَدَحْرَجُ",   "تَدَحْرُج"},
        {"حرجم", "افعنلل", "اِحْرَنْجَمَ", "يَحْرَنْجِمُ", "اِحْرِنْجَام"},
        {"قشعر", "افعللّ", "اِقْشَعَرَّ",  "يَقْشَعِرُّ",   "اِقْشِعْرَار"},
    }
    for _, c in ipairs(cases) do
        local root, pattern, past, pres, vn = c[1], c[2], c[3], c[4], c[5]
        local r = conj(root, pattern)
        self:equals(root .. "، " .. pattern .. "، الماضي", slot(r, "past_3ms"), past)
        self:equals(root .. "، " .. pattern .. "، المضارع", slot(r, "ind_3ms"), pres)
        self:equals(root .. "، " .. pattern .. "، المصدر", slot(r, "vn"), vn)
    end
end

-- الضمائر والأزمنة: نموذج كامل من «كتب»
function tests:test_conj_persons_and_moods()
    local r = conj("كتب", "فعَل يفعُل")
    local cases = {
        {"past_1s",   "كَتَبْتُ"},
        {"past_2ms",  "كَتَبْتَ"},
        {"past_3fs",  "كَتَبَتْ"},
        {"past_3fp",  "كَتَبْنَ"},
        {"ind_1s",    "أَكْتُبُ"},
        {"ind_2fs",   "تَكْتُبِينَ"},
        {"ind_3mp",   "يَكْتُبُونَ"},
        {"sub_3ms",   "يَكْتُبَ"},
        {"juss_3ms",  "يَكْتُبْ"},
        {"imp_2fs",   "اُكْتُبِي"},
        {"imp_2mp",   "اُكْتُبُوا"},
        {"imp_2fp",   "اُكْتُبْنَ"},
    }
    for _, c in ipairs(cases) do
        self:equals("كتب، " .. c[1], slot(r, c[1]), c[2])
    end
end

-- المبني للمجهول
function tests:test_conj_passive_forms()
    local r = conj("كتب", "فعَل يفعُل")
    self:equals("كتب، الماضي المجهول", slot(r, "past_pass_3ms"), "كُتِبَ")
    self:equals("كتب، المضارع المجهول", slot(r, "ind_pass_3ms"), "يُكْتَبُ")
    self:equals("كتب، المنصوب المجهول", slot(r, "sub_pass_3ms"), "يُكْتَبَ")
    self:equals("كتب، المجزوم المجهول", slot(r, "juss_pass_3ms"), "يُكْتَبْ")

    local r2 = conj("درس", "فعّل")
    self:equals("درس، فعّل، الماضي المجهول", slot(r2, "past_pass_3ms"), "دُرِّسَ")
    self:equals("درس، فعّل، المضارع المجهول", slot(r2, "ind_pass_3ms"), "يُدَرَّسُ")
end

-- الأفعال الشاذّة المعالَجة داخل الوحدة
function tests:test_conj_irregular_verbs()
    local raa = conj("رأي", "فعَل يفعَل")
    self:equals("رأى، الماضي", slot(raa, "past_3ms"), "رَأَى")
    self:equals("رأى، المضارع", slot(raa, "ind_3ms"), "يَرَى")
    self:equals("رأى، الأمر", slot(raa, "imp_2ms"), "رَ")

    local araa = conj("رأي", "أفعل")
    self:equals("أرى، الماضي", slot(araa, "past_3ms"), "أَرَى")
    self:equals("أرى، المضارع", slot(araa, "ind_3ms"), "يُرِي")

    local akal = conj("أكل", "فعَل يفعُل")
    self:equals("أكل، الماضي", slot(akal, "past_3ms"), "أَكَلَ")
    self:equals("أكل، الأمر (محذوف)", slot(akal, "imp_2ms"), "كُلْ")

    local akhadh = conj("أخذ", "فعَل يفعُل")
    self:equals("أخذ، الأمر (محذوف)", slot(akhadh, "imp_2ms"), "خُذْ")

    -- أمر: للأمر صيغتان، المحذوفة والتامّة بعد حرف عطف
    local amar = conj("أمر", "فعَل يفعُل")
    self:equals("أمر، الأمر", slot(amar, "imp_2ms"), "مُرْ/اُؤْمُرْ")

    -- سأل: للمجزوم والأمر صيغتان
    local saal = conj("سأل", "فعَل يفعَل")
    self:equals("سأل، الأمر", slot(saal, "imp_2ms"), "اِسْأَلْ/سَلْ")
    self:equals("سأل، المجزوم", slot(saal, "juss_3ms"), "يَسْأَلْ/يَسَلْ")

    -- حيّ/حيي: الإدغام والفكّ
    local hayy = conj("حيي", "فعِل يفعَل")
    self:equals("حيّ، الماضي (مدغم ومفكوك)", slot(hayy, "past_3ms"), "حَيَّ/حَيِيَ")
    self:equals("حيّ، المضارع", slot(hayy, "ind_3ms"), "يَحْيَا")
end

-- الإدغام بالتاء (var:short/long) وصيغ الإدغام المختصرة (reduced)
function tests:test_conj_variants_and_reduced()
    local short = m_ar_verb.do_generate_forms({[1] = "حيي<I/i~a.var:short>"}, "ar-conj")
    self:equals("حيي، var:short، الماضي", slot(short, "past_3ms"), "حَيَّ")
    local long = m_ar_verb.do_generate_forms({[1] = "حيي<I/i~a.var:long>"}, "ar-conj")
    self:equals("حيي، var:long، الماضي", slot(long, "past_3ms"), "حَيِيَ")

    -- اتّخذ: افتعل من «أخذ» بإبدال الهمزة تاءً وإدغامها
    local ittakhadha = m_ar_verb.do_generate_forms({[1] = "اتخذ<VIII.reduced>"}, "ar-conj")
    self:equals("اتّخذ، الماضي", slot(ittakhadha, "past_3ms"), "اِتَّخَذَ")
    self:equals("اتّخذ، المضارع", slot(ittakhadha, "ind_3ms"), "يَتَّخِذُ")
    self:equals("اتّخذ، المصدر", slot(ittakhadha, "vn"), "اِتِّخَاذ")

    -- ادّارأ: تفاعل مدغم (اِدَّارَأَ عوض تَدَارَأَ)
    local iddaaraa = m_ar_verb.do_generate_forms({[1] = "ادارأ<VI.reduced>"}, "ar-conj")
    self:equals("ادّارأ، الماضي", slot(iddaaraa, "past_3ms"), "اِدَّارَأَ")

    -- اسطاع: استفعل محذوف التاء
    local istaa = m_ar_verb.do_generate_forms({[1] = "اسطاع<X.reduced>"}, "ar-conj")
    self:equals("اسطاع، الماضي", slot(istaa, "past_3ms"), "اِسْطَاعَ")
    self:equals("اسطاع، المضارع", slot(istaa, "ind_3ms"), "يَسْطِيعُ")
end

-- المعتل: المثال والناقص واللفيف
function tests:test_conj_weak_extended()
    local cases = {
        -- جذر، وزن، الماضي، المضارع، الأمر، اسم الفاعل
        {"وعد", "فعَل يفعِل", "وَعَدَ", "يَعِدُ",   "عِدْ",       "وَاعِد"}, -- مثال واوي
        {"دعو", "فعَل يفعُل", "دَعَا", "يَدْعُو", "اُدْعُ",   "دَاعٍ"},  -- ناقص واوي
        {"شوي", "فعَل يفعِل", "شَوَى", "يَشْوِي", "اِشْوِ",   "شَاوٍ"},  -- ناقص يائي
        {"وقي", "فعَل يفعِل", "وَقَى", "يَقِي",   "قِ",         "وَاقٍ"},  -- لفيف مفروق
    }
    for _, c in ipairs(cases) do
        local root, pattern = c[1], c[2]
        local r = conj(root, pattern)
        self:equals(root .. "، الماضي", slot(r, "past_3ms"), c[3])
        self:equals(root .. "، المضارع", slot(r, "ind_3ms"), c[4])
        self:equals(root .. "، الأمر", slot(r, "imp_2ms"), c[5])
        self:equals(root .. "، اسم الفاعل", slot(r, "ap"), c[6])
    end
end

-- حواشي على حركات الماضي والمضارع، مثل <I/a[نادر]~u>.
-- كانت ترمي خطأ لوا: verify_vowel كانت تقارن الكائن نفسه لا قيمته.
function tests:test_conj_vowel_footnotes()
    local a = m_ar_verb.do_generate_forms({[1] = "كتب<I/a[نادر]~u>"}, "ar-conj")
    self:equals("كتب، حاشية على حركة الماضي", a.forms.past_3ms[1].form, "كَتَبَ")
    self:equals("كتب، حاشية على حركة الماضي، المضارع", a.forms.ind_3ms[1].form, "يَكْتُبُ")

    local b = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u[نادر]>"}, "ar-conj")
    self:equals("كتب، حاشية على حركة المضارع", b.forms.past_3ms[1].form, "كَتَبَ")

    -- أجوف: يمرّ عبر infer_radicals، وهو موضع الخطأ الأصلي
    local c = m_ar_verb.do_generate_forms({[1] = "قال<I/a[نادر]~u>"}, "ar-conj")
    self:equals("قال، حاشية على حركة الماضي", c.forms.past_3ms[1].form, "قَالَ")
end

-- q/qq/l/ll أُزيلت لأنها تستدعي [[وحدة:pron qualifier]] غير الموجودة في ويكاموس العربي.
-- المطلوب الآن خطأ واضح في التحليل، لا خطأ لوا.
function tests:test_conj_qualifier_modifiers_rejected()
    local ok, err = pcall(m_ar_verb.do_generate_forms,
        {[1] = "كتب<I/a~u.vn:كِتَابَة<q:نادر>>"}, "ar-conj")
    local msg = tostring(err)
    self:equals("q: مرفوضة", ok, false)
    self:equals("q: رسالة خطأ واضحة", not not msg:find("Unrecognized prefix"), true)
    self:equals("q: لا تستدعي pron qualifier", not not msg:find("pron qualifier"), false)
end

return tests