mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-05 22:16:35 +08:00
`DModal` listens for `keydown` on `documentElement` in the capture phase and cancels events whose `activeElement` is not inside the modal wrapper, to prevent keyboard shortcuts from leaking to the page behind the modal. Since `DIconGridPicker` replaced the select-kit `IconPicker` in the custom sidebar section modal (#38943), the picker's filter input renders inside the `#d-menu-portal-outlet` portal — outside the modal DOM — so every keystroke was cancelled and typing silently failed. Allow keydown through when `activeElement` is inside a float-kit portal (`.fk-d-menu`, `.fk-d-menu-modal`, `.fk-d-tooltip`), which covers any menu/tooltip opened from within a modal without weakening the leak guard for elements actually behind the modal. Also adds a system spec that catches this with real keyboard typing (the existing `icon_picker.filter` helper used `fill_in` + clicked the target icon which was already visible without filtering, so the bug slipped past the suite). https://meta.discourse.org/t/400945
108 lines
2.3 KiB
Ruby
108 lines
2.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module PageObjects
|
|
module Components
|
|
class DIconGridPicker < PageObjects::Components::Base
|
|
def initialize(scope = nil)
|
|
@scope = scope
|
|
end
|
|
|
|
def expand
|
|
trigger.click
|
|
self
|
|
end
|
|
|
|
def select_icon(icon_id)
|
|
find("[data-icon-id='#{icon_id}']").click
|
|
end
|
|
|
|
def select_first_icon
|
|
find("[data-icon-id]", match: :first).click
|
|
end
|
|
|
|
def filter(term)
|
|
filter_input.fill_in(with: term)
|
|
end
|
|
|
|
def type_filter(term)
|
|
filter_input.send_keys(term)
|
|
self
|
|
end
|
|
|
|
def filter_input
|
|
find(".d-icon-grid-picker__filter .filter-input")
|
|
end
|
|
|
|
def has_icon?(icon_id)
|
|
page.has_css?("[data-icon-id='#{icon_id}']")
|
|
end
|
|
|
|
def clear
|
|
scoped(".d-icon-grid-picker__clear").click
|
|
end
|
|
|
|
def value
|
|
wrapper["data-value"]
|
|
end
|
|
|
|
def has_selected_icon?(icon_id)
|
|
wrapper["data-value"] == icon_id
|
|
end
|
|
|
|
def has_no_selected_icon?
|
|
wrapper["data-value"].blank?
|
|
end
|
|
|
|
def has_clear_button?
|
|
scoped_has_css?(".d-icon-grid-picker__clear")
|
|
end
|
|
|
|
def has_no_clear_button?
|
|
scoped_has_no_css?(".d-icon-grid-picker__clear")
|
|
end
|
|
|
|
private
|
|
|
|
def wrapper
|
|
scoped(".d-icon-grid-picker")
|
|
end
|
|
|
|
def trigger
|
|
scoped(".d-icon-grid-picker-trigger")
|
|
end
|
|
|
|
def scoped(selector)
|
|
case @scope
|
|
when Capybara::Node::Element
|
|
@scope.find(selector)
|
|
when String
|
|
find("#{@scope} #{selector}")
|
|
else
|
|
find(selector)
|
|
end
|
|
end
|
|
|
|
def scoped_has_css?(selector, **opts)
|
|
case @scope
|
|
when Capybara::Node::Element
|
|
@scope.has_css?(selector, **opts)
|
|
when String
|
|
page.has_css?("#{@scope} #{selector}", **opts)
|
|
else
|
|
page.has_css?(selector, **opts)
|
|
end
|
|
end
|
|
|
|
def scoped_has_no_css?(selector, **opts)
|
|
case @scope
|
|
when Capybara::Node::Element
|
|
@scope.has_no_css?(selector, **opts)
|
|
when String
|
|
page.has_no_css?("#{@scope} #{selector}", **opts)
|
|
else
|
|
page.has_no_css?(selector, **opts)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|