023-desktop-windows.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*
  2. Desktop: float windows + context menus.
  3. Covers the window-manager and right-click surfaces of the desktop:
  4. - launching a module through openModule spawns a float window
  5. - newFloatWindow creates windows with a taskbar entry
  6. - focus brings a window to the front (z-index ordering)
  7. - maximize / restore toggles the window's max state
  8. - minimize hides the window; the close button destroys it
  9. - right-clicking the wallpaper shows the desktop context menu
  10. - right-clicking a desktop icon shows the icon context menu
  11. Window internals are driven through the app's own global functions
  12. and real button clicks, so the actual desktop.html logic runs.
  13. */
  14. "use strict";
  15. const h = require("../lib/system-harness");
  16. const SC_NAME = "e2e-win-shortcut";
  17. const SC_FILE = "user:/Desktop/" + SC_NAME + ".shortcut";
  18. async function openDesktop(page, base) {
  19. await page.goto(base + "/desktop.html", { waitUntil: "domcontentloaded" });
  20. await page.waitForSelector("#bgwrapper", { state: "visible", timeout: 20000 });
  21. await page.waitForFunction(function () {
  22. return typeof window.newFloatWindow === "function" &&
  23. Array.isArray(window.moduleInstalled) && window.moduleInstalled.length > 0;
  24. }, { timeout: 20000 });
  25. await page.waitForFunction(function () {
  26. return document.querySelectorAll("#bgwrapper .backgroundFrame").length > 0;
  27. }, { timeout: 20000 });
  28. }
  29. h.run("DESKTOP-WINDOWS", async function (env) {
  30. const base = env.baseURL;
  31. const page = await h.newPage(env.browser);
  32. await h.loginViaAPI(page, base, env.admin.username, env.admin.password);
  33. // Seed a desktop shortcut for the icon context-menu test.
  34. let resp = await h.postForm(page, base + "/system/desktop/createShortcut", {
  35. stype: "module", stext: SC_NAME, spath: "Dummy/index.html", sicon: "img/system/favicon.png"
  36. });
  37. if (resp.toLowerCase().indexOf("ok") === -1) h.fail("could not seed desktop shortcut: " + resp);
  38. await openDesktop(page, base);
  39. // ── 1. Launch a real module through the desktop launcher ──
  40. await page.evaluate(function () { openModule("Calculator"); });
  41. await page.waitForFunction(function () {
  42. var titles = document.querySelectorAll(".floatWindow .controls .title");
  43. for (var i = 0; i < titles.length; i++) {
  44. if (titles[i].textContent.indexOf("Calculator") !== -1) return true;
  45. }
  46. return false;
  47. }, { timeout: 20000 });
  48. h.ok("openModule launches a module into a float window");
  49. // A taskbar button was registered for it.
  50. const hasTaskbarBtn = await page.evaluate(function () {
  51. return document.querySelectorAll(".floatWindowButton").length > 0;
  52. });
  53. if (!hasTaskbarBtn) h.fail("launching a module did not add a taskbar button");
  54. h.ok("the launched module gets a taskbar button");
  55. // ── 2. newFloatWindow creates windows deterministically ──
  56. await page.evaluate(function () {
  57. newFloatWindow({ uid: "e2ewinA", url: "about:blank", title: "E2E Window A", left: 120, top: 120 });
  58. newFloatWindow({ uid: "e2ewinB", url: "about:blank", title: "E2E Window B", left: 260, top: 200 });
  59. });
  60. await page.waitForSelector(".floatWindow[windowId='e2ewinA']", { timeout: 10000 });
  61. await page.waitForSelector(".floatWindow[windowId='e2ewinB']", { timeout: 10000 });
  62. h.ok("newFloatWindow creates two addressable float windows");
  63. function zIndexOf(id) {
  64. return page.evaluate(function (wid) {
  65. var el = document.querySelector(".floatWindow[windowId='" + wid + "']");
  66. return parseInt(window.getComputedStyle(el).zIndex, 10) || 0;
  67. }, id);
  68. }
  69. // ── 3. Focus brings a window to the front ──
  70. // B was created last, so it starts above A.
  71. if (!((await zIndexOf("e2ewinB")) >= (await zIndexOf("e2ewinA")))) {
  72. h.fail("the last-created window should start on top");
  73. }
  74. // Focus A by mousedown on its drag bar; it must rise above B.
  75. await page.dispatchEvent(".floatWindow[windowId='e2ewinA'] .fwdragger", "mousedown", { button: 0, which: 1 });
  76. await page.waitForFunction(function () {
  77. function z(id) {
  78. var el = document.querySelector(".floatWindow[windowId='" + id + "']");
  79. return parseInt(window.getComputedStyle(el).zIndex, 10) || 0;
  80. }
  81. return z("e2ewinA") > z("e2ewinB");
  82. }, { timeout: 10000 });
  83. h.ok("focusing a window brings it above the others (z-index)");
  84. // ── 4. Maximize / restore ──
  85. await page.click(".floatWindow[windowId='e2ewinA'] .maxtoggle");
  86. await page.waitForFunction(function () {
  87. var el = document.querySelector(".floatWindow[windowId='e2ewinA']");
  88. return el && el.getAttribute("max") === "true";
  89. }, { timeout: 10000 });
  90. h.ok("maximize sets the window into its maximized state");
  91. await page.click(".floatWindow[windowId='e2ewinA'] .maxtoggle");
  92. await page.waitForFunction(function () {
  93. var el = document.querySelector(".floatWindow[windowId='e2ewinA']");
  94. return el && el.getAttribute("max") === "false";
  95. }, { timeout: 10000 });
  96. h.ok("restore returns the window to its normal state");
  97. // ── 5. Minimize hides the window ──
  98. await page.click(".floatWindow[windowId='e2ewinA'] .mintoggle");
  99. await page.waitForFunction(function () {
  100. var el = document.querySelector(".floatWindow[windowId='e2ewinA']");
  101. return el && window.getComputedStyle(el).display === "none";
  102. }, { timeout: 10000 });
  103. h.ok("minimize hides the window from the desktop");
  104. // ── 6. Close destroys the windows ──
  105. // A is hidden; close it via its own control, then close B.
  106. await page.evaluate(function () {
  107. closeFloatWindow($(".floatWindow[windowId='e2ewinA'] .closetoggle")[0], null);
  108. });
  109. await page.click(".floatWindow[windowId='e2ewinB'] .closetoggle");
  110. await page.waitForFunction(function () {
  111. return document.querySelectorAll(".floatWindow[windowId='e2ewinA'], .floatWindow[windowId='e2ewinB']").length === 0;
  112. }, { timeout: 10000 });
  113. h.ok("closing a window removes it from the desktop");
  114. // ── 7. Wallpaper right-click shows the desktop context menu ──
  115. await page.evaluate(function () {
  116. var bf = document.querySelector("#bgwrapper .backgroundFrame");
  117. bf.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 400, clientY: 320 }));
  118. });
  119. await page.waitForFunction(function () {
  120. var m = document.getElementById("contextmenu");
  121. return m && window.getComputedStyle(m).display !== "none" &&
  122. m.textContent.indexOf("Refresh") !== -1 &&
  123. m.textContent.indexOf("File Manager") !== -1;
  124. }, { timeout: 10000 });
  125. h.ok("right-clicking the wallpaper opens the desktop context menu");
  126. // Dismiss it.
  127. await page.evaluate(function () { if (typeof hideAllContextMenus === "function") hideAllContextMenus(); });
  128. // ── 8. Icon right-click shows the icon context menu ──
  129. await page.waitForFunction(function (name) {
  130. var icons = document.querySelectorAll(".launchIcon");
  131. for (var i = 0; i < icons.length; i++) {
  132. if (icons[i].textContent.indexOf(name) !== -1) return true;
  133. }
  134. return false;
  135. }, SC_NAME, { timeout: 20000 });
  136. await page.evaluate(function (name) {
  137. var icons = document.querySelectorAll(".launchIcon");
  138. for (var i = 0; i < icons.length; i++) {
  139. if (icons[i].textContent.indexOf(name) !== -1) {
  140. icons[i].dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 200, clientY: 200 }));
  141. return;
  142. }
  143. }
  144. }, SC_NAME);
  145. await page.waitForFunction(function () {
  146. var m = document.getElementById("contextmenu");
  147. return m && window.getComputedStyle(m).display !== "none" &&
  148. m.textContent.indexOf("Open") !== -1 && m.textContent.indexOf("Delete") !== -1;
  149. }, { timeout: 10000 });
  150. h.ok("right-clicking a desktop icon opens the icon context menu");
  151. // ── Cleanup ──
  152. const csrft = await h.csrfToken(page, base);
  153. await h.postForm(page, base + "/system/file_system/fileOpr", {
  154. opr: "delete", src: JSON.stringify([SC_FILE]), csrft: csrft
  155. });
  156. await page.close();
  157. });