Skip to content

Commit

Permalink
Merge pull request #655 from Seolhun/features/overlay
Browse files Browse the repository at this point in the history
Overlay에서 발생되는 reflow 개선
  • Loading branch information
Seolhun authored Dec 13, 2021
2 parents 590d03d + 808a625 commit b95c1c8
Show file tree
Hide file tree
Showing 3 changed files with 344 additions and 105 deletions.
137 changes: 86 additions & 51 deletions src/components/Overlay/Overlay.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,46 +119,57 @@ const ScrollContent = styled.div`
color: white;
`

const Template: Story<OverlayProps & ContainerProps> = ({ width, height, ...overlayProps }) => {
const targetRef = useRef<any>()
const containerRef = useRef<any>()
const OverlayTemplate: React.FC<OverlayProps & ContainerProps> = ({
children,
width: containerWidth,
height: containerHeight,
...rests
}) => {
const containerRef = useRef<any>(null)
const targetRef = useRef<any>(null)

return (
<Container
width={width}
height={height}
width={containerWidth}
height={containerHeight}
ref={containerRef}
>
<Wrapper>
<Target ref={targetRef}>
target
</Target>
<Overlay
{...overlayProps}
{...rests}
target={targetRef.current}
container={containerRef.current}
>
<Children>
<ScrollContent>
{
`Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of type
and scrambled it to make a type specimen book. It has survived not
only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem Ipsum passages,
and more recently with desktop publishing software like Aldus PageMaker
including versions of Lorem Ipsum.`
}
</ScrollContent>
</Children>
{ children }
</Overlay>
</Wrapper>
</Container>
)
}

const Template: Story = (props) => (
<OverlayTemplate {...props}>
<Children>
<ScrollContent>
{
`Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of type
and scrambled it to make a type specimen book. It has survived not
only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem Ipsum passages,
and more recently with desktop publishing software like Aldus PageMaker
including versions of Lorem Ipsum.`
}
</ScrollContent>
</Children>
</OverlayTemplate>
)

export const Primary = Template.bind({})
Primary.args = {
show: false,
Expand All @@ -181,45 +192,69 @@ const StressTestTemplate: Story<OverlayProps> = (props) => {
}, [])

return (
<Container ref={containerRef}>
<Wrapper>
<Target ref={targetRef}>
target
</Target>
<Overlay
{...props}
// 실제 값은 변하지 않지만, 100ms의 매 렌더링마다 참조가 변하고 있다.
containerStyle={{ opacity: 1 }}
target={targetRef.current}
container={containerRef.current}
>
<Children>
<ScrollContent>
{
`Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of type
and scrambled it to make a type specimen book. It has survived not
only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem Ipsum passages,
and more recently with desktop publishing software like Aldus PageMaker
including versions of Lorem Ipsum.`
}
</ScrollContent>
</Children>
</Overlay>
</Wrapper>
</Container>
<OverlayTemplate
{...props}
// 실제 값은 변하지 않지만, 100ms의 매 렌더링마다 참조가 변하고 있다.
containerStyle={{ opacity: 1 }}
target={targetRef.current}
container={containerRef.current}
>
<Children>
<ScrollContent>
{
`Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of type
and scrambled it to make a type specimen book. It has survived not
only five centuries, but also the leap into electronic typesetting,
remaining essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem Ipsum passages,
and more recently with desktop publishing software like Aldus PageMaker
including versions of Lorem Ipsum.`
}
</ScrollContent>
</Children>
</OverlayTemplate>
)
}

export const StressTest = StressTestTemplate.bind({})
StressTest.args = {
enableClickOutside: false,
}

const ChangeableChildrenTemplate: Story<OverlayProps> = (props) => {
const [items, setItems] = useState<number[]>([])

const addItem = React.useCallback(() => {
setItems([...items, Math.random()])
}, [items])

return (
<>
<button type="button" onClick={addItem}>Add</button>
<div>
<OverlayTemplate {...props}>
<Children>
{ items.map((item) => (
<div key={item}>
{ item }
</div>
)) }
</Children>
</OverlayTemplate>
</div>
</>
)
}

