Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
at main 3.7 kB view raw
1import React, { ReactNode, useState, useReducer, useLayoutEffect, useRef } from 'react'; 2import { mount } from '@cypress/react'; 3 4import { makePriorityHook } from '../usePriority'; 5 6const usePriority = makePriorityHook(); 7 8const FocusOnPriority = ( 9 { id, children = null }: 10 { id: string, children?: ReactNode } 11) => { 12 const forceUpdate = useReducer(() => [], [])[1] 13 const ref = useRef<HTMLDivElement>(null); 14 const hasPriority = usePriority(ref); 15 16 if (!(hasPriority as any).__marked) { 17 (hasPriority as any).__marked = true; 18 let current = hasPriority.current 19 Object.defineProperty(hasPriority, 'current', { 20 get() { 21 return current; 22 }, 23 set(value) { 24 current = value; 25 forceUpdate(); 26 }, 27 }) 28 } 29 30 useLayoutEffect(() => { 31 if (hasPriority.current && ref.current) { 32 ref.current!.focus(); 33 } 34 }, [hasPriority.current, ref]); 35 36 return ( 37 <div tabIndex={-1} ref={ref} id={id}> 38 {children} 39 </div> 40 ); 41}; 42 43it('allows sole element to take priority', () => { 44 mount( 45 <FocusOnPriority id="only" /> 46 ); 47 48 cy.focused().should('have.id', 'only'); 49}); 50 51it('tracks priority of sibling elements in order', () => { 52 mount( 53 <main> 54 <FocusOnPriority id="first" /> 55 <FocusOnPriority id="second" /> 56 </main> 57 ); 58 59 cy.focused().should('have.id', 'second'); 60}); 61 62it('tracks priority of nested elements in order', () => { 63 mount( 64 <FocusOnPriority id="outer"> 65 <FocusOnPriority id="inner" /> 66 </FocusOnPriority> 67 ); 68 69 cy.focused().should('have.id', 'inner'); 70}); 71 72it('should switch priorities of sibling elements as needed', () => { 73 const App = () => { 74 const [visible, setVisible] = useState(true); 75 76 return ( 77 <main> 78 <FocusOnPriority id="first" /> 79 {visible && <FocusOnPriority id="second" />} 80 <button onClick={() => setVisible(false)}>switch</button> 81 </main> 82 ); 83 }; 84 85 mount(<App />); 86 87 cy.focused().should('have.id', 'second'); 88 cy.get('button').first().click(); 89 cy.focused().should('have.id', 'first'); 90}); 91 92it('should switch priorities of nested elements as needed', () => { 93 const App = () => { 94 const [visible, setVisible] = useState(true); 95 96 return ( 97 <main> 98 <FocusOnPriority id="outer"> 99 {visible && <FocusOnPriority id="inner" />} 100 </FocusOnPriority> 101 <button onClick={() => setVisible(false)}>switch</button> 102 </main> 103 ); 104 }; 105 106 mount(<App />); 107 108 cy.focused().should('have.id', 'inner'); 109 cy.get('button').first().click(); 110 cy.focused().should('have.id', 'outer'); 111}); 112 113it('should preserve priorities when non-prioritised item is removed', () => { 114 const App = () => { 115 const [visible, setVisible] = useState(true); 116 117 return ( 118 <main> 119 {visible && <FocusOnPriority id="first" />} 120 <FocusOnPriority id="second" /> 121 <button onClick={() => setVisible(false)}>switch</button> 122 </main> 123 ); 124 }; 125 126 mount(<App />); 127 128 cy.focused().should('have.id', 'second'); 129 cy.get('button').first().click(); 130 cy.get('button').first().should('have.focus'); 131}); 132 133it('should update priorities when new prioritised item is added', () => { 134 const App = () => { 135 const [visible, setVisible] = useState(false); 136 137 return ( 138 <main> 139 <FocusOnPriority id="first" /> 140 <FocusOnPriority id="second" /> 141 {visible && <FocusOnPriority id="third" />} 142 <button onClick={() => setVisible(true)}>switch</button> 143 </main> 144 ); 145 }; 146 147 mount(<App />); 148 149 cy.focused().should('have.id', 'second'); 150 cy.get('button').first().click(); 151 cy.focused().should('have.id', 'third'); 152});