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

يضمن إجراء اختبارات التنقّل باستخدام التركيز تلقائيًا أن يقدّم تطبيقك تجربة مستخدم متّسقة ومتوقّعة على لوحات المفاتيح الخارجية ولوحات التحكّم وأدوات تسهيل الاستخدام. توفّر 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()
}