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
75 changes: 75 additions & 0 deletions src/lib/__tests__/modal-factory.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { BaseStyles, ThemeProvider } from '@primer/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('@/lib/debug-logger', () => ({
logger: { log: () => {}, warn: () => {}, error: () => {}, info: () => {} },
initDebugLogger: async () => {},
}))
vi.mock('@/lib/tippy-utils', () => ({ ensureTippyCss: () => {} }))
vi.mock('@/lib/toast-store', () => ({
toastStore: { show: () => {} },
}))

import { createModal } from '@/lib/modal-factory'
;(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true

const TestModal = createModal<{ onConfirm: () => void }>({
name: 'Test',
renderContent: (_props, _helpers) => <input data-testid="modal-input" />,
onSubmit: async () => {},
})

let mounted: Array<{ container: HTMLDivElement; root: Root }> = []

function render(node: React.ReactElement) {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
act(() => {
root.render(node)
})
mounted.push({ container, root })
return container
}

beforeEach(() => {
mounted = []
})

afterEach(() => {
for (const { container, root } of mounted) {
act(() => root.unmount())
container.remove()
}
})

describe('createModal keyboard propagation', () => {
it('stops keydown/keyup propagation to document when typing inside the modal panel', () => {
const container = render(
<ThemeProvider colorMode="day">
<BaseStyles>
<TestModal onConfirm={() => {}} onClose={() => {}} />
</BaseStyles>
</ThemeProvider>,
)

const input = container.querySelector('[data-testid="modal-input"]') as HTMLInputElement
expect(input).not.toBeNull()

const spy = vi.fn()
document.addEventListener('keydown', spy)
document.addEventListener('keyup', spy)

act(() => {
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true }))
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'a', bubbles: true }))
})

document.removeEventListener('keydown', spy)
document.removeEventListener('keyup', spy)

expect(spy).not.toHaveBeenCalled()
})
})
7 changes: 6 additions & 1 deletion src/lib/modal-factory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ export function createModal<T>(opts: CreateModalOptions<T>): React.FC<ModalCompo
aria-modal="true"
aria-label={name}
>
<Box sx={primerCss.modalPanel()} onClick={(e: React.MouseEvent) => e.stopPropagation()}>
<Box
sx={primerCss.modalPanel()}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
onKeyDown={(e: React.KeyboardEvent) => { if (e.key !== 'Escape') e.stopPropagation() }}
onKeyUp={(e: React.KeyboardEvent) => e.stopPropagation()}
>
<ModalStepHeader title={name} icon={icon} onClose={handleRequestClose} />
<Box sx={primerCss.contentArea()}>
{error && (
Expand Down
37 changes: 37 additions & 0 deletions src/ui/__tests__/bulk-flyout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,43 @@ describe('<BulkFlyout> simple mode', () => {
expect(style!.textContent).toContain('prefers-reduced-motion: no-preference')
expect(style!.textContent).toContain('@keyframes rgp-flyout-in')
})

it('stops keydown/keyup propagation to document when typing inside', () => {
const anchorRef = { current: document.createElement('button') }
document.body.appendChild(anchorRef.current)
const { find } = render(
<ThemeProvider colorMode="day">
<BaseStyles>
<BulkFlyout
mode="simple"
anchorRef={anchorRef as React.RefObject<HTMLElement>}
open={true}
onClose={() => {}}
title="Test"
>
<input data-testid="rgp-test-input" />
</BulkFlyout>
</BaseStyles>
</ThemeProvider>,
)
const input = find('[data-testid="rgp-test-input"]') as HTMLInputElement
expect(input).not.toBeNull()

const spy = vi.fn()
document.addEventListener('keydown', spy)
document.addEventListener('keyup', spy)

act(() => {
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true }))
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'a', bubbles: true }))
})

document.removeEventListener('keydown', spy)
document.removeEventListener('keyup', spy)
anchorRef.current.remove()

expect(spy).not.toHaveBeenCalled()
})
})

describe('<BulkFlyout> apply/cancel footer', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/ui/bulk-flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ export function BulkFlyout(props: BulkFlyoutProps) {
minWidth: width,
}}
data-testid="rgp-bulk-flyout"
onKeyDown={(e: React.KeyboardEvent) => e.stopPropagation()}
onKeyUp={(e: React.KeyboardEvent) => e.stopPropagation()}
>
<Box sx={{ px: 3, pt: 3, pb: 2 }}>{header}</Box>
<Box
Expand Down
8 changes: 7 additions & 1 deletion wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ export default defineConfig({
port: 3000,
},
build: {
chunkSizeWarningLimit: 900,
chunkSizeWarningLimit: 2000,
rolldownOptions: {
onwarn(warning, handler) {
if (warning.code === 'INVALID_ANNOTATION') return
handler(warning)
},
},
},
resolve: {
alias: {
Expand Down
Loading