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
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,33 @@ describe('<Drilldown />', () => {
expect(options.length).toBe(0)
expect(notAllowedChild).not.toBeInTheDocument()
})

it('should not crash for weird option ids', async () => {
const onSelect = vi.fn()
const weirdID = 'some"_weird!@#$%^&*()\\|`id'
render(
<Drilldown rootPageId="page0" onSelect={onSelect}>
<Drilldown.Page id="page0">
{data.map((option) => (
<Drilldown.Option
id={weirdID + option.id}
value={weirdID + option.id}
key={weirdID + option.id}
data-testid={weirdID + option.id}
>
{option.label}
</Drilldown.Option>
))}
</Drilldown.Page>
</Drilldown>
)
const option_1 = screen.getByTestId(weirdID + 'opt_1')
await userEvent.click(option_1)

await waitFor(() => {
expect(onSelect).toHaveBeenCalled()
})
})
})

describe('id prop', () => {
Expand Down
9 changes: 5 additions & 4 deletions packages/ui-drilldown/src/Drilldown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -585,9 +585,8 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {

focusOption(id: string) {
const container = this._containerElement

const optionElement = container?.querySelector(
`[id="${id}"]`
`[id="${CSS.escape(id)}"]`
) as HTMLSpanElement

optionElement?.focus()
Expand Down Expand Up @@ -816,7 +815,7 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {

if (event.type === 'keydown' && href) {
const optionEl = this._drilldownRef?.querySelector(
`#${id}`
`#${CSS.escape(id)}`
) as HTMLLinkElement
const isLink = optionEl.tagName.toLowerCase() === 'a'

Expand Down Expand Up @@ -1516,7 +1515,9 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
: this.currentPage.children[0]?.props.id

if (!targetId) return null
return this._popover?._contentElement?.querySelector(`#${targetId}`)
return this._popover?._contentElement?.querySelector(
`#${CSS.escape(targetId)}`
)
}}
elementRef={(element) => {
// setting ref for "Popover" version, the popover root
Expand Down
23 changes: 23 additions & 0 deletions packages/ui-select/src/Select/__new-tests__/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,29 @@ describe('<Select />', () => {
consoleErrorMock.mockRestore()
})

it('should not crash for weird option ids', async () => {
const weirdID = 'some_`w@ei:r|!@#$%^&*(()|\\.l/d"id'
vi.useFakeTimers()
const { container } = render(
<Select
renderLabel="Choose an option"
scrollToHighlightedOption
isShowingOptions
>
<Select.Option id={weirdID} key="1" value="2" isHighlighted={true}>
op1
</Select.Option>
<Select.Option id="sdfsdfsd" key="2" value="2">
op2
</Select.Option>
</Select>
)
vi.advanceTimersToNextFrame()
vi.useRealTimers()
const input = container.querySelector('input')
expect(input).toBeInTheDocument()
})

it('should have role button in Safari without onInputChange', async () => {
const { container } = render(
<Select renderLabel="Choose an option">{getOptions()}</Select>
Expand Down
31 changes: 15 additions & 16 deletions packages/ui-select/src/Select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,22 +291,21 @@ class Select extends Component<SelectProps> {
}

scrollToOption(id?: string) {
if (this._listView) {
const option = this._listView.querySelector(`[id="${id}"]`)
if (!option) return

const listItem = option.parentNode
const parentTop = getBoundingClientRect(this._listView).top
const elemTop = getBoundingClientRect(listItem).top
const parentBottom = parentTop + this._listView.clientHeight
const elemBottom =
elemTop + (listItem ? (listItem as Element).clientHeight : 0)

if (elemBottom > parentBottom) {
this._listView.scrollTop += elemBottom - parentBottom
} else if (elemTop < parentTop) {
this._listView.scrollTop -= parentTop - elemTop
}
if (!this._listView || !id) return
const option = this._listView.querySelector(`[id="${CSS.escape(id)}"]`)
if (!option) return

const listItem = option.parentNode
const parentTop = getBoundingClientRect(this._listView).top
const elemTop = getBoundingClientRect(listItem).top
const parentBottom = parentTop + this._listView.clientHeight
const elemBottom =
elemTop + (listItem ? (listItem as Element).clientHeight : 0)

if (elemBottom > parentBottom) {
this._listView.scrollTop += elemBottom - parentBottom
} else if (elemTop < parentTop) {
this._listView.scrollTop -= parentTop - elemTop
}
}

Expand Down
10 changes: 6 additions & 4 deletions packages/ui-tabs/src/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,12 @@ class Tabs extends Component<TabsProps, TabsState> {
// this is needed because keypress cancels scrolling. So we have to trigger the scrolling
// one "tick" later than the keypress
setTimeout(() => {
this.state.withTabListOverflow &&
this.showActiveTabIfOverlayed(
this._tabList!.querySelector(`#tab-${id}`)
)
if (this.state.withTabListOverflow) {
const tab = id
? this._tabList!.querySelector(`#tab-${CSS.escape(id)}`)
: null
this.showActiveTabIfOverlayed(tab)
}
}, 0)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class TopNavBarSmallViewportLayout extends Component<
setTimeout(() => {
const container = document.getElementById(this._trayContainerId)
const firstOption = container?.querySelector(
`[id="${targetId}"]`
`[id="${CSS.escape(targetId)}"]`
) as HTMLSpanElement
firstOption?.focus()
if (this._drilldownRef) {
Expand Down
Loading