export const ChangeableChildren = ChangeableChildrenTemplate.bind({})
ChangeableChildren.args = {
show: false,
position: OverlayPosition.BottomCenter,
marginX: 0,
marginY: 0,
keepInContainer: false,
withTransition: false,
enableClickOutside: true,
}
190 changes: 188 additions & 2 deletions src/components/Overlay/Overlay.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
/* External dependencies */
import React from 'react'
import { getWindow } from 'ssr-window'
import { fireEvent } from '@testing-library/dom'

/* Internal dependencies */
import { TransitionDuration } from 'Foundation'
import { render } from 'Utils/testUtils'
import OverlayProps, { ContainerRectAttr, TargetRectAttr, OverlayPosition } from './Overlay.types'
import Overlay, { OVERLAY_TEST_ID } from './Overlay'
import Overlay, { CONTAINER_TEST_ID, ESCAPE_KEY, OVERLAY_TEST_ID, WRAPPER_TEST_ID } from './Overlay'
import { getOverlayTranslation } from './utils'

const RootOverlay: React.FC<OverlayProps> = ({ children, ...rests }) => (
<div id="main">
<Overlay {...rests}>
{ children }
</Overlay>
</div>
)

describe('Overlay test >', () => {
let props: OverlayProps

Expand Down Expand Up @@ -143,7 +153,6 @@ describe('Overlay test >', () => {
containerHeight: 600,
targetHeight: 100에
targetTop: 200이므로,
target의 아래쪽 공간은 300이다.
300이 200보다 크므로, overlay는 아래쪽에 나타나야 함.
*/
Expand All @@ -165,4 +174,181 @@ describe('Overlay test >', () => {
})
})
})

