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});