backend.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta name="apple-mobile-web-app-capable" content="yes" />
  5. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
  6. <meta charset="UTF-8">
  7. <meta name="theme-color" content="#0078d4">
  8. <script src="../script/jquery.min.js"></script>
  9. <script src="../script/ao_module.js"></script>
  10. <title>ao_backend Test</title>
  11. <style>
  12. *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  13. body {
  14. font-family: 'Segoe UI Variable', 'Segoe UI', system-ui, -apple-system, sans-serif;
  15. font-size: 14px;
  16. background: #f3f3f3;
  17. color: #201f1e;
  18. min-height: 100vh;
  19. }
  20. /* ── Nav ───────────────────────────────────────────────── */
  21. .nav-bar { position: sticky; top: 0; z-index: 100; height: 48px; background: rgba(243,243,243,0.9); backdrop-filter: blur(20px) saturate(180%); border-bottom: 1px solid rgba(0,0,0,0.08); display: flex; align-items: center; padding: 0 20px; gap: 4px; }
  22. .nav-brand { font-size: 13px; font-weight: 600; color: #201f1e; margin-right: 12px; white-space: nowrap; }
  23. .nav-tab { display: inline-flex; align-items: center; height: 48px; padding: 0 12px; font-size: 13px; color: #605e5c; text-decoration: none; border-bottom: 2px solid transparent; transition: color 0.1s, border-color 0.1s; white-space: nowrap; }
  24. .nav-tab:hover { color: #201f1e; }
  25. .nav-tab.active { color: #0078d4; border-bottom-color: #0078d4; font-weight: 600; }
  26. /* ── Layout ────────────────────────────────────────────── */
  27. .content { max-width: 880px; margin: 24px auto; padding: 0 20px; }
  28. /* ── Toolbar ───────────────────────────────────────────── */
  29. .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
  30. .toolbar-sep { width: 1px; height: 20px; background: #d1d1d1; margin: 0 4px; }
  31. .summary-text { font-size: 12px; color: #797775; }
  32. .btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 16px; border-radius: 4px; font-family: inherit; font-size: 13px; cursor: pointer; border: 1px solid transparent; transition: background 0.1s; line-height: 1; white-space: nowrap; }
  33. .btn-primary { background: #0078d4; color: #fff; border-color: #0078d4; }
  34. .btn-primary:hover { background: #106ebe; }
  35. .btn-primary:disabled { opacity: 0.55; cursor: default; }
  36. .btn-secondary { background: #fff; color: #201f1e; border-color: #d1d1d1; }
  37. .btn-secondary:hover { background: #f5f5f5; }
  38. /* ── Stats pills ───────────────────────────────────────── */
  39. .stats-bar { display: none; align-items: center; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
  40. .stat-pill { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; background: #fff; border: 1px solid #e0e0e0; }
  41. .stat-pill.all { color: #201f1e; }
  42. .stat-pill.passed { background: #dff6dd; border-color: #bad7b9; color: #107c10; }
  43. .stat-pill.failed { background: #fde7e9; border-color: #f1b8bb; color: #c50f1f; }
  44. .stat-pill.pending{ color: #797775; }
  45. /* ── Test list ─────────────────────────────────────────── */
  46. .test-list { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05); margin-bottom: 20px; }
  47. .section-header {
  48. padding: 8px 16px 8px 16px;
  49. font-size: 11px;
  50. font-weight: 700;
  51. color: #fff;
  52. background: #0078d4;
  53. letter-spacing: 0.04em;
  54. text-transform: uppercase;
  55. }
  56. .section-header.appdata { background: #8764b8; }
  57. .section-header.file { background: #107c10; }
  58. .section-header.http { background: #ca5010; }
  59. .test-list-header { padding: 8px 16px; font-size: 11px; font-weight: 600; color: #797775; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; gap: 12px; }
  60. .col-name { flex: 1; }
  61. .col-status { width: 82px; text-align: center; flex-shrink: 0; }
  62. .col-action { width: 52px; flex-shrink: 0; text-align: right; }
  63. .test-item { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid #f5f5f5; transition: background 0.1s; }
  64. .test-item:last-child { border-bottom: none; }
  65. .test-item:hover { background: #fafafa; }
  66. .test-idx { font-size: 11px; color: #bbb; width: 22px; text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums; }
  67. .test-info { flex: 1; min-width: 0; }
  68. .test-name { font-size: 13px; font-weight: 500; }
  69. .test-desc { font-size: 11px; color: #a0a0a0; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: 'Cascadia Code', 'Consolas', monospace; }
  70. .status-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; width: 82px; justify-content: center; flex-shrink: 0; }
  71. .status-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
  72. .s-pending { background: #f3f3f3; color: #797775; }
  73. .s-pending .status-dot { background: #c0c0c0; }
  74. .s-running { background: #eff6fc; color: #0078d4; }
  75. .s-running .status-dot { background: #0078d4; animation: pulse 1s ease-in-out infinite; }
  76. .s-pass { background: #dff6dd; color: #107c10; }
  77. .s-pass .status-dot { background: #107c10; }
  78. .s-fail { background: #fde7e9; color: #c50f1f; }
  79. .s-fail .status-dot { background: #c50f1f; }
  80. @keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.7)} }
  81. .btn-run { padding: 4px 10px; font-size: 12px; border-radius: 3px; background: transparent; border: 1px solid #d1d1d1; color: #444; cursor: pointer; font-family: inherit; transition: background 0.1s; }
  82. .btn-run:hover { background: #f0f0f0; }
  83. .btn-run:disabled { opacity: 0.45; cursor: default; }
  84. /* ── Result area ───────────────────────────────────────── */
  85. .result-area { display: none; padding: 10px 16px 12px 50px; font-family: 'Cascadia Code', 'Consolas', monospace; font-size: 12px; line-height: 1.5; background: #fafafa; border-top: 1px solid #f0f0f0; border-left: 3px solid transparent; word-break: break-all; color: #323130; }
  86. .result-area.r-pass { border-left-color: #107c10; }
  87. .result-area.r-fail { border-left-color: #c50f1f; }
  88. </style>
  89. </head>
  90. <body>
  91. <nav class="nav-bar">
  92. <span class="nav-brand">Unit Tester</span>
  93. <a class="nav-tab" href="index.html">AGI Scripts</a>
  94. <a class="nav-tab active" href="backend.html">ao_backend</a>
  95. <a class="nav-tab" href="wstest.html">WebSocket</a>
  96. <a class="nav-tab" href="devmode.html">Dev Mode</a>
  97. </nav>
  98. <div class="content">
  99. <div class="toolbar">
  100. <button class="btn btn-primary" id="btnRunAll" onclick="runAll()">&#9654;&nbsp; Run All</button>
  101. <button class="btn btn-secondary" onclick="clearAll()">Clear</button>
  102. <div class="toolbar-sep"></div>
  103. <span class="summary-text" id="summaryText">12 tests</span>
  104. </div>
  105. <div class="stats-bar" id="statsBar">
  106. <span class="stat-pill all"><span id="statTotal">12</span> total</span>
  107. <span class="stat-pill passed">&#10003;&nbsp;<span id="statPass">0</span> passed</span>
  108. <span class="stat-pill failed">&#10007;&nbsp;<span id="statFail">0</span> failed</span>
  109. <span class="stat-pill pending">&middot;&nbsp;<span id="statPending">12</span> pending</span>
  110. </div>
  111. <div id="testContainer"></div>
  112. </div>
  113. <script>
  114. /* ── Test definitions ──────────────────────────────────── */
  115. const GROUPS = [
  116. {
  117. label: "App Data",
  118. cls: "appdata",
  119. tests: [
  120. { name: "appdata.readFile",
  121. desc: 'appdata.readFile("UnitTest/appdata.txt")',
  122. run: function(w, ok, fail) {
  123. w.appdata.readFile("UnitTest/appdata.txt", function(c) { ok(c); });
  124. }
  125. },
  126. { name: "appdata.listDir",
  127. desc: 'appdata.listDir("UnitTest/")',
  128. run: function(w, ok, fail) {
  129. w.appdata.listDir("UnitTest/", function(list) { ok(JSON.stringify(list)); });
  130. }
  131. }
  132. ]
  133. },
  134. {
  135. label: "File System",
  136. cls: "file",
  137. tests: [
  138. { name: "file.writeFile",
  139. desc: 'file.writeFile("user:/Desktop/hello.txt", "Hello World!")',
  140. run: function(w, ok, fail) {
  141. w.file.writeFile("user:/Desktop/hello.txt", "Hello World!", function(r) { ok(r); });
  142. }
  143. },
  144. { name: "file.readFile",
  145. desc: 'file.readFile("user:/Desktop/hello.txt")',
  146. run: function(w, ok, fail) {
  147. w.file.readFile("user:/Desktop/hello.txt", function(c) { ok(c); });
  148. }
  149. },
  150. { name: "file.readdir",
  151. desc: 'file.readdir("user:/Desktop/")',
  152. run: function(w, ok, fail) {
  153. w.file.readdir("user:/Desktop/", function(files) { ok(JSON.stringify(files)); });
  154. }
  155. },
  156. { name: "file.mtime",
  157. desc: 'file.mtime("user:/Desktop/hello.txt")',
  158. run: function(w, ok, fail) {
  159. w.file.mtime("user:/Desktop/hello.txt", function(t) { ok(t); });
  160. }
  161. },
  162. { name: "file.isDir",
  163. desc: 'file.isDir("user:/Desktop/hello.txt")',
  164. run: function(w, ok, fail) {
  165. w.file.isDir("user:/Desktop/hello.txt", function(r) { ok(String(r)); });
  166. }
  167. },
  168. { name: "file.filesize",
  169. desc: 'file.filesize("user:/Desktop/hello.txt")',
  170. run: function(w, ok, fail) {
  171. w.file.filesize("user:/Desktop/hello.txt", function(s) { ok(s + " bytes"); });
  172. }
  173. },
  174. { name: "file.aglob",
  175. desc: 'file.aglob("user:/Desktop/*.mp3")',
  176. run: function(w, ok, fail) {
  177. w.file.aglob("user:/Desktop/*.mp3", undefined, function(r) { ok(JSON.stringify(r)); });
  178. }
  179. }
  180. ]
  181. },
  182. {
  183. label: "HTTP",
  184. cls: "http",
  185. tests: [
  186. { name: "http.get",
  187. desc: 'http.get("https://www.google.com/robots.txt")',
  188. run: function(w, ok, fail) {
  189. w.http.get("https://www.google.com/robots.txt", function(r) {
  190. ok(r ? r.slice(0, 120) + (r.length > 120 ? "…" : "") : "(empty)");
  191. });
  192. }
  193. },
  194. { name: "http.post",
  195. desc: 'http.post("https://www.google.com/robots.txt", undefined)',
  196. run: function(w, ok, fail) {
  197. w.http.post("https://www.google.com/robots.txt", undefined, function(r) {
  198. ok(r ? r.slice(0, 120) + (r.length > 120 ? "…" : "") : "(empty)");
  199. });
  200. }
  201. }
  202. ]
  203. }
  204. ];
  205. /* ── Flatten for stats ─────────────────────────────────── */
  206. var allTests = [];
  207. GROUPS.forEach(function(g) {
  208. g.tests.forEach(function(t) {
  209. t.state = "pending";
  210. t.id = allTests.length;
  211. allTests.push(t);
  212. });
  213. });
  214. /* ── Backend wrapper (init once) ───────────────────────── */
  215. var backendWrapper = ao_module_backend();
  216. backendWrapper.start("UnitTest/ao_backend.js");
  217. /* ── Build UI ──────────────────────────────────────────── */
  218. (function buildUI() {
  219. let container = document.getElementById("testContainer");
  220. GROUPS.forEach(function(g) {
  221. let html = '<div class="test-list">' +
  222. '<div class="section-header ' + g.cls + '">' + g.label + '</div>' +
  223. '<div class="test-list-header">' +
  224. '<span style="width:22px;flex-shrink:0"></span>' +
  225. '<span class="col-name">Test</span>' +
  226. '<span class="col-status">Status</span>' +
  227. '<span class="col-action"></span>' +
  228. '</div>';
  229. g.tests.forEach(function(t) {
  230. html += '<div class="test-item" id="item_' + t.id + '">' +
  231. '<span class="test-idx">' + (t.id + 1) + '</span>' +
  232. '<div class="test-info">' +
  233. '<div class="test-name">' + escHtml(t.name) + '</div>' +
  234. '<div class="test-desc">' + escHtml(t.desc) + '</div>' +
  235. '</div>' +
  236. '<span class="status-badge s-pending" id="badge_' + t.id + '">' +
  237. '<span class="status-dot"></span>Pending' +
  238. '</span>' +
  239. '<div class="col-action">' +
  240. '<button class="btn-run" id="runbtn_' + t.id + '" onclick="runTest(' + t.id + ')">Run</button>' +
  241. '</div>' +
  242. '</div>' +
  243. '<div class="result-area" id="result_' + t.id + '"></div>';
  244. });
  245. html += '</div>';
  246. container.innerHTML += html;
  247. });
  248. updateStats();
  249. document.getElementById("statsBar").style.display = "flex";
  250. })();
  251. /* ── Run logic ─────────────────────────────────────────── */
  252. function runTest(id) {
  253. let t = allTests[id];
  254. let btn = document.getElementById("runbtn_" + id);
  255. setStatus(id, "running");
  256. if (btn) btn.disabled = true;
  257. let timer = setTimeout(function() {
  258. setStatus(id, "fail");
  259. showResult(id, "r-fail", "Timeout — no response after 10 s");
  260. if (btn) btn.disabled = false;
  261. }, 10000);
  262. t.run(backendWrapper,
  263. function(result) { // ok
  264. clearTimeout(timer);
  265. setStatus(id, "pass");
  266. showResult(id, "r-pass", escHtml(String(result || "(empty)")));
  267. if (btn) btn.disabled = false;
  268. },
  269. function(reason) { // fail
  270. clearTimeout(timer);
  271. setStatus(id, "fail");
  272. showResult(id, "r-fail", escHtml(String(reason || "Unknown error")));
  273. if (btn) btn.disabled = false;
  274. }
  275. );
  276. }
  277. function runAll() {
  278. let btn = document.getElementById("btnRunAll");
  279. btn.disabled = true;
  280. btn.textContent = "Running…";
  281. allTests.forEach(function(t) { runTest(t.id); });
  282. let check = setInterval(function() {
  283. if (!allTests.some(function(t) { return t.state === "running"; })) {
  284. clearInterval(check);
  285. btn.disabled = false;
  286. btn.innerHTML = "&#9654;&nbsp; Run All";
  287. }
  288. }, 300);
  289. }
  290. function clearAll() {
  291. allTests.forEach(function(t) {
  292. t.state = "pending";
  293. setStatus(t.id, "pending");
  294. let r = document.getElementById("result_" + t.id);
  295. if (r) { r.style.display = "none"; r.innerHTML = ""; r.className = "result-area"; }
  296. });
  297. updateStats();
  298. }
  299. /* ── Helpers ───────────────────────────────────────────── */
  300. function setStatus(id, state) {
  301. allTests[id].state = state;
  302. let badge = document.getElementById("badge_" + id);
  303. if (!badge) return;
  304. let labels = { pending:"Pending", running:"Running", pass:"Passed", fail:"Failed" };
  305. badge.className = "status-badge s-" + state;
  306. badge.innerHTML = '<span class="status-dot"></span>' + labels[state];
  307. updateStats();
  308. }
  309. function showResult(id, cls, html) {
  310. let div = document.getElementById("result_" + id);
  311. if (!div) return;
  312. div.className = "result-area " + cls;
  313. div.innerHTML = html;
  314. div.style.display = "block";
  315. }
  316. function updateStats() {
  317. let total = allTests.length;
  318. let passed = allTests.filter(function(t) { return t.state === "pass"; }).length;
  319. let failed = allTests.filter(function(t) { return t.state === "fail"; }).length;
  320. let pending = allTests.filter(function(t) { return t.state !== "pass" && t.state !== "fail"; }).length;
  321. document.getElementById("statTotal").textContent = total;
  322. document.getElementById("statPass").textContent = passed;
  323. document.getElementById("statFail").textContent = failed;
  324. document.getElementById("statPending").textContent = pending;
  325. }
  326. function escHtml(s) {
  327. return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
  328. }
  329. </script>
  330. </body>
  331. </html>