describe('Props and Event', () => {
const renderRootOverlay = (optionProps?: OverlayProps) => render(<RootOverlay {...props} {...optionProps} />)

beforeEach(() => {
props = {
show: true,
className: '',
containerClassName: '',
position: OverlayPosition.LeftCenter,
marginX: 0,
marginY: 0,
keepInContainer: false,
withTransition: false,
enableClickOutside: false,
children: 'Test Overlay',
}
})

describe('Props', () => {
describe('show', () => {
describe('is True', () => {
it('container style', () => {
const { getByTestId } = renderRootOverlay()
const overlay = getByTestId(CONTAINER_TEST_ID)
expect(overlay).toHaveStyle('position: fixed')
expect(overlay).toHaveStyle('top: 0')
expect(overlay).toHaveStyle('right: 0')
expect(overlay).toHaveStyle('bottom: 0')
expect(overlay).toHaveStyle('left: 0')
expect(overlay).toHaveStyle('width: 100%')
expect(overlay).toHaveStyle('height: 100%')
expect(overlay).toHaveStyle('pointer-events: all')
})

it('wrapper style', () => {
const { getByTestId } = renderRootOverlay()
const overlay = getByTestId(WRAPPER_TEST_ID)
expect(overlay).toHaveStyle('position: relative')
expect(overlay).toHaveStyle('width: 100%')
expect(overlay).toHaveStyle('height: 100%')
})

it('overlay style', () => {
const { getByTestId } = renderRootOverlay()
const overlay = getByTestId(OVERLAY_TEST_ID)
expect(overlay).toHaveStyle('position: absolute')
})
})

describe('is False', () => {
it('container style', () => {
const { container } = renderRootOverlay()
// <main id="main" />
expect(container.children.length).toBe(1)
})
})
})

describe('className', () => {
it('is transferred', () => {
const CLASSNAME = 'Test__Overlay'
const { getByTestId } = renderRootOverlay({ className: CLASSNAME })
const overlay = getByTestId(OVERLAY_TEST_ID)
expect(overlay).toHaveClass(CLASSNAME)
})
})

describe('style', () => {
it('is transferred', () => {
const STYLE: React.CSSProperties = {
width: '100px',
}
const { getByTestId } = renderRootOverlay({ style: STYLE })
const overlay = getByTestId(OVERLAY_TEST_ID)
expect(overlay).toHaveStyle('width: 100px')
})
})

describe('containerClassName', () => {
it('is transferred', () => {
const CLASSNAME = 'Test__Container'
const { getByTestId } = renderRootOverlay({ containerClassName: CLASSNAME })
const overlay = getByTestId(CONTAINER_TEST_ID)
expect(overlay).toHaveClass(CLASSNAME)
})
})

describe('containerStyle', () => {
it('is transferred', () => {
const STYLE: React.CSSProperties = {
width: '100px',
}
const { getByTestId } = renderRootOverlay({ containerStyle: STYLE })
const overlay = getByTestId(CONTAINER_TEST_ID)
expect(overlay).toHaveStyle('width: 100px')
})
})

describe('enableClickOutside', () => {
document.onclick = jest.fn()
const onHide = jest.fn()

afterEach(jest.clearAllMocks)

it('is True', () => {
const { getByTestId } = renderRootOverlay({ enableClickOutside: true, onHide })
const overlay = getByTestId(CONTAINER_TEST_ID)

overlay.click()
expect(document.onclick).toHaveBeenCalledTimes(1)
expect(onHide).toHaveBeenCalledTimes(1)
overlay.click()
expect(document.onclick).toHaveBeenCalledTimes(2)
expect(onHide).toHaveBeenCalledTimes(2)
})

it('is False - click is stopPropagation ', () => {
const { getByTestId } = renderRootOverlay()
const overlay = getByTestId(CONTAINER_TEST_ID)

overlay.click()
expect(document.onclick).toHaveBeenCalledTimes(0)
expect(onHide).toHaveBeenCalledTimes(0)
overlay.click()
expect(document.onclick).toHaveBeenCalledTimes(0)
expect(onHide).toHaveBeenCalledTimes(0)
})
})

describe('withTransition', () => {
it('is True', () => {
const { getByTestId } = renderRootOverlay({ withTransition: true })
const overlay = getByTestId(OVERLAY_TEST_ID)

expect(overlay).toHaveStyle(`transition-property: ${['top', 'opacity'].join(',')}`)
expect(overlay).toHaveStyle(`transition-duration: ${TransitionDuration.S}ms`)
expect(overlay).toHaveStyle('transition-timing-function: cubic-bezier(.3,0,0,1)')
expect(overlay).toHaveStyle('transition-delay: 0ms')
})
})
})

describe('Event', () => {
describe('keydown', () => {
document.onkeydown = jest.fn()
const onHide = jest.fn()

afterEach(jest.clearAllMocks)

it('is Triggered By Escape', () => {
const { getByTestId } = renderRootOverlay({ withTransition: true, onHide })
const overlay = getByTestId(OVERLAY_TEST_ID)
fireEvent.keyDown(overlay, { key: ESCAPE_KEY })
expect(document.onkeydown).toHaveBeenCalledTimes(1)
expect(onHide).toHaveBeenCalledTimes(1)
fireEvent.keyDown(overlay, { key: ESCAPE_KEY })
expect(document.onkeydown).toHaveBeenCalledTimes(2)
expect(onHide).toHaveBeenCalledTimes(2)
})

it('is not Triggered By All keys except Escape', () => {
const { getByTestId } = renderRootOverlay({ withTransition: true, onHide })
const overlay = getByTestId(OVERLAY_TEST_ID)
fireEvent.keyDown(overlay, { key: 'Enter' })
expect(document.onkeydown).toHaveBeenCalledTimes(1)
expect(onHide).toHaveBeenCalledTimes(0)
fireEvent.keyDown(overlay, { key: 'ArrowRight' })
expect(document.onkeydown).toHaveBeenCalledTimes(2)
expect(onHide).toHaveBeenCalledTimes(0)
fireEvent.keyDown(overlay, { key: 'Z' })
expect(document.onkeydown).toHaveBeenCalledTimes(3)
expect(onHide).toHaveBeenCalledTimes(0)
})
})
})
})
})
Loading

0 comments on commit b95c1c8

Please sign in to comment.