summaryrefslogtreecommitdiffstats
path: root/toolkit/components/normandy/test/browser/browser_Heartbeat.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/normandy/test/browser/browser_Heartbeat.js
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/normandy/test/browser/browser_Heartbeat.js')
-rw-r--r--toolkit/components/normandy/test/browser/browser_Heartbeat.js262
1 files changed, 262 insertions, 0 deletions
diff --git a/toolkit/components/normandy/test/browser/browser_Heartbeat.js b/toolkit/components/normandy/test/browser/browser_Heartbeat.js
new file mode 100644
index 0000000000..0166c4d7b0
--- /dev/null
+++ b/toolkit/components/normandy/test/browser/browser_Heartbeat.js
@@ -0,0 +1,262 @@
+"use strict";
+
+const { Heartbeat } = ChromeUtils.importESModule(
+ "resource://normandy/lib/Heartbeat.sys.mjs"
+);
+
+/**
+ * Assert an array is in non-descending order, and that every element is a number
+ */
+function assertOrdered(arr) {
+ for (let i = 0; i < arr.length; i++) {
+ Assert.equal(typeof arr[i], "number", `element ${i} is type "number"`);
+ }
+ for (let i = 0; i < arr.length - 1; i++) {
+ Assert.lessOrEqual(
+ arr[i],
+ arr[i + 1],
+ `element ${i} is less than or equal to element ${i + 1}`
+ );
+ }
+}
+
+/* Close every notification in a target window and notification box */
+function closeAllNotifications(targetWindow, notificationBox) {
+ if (notificationBox.allNotifications.length === 0) {
+ return Promise.resolve();
+ }
+
+ return new Promise(resolve => {
+ const notificationSet = new Set(notificationBox.allNotifications);
+
+ const observer = new targetWindow.MutationObserver(mutations => {
+ for (const mutation of mutations) {
+ for (let i = 0; i < mutation.removedNodes.length; i++) {
+ const node = mutation.removedNodes.item(i);
+ if (notificationSet.has(node)) {
+ notificationSet.delete(node);
+ }
+ }
+ }
+ if (notificationSet.size === 0) {
+ Assert.equal(
+ notificationBox.allNotifications.length,
+ 0,
+ "No notifications left"
+ );
+ observer.disconnect();
+ resolve();
+ }
+ });
+
+ observer.observe(notificationBox.stack, { childList: true });
+
+ for (const notification of notificationBox.allNotifications) {
+ notification.close();
+ }
+ });
+}
+
+/* Check that the correct telemetry was sent */
+function assertTelemetrySent(hb, eventNames) {
+ return new Promise(resolve => {
+ hb.eventEmitter.once("TelemetrySent", payload => {
+ const events = [0];
+ for (const name of eventNames) {
+ Assert.equal(
+ typeof payload[name],
+ "number",
+ `payload field ${name} is a number`
+ );
+ events.push(payload[name]);
+ }
+ events.push(Date.now());
+
+ assertOrdered(events);
+ resolve();
+ });
+ });
+}
+
+function getStars(notice) {
+ return notice.buttonContainer.querySelectorAll(".star-x");
+}
+
+add_setup(async function () {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ // Open a new tab to keep the window open.
+ await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "https://example.com"
+ );
+});
+
+// Several of the behaviors of heartbeat prompt are mutually exclusive, so checks are broken up
+// into three batches.
+
+/* Batch #1 - General UI, Stars, and telemetry data */
+add_task(async function () {
+ const targetWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ const notificationBox = targetWindow.gNotificationBox;
+
+ const preCount = notificationBox.allNotifications.length;
+ const hb = new Heartbeat(targetWindow, {
+ testing: true,
+ flowId: "test",
+ message: "test",
+ engagementButtonLabel: undefined,
+ learnMoreMessage: "Learn More",
+ learnMoreUrl: "https://example.org/learnmore",
+ });
+
+ // Check UI
+ const learnMoreEl = hb.notice.messageText.querySelector(".text-link");
+ Assert.equal(
+ notificationBox.allNotifications.length,
+ preCount + 1,
+ "Correct number of notifications open"
+ );
+ Assert.equal(getStars(hb.notice).length, 5, "Correct number of stars");
+ Assert.equal(
+ hb.notice.buttonContainer.querySelectorAll(".notification-button").length,
+ 0,
+ "Engagement button not shown"
+ );
+ Assert.equal(
+ learnMoreEl.href,
+ "https://example.org/learnmore",
+ "Learn more url correct"
+ );
+ Assert.equal(learnMoreEl.value, "Learn More", "Learn more label correct");
+ // There's a space included before the learn more link in proton.
+ Assert.equal(
+ hb.notice.messageText.textContent,
+ "test ",
+ "Message is correct"
+ );
+
+ // Check that when clicking the learn more link, a tab opens with the right URL
+ let loadedPromise;
+ const tabOpenPromise = new Promise(resolve => {
+ targetWindow.gBrowser.tabContainer.addEventListener(
+ "TabOpen",
+ event => {
+ let tab = event.target;
+ loadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ url => url && url !== "about:blank"
+ );
+ resolve(tab);
+ },
+ { once: true }
+ );
+ });
+ learnMoreEl.click();
+ const tab = await tabOpenPromise;
+ const tabUrl = await loadedPromise;
+
+ Assert.equal(
+ tabUrl,
+ "https://example.org/learnmore",
+ "Learn more link opened the right url"
+ );
+
+ const telemetrySentPromise = assertTelemetrySent(hb, [
+ "offeredTS",
+ "learnMoreTS",
+ "closedTS",
+ ]);
+ // Close notification to trigger telemetry to be sent
+ await closeAllNotifications(targetWindow, notificationBox);
+ await telemetrySentPromise;
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Batch #2 - Engagement buttons
+add_task(async function () {
+ const targetWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ const notificationBox = targetWindow.gNotificationBox;
+ const hb = new Heartbeat(targetWindow, {
+ testing: true,
+ flowId: "test",
+ message: "test",
+ engagementButtonLabel: "Click me!",
+ postAnswerUrl: "https://example.org/postAnswer",
+ learnMoreMessage: "Learn More",
+ learnMoreUrl: "https://example.org/learnMore",
+ });
+ const engagementButton = hb.notice.buttonContainer.querySelector(
+ ".notification-button"
+ );
+
+ Assert.equal(getStars(hb.notice).length, 0, "Stars not shown");
+ Assert.ok(engagementButton, "Engagement button added");
+ Assert.equal(
+ engagementButton.label,
+ "Click me!",
+ "Engagement button has correct label"
+ );
+
+ const engagementEl = hb.notice.buttonContainer.querySelector(
+ ".notification-button"
+ );
+ let loadedPromise;
+ const tabOpenPromise = new Promise(resolve => {
+ targetWindow.gBrowser.tabContainer.addEventListener(
+ "TabOpen",
+ event => {
+ let tab = event.target;
+ loadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ url => url && url !== "about:blank"
+ );
+ resolve(tab);
+ },
+ { once: true }
+ );
+ });
+ engagementEl.click();
+ const tab = await tabOpenPromise;
+ const tabUrl = await loadedPromise;
+ // the postAnswer url gets query parameters appended onto the end, so use Assert.startsWith instead of Assert.equal
+ Assert.ok(
+ tabUrl.startsWith("https://example.org/postAnswer"),
+ "Engagement button opened the right url"
+ );
+
+ const telemetrySentPromise = assertTelemetrySent(hb, [
+ "offeredTS",
+ "engagedTS",
+ "closedTS",
+ ]);
+ // Close notification to trigger telemetry to be sent
+ await closeAllNotifications(targetWindow, notificationBox);
+ await telemetrySentPromise;
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Batch 3 - Closing the window while heartbeat is open
+add_task(async function () {
+ const targetWindow = await BrowserTestUtils.openNewBrowserWindow();
+
+ const hb = new Heartbeat(targetWindow, {
+ testing: true,
+ flowId: "test",
+ message: "test",
+ });
+
+ const telemetrySentPromise = assertTelemetrySent(hb, [
+ "offeredTS",
+ "windowClosedTS",
+ ]);
+ // triggers sending ping to normandy
+ await BrowserTestUtils.closeWindow(targetWindow);
+ await telemetrySentPromise;
+});
+
+add_task(async function cleanup() {
+ const win = Services.wm.getMostRecentWindow("navigator:browser");
+ await BrowserTestUtils.closeWindow(win);
+});