1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5"use strict";
6
7const EXPORTED_SYMBOLS = [
8  "assert_quick_filter_button_enabled",
9  "assert_quick_filter_bar_visible",
10  "toggle_quick_filter_bar",
11  "assert_constraints_expressed",
12  "toggle_boolean_constraints",
13  "toggle_tag_constraints",
14  "toggle_tag_mode",
15  "assert_tag_constraints_visible",
16  "assert_tag_constraints_checked",
17  "toggle_text_constraints",
18  "assert_text_constraints_checked",
19  "set_filter_text",
20  "assert_filter_text",
21  "assert_results_label_count",
22  "clear_constraints",
23];
24
25var fdh = ChromeUtils.import(
26  "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
27);
28
29var { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
30
31var mc = fdh.mc;
32// disable the deferred search processing!
33mc.window.QuickFilterBarMuxer.deferredUpdateSearch =
34  mc.window.QuickFilterBarMuxer.updateSearch;
35
36/**
37 * Maps names to bar DOM ids to simplify checking.
38 */
39var nameToBarDomId = {
40  sticky: "qfb-sticky",
41  unread: "qfb-unread",
42  starred: "qfb-starred",
43  addrbook: "qfb-inaddrbook",
44  tags: "qfb-tags",
45  attachments: "qfb-attachment",
46};
47
48function assert_quick_filter_button_enabled(aEnabled) {
49  if (mc.e("qfb-show-filter-bar").disabled == aEnabled) {
50    throw new Error(
51      "Quick filter bar button should be " + (aEnabled ? "enabled" : "disabled")
52    );
53  }
54}
55
56function assert_quick_filter_bar_visible(aVisible) {
57  if (mc.e("quick-filter-bar").getBoundingClientRect().height > 0 != aVisible) {
58    throw new Error(
59      "Quick filter bar should be " + (aVisible ? "visible" : "collapsed")
60    );
61  }
62}
63
64/**
65 * Toggle the state of the message filter bar as if by a mouse click.
66 */
67function toggle_quick_filter_bar() {
68  mc.click(mc.e("qfb-show-filter-bar"));
69  fdh.wait_for_all_messages_to_load();
70}
71
72/**
73 * Assert that the state of the constraints visually expressed by the bar is
74 * consistent with the passed-in constraints.  This method does not verify
75 * that the search constraints are in effect.  Check that elsewhere.
76 */
77function assert_constraints_expressed(aConstraints) {
78  for (let name in nameToBarDomId) {
79    let domId = nameToBarDomId[name];
80    let expectedValue = name in aConstraints ? aConstraints[name] : false;
81    let domNode = mc.e(domId);
82    if (domNode.checked !== expectedValue) {
83      throw new Error(name + "'s checked state should be " + expectedValue);
84    }
85  }
86}
87
88/**
89 * Toggle the given filter buttons by name (from nameToBarDomId); variable
90 * argument magic enabled.
91 */
92function toggle_boolean_constraints(...aArgs) {
93  aArgs.forEach(arg => mc.click(mc.e(nameToBarDomId[arg])));
94  fdh.wait_for_all_messages_to_load(mc);
95}
96
97/**
98 * Toggle the tag faceting buttons by tag key.  Wait for messages after.
99 */
100function toggle_tag_constraints(...aArgs) {
101  let qfbButtons = mc.e("quick-filter-bar-tab-bar");
102  aArgs.forEach(function(arg) {
103    let tagId = "qfb-tag-" + arg;
104    qfbButtons.ensureElementIsVisible(mc.e(tagId));
105    mc.click(mc.e(tagId));
106  });
107  fdh.wait_for_all_messages_to_load(mc);
108}
109
110/**
111 * Set the tag filtering mode. Wait for messages after.
112 */
113function toggle_tag_mode() {
114  let qbm = mc.e("qfb-boolean-mode");
115  if (qbm.value === "AND") {
116    qbm.selectedIndex--; // = move to "OR";
117    Assert.equal(qbm.value, "OR", "qfb-boolean-mode has wrong state");
118  } else if (qbm.value === "OR") {
119    qbm.selectedIndex++; // = move to "AND";
120    Assert.equal(qbm.value, "AND", "qfb-boolean-mode has wrong state");
121  } else {
122    throw new Error("qfb-boolean-mode value=" + qbm.value);
123  }
124  fdh.wait_for_all_messages_to_load(mc);
125}
126
127/**
128 * Verify that tag buttons exist for exactly the given set of tag keys in the
129 *  provided variable argument list.  Ordering is significant.
130 */
131function assert_tag_constraints_visible(...aArgs) {
132  // the stupid bar should be visible if any arguments are specified
133  if (aArgs.length > 0 && mc.e("quick-filter-bar-tab-bar").collapsed) {
134    throw new Error("The tag bar should not be collapsed!");
135  }
136
137  let kids = mc.e("quick-filter-bar-tab-bar").children;
138  let tagLength = kids.length - 1; // -1 for the qfb-boolean-mode widget
139  // this is bad error reporting in here for now.
140  if (tagLength != aArgs.length) {
141    throw new Error(
142      "Mismatch in expected tag count and actual. " +
143        "Expected " +
144        aArgs.length +
145        " actual " +
146        tagLength
147    );
148  }
149  for (let iArg = 0; iArg < aArgs.length; iArg++) {
150    let nodeId = "qfb-tag-" + aArgs[iArg];
151    if (nodeId != kids[iArg + 1].id) {
152      throw new Error(
153        "Mismatch at tag " +
154          iArg +
155          " expected " +
156          nodeId +
157          " but got " +
158          kids[iArg + 1].id
159      );
160    }
161  }
162}
163
164/**
165 * Verify that only the buttons corresponding to the provided tag keys are
166 * checked.
167 */
168function assert_tag_constraints_checked(...aArgs) {
169  let expected = {};
170  for (let arg of aArgs) {
171    let nodeId = "qfb-tag-" + arg;
172    expected[nodeId] = true;
173  }
174
175  let kids = mc.e("quick-filter-bar-tab-bar").children;
176  for (let iNode = 0; iNode < kids.length; iNode++) {
177    let node = kids[iNode];
178    if (node.checked != node.id in expected) {
179      throw new Error(
180        "node " +
181          node.id +
182          " should " +
183          (node.id in expected ? "be " : "not be ") +
184          "checked."
185      );
186    }
187  }
188}
189
190var nameToTextDomId = {
191  sender: "qfb-qs-sender",
192  recipients: "qfb-qs-recipients",
193  subject: "qfb-qs-subject",
194  body: "qfb-qs-body",
195};
196
197function toggle_text_constraints(...aArgs) {
198  aArgs.forEach(arg => mc.click(mc.e(nameToTextDomId[arg])));
199  fdh.wait_for_all_messages_to_load(mc);
200}
201
202/**
203 * Assert that the text constraint buttons are checked.  Variable-argument
204 *  support where the arguments are one of sender/recipients/subject/body.
205 */
206function assert_text_constraints_checked(...aArgs) {
207  let expected = {};
208  for (let arg of aArgs) {
209    let nodeId = nameToTextDomId[arg];
210    expected[nodeId] = true;
211  }
212
213  let kids = mc.e("quick-filter-bar-filter-text-bar").children;
214  for (let iNode = 0; iNode < kids.length; iNode++) {
215    let node = kids[iNode];
216    if (node.tagName == "label") {
217      continue;
218    }
219    if (node.checked != node.id in expected) {
220      throw new Error(
221        "node " +
222          node.id +
223          " should " +
224          (node.id in expected ? "be " : "not be ") +
225          "checked."
226      );
227    }
228  }
229}
230
231/**
232 * Set the text in the text filter box, trigger it like enter was pressed, then
233 *  wait for all messages to load.
234 */
235function set_filter_text(aText) {
236  // We're not testing the reliability of the textbox widget; just poke our text
237  // in and trigger the command logic.
238  let textbox = mc.e("qfb-qs-textbox");
239  textbox.value = aText;
240  textbox.doCommand();
241  fdh.wait_for_all_messages_to_load(mc);
242}
243
244function assert_filter_text(aText) {
245  let textbox = mc.e("qfb-qs-textbox");
246  if (textbox.value != aText) {
247    throw new Error(
248      "Expected text filter value of '" + aText + "' but got '" + textbox.value
249    );
250  }
251}
252
253/**
254 * Assert that the results label is telling us there are aCount messages
255 *  using the appropriate string.
256 */
257function assert_results_label_count(aCount) {
258  let resultsLabel = mc.e("qfb-results-label");
259  if (aCount == 0) {
260    if (resultsLabel.value != resultsLabel.getAttribute("noresultsstring")) {
261      throw new Error(
262        "results label should be displaying the no messages case"
263      );
264    }
265  } else {
266    let s = resultsLabel.value;
267    s = s.substring(0, s.indexOf(" "));
268    if (parseInt(s) !== aCount) {
269      throw new Error(
270        "Result count is displaying " + s + " but should show " + aCount
271      );
272    }
273  }
274}
275
276/**
277 * Clear active constraints via any means necessary; state cleanup for testing,
278 *  not to be used as part of a test.  Unlike normal clearing, this will kill
279 *  the sticky bit.
280 *
281 * This is automatically called by the test teardown helper.
282 */
283function clear_constraints() {
284  mc.window.QuickFilterBarMuxer._testHelperResetFilterState();
285}
286