Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Cypress coverage #1507

Closed
wants to merge 27 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a7ee4ff
Implement dom-based keyboard navigation for year selection
dmtrKovalenko Feb 3, 2020
6c1c7df
Make calendar keyboard navigation also focus-based
dmtrKovalenko Feb 4, 2020
501a8ad
Fix animation freesing because of .focus() in Calendar
dmtrKovalenko Feb 5, 2020
1f05deb
Add proper aria-label's for calendar
dmtrKovalenko Feb 5, 2020
60813cc
Properly handle focus when switching months
dmtrKovalenko Feb 7, 2020
dfbce44
Announce month and year switching
dmtrKovalenko Feb 7, 2020
8be5e08
Make props for customizing aria-label texts
dmtrKovalenko Feb 8, 2020
98916ea
Fix type for useKeyDown handler
dmtrKovalenko Feb 8, 2020
c4ce41f
Add aria-label for mobile keyboard input button
dmtrKovalenko Feb 8, 2020
6166587
Fix Prop inference after changing interfaces
dmtrKovalenko Feb 8, 2020
a387460
Implement force quitting picker onKeyDown
dmtrKovalenko Feb 10, 2020
80cad80
Aria-labels and live regions for date & time picker
dmtrKovalenko Feb 10, 2020
d70de96
Fix day and clock numbers not autofocusing on appear
dmtrKovalenko Feb 10, 2020
cbf87eb
Fix missing container if `reduceAnimations`
dmtrKovalenko Feb 11, 2020
5badc9d
Better default focus management
dmtrKovalenko Feb 11, 2020
12965cd
Add more keyboard shorcats
dmtrKovalenko Feb 11, 2020
e7398fd
Add accessibility guide
dmtrKovalenko Feb 11, 2020
685224b
Fix styles of markdown table
dmtrKovalenko Feb 11, 2020
4d700e5
Fix grammar in accessibility guideline
dmtrKovalenko Feb 11, 2020
9d82f42
Fix crashing on openning invalid date
dmtrKovalenko Feb 13, 2020
dfc2c24
Make focus visible when root element of wrapper is focused
dmtrKovalenko Feb 13, 2020
0c94104
Fix not closing mobile dialog with esc
dmtrKovalenko Feb 13, 2020
0492aef
Better match material design accessibility spec
dmtrKovalenko Feb 13, 2020
7f03584
Fix ts error and linters
dmtrKovalenko Feb 13, 2020
92e399e
Add keyboard navigation cypress tests
dmtrKovalenko Feb 13, 2020
7716d26
Skip flaky test
dmtrKovalenko Feb 13, 2020
52809e3
Experiment with cypress coverage
dmtrKovalenko Feb 13, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Properly handle focus when switching months
dmtrKovalenko committed Feb 7, 2020
commit 60813cc5c3403b742fe309a8a8aafd62494561d6
4 changes: 2 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@
"@types/prismjs": "^1.9.1",
"@types/react": "^16.8.13",
"@types/react-kawaii": "^0.11.0",
"@types/react-redux": "^7.1.7",
"@types/sinon": "^7.0.13",
"@zeit/next-bundle-analyzer": "^0.1.2",
"@zeit/next-css": "^1.0.1",
@@ -71,8 +72,7 @@
"sinon": "^7.3.2",
"styled-jsx": "^3.2.4",
"ts-loader": "^6.0.2",
"typescript": "^3.4.4",
"@types/react-redux": "^7.1.7"
"typescript": "^3.4.4"
},
"devDependencies": {
"dotenv": "^7.0.0",
51 changes: 22 additions & 29 deletions lib/src/views/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -60,6 +60,10 @@ export interface CalendarProps {
currentMonth: MaterialUiPickersDate;
onMonthChange: (date: MaterialUiPickersDate) => void;
reduceAnimations: boolean;
focusedDay: MaterialUiPickersDate | null;
changeFocusedDay: (newFocusedDay: MaterialUiPickersDate) => void;
isMonthSwitchingAnimating: boolean;
onMonthSwitchingAnimationEnd: () => void;
wrapperVariant: WrapperVariant | null;
}

@@ -104,14 +108,17 @@ export const useStyles = makeStyles(theme => ({

export const Calendar: React.FC<CalendarProps> = ({
date,
isMonthSwitchingAnimating,
onMonthSwitchingAnimationEnd,
focusedDay,
changeFocusedDay,
onChange,
minDate,
maxDate,
slideDirection,
disableFuture,
disablePast,
currentMonth,
onMonthChange,
renderDay,
reduceAnimations,
allowKeyboardControl,
@@ -122,8 +129,6 @@ export const Calendar: React.FC<CalendarProps> = ({
const utils = useUtils();
const theme = useTheme();
const classes = useStyles();
const [isAnimating, setIsAnimating] = React.useState(false);
const [focusedDay, setFocusedDay] = React.useState<MaterialUiPickersDate>(date);

const handleDaySelect = React.useCallback(
(day: MaterialUiPickersDate, isFinish = true) => {
@@ -132,19 +137,6 @@ export const Calendar: React.FC<CalendarProps> = ({
[date, onChange, utils]
);

const focusDay = React.useCallback(
(day: MaterialUiPickersDate) => {
if (day && !isDateDisabled(day)) {
if (!utils.isSameMonth(day, currentMonth)) {
onMonthChange(utils.startOfMonth(day));
}

setFocusedDay(day);
}
},
[currentMonth, isDateDisabled, onMonthChange, utils]
);

React.useEffect(() => {
if (isDateDisabled(date)) {
const closestEnabledDate = findClosestEnabledDate({
@@ -161,18 +153,15 @@ export const Calendar: React.FC<CalendarProps> = ({
}
}, []); // eslint-disable-line

React.useEffect(() => {
setFocusedDay(date);
}, [date]);

const nowFocusedDay = focusedDay || date;
useGlobalKeyDown(Boolean(allowKeyboardControl && wrapperVariant !== 'static'), {
[keycode.Enter]: () => handleDaySelect(focusedDay, true),
[keycode.ArrowUp]: () => focusDay(utils.addDays(focusedDay, -7)),
[keycode.ArrowDown]: () => focusDay(utils.addDays(focusedDay, 7)),
[keycode.Enter]: () => handleDaySelect(nowFocusedDay, true),
[keycode.ArrowUp]: () => changeFocusedDay(utils.addDays(nowFocusedDay, -7)),
[keycode.ArrowDown]: () => changeFocusedDay(utils.addDays(nowFocusedDay, 7)),
[keycode.ArrowLeft]: () =>
focusDay(utils.addDays(focusedDay, theme.direction === 'ltr' ? -1 : 1)),
changeFocusedDay(utils.addDays(nowFocusedDay, theme.direction === 'ltr' ? -1 : 1)),
[keycode.ArrowRight]: () =>
focusDay(utils.addDays(focusedDay, theme.direction === 'ltr' ? 1 : -1)),
changeFocusedDay(utils.addDays(nowFocusedDay, theme.direction === 'ltr' ? 1 : -1)),
});

const selectedDate = utils.startOfDay(date);
@@ -192,8 +181,7 @@ export const Calendar: React.FC<CalendarProps> = ({
</div>

<SlideTransition
onEnter={() => setIsAnimating(true)}
onExited={() => setIsAnimating(false)}
onExited={onMonthSwitchingAnimationEnd}
reduceAnimations={reduceAnimations}
slideDirection={slideDirection}
transKey={currentMonthNumber}
@@ -209,9 +197,14 @@ export const Calendar: React.FC<CalendarProps> = ({
let dayComponent = (
<Day
day={day}
isAnimating={isAnimating}
isAnimating={isMonthSwitchingAnimating}
disabled={disabled}
focused={utils.isSameDay(day, focusedDay)}
focused={Boolean(focusedDay) && utils.isSameDay(day, focusedDay)}
onFocus={() => changeFocusedDay(day)}
focusable={
Boolean(nowFocusedDay) &&
utils.toJsDate(nowFocusedDay).getDate() === utils.toJsDate(day).getDate()
}
isToday={utils.isSameDay(day, now)}
hidden={!isDayInCurrentMonth}
isInCurrentMonth={isDayInCurrentMonth}
68 changes: 52 additions & 16 deletions lib/src/views/Calendar/CalendarView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { IUtils } from '@date-io/core/IUtils';
import { YearSelection } from './YearSelection';
import { CalendarHeader } from './CalendarHeader';
import { MonthSelection } from './MonthSelection';
@@ -62,31 +63,42 @@ interface ChangeMonthPayload {
newMonth: MaterialUiPickersDate;
}

function calendarStateReducer(
state: {
loadingQueue: number;
currentMonth: MaterialUiPickersDate;
slideDirection: SlideDirection;
},
interface State {
isMonthSwitchingAnimating: boolean;
loadingQueue: number;
currentMonth: MaterialUiPickersDate;
focusedDay: MaterialUiPickersDate | null;
slideDirection: SlideDirection;
}

const createCalendarStateReducer = (
reduceAnimations: boolean,
utils: IUtils<MaterialUiPickersDate>
) => (
state: State,
action:
| ReducerAction<'popLoadingQueue'>
| ReducerAction<'finishMonthSwitchingAnimation'>
| ReducerAction<'changeMonth', ChangeMonthPayload>
| ReducerAction<'changeMonthLoading', ChangeMonthPayload>
) {
| ReducerAction<'changeFocusedDay', { focusedDay: MaterialUiPickersDate }>
): State => {
switch (action.type) {
case 'changeMonthLoading': {
return {
...state,
loadingQueue: state.loadingQueue + 1,
slideDirection: action.direction,
currentMonth: action.newMonth,
isMonthSwitchingAnimating: !reduceAnimations,
};
}
case 'changeMonth': {
return {
...state,
slideDirection: action.direction,
currentMonth: action.newMonth,
isMonthSwitchingAnimating: !reduceAnimations,
};
}
case 'popLoadingQueue': {
@@ -95,8 +107,24 @@ function calendarStateReducer(
loadingQueue: state.loadingQueue <= 0 ? 0 : state.loadingQueue - 1,
};
}
case 'finishMonthSwitchingAnimation': {
return {
...state,
isMonthSwitchingAnimating: false,
};
}
case 'changeFocusedDay': {
const needMonthSwitch = !utils.isSameMonth(state.currentMonth, action.focusedDay);
return {
...state,
focusedDay: action.focusedDay,
isMonthSwitchingAnimating: needMonthSwitch && !reduceAnimations,
currentMonth: needMonthSwitch ? utils.startOfMonth(action.focusedDay) : state.currentMonth,
slideDirection: utils.isAfterDay(action.focusedDay, state.currentMonth) ? 'left' : 'right',
};
}
}
}
};

export const useStyles = makeStyles(
{
@@ -132,14 +160,16 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
const maxDate = useParsedDate(unparsedMaxDate);
const wrapperVariant = React.useContext(WrapperVariantContext);

const [{ currentMonth, loadingQueue, slideDirection }, dispatch] = React.useReducer(
calendarStateReducer,
{
loadingQueue: 0,
currentMonth: utils.startOfMonth(date),
slideDirection: 'left',
}
);
const [
{ currentMonth, isMonthSwitchingAnimating, focusedDay, loadingQueue, slideDirection },
dispatch,
] = React.useReducer(createCalendarStateReducer(reduceAnimations, utils), {
isMonthSwitchingAnimating: false,
loadingQueue: 0,
focusedDay: null,
currentMonth: utils.startOfMonth(date),
slideDirection: 'left',
});

const handleChangeMonth = React.useCallback(
(payload: ChangeMonthPayload) => {
@@ -253,6 +283,12 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
) : (
<Calendar
{...other}
isMonthSwitchingAnimating={isMonthSwitchingAnimating}
onMonthSwitchingAnimationEnd={() =>
dispatch({ type: 'finishMonthSwitchingAnimation' })
}
focusedDay={focusedDay}
changeFocusedDay={focusedDay => dispatch({ type: 'changeFocusedDay', focusedDay })}
reduceAnimations={reduceAnimations}
currentMonth={currentMonth}
slideDirection={slideDirection}
17 changes: 13 additions & 4 deletions lib/src/views/Calendar/Day.tsx
Original file line number Diff line number Diff line change
@@ -57,7 +57,9 @@ export interface DayProps extends ButtonBaseProps {
/** The date to show */
day: MaterialUiPickersDate;
/** Is focused by keyboard navigation */
focused: boolean;
focused?: boolean;
/** Can be focused by tabbing in */
focusable?: boolean;
/** Is day in current month */
isInCurrentMonth: boolean;
/** Is switching month animation going on right now */
@@ -76,8 +78,10 @@ export const Day: React.FC<DayProps> = ({
isInCurrentMonth,
isToday,
selected,
focused,
focused = false,
focusable = false,
isAnimating,
onFocus,
...other
}) => {
const ref = React.useRef<HTMLButtonElement>(null);
@@ -103,9 +107,14 @@ export const Day: React.FC<DayProps> = ({
centerRipple
focusRipple
data-mui-test="day"
aria-label={utils.format(day, 'fullDate')}
className={className}
tabIndex={selected ? 0 : -1}
onFocus={e => {
if (!focused && onFocus) {
onFocus(e);
}
}}
aria-label={utils.format(day, 'fullDate')}
tabIndex={focused || focusable ? 0 : -1}
{...other}
>
<span className={classes.dayLabel}>{utils.format(day, 'dayOfMonth')}</span>
17 changes: 14 additions & 3 deletions lib/src/views/Calendar/DayWrapper.tsx
Original file line number Diff line number Diff line change
@@ -16,13 +16,24 @@ const DayWrapper: React.FC<DayWrapperProps> = ({
dayInCurrentMonth,
...other
}) => {
const handleClick = React.useCallback(() => onSelect(value), [onSelect, value]);
const handleSelection = () => {
if (dayInCurrentMonth && !disabled) {
onSelect(value);
}
};

return (
<div
role="presentation"
onClick={dayInCurrentMonth && !disabled ? handleClick : undefined}
onKeyPress={dayInCurrentMonth && !disabled ? handleClick : undefined}
onClick={handleSelection}
onKeyPress={e => {
if (e.key === 'Enter' || e.key === ' ') {
handleSelection();

e.preventDefault();
e.stopPropagation();
}
}}
children={children}
{...other}
/>
2 changes: 1 addition & 1 deletion lib/src/views/Calendar/SlideTransition.tsx
Original file line number Diff line number Diff line change
@@ -97,7 +97,7 @@ const SlideTransition: React.SFC<SlideTransitionProps> = ({
<CSSTransition
mountOnEnter
unmountOnExit
key={transKey + slideDirection}
key={transKey}
timeout={slideAnimationDuration}
classNames={transitionClasses}
children={children}
Loading