sidepanel-account-records-manager.test.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
  5. function extractFunction(name) {
  6. const markers = [`async function ${name}(`, `function ${name}(`];
  7. const start = markers
  8. .map((marker) => sidepanelSource.indexOf(marker))
  9. .find((index) => index >= 0);
  10. if (start < 0) {
  11. throw new Error(`missing function ${name}`);
  12. }
  13. let parenDepth = 0;
  14. let signatureEnded = false;
  15. let braceStart = -1;
  16. for (let i = start; i < sidepanelSource.length; i += 1) {
  17. const ch = sidepanelSource[i];
  18. if (ch === '(') {
  19. parenDepth += 1;
  20. } else if (ch === ')') {
  21. parenDepth -= 1;
  22. if (parenDepth === 0) {
  23. signatureEnded = true;
  24. }
  25. } else if (ch === '{' && signatureEnded) {
  26. braceStart = i;
  27. break;
  28. }
  29. }
  30. let depth = 0;
  31. let end = braceStart;
  32. for (; end < sidepanelSource.length; end += 1) {
  33. const ch = sidepanelSource[end];
  34. if (ch === '{') depth += 1;
  35. if (ch === '}') {
  36. depth -= 1;
  37. if (depth === 0) {
  38. end += 1;
  39. break;
  40. }
  41. }
  42. }
  43. return sidepanelSource.slice(start, end);
  44. }
  45. function createButton() {
  46. return {
  47. disabled: false,
  48. textContent: '',
  49. hidden: false,
  50. listeners: {},
  51. addEventListener(type, handler) {
  52. this.listeners[type] = handler;
  53. },
  54. };
  55. }
  56. function createContainer() {
  57. return {
  58. innerHTML: '',
  59. textContent: '',
  60. hidden: true,
  61. listeners: {},
  62. addEventListener(type, handler) {
  63. this.listeners[type] = handler;
  64. },
  65. };
  66. }
  67. test('sidepanel html contains account records overlay and manager script', () => {
  68. const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
  69. const managerIndex = html.indexOf('<script src="account-records-manager.js"></script>');
  70. const sidepanelIndex = html.indexOf('<script src="sidepanel.js"></script>');
  71. assert.match(html, /id="btn-open-account-records"/);
  72. assert.match(html, /id="account-records-overlay"/);
  73. assert.match(html, /id="account-records-list"/);
  74. assert.match(html, /id="account-records-stats"/);
  75. assert.match(html, /id="btn-clear-account-records"/);
  76. assert.match(html, /id="input-sub2api-default-proxy"/);
  77. assert.notEqual(managerIndex, -1);
  78. assert.notEqual(sidepanelIndex, -1);
  79. assert.ok(managerIndex < sidepanelIndex);
  80. });
  81. test('sidepanel account records helper normalizes snapshot helper base url', () => {
  82. const bundle = [
  83. extractFunction('normalizeAccountRunHistoryHelperBaseUrlValue'),
  84. ].join('\n');
  85. const api = new Function(`
  86. const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
  87. ${bundle}
  88. return { normalizeAccountRunHistoryHelperBaseUrlValue };
  89. `)();
  90. assert.equal(
  91. api.normalizeAccountRunHistoryHelperBaseUrlValue('http://127.0.0.1:17373/sync-account-run-records'),
  92. 'http://127.0.0.1:17373'
  93. );
  94. });
  95. test('account records manager exposes a factory and renders summarized paginated records', () => {
  96. const source = fs.readFileSync('sidepanel/account-records-manager.js', 'utf8');
  97. const windowObject = {};
  98. const api = new Function('window', `${source}; return window.SidepanelAccountRecordsManager;`)(windowObject);
  99. assert.equal(typeof api?.createAccountRecordsManager, 'function');
  100. const btnOpenAccountRecords = createButton();
  101. const btnCloseAccountRecords = createButton();
  102. const btnClearAccountRecords = createButton();
  103. const btnAccountRecordsPrev = createButton();
  104. const btnAccountRecordsNext = createButton();
  105. const overlay = createContainer();
  106. const list = createContainer();
  107. const stats = createContainer();
  108. const meta = createContainer();
  109. const pageLabel = createContainer();
  110. const manager = api.createAccountRecordsManager({
  111. state: {
  112. getLatestState: () => ({
  113. accountRunHistory: [
  114. {
  115. email: 'success@example.com',
  116. password: 'secret',
  117. finalStatus: 'success',
  118. finishedAt: '2026-04-17T04:31:00.000Z',
  119. retryCount: 0,
  120. failureLabel: '流程完成',
  121. },
  122. {
  123. email: 'failed@example.com',
  124. password: 'secret',
  125. finalStatus: 'failed',
  126. finishedAt: '2026-04-17T04:29:00.000Z',
  127. retryCount: 2,
  128. failureLabel: '出现手机号验证',
  129. },
  130. ],
  131. }),
  132. syncLatestState() {},
  133. },
  134. dom: {
  135. accountRecordsList: list,
  136. accountRecordsMeta: meta,
  137. accountRecordsOverlay: overlay,
  138. accountRecordsPageLabel: pageLabel,
  139. accountRecordsStats: stats,
  140. btnAccountRecordsNext,
  141. btnAccountRecordsPrev,
  142. btnClearAccountRecords,
  143. btnCloseAccountRecords,
  144. btnOpenAccountRecords,
  145. },
  146. helpers: {
  147. escapeHtml: (value) => String(value || ''),
  148. openConfirmModal: async () => true,
  149. showToast() {},
  150. },
  151. runtime: {
  152. sendMessage: async () => ({ clearedCount: 2 }),
  153. },
  154. constants: {
  155. displayTimeZone: 'Asia/Shanghai',
  156. pageSize: 10,
  157. },
  158. });
  159. assert.equal(typeof manager.bindEvents, 'function');
  160. assert.equal(typeof manager.render, 'function');
  161. assert.equal(typeof manager.openPanel, 'function');
  162. manager.bindEvents();
  163. manager.render();
  164. assert.match(meta.textContent, /共 2 条/);
  165. assert.match(stats.innerHTML, /重试/);
  166. assert.match(list.innerHTML, /success@example\.com/);
  167. assert.match(list.innerHTML, /出现手机号验证/);
  168. assert.match(list.innerHTML, /重试 2/);
  169. assert.equal(pageLabel.textContent, '1 / 1');
  170. assert.equal(btnClearAccountRecords.disabled, false);
  171. });