اختبار التنقّل باستخدام التركيز

يضمن إجراء اختبارات التنقّل باستخدام التركيز تلقائيًا أن يقدّم تطبيقك تجربة مستخدم متسقة ويمكن توقّعها على مستوى لوحات المفاتيح الخارجية ولوحات التحكّم وأدوات تسهيل الاستخدام. توفّر Compose واجهات برمجة تطبيقات مدمجة للاختبار لمحاكاة إدخال المفاتيح والتحقّق من حالات التركيز.

إعداد اختبارات التركيز

لكتابة اختبارات التركيز، استخدِم createComposeRule() من مكتبة اختبار Compose:

@RunWith(AndroidJUnit4::class)
class FocusNavigationTest {
    @get:Rule
    val composeTestRule = createComposeRule()

التحقّق من أهداف التركيز

يمكنك التحقّق مما إذا كان بإمكان أحد عناصر واجهة المستخدم تلقّي التركيز من خلال النقر عليه أو طلب التركيز عليه، ثم التأكّد من حالته باستخدام assertIsFocused() وassertIsNotFocused().

@Test
fun interactiveElement_isFocusTarget() {
    composeTestRule.setContent {
        AppTheme {
            CardListScreen()
        }
    }

    val firstCard = composeTestRule.onNodeWithTag("card_1")
    val secondCard = composeTestRule.onNodeWithTag("card_2")

    // Focus the first element
    firstCard.performClick()
    firstCard.assertIsFocused()
    secondCard.assertIsNotFocused()
}

اختبار التنقّل في التركيز الأحادي الأبعاد

محاكاة الضغط على المفتاحين Tab وShift+Tab باستخدام performKeyInput للتحقّق من تقدّم التركيز خلال واجهة المستخدم حسب ترتيب الظهور كأفضل ممارسة، يجب التحقّق من صحة كل من التنقّل للأمام (Key.Tab) والتنقّل للخلف (Key.Tab مع الضغط باستمرار على Key.ShiftLeft) لضمان سلامة التنقّل في الاتجاهين:

@Test
fun tabKey_navigatesInAppearanceOrder() {
    composeTestRule.setContent {
        AppTheme {
            CardListScreen()
        }
    }

    val firstCard = composeTestRule.onNodeWithTag("card_1")
    val secondCard = composeTestRule.onNodeWithTag("card_2")
    val thirdCard = composeTestRule.onNodeWithTag("card_3")

    firstCard.performClick()
    firstCard.assertIsFocused()

    // Press Tab -> Moves to second card
    firstCard.performKeyInput {
        pressKey(Key.Tab)
    }
    secondCard.assertIsFocused()
    firstCard.assertIsNotFocused()

    // Press Tab -> Moves to third card
    secondCard.performKeyInput {
        pressKey(Key.Tab)
    }
    thirdCard.assertIsFocused()

    // Press Shift + Tab -> Moves backward to second card
    thirdCard.performKeyInput {
        withKeyDown(Key.ShiftLeft) {
            pressKey(Key.Tab)
        }
    }
    secondCard.assertIsFocused()
}

اختبار التنقّل في الاتجاهين

محاكاة مفاتيح الأسهم أو مفاتيح الاتجاهات في لوحة التحكّم باستخدام Key.DirectionDown وKey.DirectionUp وKey.DirectionRight وKey.DirectionLeft

@Test
fun arrowKeys_moveFocusTwoDimensionallyWithoutWrap() {
    composeTestRule.setContent {
        AppTheme {
            GridLayoutScreen()
        }
    }

    val topLeftButton = composeTestRule.onNodeWithTag("btn_top_left")
    val bottomLeftButton = composeTestRule.onNodeWithTag("btn_bottom_left")

    topLeftButton.performClick()
    topLeftButton.assertIsFocused()

    // Down arrow moves focus downward to bottom-left button
    topLeftButton.performKeyInput {
        pressKey(Key.DirectionDown)
    }
    bottomLeftButton.assertIsFocused()

    // Directional keys do not wrap around: pressing Down on bottom element stays focused
    bottomLeftButton.performKeyInput {
        pressKey(Key.DirectionDown)
    }
    bottomLeftButton.assertIsFocused()
}

اختبار سلوك التركيز على حقل النص

تأكَّد من أنّ حقول النص ذات السطر الواحد تنقل التركيز عند الضغط على Tab، بينما تحتفظ حقول النص المتعدّدة الأسطر بالتركيز:

@Test
fun textField_handlesTabAccordingToLineLimits() {
    composeTestRule.setContent {
        AppTheme {
            FormScreen()
        }
    }

    val singleLineNode = composeTestRule.onNodeWithTag("single_line_field")
    val multiLineNode = composeTestRule.onNodeWithTag("multi_line_field")
    val submitButtonNode = composeTestRule.onNodeWithTag("submit_button")

    // Single-line field advances focus on Tab
    singleLineNode.performClick()
    singleLineNode.assertIsFocused()
    singleLineNode.performKeyInput { pressKey(Key.Tab) }
    multiLineNode.assertIsFocused()

    // Multi-line field keeps focus and inserts '\t'
    multiLineNode.performKeyInput { pressKey(Key.Tab) }
    multiLineNode.assertIsFocused()
    submitButtonNode.assertIsNotFocused()

    // Shift + Tab escapes multi-line field to previous target
    multiLineNode.performKeyInput {
        withKeyDown(Key.ShiftLeft) { pressKey(Key.Tab) }
    }
    singleLineNode.assertIsFocused()
}