summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js
blob: 9f407ef157d8a4f86c2787ed27e73e07dfb3b129 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// NOTE about testing methodology:
// This test checks whether popovers are hidden *after* the appropriate de-hover
// delay. The delay used for testing is kept low, to avoid this test taking too
// long, but that means that sometimes on a slow bot/client, the delay can
// elapse before we are able to check the popover status. And that can make this
// test flaky. To avoid that, the msSinceMouseOver() function is used to check
// that not-too-much time has passed, and if it has, the test is simply skipped.

const hoverDelays = 100; // This needs to match the style block below.
const hoverWaitTime = 200; // How long to wait to cover the delay for sure.

async function initialPopoverShow(invoker) {
  const popover = invoker.popoverTargetElement;
  assert_false(popover.matches(':popover-open'));
  await mouseOver(invoker); // Always start with the mouse over the invoker
  popover.showPopover();
  assert_true(popover.matches(':popover-open'));
}

function runHoverHideTest(popoverType, invokerType, invokerAction) {
  const descr = `popover=${popoverType}, invoker=${invokerType}, popovertargetaction=${invokerAction}`;
  promise_test(async (t) => {
    const {popover,invoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
    await initialPopoverShow(invoker);
    await mouseOver(unrelated);
    let showing = popover.matches(':popover-open');
    if (msSinceMouseOver() >= hoverDelays)
      return; // The WPT runner was too slow.
    assert_true(showing,'popover shouldn\'t immediately hide');
    await mouseHover(unrelated,hoverWaitTime);
    assert_false(popover.matches(':popover-open'),'popover should hide after delay');
  },`The popover-hide-delay causes a popover to be hidden after a delay, ${descr}`);

  promise_test(async (t) => {
    const {popover,invoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
    await initialPopoverShow(invoker);
    await mouseHover(popover,hoverWaitTime);
    assert_true(popover.matches(':popover-open'),'hovering the popover should keep it showing');
    await mouseOver(unrelated);
    let showing = popover.matches(':popover-open');
    if (msSinceMouseOver() >= hoverDelays)
      return; // The WPT runner was too slow.
    assert_true(showing,'subsequently hovering unrelated element shouldn\'t immediately hide the popover');
    await mouseHover(unrelated,hoverWaitTime);
    assert_false(popover.matches(':popover-open'),'hovering unrelated element should hide popover after delay');
  },`hovering the popover keeps it from being hidden, ${descr}`);

  promise_test(async (t) => {
    const {popover,invoker,mouseOverInvoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
    await initialPopoverShow(invoker);
    assert_true(popover.matches(':popover-open'));
    await mouseHover(popover,hoverWaitTime);
    await mouseHover(mouseOverInvoker,hoverWaitTime);
    assert_true(popover.matches(':popover-open'),'Moving hover between invoker and popover should keep popover from being hidden');
    await mouseHover(unrelated,hoverWaitTime);
    assert_false(popover.matches(':popover-open'),'Moving hover to unrelated should finally hide the popover');
  },`hovering an invoking element keeps the popover from being hidden, ${descr}`);
}

function runHoverHideTestsForInvokerAction(invokerAction) {
  promise_test(async (t) => {
    const {popover,invoker} = makeTestParts(t, 'auto', 'button', 'show');
    assert_false(popover.matches(':popover-open'));
    assert_true(invoker.matches('[popovertarget]'),'invoker needs to match [popovertarget]');
    assert_equals(invoker.popoverTargetElement,popover,'invoker should point to popover');
    await mouseHover(invoker,hoverWaitTime);
    assert_true(msSinceMouseOver() >= hoverWaitTime,'waitForHoverTime should wait the specified time');
    assert_true(hoverWaitTime > hoverDelays,'hoverDelays is the value from CSS, hoverWaitTime should be longer than that');
    assert_equals(getComputedStyleTimeMs(invoker,'popoverShowDelay'),hoverDelays,'popover-show-delay is incorrect');
    assert_equals(getComputedStyleTimeMs(popover,'popoverHideDelay'),hoverDelays,'popover-hide-delay is incorrect');
  },'Test the harness');

  // Run for all invoker and popover types.
  ["button","input"].forEach(invokerType => {
    ["auto","hint","manual"].forEach(popoverType => {
      runHoverHideTest(popoverType, invokerType, invokerAction);
    });
  });
}

// Setup stuff
const unrelated = document.createElement('div');
unrelated.id = 'unrelated';
unrelated.textContent = 'Unrelated element';
const style = document.createElement('style');
document.body.append(unrelated,style);
style.textContent = `
  div, button, input {
    /* Fixed position everything to ensure nothing overlaps */
    position: fixed;
    max-height: 100px;
  }
  #unrelated {top: 100px;}
  [popovertarget] {
    top:200px;
    popover-show-delay: 100ms;
  }
  [popover] {
    width: 200px;
    height: 100px;
    top:300px;
    popover-hide-delay: 100ms;
  }
`;

function makeTestParts(t,popoverType,invokerType,invokerAction) {
  const popover = document.createElement('div');
  popover.id = `popover-${popoverType}-${invokerType}-${invokerAction}`;
  document.body.appendChild(popover);
  popover.popover = popoverType;
  assert_equals(popover.popover, popoverType, `Type ${popoverType} not supported`);
  const invoker = document.createElement(invokerType);
  document.body.appendChild(invoker);
  invoker.popoverTargetElement = popover;
  invoker.popoverTargetAction = invokerAction;
  assert_equals(invoker.popoverTargetAction, invokerAction, `Invoker action ${invokerAction} not supported`);
  let mouseOverInvoker;
  switch (invokerType) {
    case 'button':
      invoker.innerHTML = '<span><span data-note=nested_element>Click me</span></span>';
      mouseOverInvoker = invoker.firstElementChild.firstElementChild;
      assert_true(!!mouseOverInvoker);
      break;
    case 'input':
      invoker.type = 'button';
      mouseOverInvoker = invoker;
      break;
    default:
      assert_unreached('Invalid invokerType ' + invokerType);
      break;
  }
  t.add_cleanup(() => {popover.remove(); invoker.remove();});
  return {popover, invoker, mouseOverInvoker};
}

function getComputedStyleTimeMs(element,property) {
  // Times are in seconds, so just strip off the 's'.
  return Number(getComputedStyle(element)[property].slice(0,-1))*1000;
}