Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 189 additions & 1 deletion cypress/component/Modal.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* SOFTWARE.
*/
import React, { useState } from 'react'
import { Modal, View, Button } from '@instructure/ui'
import { Tooltip, Modal, Button, CloseButton, View } from '@instructure/ui'
import 'cypress-real-events'

import '../support/component'
Expand Down Expand Up @@ -171,4 +171,192 @@ describe('<Modal/>', () => {

cy.get('[data-testid="modal-content"]').should('not.exist')
})

it('should not close with shouldCloseOnDocumentClick when Tooltip inside is clicked on', async () => {
const TestModal = () => {
const [open, setOpen] = useState(false)

return (
<div>
<Button
onClick={() => {
setOpen(true)
}}
>
Open the Modal
</Button>
<Modal
label="modal"
open={open}
onDismiss={() => {
setOpen(false)
}}
shouldCloseOnDocumentClick
>
<CloseButton
screenReaderLabel="Close"
onClick={() => {
setOpen(false)
}}
/>
<Tooltip renderTip="Tooltip!">
<Button data-testid="trigger">Hello</Button>
</Tooltip>
</Modal>
</div>
)
}
cy.mount(<TestModal />)

cy.contains('Open the Modal').click()

cy.get('[data-testid="trigger"]').then(($trigger) => {
const tooltipId = $trigger.attr('data-position-target')
const tooltip = `span[data-position-content="${tooltipId}"]`

cy.get(tooltip).should('not.be.visible')

cy.get('[data-testid="trigger"]')
.realHover()
.then(() => {
cy.get(tooltip).should('be.visible')
})

cy.get(tooltip)
.realClick()
.wait(500)
.then(() => {
cy.get(tooltip).should('be.visible')
cy.get('[role="dialog"]').should('be.visible')
})
})
})

it('should not close with shouldCloseOnDocumentClick when inside Tooltip has renderTip with HTML content', async () => {
const TestModal = () => {
const [open, setOpen] = useState(false)

return (
<div>
<Button
onClick={() => {
setOpen(true)
}}
>
Open the Modal
</Button>
<Modal
label="modal"
open={open}
onDismiss={() => {
setOpen(false)
}}
shouldCloseOnDocumentClick
>
<CloseButton
screenReaderLabel="Close"
onClick={() => {
setOpen(false)
}}
/>
<Tooltip
renderTip={
<div>
<div>HTML content</div>
</div>
}
>
<Button data-testid="trigger">Hello</Button>
</Tooltip>
</Modal>
</div>
)
}
cy.mount(<TestModal />)

cy.contains('Open the Modal').click()

cy.get('[data-testid="trigger"]').then(($trigger) => {
const tooltipId = $trigger.attr('data-position-target')
const tooltip = `span[data-position-content="${tooltipId}"]`

cy.get(tooltip).should('not.be.visible')

cy.get('[data-testid="trigger"]')
.realHover()
.then(() => {
cy.get(tooltip).should('be.visible')
})

cy.get(tooltip)
.realClick()
.wait(500)
.then(() => {
cy.get(tooltip).should('be.visible')
cy.get('[role="dialog"]').should('be.visible')
})
})
})

it('should not close with shouldCloseOnDocumentClick when ToolTip button is focused and Tooltip is clicked', async () => {
const TestModal = () => {
const [open, setOpen] = useState(false)

return (
<div>
<Button
onClick={() => {
setOpen(true)
}}
>
Open the Modal
</Button>
<Modal
label="modal"
open={open}
onDismiss={() => {
setOpen(false)
}}
shouldCloseOnDocumentClick
>
<CloseButton
screenReaderLabel="Close"
onClick={() => {
setOpen(false)
}}
/>
<Tooltip renderTip={<div>HTML content</div>}>
<Button data-testid="trigger">Hello</Button>
</Tooltip>
</Modal>
</div>
)
}
cy.mount(<TestModal />)

cy.contains('Open the Modal').click()

cy.get('[data-testid="trigger"]').then(($trigger) => {
const tooltipId = $trigger.attr('data-position-target')
const tooltip = `span[data-position-content="${tooltipId}"]`

cy.get(tooltip).should('not.be.visible')

cy.get('[data-testid="trigger"]')
.realClick()
.then(() => {
cy.get(tooltip).should('be.visible')
cy.get('[data-testid="trigger"]')
.should('have.focus')
.then(() => {
cy.get(tooltip)
.realClick()
.wait(500)
.then(() => {
cy.get('[role="dialog"]').should('be.visible')
})
})
})
})
})
})
11 changes: 9 additions & 2 deletions packages/ui-a11y-utils/src/FocusRegion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
contains,
addEventListener,
ownerDocument,
findTabbable
findTabbable,
canUseDOM
} from '@instructure/ui-dom-utils'
import { uid } from '@instructure/uid'
import { logError as error } from '@instructure/console'
Expand Down Expand Up @@ -96,7 +97,13 @@ class FocusRegion {
this._options.shouldCloseOnDocumentClick &&
event.button === 0 &&
event.detail > 0 && // if event.detail is 0 then this is a keyboard and not a mouse press
!this._contextContainsTarget
!this._contextContainsTarget &&
//this prevents clicking on Tooltip from closing the parent dialog
!(
canUseDOM &&
this._documentClickTarget instanceof HTMLElement &&
this._documentClickTarget.closest('[role="tooltip"]')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closest returns the first (starting at element) inclusive ancestor that matches selectors, and null otherwise.

)
) {
this.handleDismiss(event, true)
}
Expand Down
Loading