test_fxview_tab_list.html (14435B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>FxviewTabList Tests</title> 6 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 7 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> 8 <link rel="localization" href="browser/firefoxView.ftl"> 9 <link rel="localization" href="browser/fxviewTabList.ftl"> 10 <link rel="localization" href="browser/places.ftl"> 11 <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> 12 <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"> 13 <script type="module" src="chrome://browser/content/firefoxview/fxview-tab-list.mjs"></script> 14 </head> 15 <body> 16 <p id="display"></p> 17 <div id="content" style="max-width: 750px"> 18 <fxview-tab-list class="history" .dateTimeFormat="relative" .hasPopup="menu"> 19 <panel-list slot="menu"> 20 <panel-item data-l10n-id="fxviewtabrow-delete"></panel-item> 21 <panel-item data-l10n-id="fxviewtabrow-forget-about-this-site"></panel-item> 22 <hr /> 23 <panel-item data-l10n-id="fxviewtabrow-open-in-window"></panel-item> 24 <panel-item data-l10n-id="fxviewtabrow-open-in-private-window"></panel-item> 25 <hr /> 26 <panel-item data-l10n-id="fxviewtabrow-add-bookmark"></panel-item> 27 <panel-item data-l10n-id="fxviewtabrow-save-to-pocket"></panel-item> 28 <panel-item data-l10n-id="fxviewtabrow-copy-link"></panel-item> 29 </panel-list> 30 </fxview-tab-list> 31 </div> 32 <pre id="test"> 33 <script class="testbody" type="application/javascript"> 34 Services.scriptloader.loadSubScript( 35 "chrome://browser/content/utilityOverlay.js", 36 this 37 ); 38 39 const { BrowserTestUtils } = ChromeUtils.importESModule( 40 "resource://testing-common/BrowserTestUtils.sys.mjs" 41 ); 42 const { PlacesQuery } = ChromeUtils.importESModule( 43 "resource://gre/modules/PlacesQuery.sys.mjs" 44 ); 45 const { PlacesUtils } = ChromeUtils.importESModule( 46 "resource://gre/modules/PlacesUtils.sys.mjs" 47 ); 48 const { PlacesUIUtils } = ChromeUtils.importESModule( 49 "moz-src:///browser/components/places/PlacesUIUtils.sys.mjs" 50 ); 51 const { PlacesTestUtils } = ChromeUtils.importESModule( 52 "resource://testing-common/PlacesTestUtils.sys.mjs" 53 ); 54 55 const fxviewTabList = document.querySelector("fxview-tab-list"); 56 let tabItems = []; 57 const placesQuery = new PlacesQuery(); 58 59 const URLs = [ 60 "http://mochi.test:8888/browser/", 61 "https://www.example.com/", 62 "https://example.net/", 63 "https://example.org/", 64 "https://www.mozilla.org/" 65 ]; 66 67 async function addHistoryItems() { 68 await PlacesUtils.history.clear(); 69 let history = await placesQuery.getHistory(); 70 71 const now = new Date(); 72 await PlacesUtils.history.insert({ 73 url: URLs[0], 74 title: "Example Domain 1", 75 visits: [{ date: now }], 76 }); 77 let historyUpdated = Promise.withResolvers(); 78 placesQuery.observeHistory(newHistory => { 79 history = newHistory; 80 historyUpdated.resolve(); 81 }); 82 await PlacesUtils.history.insert({ 83 url: URLs[1], 84 title: "Example Domain 2", 85 visits: [{ date: now }], 86 }); 87 await historyUpdated.promise; 88 historyUpdated = Promise.withResolvers(); 89 placesQuery.observeHistory(newHistory => { 90 history = newHistory; 91 historyUpdated.resolve(); 92 }); 93 await PlacesUtils.history.insert({ 94 url: URLs[2], 95 title: "Example Domain 3", 96 visits: [{ date: now }], 97 }); 98 await historyUpdated.promise; 99 historyUpdated = Promise.withResolvers(); 100 placesQuery.observeHistory(newHistory => { 101 history = newHistory; 102 historyUpdated.resolve(); 103 }); 104 await PlacesUtils.history.insert({ 105 url: URLs[3], 106 title: "Example Domain 4", 107 visits: [{ date: now }], 108 }); 109 await historyUpdated.promise; 110 111 fxviewTabList.tabItems = Array.from(history.values()).flat().map(visit => ({ 112 ...visit, 113 time: visit.date.getTime(), 114 title: visit.title || visit.url, 115 icon: `page-icon:${visit.url}`, 116 primaryL10nId: "fxviewtabrow-tabs-list-tab", 117 primaryL10nArgs: JSON.stringify({ 118 targetURI: visit.url, 119 }), 120 secondaryL10nId: "fxviewtabrow-options-menu-button", 121 secondaryL10nArgs: JSON.stringify({ 122 tabTitle: visit.title || visit.url, 123 }), 124 })); 125 126 await fxviewTabList.getUpdateComplete(); 127 tabItems = Array.from(fxviewTabList.rowEls); 128 } 129 130 function getCurrentDisplayDate() { 131 let lastItemMainEl = tabItems[tabItems.length - 1].mainEl; 132 return lastItemMainEl.querySelector("#fxview-tab-row-date span:not([hidden])")?.textContent.trim() ?? ""; 133 } 134 135 function getCurrentDisplayTime() { 136 let lastItemMainEl = tabItems[tabItems.length - 1].mainEl; 137 return lastItemMainEl.querySelector("#fxview-tab-row-time")?.textContent.trim() ?? ""; 138 } 139 140 function isActiveElement(expectedLinkEl) { 141 return expectedLinkEl.getRootNode().activeElement == expectedLinkEl; 142 } 143 144 function onPrimaryAction(e) { 145 let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser; 146 gBrowser.addTrustedTab(e.originalTarget.url); 147 } 148 149 function onSecondaryAction(e) { 150 e.target.querySelector("panel-list").toggle(e.detail.originalEvent); 151 } 152 153 add_setup(function setup() { 154 fxviewTabList.addEventListener("fxview-tab-list-primary-action", onPrimaryAction); 155 fxviewTabList.addEventListener("fxview-tab-list-secondary-action", onSecondaryAction); 156 fxviewTabList.updatesPaused = false; 157 }); 158 159 /** 160 * Tests that history items are loaded in the expected order 161 */ 162 add_task(async function test_list_ordering() { 163 await addHistoryItems(); 164 is( 165 tabItems.length, 166 4, 167 "Four history items are shown in the list." 168 ); 169 170 // Check ordering 171 ok( 172 tabItems[0].title === "Example Domain 4", 173 "First history item in fxview-tab-list is in the correct order." 174 ) 175 176 ok( 177 tabItems[3].title === "Example Domain 1", 178 "Last history item in fxview-tab-list is in the correct order." 179 ) 180 }); 181 182 /** 183 * Tests the primary action function is triggered when selecting the main row element 184 */ 185 add_task(async function test_primary_action(){ 186 await addHistoryItems(); 187 let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser; 188 let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, tabItems[0].url); 189 tabItems[0].mainEl.click(); 190 await newTabPromise; 191 192 is( 193 tabItems.length, 194 4, 195 "Four history items are still shown in the list." 196 ); 197 198 await BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); 199 }); 200 201 /** 202 * Tests that a max tabs length value can be given to fxview-tab-list 203 */ 204 add_task(async function test_max_list_items() { 205 const mockMaxTabsLength = 3; 206 207 // override this value for testing purposes 208 fxviewTabList.maxTabsLength = mockMaxTabsLength; 209 await addHistoryItems(); 210 211 is( 212 tabItems.length, 213 mockMaxTabsLength, 214 `fxview-tabs-list should have ${mockMaxTabsLength} list items` 215 ); 216 217 // Add new history items 218 let history = await placesQuery.getHistory(); 219 220 const now = new Date(); 221 await PlacesUtils.history.insert({ 222 url: URLs[4], 223 title: "Internet for people, not profits - Mozilla", 224 visits: [{ date: now }], 225 }); 226 let historyUpdated = Promise.withResolvers(); 227 placesQuery.observeHistory(newHistory => { 228 history = newHistory; 229 historyUpdated.resolve(); 230 }); 231 await historyUpdated.promise; 232 233 ok( 234 [...history.values()].reduce((acc, {length}) => acc + length, 0) === 5, 235 "Five total history items after inserting another node" 236 ); 237 238 // Update fxview-tab-list component with latest history data 239 fxviewTabList.tabItems = [...history.values()].flat(); 240 await fxviewTabList.getUpdateComplete(); 241 tabItems = Array.from(fxviewTabList.rowEls); 242 243 is( 244 tabItems.length, 245 mockMaxTabsLength, 246 `fxview-tabs-list should have ${mockMaxTabsLength} list items` 247 ); 248 249 ok( 250 tabItems[0].title === "Internet for people, not profits - Mozilla", 251 "History list has been updated with the expected maxTabsLength." 252 ) 253 fxviewTabList.maxTabsLength = 25; 254 }); 255 256 /** 257 * Tests keyboard navigation of the fxview-tab-list component 258 */ 259 add_task(async function test_keyboard_navigation() { 260 const arrowDown = async () => { 261 info("Arrow down"); 262 synthesizeKey("KEY_ArrowDown", {}); 263 await fxviewTabList.getUpdateComplete(); 264 }; 265 const arrowUp = async () => { 266 info("Arrow up"); 267 synthesizeKey("KEY_ArrowUp", {}); 268 await fxviewTabList.getUpdateComplete(); 269 }; 270 const arrowRight = async () => { 271 info("Arrow right"); 272 synthesizeKey("KEY_ArrowRight", {}); 273 await fxviewTabList.getUpdateComplete(); 274 }; 275 const arrowLeft = async () => { 276 info("Arrow left"); 277 synthesizeKey("KEY_ArrowLeft", {}); 278 await fxviewTabList.getUpdateComplete(); 279 }; 280 281 await addHistoryItems(); 282 tabItems[0].mainEl.focus(); 283 ok( 284 isActiveElement(tabItems[0].mainEl), 285 "Focus should be on the first main element of the list" 286 ); 287 288 // Arrow down/up the list 289 await arrowDown(); 290 ok( 291 isActiveElement(tabItems[1].mainEl), 292 "Focus should be on the second main element of the list" 293 ); 294 await arrowDown(); 295 ok( 296 isActiveElement(tabItems[2].mainEl), 297 "Focus should be on the third main element of the list" 298 ); 299 await arrowDown(); 300 ok( 301 isActiveElement(tabItems[3].mainEl), 302 "Focus should be on the fourth main element of the list" 303 ); 304 await arrowUp(); 305 ok( 306 isActiveElement(tabItems[2].mainEl), 307 "Focus should be on the third main element of the list" 308 ); 309 await arrowUp(); 310 ok( 311 isActiveElement(tabItems[1].mainEl), 312 "Focus should be on the second main element of the list" 313 ); 314 await arrowUp(); 315 ok( 316 isActiveElement(tabItems[0].mainEl), 317 "Focus should be on the first main element of the list" 318 ); 319 await arrowRight(); 320 ok( 321 isActiveElement(tabItems[0].secondaryButtonEl), 322 "Focus should be on the first row's context menu button element of the list" 323 ); 324 await arrowDown(); 325 ok( 326 isActiveElement(tabItems[1].secondaryButtonEl), 327 "Focus should be on the second row's context menu button element of the list" 328 ); 329 await arrowLeft(); 330 ok( 331 isActiveElement(tabItems[1].mainEl), 332 "Focus should be on the second main element of the list" 333 ); 334 await arrowUp(); 335 ok( 336 isActiveElement(tabItems[0].mainEl), 337 "Focus should be on the first main element of the list" 338 ); 339 }); 340 341 /** 342 * Tests relative time format for the fxview-tab-list component 343 */ 344 add_task(async function test_relative_format() { 345 await addHistoryItems(); 346 ok( 347 getCurrentDisplayDate().includes("Just now"), 348 "Current dateTime format is 'relative' and date displays 'Just now' initially" 349 ); 350 ok( 351 !getCurrentDisplayTime().length, 352 "Current dateTime format is 'relative' and time displays an empty string" 353 ); 354 }); 355 356 /** 357 * Tests date only format for the fxview-tab-list component 358 */ 359 add_task(async function test_date_only_format() { 360 await addHistoryItems(); 361 362 // Check date only format 363 fxviewTabList.dateTimeFormat = "date"; 364 await fxviewTabList.getUpdateComplete(); 365 await BrowserTestUtils.waitForCondition(() => { 366 return getCurrentDisplayDate().includes("/"); 367 }); 368 ok( 369 getCurrentDisplayDate().includes("/"), 370 "Current dateTime format is 'date' and displays the current date" 371 ); 372 ok( 373 !getCurrentDisplayTime().length, 374 "Current dateTime format is 'date' and time displays an empty string" 375 ); 376 }); 377 378 /** 379 * Tests time only format for the fxview-tab-list component 380 */ 381 add_task(async function test_time_only_format() { 382 await addHistoryItems(); 383 384 // Check time only format 385 fxviewTabList.dateTimeFormat = "time"; 386 await fxviewTabList.getUpdateComplete(); 387 await BrowserTestUtils.waitForCondition(() => { 388 return getCurrentDisplayTime().includes("AM") || getCurrentDisplayTime().includes("PM"); 389 }); 390 ok( 391 !getCurrentDisplayDate().length, 392 "Current dateTime format is 'time' and date displays an empty string" 393 ); 394 ok( 395 getCurrentDisplayTime().includes("AM") || getCurrentDisplayTime().includes("PM"), 396 "Current dateTime format is 'time' and displays the current time" 397 ); 398 }); 399 400 /** 401 * Tests date and time format for the fxview-tab-list component 402 */ 403 add_task(async function test_date_and_time_format() { 404 await addHistoryItems(); 405 406 // Check date and time format 407 fxviewTabList.dateTimeFormat = "dateTime"; 408 await fxviewTabList.getUpdateComplete(); 409 await BrowserTestUtils.waitForCondition(() => { 410 return getCurrentDisplayDate().includes("/") && 411 (getCurrentDisplayTime().includes("AM") || getCurrentDisplayTime().includes("PM")); 412 }); 413 ok( 414 getCurrentDisplayDate().includes("/"), 415 "Current dateTime format is 'dateTime' and date displays the current date" 416 ); 417 ok( 418 getCurrentDisplayTime().includes("AM") || getCurrentDisplayTime().includes("PM"), 419 "Current dateTime format is 'dateTime' and displays the current time" 420 ); 421 422 // Reset dateTimeFormat to relative before next test 423 fxviewTabList.dateTimeFormat = "relative"; 424 await fxviewTabList.getUpdateComplete(); 425 }); 426 427 /** 428 * Tests that relative time updates properly for the fxview-tab-list component 429 */ 430 add_task(async function test_relative_time_updates() { 431 await addHistoryItems(); 432 433 await BrowserTestUtils.waitForCondition(() => { 434 return getCurrentDisplayDate().includes("Just now"); 435 }); 436 437 ok( 438 getCurrentDisplayDate().includes("Just now"), 439 "Current date element displays 'Just now' initially" 440 ); 441 442 // Set the updateTimeMs pref to something low to check that relative time updates properly 443 const TAB_UPDATE_TIME_MS = 500; 444 await SpecialPowers.pushPrefEnv({ 445 set: [["browser.tabs.firefox-view.updateTimeMs", TAB_UPDATE_TIME_MS]], 446 }); 447 await BrowserTestUtils.waitForCondition(() => { 448 return !getCurrentDisplayDate().includes("now"); 449 }); 450 info("Currently displayed date is something other than 'Just now'"); 451 452 await SpecialPowers.popPrefEnv(); 453 }); 454 </script> 455 </pre> 456 </body> 457 </html>