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