UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

431 lines (372 loc) 13.9 kB
/* eslint-disable @typescript-eslint/no-empty-function */ import { renderHook, act } from '@testing-library/react-hooks'; import useOrientationBasedKeyboardNavigation, { IUseOrientationBasedKeyboardNavigationProps, } from './useOrientationBasedKeyboardNavigation'; import { KeyboardEvent } from 'react'; import SpatialNavigationProvider from '../components/SpatialNavigationProvider'; describe('useOrientationBasedKeyboardNavigation', () => { const defaultProps = { listSize: 2, orientation: 'vertical', noLoop: false, contextProps: {}, } as IUseOrientationBasedKeyboardNavigationProps; const stopPropagation = () => {}; const stopImmediatePropagation = () => {}; const preventDefault = () => {}; const baseEvent = { stopPropagation, nativeEvent: { stopImmediatePropagation }, preventDefault, }; const downArrowEvent = { key: 'ArrowDown', ...baseEvent } as KeyboardEvent<HTMLElement>; const upArrowEvent = { key: 'ArrowUp', ...baseEvent } as KeyboardEvent<HTMLElement>; const leftArrowEvent = { key: 'ArrowLeft', ...baseEvent } as KeyboardEvent<HTMLElement>; const rightArrowEvent = { key: 'ArrowRight', ...baseEvent } as KeyboardEvent<HTMLElement>; it('should navigate correctly by default', () => { const { result } = renderHook(() => useOrientationBasedKeyboardNavigation(defaultProps)); expect(result.current.getContext().noLoop).toStrictEqual(defaultProps.noLoop); act(() => { result.current.keyboardProps.onKeyDown(downArrowEvent); }); const { getCurrentFocus } = result.current.getContext(); expect(getCurrentFocus()).toStrictEqual(1); act(() => { result.current.keyboardProps.onKeyDown(downArrowEvent); }); // no loop true so should 0 expect(getCurrentFocus()).toStrictEqual(0); act(() => { result.current.keyboardProps.onKeyDown(leftArrowEvent); }); expect(getCurrentFocus()).toStrictEqual(0); act(() => { result.current.keyboardProps.onKeyDown(upArrowEvent); }); // no loop true should be 1 expect(getCurrentFocus()).toStrictEqual(1); }); it('should navigate correctly when horizontal and noLoop', () => { const props = { noLoop: true, orientation: 'horizontal', } as Partial<IUseOrientationBasedKeyboardNavigationProps>; const { result } = renderHook(() => useOrientationBasedKeyboardNavigation({ ...defaultProps, ...props }) ); act(() => { result.current.keyboardProps.onKeyDown(rightArrowEvent); }); const { getCurrentFocus } = result.current.getContext(); expect(getCurrentFocus()).toStrictEqual(1); act(() => { result.current.keyboardProps.onKeyDown(rightArrowEvent); }); // no loop false so shouldn't wrap back to 0 expect(getCurrentFocus()).toStrictEqual(1); act(() => { result.current.keyboardProps.onKeyDown(leftArrowEvent); }); expect(getCurrentFocus()).toStrictEqual(0); act(() => { result.current.keyboardProps.onKeyDown(leftArrowEvent); }); // no loop false so shouldn't wrap back to 1 expect(getCurrentFocus()).toStrictEqual(0); act(() => { result.current.keyboardProps.onKeyDown(downArrowEvent); }); expect(getCurrentFocus()).toStrictEqual(0); }); describe('list size changes', () => { it.each([ // removing items { focusedIndex: 0, expectedNextFocus: 0, newListSize: 4 }, { focusedIndex: 1, expectedNextFocus: 1, newListSize: 4 }, { focusedIndex: 2, expectedNextFocus: 2, newListSize: 4 }, { focusedIndex: 3, expectedNextFocus: 3, newListSize: 4 }, { focusedIndex: 4, expectedNextFocus: 3, newListSize: 4 }, { focusedIndex: 4, expectedNextFocus: 2, newListSize: 3 }, { focusedIndex: 4, expectedNextFocus: 1, newListSize: 2 }, { focusedIndex: 4, expectedNextFocus: 0, newListSize: 1 }, // adding items { focusedIndex: 0, expectedNextFocus: 0, newListSize: 6 }, { focusedIndex: 1, expectedNextFocus: 1, newListSize: 6 }, { focusedIndex: 2, expectedNextFocus: 2, newListSize: 6 }, { focusedIndex: 3, expectedNextFocus: 3, newListSize: 6 }, { focusedIndex: 4, expectedNextFocus: 4, newListSize: 6 }, ])( 'should handle list size changing with numeric indexes - non-focused item removed', ({ focusedIndex, expectedNextFocus, newListSize }) => { const { result, rerender } = renderHook( (props: any) => useOrientationBasedKeyboardNavigation(props), { initialProps: { listSize: 5, orientation: 'vertical' }, } ); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(0); act(() => { result.current.getContext().setCurrentFocus(focusedIndex); }); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(focusedIndex); act(() => { rerender({ listSize: newListSize, orientation: 'vertical' }); }); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(expectedNextFocus); } ); it.each([ // removing items { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'a', finalIndexes: ['a', 'b', 'c', 'd'], expectedNextFocus: 'a', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'b', finalIndexes: ['a', 'b', 'c', 'd'], expectedNextFocus: 'b', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'c', finalIndexes: ['a', 'b', 'c', 'd'], expectedNextFocus: 'c', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'd', finalIndexes: ['a', 'b', 'c', 'd'], expectedNextFocus: 'd', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'e', finalIndexes: ['a', 'b', 'c', 'd'], expectedNextFocus: 'd', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'e', finalIndexes: ['a', 'b', 'c'], expectedNextFocus: 'c', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e', 'f'], focusedIndex: 'c', finalIndexes: ['a', 'b', 'e', 'f'], expectedNextFocus: 'b', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'a', finalIndexes: ['b', 'c', 'd', 'e'], expectedNextFocus: 'b', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'b', finalIndexes: ['b', 'c', 'd', 'e'], expectedNextFocus: 'b', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'c', finalIndexes: ['b', 'c', 'd', 'e'], expectedNextFocus: 'c', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'd', finalIndexes: ['b', 'c', 'd', 'e'], expectedNextFocus: 'd', }, { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'e', finalIndexes: ['b', 'c', 'd', 'e'], expectedNextFocus: 'e', }, // adding items { initialIndexes: ['a', 'b', 'c', 'd'], focusedIndex: 'a', finalIndexes: ['a', 'b', 'c', 'd', 'e'], expectedNextFocus: 'a', }, { initialIndexes: ['a', 'b', 'c', 'd'], focusedIndex: 'b', finalIndexes: ['a', 'b', 'c', 'd', 'e'], expectedNextFocus: 'b', }, { initialIndexes: ['a', 'b', 'c', 'd'], focusedIndex: 'c', finalIndexes: ['a', 'b', 'c', 'd', 'e'], expectedNextFocus: 'c', }, { initialIndexes: ['a', 'b', 'c', 'd'], focusedIndex: 'd', finalIndexes: ['a', 'b', 'c', 'd', 'e'], expectedNextFocus: 'd', }, { initialIndexes: ['a', 'b', 'd', 'e'], focusedIndex: 'b', finalIndexes: ['a', 'b', 'c', 'd', 'e'], expectedNextFocus: 'b', }, { initialIndexes: ['a', 'b', 'd', 'e'], focusedIndex: 'd', finalIndexes: ['a', 'b', 'c', 'd', 'e'], expectedNextFocus: 'd', }, // swap all the items { initialIndexes: ['a', 'b', 'c', 'd', 'e'], focusedIndex: 'c', finalIndexes: ['g', 'h', 'i', 'j', 'k'], expectedNextFocus: 'k', }, ])( 'should handle list size changing with non-numeric indexes', ({ initialIndexes, focusedIndex, finalIndexes, expectedNextFocus }) => { const { result, rerender } = renderHook( (props: any) => useOrientationBasedKeyboardNavigation(props), { initialProps: { listSize: initialIndexes.length, orientation: 'vertical', allItemIndexes: initialIndexes, }, } ); expect(result.current.getContext()).toEqual({ // currentFocus: initialIndexes[0], getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(initialIndexes[0]); act(() => { result.current.getContext().setCurrentFocus(focusedIndex); }); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(focusedIndex); act(() => { rerender({ listSize: finalIndexes.length, orientation: 'vertical', allItemIndexes: finalIndexes, }); }); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(expectedNextFocus); } ); it('should gracefully handle non-numeric item indexes without allItemIndexes', () => { const { result, rerender } = renderHook( (props: any) => useOrientationBasedKeyboardNavigation(props), { initialProps: { listSize: 3, orientation: 'vertical' }, } ); jest.spyOn(console, 'warn'); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual(0); act(() => { result.current.getContext().setCurrentFocus('c'); }); expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual('c'); act(() => { rerender({ listSize: 2, orientation: 'vertical' }); }); // We can't handle non-numeric indexes without allItemIndexes // So the current focus remains unchanged expect(result.current.getContext()).toEqual({ getCurrentFocus: expect.any(Function), addFocusCallback: expect.any(Function), isFocusedWithin: false, noLoop: undefined, setCurrentFocus: expect.any(Function), setUpdateFocusBlocked: expect.any(Function), }); expect(result.current.getContext().getCurrentFocus()).toEqual('c'); expect(console.warn).toHaveBeenCalledWith( 'Unable to handle non-numeric index without allItemIndexes', 'c' ); }); }); describe('with Spatial navigation context', () => { it('should be always in no loop mode', () => { const { result } = renderHook( () => useOrientationBasedKeyboardNavigation({ ...defaultProps, noLoop: false }), { wrapper: SpatialNavigationProvider } ); expect(result.current.getContext().noLoop).toStrictEqual(true); }); }); });