mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
LibWeb/HTML: Dispatch command
events
Command events are now dispatched when buttons are activated. The special commands for popovers and dialogs are also implemented.
This commit is contained in:
parent
9e31ee3778
commit
8f0b967c05
14 changed files with 1037 additions and 4 deletions
|
@ -26,6 +26,7 @@ interface mixin GlobalEventHandlers {
|
|||
attribute EventHandler onchange;
|
||||
attribute EventHandler onclick;
|
||||
attribute EventHandler onclose;
|
||||
attribute EventHandler oncommand;
|
||||
attribute EventHandler oncontextlost;
|
||||
attribute EventHandler oncontextmenu;
|
||||
attribute EventHandler oncontextrestored;
|
||||
|
|
|
@ -151,6 +151,7 @@ namespace AttributeNames {
|
|||
__ENUMERATE_HTML_ATTRIBUTE(onchange, "onchange") \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onclick, "onclick") \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onclose, "onclose") \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncommand, "oncommand") \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncontextlost, "oncontextlost") \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncontextmenu, "oncontextmenu") \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncontextrestored, "oncontextrestored") \
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Web::HTML::EventNames {
|
|||
__ENUMERATE_HTML_EVENT(change) \
|
||||
__ENUMERATE_HTML_EVENT(click) \
|
||||
__ENUMERATE_HTML_EVENT(close) \
|
||||
__ENUMERATE_HTML_EVENT(command) \
|
||||
__ENUMERATE_HTML_EVENT(complete) \
|
||||
__ENUMERATE_HTML_EVENT(connect) \
|
||||
__ENUMERATE_HTML_EVENT(contextlost) \
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
E(onchange, HTML::EventNames::change) \
|
||||
E(onclick, UIEvents::EventNames::click) \
|
||||
E(onclose, HTML::EventNames::close) \
|
||||
E(oncommand, HTML::EventNames::command) \
|
||||
E(oncontextlost, HTML::EventNames::contextlost) \
|
||||
E(oncontextmenu, HTML::EventNames::contextmenu) \
|
||||
E(oncontextrestored, HTML::EventNames::contextrestored) \
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#include <LibWeb/Bindings/HTMLButtonElementPrototype.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/HTML/CommandEvent.h>
|
||||
#include <LibWeb/HTML/HTMLButtonElement.h>
|
||||
#include <LibWeb/HTML/HTMLFormElement.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -153,12 +155,111 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event)
|
|||
return;
|
||||
}
|
||||
|
||||
// FIXME: 4. Let target be the result of running element's get the commandfor associated element.
|
||||
// FIXME: 5. If target is not null:
|
||||
// ...
|
||||
// 4. Let target be the result of running element's get the commandfor associated element.
|
||||
// AD-HOC: Target needs to be an HTML Element in the following steps.
|
||||
GC::Ptr<HTMLElement> target = as_if<HTMLElement>(m_command_for_element.ptr());
|
||||
if (!target) {
|
||||
auto target_id = attribute(AttributeNames::commandfor);
|
||||
if (target_id.has_value()) {
|
||||
root().for_each_in_inclusive_subtree_of_type<HTMLElement>([&](auto& candidate) {
|
||||
if (candidate.attribute(HTML::AttributeNames::id) == target_id.value()) {
|
||||
target = &candidate;
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If target is not null:
|
||||
if (target) {
|
||||
// 1. Let command be element's command attribute.
|
||||
auto command = this->command();
|
||||
|
||||
// 2. If command is in the Unknown state, then return.
|
||||
if (command.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Let isPopover be true if target's popover attribute is not in the no popover state; otherwise false.
|
||||
auto is_popover = target->popover().has_value();
|
||||
|
||||
// 4. If isPopover is false and command is not in the Custom state:
|
||||
auto command_is_in_custom_state = command.starts_with_bytes("--"sv);
|
||||
if (!is_popover && !command.starts_with_bytes("--"sv)) {
|
||||
// 1. Assert: target's namespace is the HTML namespace.
|
||||
VERIFY(target->namespace_uri() == Namespace::HTML);
|
||||
|
||||
// 2. If this standard does not define is valid invoker command steps for target's local name, then return.
|
||||
// 3. Otherwise, if the result of running target's corresponding is valid invoker command steps given command is false, then return.
|
||||
if (!target->is_valid_invoker_command(command))
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Let continue be the result of firing an event named command at target, using CommandEvent, with its command attribute initialized to command, its source attribute initialized to element, and its cancelable and composed attributes initialized to true.
|
||||
// SPEC-NOTE: DOM standard issue #1328 tracks how to better standardize associated event data in a way which makes sense on Events. Currently an event attribute initialized to a value cannot also have a getter, and so an internal slot (or map of additional fields) is required to properly specify this.
|
||||
CommandEventInit event_init {};
|
||||
event_init.command = command;
|
||||
event_init.source = this;
|
||||
event_init.cancelable = true;
|
||||
event_init.composed = true;
|
||||
|
||||
auto event = CommandEvent::create(realm(), HTML::EventNames::command, move(event_init));
|
||||
event->set_is_trusted(true);
|
||||
auto continue_ = target->dispatch_event(event);
|
||||
|
||||
// 6. If continue is false, then return.
|
||||
if (!continue_)
|
||||
return;
|
||||
|
||||
// 7. If target is not connected, then return.
|
||||
if (!target->is_connected())
|
||||
return;
|
||||
|
||||
// 8. If command is in the Custom state, then return.
|
||||
if (command_is_in_custom_state)
|
||||
return;
|
||||
|
||||
// AD-HOC: The parameters provided in the spec do not match the function signatures in the following steps.
|
||||
// The inconsistent parameters were therefore selected ad hoc.
|
||||
|
||||
// 9. If command is in the Hide Popover state:
|
||||
if (command == "hide-popover") {
|
||||
// 1. If the result of running check popover validity given target, true, false, and null is true, then run the hide popover algorithm given target, true, true, and false.
|
||||
if (MUST(target->check_popover_validity(ExpectedToBeShowing::Yes, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
|
||||
MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No));
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Otherwise, if command is in the Toggle Popover state:
|
||||
else if (command == "toggle-popover") {
|
||||
// 1. If the result of running check popover validity given target, false, false, and null is true, then run the show popover algorithm given target, true, true, and false.
|
||||
if (MUST(target->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
|
||||
MUST(target->show_popover(ThrowExceptions::No, this));
|
||||
}
|
||||
|
||||
// 2. Otheriwse, if the result of running check popover validity given target, true, false, and null is true, then run the hide popover algorithm given target, true, true, and false.
|
||||
else if (MUST(target->check_popover_validity(ExpectedToBeShowing::Yes, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
|
||||
MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No));
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Otherwise, if command is in the Show Popover state:
|
||||
else if (command == "show-popover") {
|
||||
// 1. If the result of running check popover validity given target, false, false, and null is true, then run the show popover algorithm given target, true, true, and false.
|
||||
if (MUST(target->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
|
||||
MUST(target->show_popover(ThrowExceptions::No, this));
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Otherwise, if this standard defines invoker command steps for target's local name, then run the corresponding invoker command steps given target, element and command.
|
||||
else {
|
||||
target->invoker_command_steps(*this, command);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Otherwise, run the popover target attribute activation behavior given element and event's target.
|
||||
if (event.target() && event.target()->is_dom_node())
|
||||
else if (event.target() && event.target()->is_dom_node())
|
||||
PopoverInvokerElement::popover_target_activation_behaviour(*this, as<DOM::Node>(*event.target()));
|
||||
}
|
||||
|
||||
|
|
|
@ -443,4 +443,39 @@ void HTMLDialogElement::set_is_modal(bool is_modal)
|
|||
invalidate_style(DOM::StyleInvalidationReason::NodeRemove);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:is-valid-invoker-command-steps
|
||||
bool HTMLDialogElement::is_valid_invoker_command(String& command)
|
||||
{
|
||||
// 1. If command is in the Close state or in the Show Modal state, then return true.
|
||||
if (command == "close" || command == "show-modal")
|
||||
return true;
|
||||
|
||||
// 2. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:invoker-command-steps
|
||||
void HTMLDialogElement::invoker_command_steps(DOM::Element& invoker, String& command)
|
||||
{
|
||||
// 1. If element is in the popover showing state, then return.
|
||||
if (popover_visibility_state() == PopoverVisibilityState::Showing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If command is in the Close state and element has an open attribute:
|
||||
if (command == "close" && has_attribute(AttributeNames::open)) {
|
||||
// 1. Let value be invoker's value.
|
||||
// FIXME: This assumes invoker is a button.
|
||||
auto value = invoker.get_attribute(AttributeNames::value);
|
||||
|
||||
// 2. Close the dialog element with value.
|
||||
close_the_dialog(value);
|
||||
}
|
||||
|
||||
// 3. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element.
|
||||
if (command == "show-modal" && !has_attribute(AttributeNames::open)) {
|
||||
MUST(show_a_modal_dialog(*this));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ public:
|
|||
bool is_modal() const { return m_is_modal; }
|
||||
void set_is_modal(bool);
|
||||
|
||||
bool is_valid_invoker_command(String&) override;
|
||||
void invoker_command_steps(DOM::Element&, String&) override;
|
||||
|
||||
private:
|
||||
HTMLDialogElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
|
|
@ -151,6 +151,9 @@ public:
|
|||
|
||||
bool is_inert() const { return m_inert; }
|
||||
|
||||
virtual bool is_valid_invoker_command(String&) { return false; }
|
||||
virtual void invoker_command_steps(DOM::Element&, String&) { }
|
||||
|
||||
protected:
|
||||
HTMLElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 39 tests
|
||||
|
||||
38 Pass
|
||||
1 Fail
|
||||
Pass event dispatches on click with addEventListener
|
||||
Pass event dispatches on click with oncommand property
|
||||
Pass setting custom command property to --foo (must include dash) sets event command
|
||||
Pass setting custom command attribute to --foo (must include dash) sets event command
|
||||
Pass setting custom command property to --foo- (must include dash) sets event command
|
||||
Pass setting custom command attribute to --foo- (must include dash) sets event command
|
||||
Pass setting custom command property to --cAsE-cArRiEs (must include dash) sets event command
|
||||
Pass setting custom command attribute to --cAsE-cArRiEs (must include dash) sets event command
|
||||
Pass setting custom command property to -- (must include dash) sets event command
|
||||
Pass setting custom command attribute to -- (must include dash) sets event command
|
||||
Pass setting custom command property to --a- (must include dash) sets event command
|
||||
Pass setting custom command attribute to --a- (must include dash) sets event command
|
||||
Pass setting custom command property to --a-b (must include dash) sets event command
|
||||
Pass setting custom command attribute to --a-b (must include dash) sets event command
|
||||
Pass setting custom command property to --- (must include dash) sets event command
|
||||
Pass setting custom command attribute to --- (must include dash) sets event command
|
||||
Pass setting custom command property to --show-picker (must include dash) sets event command
|
||||
Pass setting custom command attribute to --show-picker (must include dash) sets event command
|
||||
Pass setting custom command property to -foo (no dash) did not dispatch an event
|
||||
Pass setting custom command attribute to -foo (no dash) did not dispatch an event
|
||||
Pass setting custom command property to -foo- (no dash) did not dispatch an event
|
||||
Pass setting custom command attribute to -foo- (no dash) did not dispatch an event
|
||||
Pass setting custom command property to foo-bar (no dash) did not dispatch an event
|
||||
Pass setting custom command attribute to foo-bar (no dash) did not dispatch an event
|
||||
Pass setting custom command property to -foo bar (no dash) did not dispatch an event
|
||||
Pass setting custom command attribute to -foo bar (no dash) did not dispatch an event
|
||||
Pass setting custom command property to —-emdash (no dash) did not dispatch an event
|
||||
Pass setting custom command attribute to —-emdash (no dash) did not dispatch an event
|
||||
Pass setting custom command property to hidedocument (no dash) did not dispatch an event
|
||||
Pass setting custom command attribute to hidedocument (no dash) did not dispatch an event
|
||||
Pass event does not dispatch if click:preventDefault is called
|
||||
Pass event does not dispatch on input[type=button]
|
||||
Pass event does not dispatch if invoker is disabled
|
||||
Pass event does NOT dispatch if button is form associated, with implicit type
|
||||
Pass event does NOT dispatch if button is form associated, with explicit type=invalid
|
||||
Pass event dispatches if button is form associated, with explicit type=button
|
||||
Pass event does NOT dispatch if button is form associated, with explicit type=submit
|
||||
Pass event does NOT dispatch if button is form associated, with explicit type=reset
|
||||
Fail event dispatches if invokee is non-HTML Element
|
|
@ -0,0 +1,56 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 50 tests
|
||||
|
||||
48 Pass
|
||||
2 Fail
|
||||
Pass invoking (with command property as show-modal) closed dialog opens as modal
|
||||
Pass invoking (with command property as show-modal) closed dialog with preventDefault is noop
|
||||
Pass invoking (with command property as show-modal) while changing command still opens as modal
|
||||
Pass invoking (with command attribute as show-modal) closed dialog opens as modal
|
||||
Pass invoking (with command attribute as show-modal) closed dialog with preventDefault is noop
|
||||
Pass invoking (with command attribute as show-modal) while changing command still opens as modal
|
||||
Pass invoking (with command property as sHoW-mOdAl) closed dialog opens as modal
|
||||
Pass invoking (with command property as sHoW-mOdAl) closed dialog with preventDefault is noop
|
||||
Pass invoking (with command property as sHoW-mOdAl) while changing command still opens as modal
|
||||
Pass invoking (with command attribute as sHoW-mOdAl) closed dialog opens as modal
|
||||
Pass invoking (with command attribute as sHoW-mOdAl) closed dialog with preventDefault is noop
|
||||
Pass invoking (with command attribute as sHoW-mOdAl) while changing command still opens as modal
|
||||
Pass invoking to close (with command property as close) open dialog closes
|
||||
Pass invoking to close (with command property as close) open dialog closes and sets returnValue
|
||||
Pass invoking to close (with command property as close) open dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command property as close) open modal dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command property as close) open dialog while changing command still closes
|
||||
Pass invoking to close (with command property as close) open modal dialog while changing command still closes
|
||||
Pass invoking to close (with command attribute as close) open dialog closes
|
||||
Pass invoking to close (with command attribute as close) open dialog closes and sets returnValue
|
||||
Pass invoking to close (with command attribute as close) open dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command attribute as close) open modal dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command attribute as close) open dialog while changing command still closes
|
||||
Pass invoking to close (with command attribute as close) open modal dialog while changing command still closes
|
||||
Pass invoking to close (with command property as cLoSe) open dialog closes
|
||||
Pass invoking to close (with command property as cLoSe) open dialog closes and sets returnValue
|
||||
Pass invoking to close (with command property as cLoSe) open dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command property as cLoSe) open modal dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command property as cLoSe) open dialog while changing command still closes
|
||||
Pass invoking to close (with command property as cLoSe) open modal dialog while changing command still closes
|
||||
Pass invoking to close (with command attribute as cLoSe) open dialog closes
|
||||
Pass invoking to close (with command attribute as cLoSe) open dialog closes and sets returnValue
|
||||
Pass invoking to close (with command attribute as cLoSe) open dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command attribute as cLoSe) open modal dialog with preventDefault is no-op
|
||||
Pass invoking to close (with command attribute as cLoSe) open dialog while changing command still closes
|
||||
Pass invoking to close (with command attribute as cLoSe) open modal dialog while changing command still closes
|
||||
Pass invoking (as show-modal) open dialog is noop
|
||||
Pass invoking (as show-modal) open modal, while changing command still a no-op
|
||||
Pass invoking (as show-modal) closed popover dialog opens as modal
|
||||
Pass invoking (as close) already closed dialog is noop
|
||||
Pass invoking (as show-modal) dialog as open popover=manual is noop
|
||||
Pass invoking (as show-modal) dialog as open popover=auto is noop
|
||||
Pass invoking (as close) dialog as open popover=manual is noop
|
||||
Pass invoking (as close) dialog as open popover=auto is noop
|
||||
Pass invoking (as show-modal) dialog that is removed is noop
|
||||
Fail invoking (as show-modal) dialog from a detached invoker
|
||||
Pass invoking (as show-modal) detached dialog from a detached invoker
|
||||
Fail invoking (as close) dialog that is removed is noop
|
||||
Pass invoking (as close) dialog from a detached invoker
|
||||
Pass invoking (as close) detached dialog from a detached invoker
|
|
@ -0,0 +1,32 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 27 tests
|
||||
|
||||
27 Pass
|
||||
Pass changing command attribute inside invokeevent doesn't impact the invocation
|
||||
Pass invoking (as toggle-popover) closed popover opens
|
||||
Pass invoking (as toggle-popover) closed popover with preventDefault does not open
|
||||
Pass invoking (as show-popover) closed popover opens
|
||||
Pass invoking (as show-popover) closed popover with preventDefault does not open
|
||||
Pass invoking (as tOgGlE-pOpOvEr) closed popover opens
|
||||
Pass invoking (as tOgGlE-pOpOvEr) closed popover with preventDefault does not open
|
||||
Pass invoking (as sHoW-pOpOvEr) closed popover opens
|
||||
Pass invoking (as sHoW-pOpOvEr) closed popover with preventDefault does not open
|
||||
Pass invoking (as toggle-popover) open popover closes
|
||||
Pass invoking (as toggle-popover) open popover with preventDefault does not close
|
||||
Pass invoking (as toggle-popover) from within open popover closes
|
||||
Pass invoking (as toggle-popover) from within open popover with preventDefault does not close
|
||||
Pass invoking (as hide-popover) open popover closes
|
||||
Pass invoking (as hide-popover) open popover with preventDefault does not close
|
||||
Pass invoking (as hide-popover) from within open popover closes
|
||||
Pass invoking (as hide-popover) from within open popover with preventDefault does not close
|
||||
Pass invoking (as tOgGlE-pOpOvEr) open popover closes
|
||||
Pass invoking (as tOgGlE-pOpOvEr) open popover with preventDefault does not close
|
||||
Pass invoking (as tOgGlE-pOpOvEr) from within open popover closes
|
||||
Pass invoking (as tOgGlE-pOpOvEr) from within open popover with preventDefault does not close
|
||||
Pass invoking (as hIdE-pOpOvEr) open popover closes
|
||||
Pass invoking (as hIdE-pOpOvEr) open popover with preventDefault does not close
|
||||
Pass invoking (as hIdE-pOpOvEr) from within open popover closes
|
||||
Pass invoking (as hIdE-pOpOvEr) from within open popover with preventDefault does not close
|
||||
Pass invoking (as show-popover) open popover is noop
|
||||
Pass invoking (as hide-popover) closed popover is noop
|
|
@ -0,0 +1,226 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
|
||||
<meta name="timeout" content="long" />
|
||||
<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/invoker-utils.js"></script>
|
||||
|
||||
<div id="invokee"></div>
|
||||
<button id="invokerbutton" commandfor="invokee" command="--custom-command"></button>
|
||||
<input type="button" id="invalidbutton" commandfor="invokee" command="--custom-command">
|
||||
<form id="aform"></form>
|
||||
|
||||
<script>
|
||||
aform.addEventListener('submit', (e) => (e.preventDefault()));
|
||||
|
||||
function resetState() {
|
||||
invokerbutton.setAttribute("commandfor", "invokee");
|
||||
invokerbutton.setAttribute("command", "--custom-command");
|
||||
invokerbutton.removeAttribute("disabled");
|
||||
invokerbutton.removeAttribute("form");
|
||||
invokerbutton.removeAttribute("type");
|
||||
}
|
||||
|
||||
test(function (t) {
|
||||
let event = null;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.click();
|
||||
assert_true(event instanceof CommandEvent, "event is CommandEvent");
|
||||
assert_equals(event.type, "command", "type");
|
||||
assert_equals(event.bubbles, false, "bubbles");
|
||||
assert_equals(event.composed, true, "composed");
|
||||
assert_equals(event.isTrusted, true, "isTrusted");
|
||||
assert_equals(event.command, "--custom-command", "command");
|
||||
assert_equals(event.target, invokee, "target");
|
||||
assert_equals(event.source, invokerbutton, "source");
|
||||
}, "event dispatches on click with addEventListener");
|
||||
|
||||
test(function (t) {
|
||||
let event = null;
|
||||
t.add_cleanup(() => {
|
||||
invokee.oncommand = null;
|
||||
});
|
||||
invokee.oncommand = (e) => (event = e);
|
||||
invokerbutton.click();
|
||||
assert_true(event instanceof CommandEvent, "event is CommandEvent");
|
||||
assert_equals(event.type, "command", "type");
|
||||
assert_equals(event.bubbles, false, "bubbles");
|
||||
assert_equals(event.composed, true, "composed");
|
||||
assert_equals(event.isTrusted, true, "isTrusted");
|
||||
assert_equals(event.command, "--custom-command", "command");
|
||||
assert_equals(event.target, invokee, "target");
|
||||
assert_equals(event.source, invokerbutton, "source");
|
||||
}, "event dispatches on click with oncommand property");
|
||||
|
||||
// valid custom invokeactions
|
||||
["--foo", "--foo-", "--cAsE-cArRiEs", "--", "--a-", "--a-b", "---", "--show-picker"].forEach(
|
||||
(command) => {
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let event = null;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.command = command;
|
||||
invokerbutton.click();
|
||||
assert_true(event instanceof CommandEvent, "event is CommandEvent");
|
||||
assert_equals(event.type, "command", "type");
|
||||
assert_equals(event.bubbles, false, "bubbles");
|
||||
assert_equals(event.composed, true, "composed");
|
||||
assert_equals(event.isTrusted, true, "isTrusted");
|
||||
assert_equals(event.command, command, "command");
|
||||
assert_equals(event.target, invokee, "target");
|
||||
assert_equals(event.source, invokerbutton, "source");
|
||||
}, `setting custom command property to ${command} (must include dash) sets event command`);
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let event = null;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.setAttribute("command", command);
|
||||
invokerbutton.click();
|
||||
assert_true(event instanceof CommandEvent, "event is CommandEvent");
|
||||
assert_equals(event.type, "command", "type");
|
||||
assert_equals(event.bubbles, false, "bubbles");
|
||||
assert_equals(event.composed, true, "composed");
|
||||
assert_equals(event.isTrusted, true, "isTrusted");
|
||||
assert_equals(event.command, command, "command");
|
||||
assert_equals(event.target, invokee, "target");
|
||||
assert_equals(event.source, invokerbutton, "source");
|
||||
}, `setting custom command attribute to ${command} (must include dash) sets event command`);
|
||||
},
|
||||
);
|
||||
|
||||
// invalid custom invokeactions
|
||||
["-foo", "-foo-", "foo-bar", "-foo bar", "—-emdash", "hidedocument"].forEach((command) => {
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let event = null;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.command = command;
|
||||
invokerbutton.click();
|
||||
assert_equals(event, null, "event should not have fired");
|
||||
}, `setting custom command property to ${command} (no dash) did not dispatch an event`);
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let event = null;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.setAttribute("command", command);
|
||||
invokerbutton.click();
|
||||
assert_equals(event, null, "event should not have fired");
|
||||
}, `setting custom command attribute to ${command} (no dash) did not dispatch an event`);
|
||||
});
|
||||
|
||||
test(function (t) {
|
||||
let called = false;
|
||||
invokerbutton.addEventListener(
|
||||
"click",
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
invokee.addEventListener(
|
||||
"command",
|
||||
(event) => {
|
||||
called = true;
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
invokerbutton.click();
|
||||
assert_false(called, "event was not called");
|
||||
}, "event does not dispatch if click:preventDefault is called");
|
||||
|
||||
test(function (t) {
|
||||
let event = null;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invalidbutton.click();
|
||||
assert_equals(event, null, "command should not have fired");
|
||||
}, "event does not dispatch on input[type=button]");
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let called = false;
|
||||
invokee.addEventListener("command", (e) => (called = true), { once: true });
|
||||
invokerbutton.setAttribute("disabled", "");
|
||||
invokerbutton.click();
|
||||
assert_false(called, "event was not called");
|
||||
}, "event does not dispatch if invoker is disabled");
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let called = false;
|
||||
invokee.addEventListener("command", (e) => (called = true), { once: true });
|
||||
invokerbutton.setAttribute("form", "aform");
|
||||
invokerbutton.removeAttribute("type");
|
||||
invokerbutton.click();
|
||||
assert_false(called, "event was not called");
|
||||
}, "event does NOT dispatch if button is form associated, with implicit type");
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let called = false;
|
||||
invokee.addEventListener("command", (e) => (called = true), { once: true });
|
||||
invokerbutton.setAttribute("form", "aform");
|
||||
invokerbutton.setAttribute("type", "invalid");
|
||||
invokerbutton.click();
|
||||
assert_false(called, "event was not called");
|
||||
}, "event does NOT dispatch if button is form associated, with explicit type=invalid");
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let event;
|
||||
invokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.setAttribute("form", "aform");
|
||||
invokerbutton.setAttribute("type", "button");
|
||||
invokerbutton.click();
|
||||
assert_true(event instanceof CommandEvent, "event is CommandEvent");
|
||||
assert_equals(event.type, "command", "type");
|
||||
assert_equals(event.bubbles, false, "bubbles");
|
||||
assert_equals(event.composed, true, "composed");
|
||||
assert_equals(event.isTrusted, true, "isTrusted");
|
||||
assert_equals(event.command, "--custom-command", "command");
|
||||
assert_equals(event.target, invokee, "target");
|
||||
assert_equals(event.source, invokerbutton, "source");
|
||||
}, "event dispatches if button is form associated, with explicit type=button");
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let called = false;
|
||||
invokee.addEventListener("command", (e) => (called = true), { once: true });
|
||||
invokerbutton.setAttribute("form", "aform");
|
||||
invokerbutton.setAttribute("type", "submit");
|
||||
invokerbutton.click();
|
||||
assert_false(called, "event was not called");
|
||||
}, "event does NOT dispatch if button is form associated, with explicit type=submit");
|
||||
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
let called = false;
|
||||
invokee.addEventListener("command", (e) => (called = true), { once: true });
|
||||
invokerbutton.setAttribute("form", "aform");
|
||||
invokerbutton.setAttribute("type", "reset");
|
||||
invokerbutton.click();
|
||||
assert_false(called, "event was called");
|
||||
}, "event does NOT dispatch if button is form associated, with explicit type=reset");
|
||||
|
||||
test(function (t) {
|
||||
svgInvokee = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svgInvokee.setAttribute("id", "svg-invokee");
|
||||
t.add_cleanup(resetState);
|
||||
document.body.append(svgInvokee);
|
||||
assert_false(svgInvokee instanceof HTMLElement);
|
||||
assert_true(svgInvokee instanceof Element);
|
||||
let event = null;
|
||||
svgInvokee.addEventListener("command", (e) => (event = e), { once: true });
|
||||
invokerbutton.setAttribute("commandfor", "svg-invokee");
|
||||
invokerbutton.setAttribute("command", "--custom-command");
|
||||
assert_equals(invokerbutton.commandForElement, svgInvokee);
|
||||
invokerbutton.click();
|
||||
assert_not_equals(event, null, "event was called");
|
||||
assert_true(event instanceof CommandEvent, "event is CommandEvent");
|
||||
assert_equals(event.source, invokerbutton, "event.invoker is set to right element");
|
||||
assert_equals(event.target, svgInvokee, "event.target is set to right element");
|
||||
}, "event dispatches if invokee is non-HTML Element");
|
||||
</script>
|
|
@ -0,0 +1,376 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
|
||||
<meta name="timeout" content="long">
|
||||
<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/invoker-utils.js"></script>
|
||||
|
||||
<dialog id="invokee">
|
||||
<button id="containedinvoker" commandfor="invokee" command="close"></button>
|
||||
</dialog>
|
||||
<button id="invokerbutton" commandfor="invokee" command="show-modal"></button>
|
||||
|
||||
<script>
|
||||
function resetState() {
|
||||
invokee.close();
|
||||
try { invokee.hidePopover(); } catch {}
|
||||
invokee.removeAttribute("popover");
|
||||
invokee.returnValue = '';
|
||||
invokerbutton.setAttribute("command", "show-modal");
|
||||
containedinvoker.setAttribute("command", "close");
|
||||
containedinvoker.removeAttribute("value");
|
||||
}
|
||||
|
||||
// opening a dialog
|
||||
|
||||
["show-modal", /* test case sensitivity */ "sHoW-mOdAl"].forEach(
|
||||
(command) => {
|
||||
["property", "attribute"].forEach((setType) => {
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
if (setType === "property") {
|
||||
invokerbutton.command = command;
|
||||
} else {
|
||||
invokerbutton.setAttribute("command", command);
|
||||
}
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (with command ${setType} as ${command}) closed dialog opens as modal`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
if (setType === "property") {
|
||||
invokerbutton.command = command;
|
||||
} else {
|
||||
invokerbutton.setAttribute("command", command);
|
||||
}
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (with command ${setType} as ${command}) closed dialog with preventDefault is noop`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokee.addEventListener(
|
||||
"command",
|
||||
(e) => {
|
||||
invokerbutton.setAttribute("command", "close");
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
if (setType === "property") {
|
||||
invokerbutton.command = command;
|
||||
} else {
|
||||
invokerbutton.setAttribute("command", command);
|
||||
}
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (with command ${setType} as ${command}) while changing command still opens as modal`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// closing an already open dialog
|
||||
|
||||
["close", /* test case sensitivity */ "cLoSe"].forEach((command) => {
|
||||
["property", "attribute"].forEach((setType) => {
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.show();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
if (setType === "property") {
|
||||
containedinvoker.command = command;
|
||||
} else {
|
||||
containedinvoker.setAttribute("command", command);
|
||||
}
|
||||
containedinvoker.click();
|
||||
assert_equals(invokee.returnValue, "");
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking to close (with command ${setType} as ${command}) open dialog closes`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.show();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
if (setType === "property") {
|
||||
containedinvoker.command = command;
|
||||
} else {
|
||||
containedinvoker.setAttribute("command", command);
|
||||
}
|
||||
containedinvoker.setAttribute("value", "foo");
|
||||
containedinvoker.click();
|
||||
assert_equals(invokee.returnValue, "foo");
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking to close (with command ${setType} as ${command}) open dialog closes and sets returnValue`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.show();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
if (typeof command === "string") {
|
||||
if (setType === "property") {
|
||||
containedinvoker.command = command;
|
||||
} else {
|
||||
containedinvoker.setAttribute("command", command);
|
||||
}
|
||||
}
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
containedinvoker.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking to close (with command ${setType} as ${command}) open dialog with preventDefault is no-op`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.showModal();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
if (setType === "property") {
|
||||
containedinvoker.command = command;
|
||||
} else {
|
||||
containedinvoker.setAttribute("command", command);
|
||||
}
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
containedinvoker.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking to close (with command ${setType} as ${command}) open modal dialog with preventDefault is no-op`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.show();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
if (setType === "property") {
|
||||
containedinvoker.command = command;
|
||||
} else {
|
||||
containedinvoker.setAttribute("command", command);
|
||||
}
|
||||
invokee.addEventListener(
|
||||
"command",
|
||||
(e) => {
|
||||
containedinvoker.setAttribute("command", "show");
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
containedinvoker.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking to close (with command ${setType} as ${command}) open dialog while changing command still closes`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.showModal();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
if (setType === "property") {
|
||||
containedinvoker.command = command;
|
||||
} else {
|
||||
containedinvoker.setAttribute("command", command);
|
||||
}
|
||||
invokee.addEventListener(
|
||||
"command",
|
||||
(e) => {
|
||||
containedinvoker.setAttribute("command", "show");
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
containedinvoker.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking to close (with command ${setType} as ${command}) open modal dialog while changing command still closes`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// show-modal explicit behaviours
|
||||
|
||||
promise_test(async function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
containedinvoker.setAttribute("command", "show-Modal");
|
||||
invokee.show();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
containedinvoker.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
}, "invoking (as show-modal) open dialog is noop");
|
||||
|
||||
promise_test(async function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
containedinvoker.setAttribute("command", "show-modal");
|
||||
invokee.showModal();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
invokee.addEventListener(
|
||||
"command",
|
||||
(e) => {
|
||||
containedinvoker.setAttribute("command", "close");
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
}, "invoking (as show-modal) open modal, while changing command still a no-op");
|
||||
|
||||
promise_test(async function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.setAttribute("command", "show-modal");
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokee.setAttribute("popover", "auto");
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.open, "invokee.open");
|
||||
assert_true(invokee.matches(":modal"), "invokee :modal");
|
||||
}, "invoking (as show-modal) closed popover dialog opens as modal");
|
||||
|
||||
// close explicit behaviours
|
||||
|
||||
promise_test(async function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.setAttribute("command", "close");
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
containedinvoker.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
}, "invoking (as close) already closed dialog is noop");
|
||||
|
||||
// Open Popovers using Dialog actions
|
||||
["show-modal", "close"].forEach((command) => {
|
||||
["manual", "auto"].forEach((popoverState) => {
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokee.setAttribute("popover", popoverState);
|
||||
invokee.showPopover();
|
||||
containedinvoker.setAttribute("command", command);
|
||||
assert_true(
|
||||
invokee.matches(":popover-open"),
|
||||
"invokee :popover-open",
|
||||
);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
containedinvoker.click();
|
||||
assert_true(
|
||||
invokee.matches(":popover-open"),
|
||||
"invokee :popover-open",
|
||||
);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (as ${command}) dialog as open popover=${popoverState} is noop`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Elements being disconnected during invoke steps
|
||||
["show-modal", "close"].forEach((command) => {
|
||||
promise_test(
|
||||
async function (t) {
|
||||
t.add_cleanup(() => {
|
||||
document.body.prepend(invokee);
|
||||
resetState();
|
||||
});
|
||||
const invokee = document.querySelector("#invokee");
|
||||
invokerbutton.setAttribute("command", command);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokee.addEventListener(
|
||||
"command",
|
||||
(e) => {
|
||||
invokee.remove();
|
||||
},
|
||||
{
|
||||
once: true,
|
||||
},
|
||||
);
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (as ${command}) dialog that is removed is noop`,
|
||||
);
|
||||
|
||||
promise_test(
|
||||
async function (t) {
|
||||
const invokerbutton = document.createElement("button");
|
||||
invokerbutton.commandForElement = invokee;
|
||||
invokerbutton.setAttribute("command", command);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (as ${command}) dialog from a detached invoker`,
|
||||
);
|
||||
|
||||
promise_test(
|
||||
async function (t) {
|
||||
const invokerbutton = document.createElement("button");
|
||||
const invokee = document.createElement("dialog");
|
||||
invokerbutton.commandForElementt = invokee;
|
||||
invokerbutton.setAttribute("command", command);
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.open, "invokee.open");
|
||||
assert_false(invokee.matches(":modal"), "invokee :modal");
|
||||
},
|
||||
`invoking (as ${command}) detached dialog from a detached invoker`,
|
||||
);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,152 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
|
||||
<meta name="timeout" content="long" />
|
||||
<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/invoker-utils.js"></script>
|
||||
|
||||
<div id="invokee" popover>
|
||||
<button id="containedinvoker" commandfor="invokee" command="hide-popover"></button>
|
||||
</div>
|
||||
<button id="invokerbutton" commandfor="invokee" command="toggle-popover"></button>
|
||||
|
||||
<script>
|
||||
function resetState() {
|
||||
invokerbutton.setAttribute("commandfor", "invokee");
|
||||
invokerbutton.setAttribute("command", "toggle-popover");
|
||||
containedinvoker.setAttribute("commandfor", "invokee");
|
||||
containedinvoker.setAttribute("command", "hide-popover");
|
||||
try {
|
||||
invokee.hidePopover();
|
||||
} catch {}
|
||||
invokee.setAttribute("popover", "");
|
||||
}
|
||||
|
||||
promise_test(async function (t) {
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
invokee.addEventListener("command", (e) => { invokerbutton.setAttribute('command', 'hide-popover'); }, {
|
||||
once: true,
|
||||
});
|
||||
invokerbutton.click();
|
||||
t.add_cleanup(resetState);
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
}, "changing command attribute inside invokeevent doesn't impact the invocation");
|
||||
|
||||
// Open actions
|
||||
[
|
||||
"toggle-popover",
|
||||
"show-popover",
|
||||
/* test case sensitivity */
|
||||
"tOgGlE-pOpOvEr",
|
||||
"sHoW-pOpOvEr",
|
||||
].forEach((command) => {
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.command = command;
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
},
|
||||
`invoking (as ${command}) closed popover opens`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.command = command;
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
},
|
||||
`invoking (as ${command}) closed popover with preventDefault does not open`,
|
||||
);
|
||||
});
|
||||
|
||||
// Close actions
|
||||
[
|
||||
"toggle-popover",
|
||||
"hide-popover",
|
||||
/* test case sensitivity */
|
||||
"tOgGlE-pOpOvEr",
|
||||
"hIdE-pOpOvEr",
|
||||
].forEach((command) => {
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.command = command;
|
||||
invokee.showPopover();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
},
|
||||
`invoking (as ${command}) open popover closes`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.command = command;
|
||||
invokee.showPopover();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
},
|
||||
`invoking (as ${command}) open popover with preventDefault does not close`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
containedinvoker.command = command;
|
||||
invokee.showPopover();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
containedinvoker.click();
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
},
|
||||
`invoking (as ${command}) from within open popover closes`,
|
||||
);
|
||||
|
||||
test(
|
||||
function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
containedinvoker.command = command;
|
||||
invokee.showPopover();
|
||||
invokee.addEventListener("command", (e) => e.preventDefault(), {
|
||||
once: true,
|
||||
});
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
containedinvoker.click();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
},
|
||||
`invoking (as ${command}) from within open popover with preventDefault does not close`,
|
||||
);
|
||||
});
|
||||
|
||||
// show-popover specific
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.setAttribute("command", "show-popover");
|
||||
invokee.showPopover();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
invokerbutton.click();
|
||||
assert_true(invokee.matches(":popover-open"));
|
||||
}, "invoking (as show-popover) open popover is noop");
|
||||
|
||||
// hide-popover specific
|
||||
test(function (t) {
|
||||
t.add_cleanup(resetState);
|
||||
invokerbutton.setAttribute("command", "hide-popover");
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
invokerbutton.click();
|
||||
assert_false(invokee.matches(":popover-open"));
|
||||
}, "invoking (as hide-popover) closed popover is noop");
|
||||
</script>
|
Loading…
Add table
Reference in a new issue