Skip to content

Commit

Permalink
chore: use event listeners to show/hide button because classList is u…
Browse files Browse the repository at this point in the history
…nstable #2025
  • Loading branch information
marek-mihok committed Aug 24, 2023
1 parent e388179 commit d3d83fd
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 18 deletions.
17 changes: 8 additions & 9 deletions ui/src/copyable_text.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import { fireEvent, render } from '@testing-library/react'
import React from 'react'
import { CopyableText, XCopyableText } from './copyable_text'
import userEvent from '@testing-library/user-event'

const name = 'name'
const copyableTextProps: CopyableText = { name, value: '', label: '' }
Expand Down Expand Up @@ -44,19 +45,17 @@ describe('CopyableText.tsx', () => {

it('Shows copy to clipboard button on hover - multiline text', async () => {
const { container } = render(<XCopyableText model={{ ...copyableTextProps, multiline: true }} />)
await new Promise(resolve => setTimeout(resolve, 500))

const copyButton = container.querySelector('button')
const textfield = container.querySelector('textarea')!
const textfield = container.querySelector('textarea')!.parentElement!
expect(copyButton).toBeInTheDocument()
expect(copyButton).toBeVisible()
// Move cursor out of container.
fireEvent.mouseLeave(textfield)
await new Promise(resolve => setTimeout(resolve, 1000))

expect(copyButton).not.toBeVisible()

fireEvent.mouseEnter(textfield)
await new Promise(resolve => setTimeout(resolve, 1000))
userEvent.hover(textfield)
expect(container.querySelector('button')).toBeVisible()

expect(copyButton).toBeVisible()
fireEvent.mouseLeave(textfield)
expect(copyButton).not.toBeVisible()
})
})
18 changes: 10 additions & 8 deletions ui/src/copyable_text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ const
visible: {
opacity: 1,
},
hover: {
$nest: {
'&:hover #copybutton': { opacity: 1 }
}
},
btn: {
minWidth: 'initial',
position: 'absolute',
Expand Down Expand Up @@ -79,6 +74,7 @@ export const ClipboardCopyButton = ({ value, anchorElement, showOnHoverOnly = fa
const
timeoutRef = React.useRef<U>(),
[copied, setCopied] = React.useState(false),
[visible, setVisible] = React.useState(!showOnHoverOnly),
btnContainerRef = React.useRef<HTMLDivElement>(document.createElement('div')),
onClick = React.useCallback(async () => {
if (!anchorElement) return
Expand All @@ -100,13 +96,19 @@ export const ClipboardCopyButton = ({ value, anchorElement, showOnHoverOnly = fa
title='Copy to clipboard'
onClick={onClick}
iconProps={{ iconName: copied ? 'CheckMark' : 'Copy' }}
className={clas(css.btn, copied ? css.copiedBtn : '', showOnHoverOnly ? css.animate : '', showOnHoverOnly ? '' : css.visible)}
/>, [copied, onClick, showOnHoverOnly])
className={clas(css.btn, copied ? css.copiedBtn : '', showOnHoverOnly ? css.animate : '', visible ? css.visible : '')}
/>, [copied, onClick, showOnHoverOnly, visible])

React.useEffect(() => {
if (!anchorElement) return
if (portal) ReactDOM.render(ReactDOM.createPortal(CopyButton, anchorElement), btnContainerRef.current)
if (showOnHoverOnly) anchorElement.classList.add(css.hover)
if (showOnHoverOnly) {
anchorElement.addEventListener('mouseenter', () => setVisible(true))
anchorElement.addEventListener('mouseleave', (ev: MouseEvent) => {
if ((ev.relatedTarget as HTMLElement)?.id === 'copybutton') return
setVisible(false)
})
}
}, [CopyButton, anchorElement, portal, showOnHoverOnly])

React.useEffect(() => () => window.clearTimeout(timeoutRef.current), [])
Expand Down
46 changes: 45 additions & 1 deletion ui/src/markdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,34 @@
import { fireEvent, render } from '@testing-library/react'
import React from 'react'
import { Markdown } from './markdown'
import userEvent from '@testing-library/user-event'

const source = 'The quick brown [fox](?fox) jumps over the lazy [dog](dog).'
const codeBlockSource = `
\`\`\`py
from h2o_wave import main, app, Q, ui
@app('/')
async def serve(q: Q):
# Display a Hello, world! message.
q.page['hello'] = ui.markdown_card(
box='1 1 4 4',
title='Hello',
content='Hello, world!'
)
await q.page.save()
\`\`\`
`

describe('Markdown.tsx', () => {

// Jest JSDOM does not support event system, so we can only check if the event was dispatched.
it('Dispatches a custom event when link prefixed with "?"', () => {
const dispatchEventMock = jest.fn()
window.dispatchEvent = dispatchEventMock
const { getByText } = render(<Markdown source='The quick brown [fox](?fox) jumps over the lazy [dog](dog).' />)
const { getByText } = render(<Markdown source={source} />)

fireEvent.click(getByText('fox'))
expect(dispatchEventMock).toHaveBeenCalled()
Expand All @@ -31,4 +51,28 @@ describe('Markdown.tsx', () => {
fireEvent.click(getByText('dog'))
expect(dispatchEventMock).not.toHaveBeenCalled()
})

it('Shows copy to clipboard button on hover over code block', async () => {
const { container } = render(<Markdown source={codeBlockSource} />)
await new Promise(resolve => setTimeout(resolve, 500))

const copyButton = container.querySelector('button')
const textfield = container.querySelectorAll('code')[1]!

expect(textfield).toBeInTheDocument()
expect(copyButton).toBeInTheDocument()
expect(copyButton).not.toBeVisible()

userEvent.hover(textfield)
expect(container.querySelector('button')).toBeVisible()
})

it('Does not render copy to clipboard button - markdown without code block', async () => {
const { container } = render(<Markdown source={source} />)
await new Promise(resolve => setTimeout(resolve, 500))

const copyButton = container.querySelector('button')

expect(copyButton).not.toBeInTheDocument()
})
})

0 comments on commit d3d83fd

Please sign in to comment.