mirror of
https://github.com/material-components/material-components-android.git
synced 2026-02-20 08:39:55 +08:00
This change fixes a keyboard trap in MaterialDatePicker where the TAB key focus was stuck within the month grid. TAB navigation is now limited to the days within the current month, allowing focus to move out of the picker. For navigating between months, this change introduces DPAD (left/right arrow key) navigation. When on the first or last valid day of the month, the arrow keys will navigate to the previous or next month. This CL also prevents keyboard focus from landing on disabled dates. Since GridView does not natively support skipping disabled items, custom logic has been added to find and focus on the nearest valid day during keyboard navigation. Finally, a bug that caused focus to incorrectly jump to a previous, non-visible month during TAB navigation has been fixed. This was caused by RecyclerView's view-recycling mechanism. The solution ensures that only the currently visible month is focusable, preventing focus from moving to off-screen months. PiperOrigin-RevId: 834271529
146 lines
5.0 KiB
Java
146 lines
5.0 KiB
Java
/*
|
|
* Copyright 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.google.android.material.datepicker;
|
|
|
|
import com.google.android.material.test.R;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
|
|
import android.content.Context;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.test.core.app.ApplicationProvider;
|
|
import java.util.Calendar;
|
|
import org.junit.Before;
|
|
import org.junit.Rule;
|
|
import org.junit.Test;
|
|
import org.junit.rules.ExpectedException;
|
|
import org.junit.runner.RunWith;
|
|
import org.robolectric.Robolectric;
|
|
import org.robolectric.RobolectricTestRunner;
|
|
import org.robolectric.annotation.internal.DoNotInstrument;
|
|
|
|
@RunWith(RobolectricTestRunner.class)
|
|
@DoNotInstrument
|
|
public class MonthsPagerAdapterTest {
|
|
|
|
private Context context;
|
|
private Month feb2016;
|
|
private Month march2016;
|
|
private Month april2016;
|
|
|
|
@Before
|
|
public void setupMonthAdapters() {
|
|
ApplicationProvider.getApplicationContext().setTheme(R.style.Theme_MaterialComponents_Light);
|
|
context =
|
|
Robolectric.buildActivity(AppCompatActivity.class).setup().get().getApplicationContext();
|
|
feb2016 = Month.create(2016, Calendar.FEBRUARY);
|
|
march2016 = Month.create(2016, Calendar.MARCH);
|
|
april2016 = Month.create(2016, Calendar.APRIL);
|
|
}
|
|
|
|
@Test
|
|
public void startingPageCalculated() {
|
|
MonthsPagerAdapter monthsAdapter =
|
|
new MonthsPagerAdapter(
|
|
context,
|
|
/* dateSelector= */ null,
|
|
new CalendarConstraints.Builder()
|
|
.setStart(feb2016.timeInMillis)
|
|
.setEnd(april2016.timeInMillis)
|
|
.setOpenAt(march2016.timeInMillis)
|
|
.build(),
|
|
/* dayViewDecorator= */ null,
|
|
/* onDayClickListener= */ null,
|
|
/* onMonthNavigationListener= */ null);
|
|
assertEquals(3, monthsAdapter.getItemCount());
|
|
assertEquals(1, monthsAdapter.getPosition(march2016));
|
|
}
|
|
|
|
@Test
|
|
public void singleMonthConstructs() {
|
|
MonthsPagerAdapter monthsAdapter =
|
|
new MonthsPagerAdapter(
|
|
context,
|
|
/* dateSelector= */ null,
|
|
new CalendarConstraints.Builder()
|
|
.setStart(feb2016.timeInMillis)
|
|
.setEnd(feb2016.timeInMillis)
|
|
.setOpenAt(feb2016.timeInMillis)
|
|
.build(),
|
|
/* dayViewDecorator= */ null,
|
|
/* onDayClickListener= */ null,
|
|
/* onMonthNavigationListener= */ null);
|
|
assertEquals(1, monthsAdapter.getItemCount());
|
|
assertEquals(0, monthsAdapter.getPosition(feb2016));
|
|
}
|
|
|
|
@Rule public ExpectedException exceptionRule = ExpectedException.none();
|
|
|
|
@Test
|
|
public void illegalStartMonthFails() {
|
|
exceptionRule.expect(IllegalArgumentException.class);
|
|
new MonthsPagerAdapter(
|
|
context,
|
|
/* dateSelector= */ null,
|
|
new CalendarConstraints.Builder()
|
|
.setStart(feb2016.timeInMillis)
|
|
.setEnd(march2016.timeInMillis)
|
|
.setOpenAt(april2016.timeInMillis)
|
|
.build(),
|
|
/* dayViewDecorator= */ null,
|
|
/* onDayClickListener= */ null,
|
|
/* onMonthNavigationListener= */ null);
|
|
}
|
|
|
|
@Test
|
|
public void illegalLastMonthFails() {
|
|
exceptionRule.expect(IllegalArgumentException.class);
|
|
new MonthsPagerAdapter(
|
|
context,
|
|
/* dateSelector= */ null,
|
|
new CalendarConstraints.Builder()
|
|
.setStart(march2016.timeInMillis)
|
|
.setEnd(feb2016.timeInMillis)
|
|
.setOpenAt(march2016.timeInMillis)
|
|
.build(),
|
|
/* dayViewDecorator= */ null,
|
|
/* onDayClickListener= */ null,
|
|
/* onMonthNavigationListener= */ null);
|
|
}
|
|
|
|
@Test
|
|
public void pageTitles() {
|
|
MonthsPagerAdapter monthsAdapter =
|
|
new MonthsPagerAdapter(
|
|
context,
|
|
/* dateSelector= */ null,
|
|
new CalendarConstraints.Builder()
|
|
.setStart(feb2016.timeInMillis)
|
|
.setEnd(april2016.timeInMillis)
|
|
.setOpenAt(march2016.timeInMillis)
|
|
.build(),
|
|
/* dayViewDecorator= */ null,
|
|
/* onDayClickListener= */ null,
|
|
/* onMonthNavigationListener= */ null);
|
|
assertEquals(
|
|
feb2016.getLongName(), monthsAdapter.getPageTitle(/* position= */ 0).toString());
|
|
assertEquals(
|
|
march2016.getLongName(), monthsAdapter.getPageTitle(/* position= */ 1).toString());
|
|
assertEquals(
|
|
april2016.getLongName(), monthsAdapter.getPageTitle(/* position= */ 2).toString());
|
|
}
|
|
}
|