@@ -3,9 +3,11 @@ import {Overlay, Box, Text, ButtonDanger, Button} from '..'
33import { render , cleanup , waitFor , fireEvent , act } from '@testing-library/react'
44import userEvent from '@testing-library/user-event'
55import { axe , toHaveNoViolations } from 'jest-axe'
6+ import '@testing-library/jest-dom'
67import theme from '../theme'
78import BaseStyles from '../BaseStyles'
89import { ThemeProvider } from '../ThemeProvider'
10+ import { NestedOverlays , MemexNestedOverlays , MemexIssueOverlay } from '../stories/Overlay.stories'
911
1012expect . extend ( toHaveNoViolations )
1113
@@ -100,4 +102,115 @@ describe('Overlay', () => {
100102 const cancelButtons = queryAllByText ( 'Cancel' )
101103 expect ( cancelButtons ) . toHaveLength ( 0 )
102104 } )
105+
106+ it ( 'should close the top most overlay on escape' , ( ) => {
107+ const container = render (
108+ < ThemeProvider >
109+ < NestedOverlays />
110+ </ ThemeProvider >
111+ )
112+
113+ // open first menu
114+ userEvent . click ( container . getByLabelText ( 'Add this repository to a list' ) )
115+ expect ( container . getByText ( 'Add to list' ) ) . toBeInTheDocument ( )
116+
117+ // open second menu
118+ userEvent . click ( container . getByText ( 'Create list' ) )
119+ expect ( container . getByPlaceholderText ( 'Name this list' ) ) . toBeInTheDocument ( )
120+
121+ // hitting escape on input should close the second menu but not the first
122+ fireEvent . keyDown ( container . getByPlaceholderText ( 'Name this list' ) , { key : 'Escape' , code : 'Escape' } )
123+ expect ( container . queryByPlaceholderText ( 'Name this list' ) ) . not . toBeInTheDocument ( )
124+ // this breaks:
125+ expect ( container . getByText ( 'Add to list' ) ) . toBeInTheDocument ( )
126+
127+ // hitting escape again in first overlay should close it
128+ fireEvent . keyDown ( container . getByText ( 'Add to list' ) , { key : 'Escape' , code : 'Escape' } )
129+ expect ( container . queryByText ( 'Add to list' ) ) . not . toBeInTheDocument ( )
130+ } )
131+
132+ it ( 'memex repro: should only close the dropdown when escape is pressed' , ( ) => {
133+ const container = render (
134+ < ThemeProvider >
135+ < MemexNestedOverlays />
136+ </ ThemeProvider >
137+ )
138+
139+ // open first menu
140+ userEvent . click ( container . getByLabelText ( 'Add custom iteration' ) )
141+ expect ( container . getByLabelText ( 'Change duration unit' ) ) . toBeInTheDocument ( )
142+
143+ // open dropdown menu
144+ userEvent . click ( container . getByLabelText ( 'Change duration unit' ) )
145+ expect ( container . getByRole ( 'menu' ) ) . toBeInTheDocument ( )
146+
147+ // hitting escape on menu item should close the dropdown menu but not the overlay
148+ fireEvent . keyDown ( container . getByRole ( 'menu' ) , { key : 'Escape' , code : 'Escape' } )
149+ expect ( container . queryByRole ( 'menu' ) ) . not . toBeInTheDocument ( )
150+ // this breaks:
151+ expect ( container . getByLabelText ( 'Change duration unit' ) ) . toBeInTheDocument ( )
152+ } )
153+
154+ it ( 'memex repro: should not close overlay when input has event.preventDefault' , ( ) => {
155+ const container = render (
156+ < ThemeProvider >
157+ < MemexIssueOverlay />
158+ </ ThemeProvider >
159+ )
160+
161+ // clicking the title opens overlay
162+ userEvent . click ( container . getByText ( 'Implement draft issue editor' ) )
163+ expect ( container . getByLabelText ( 'Change issue title' ) ) . toBeInTheDocument ( )
164+
165+ // clicking the button changes to input
166+ userEvent . click ( container . getByLabelText ( 'Change issue title' ) )
167+ expect ( container . getByDisplayValue ( 'Implement draft issue editor' ) ) . toBeInTheDocument ( )
168+
169+ // pressing Escape inside input brings back the button but does not close the overlay
170+ fireEvent . keyDown ( container . getByDisplayValue ( 'Implement draft issue editor' ) , { key : 'Escape' , code : 'Escape' } )
171+ expect ( container . getByLabelText ( 'Change issue title' ) ) . toBeInTheDocument ( )
172+
173+ // hitting Escape again should close the Overlay
174+ fireEvent . keyDown ( container . getByLabelText ( 'Change issue title' ) , { key : 'Escape' , code : 'Escape' } )
175+ expect ( container . queryByLabelText ( 'Change issue title' ) ) . not . toBeInTheDocument ( )
176+ } )
177+
178+ // https://github.com/primer/react/issues/1802
179+ // eslint-disable-next-line jest/no-disabled-tests
180+ it . skip ( 'memex repro: should not leak overlay events to the document' , ( ) => {
181+ const mockHandler = jest . fn ( )
182+ const BugRepro1802 = ( ) => {
183+ const [ isOpen , setIsOpen ] = useState ( false )
184+ const closeOverlay = ( ) => setIsOpen ( false )
185+ const buttonRef = useRef < HTMLButtonElement > ( null )
186+
187+ React . useEffect ( ( ) => {
188+ document . addEventListener ( 'keydown' , mockHandler )
189+ return ( ) => document . removeEventListener ( 'keydown' , mockHandler )
190+ } , [ ] )
191+
192+ return (
193+ < ThemeProvider theme = { theme } >
194+ < BaseStyles >
195+ < Button ref = { buttonRef } onClick = { ( ) => setIsOpen ( true ) } >
196+ open overlay
197+ </ Button >
198+ { isOpen ? (
199+ < Overlay returnFocusRef = { buttonRef } onEscape = { closeOverlay } onClickOutside = { closeOverlay } >
200+ < Text > Text inside Overlay</ Text >
201+ </ Overlay >
202+ ) : null }
203+ </ BaseStyles >
204+ </ ThemeProvider >
205+ )
206+ }
207+
208+ const container = render ( < BugRepro1802 /> )
209+
210+ userEvent . click ( container . getByText ( 'open overlay' ) )
211+ fireEvent . keyDown ( container . getByText ( 'Text inside Overlay' ) , { key : 'Escape' , code : 'Escape' } )
212+
213+ // if stopPropagation worked, mockHandler would not have been called
214+ expect ( mockHandler ) . toHaveBeenCalledTimes ( 0 )
215+ } )
103216} )
0 commit comments