openxiangda-cli 2.0.0-alpha.18 → 2.0.0-alpha.183

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/README.md +6 -14
  2. package/bin/run.js +79 -1
  3. package/dist/base.d.ts +60 -5
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +197 -10
  6. package/dist/base.js.map +1 -1
  7. package/dist/commands/accept.d.ts +28 -0
  8. package/dist/commands/accept.d.ts.map +1 -0
  9. package/dist/commands/accept.js +21 -0
  10. package/dist/commands/accept.js.map +1 -0
  11. package/dist/commands/admin.d.ts +32 -0
  12. package/dist/commands/admin.d.ts.map +1 -0
  13. package/dist/commands/admin.js +25 -0
  14. package/dist/commands/admin.js.map +1 -0
  15. package/dist/commands/cancel.d.ts +19 -1
  16. package/dist/commands/cancel.d.ts.map +1 -1
  17. package/dist/commands/cancel.js +1 -1
  18. package/dist/commands/cancel.js.map +1 -1
  19. package/dist/commands/check.d.ts +23 -1
  20. package/dist/commands/check.d.ts.map +1 -1
  21. package/dist/commands/check.js +21 -4
  22. package/dist/commands/check.js.map +1 -1
  23. package/dist/commands/context.d.ts +27 -0
  24. package/dist/commands/context.d.ts.map +1 -0
  25. package/dist/commands/context.js +10 -0
  26. package/dist/commands/context.js.map +1 -0
  27. package/dist/commands/create.d.ts +40 -0
  28. package/dist/commands/create.d.ts.map +1 -0
  29. package/dist/commands/create.js +475 -0
  30. package/dist/commands/create.js.map +1 -0
  31. package/dist/commands/deploy.d.ts +28 -8
  32. package/dist/commands/deploy.d.ts.map +1 -1
  33. package/dist/commands/deploy.js +30 -18
  34. package/dist/commands/deploy.js.map +1 -1
  35. package/dist/commands/dev.d.ts +23 -1
  36. package/dist/commands/dev.d.ts.map +1 -1
  37. package/dist/commands/dev.js +28 -4
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/docs.d.ts +30 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +20 -0
  42. package/dist/commands/docs.js.map +1 -0
  43. package/dist/commands/login.d.ts +29 -0
  44. package/dist/commands/login.d.ts.map +1 -0
  45. package/dist/commands/{auth/login.js → login.js} +27 -9
  46. package/dist/commands/login.js.map +1 -0
  47. package/dist/commands/logs.d.ts +22 -2
  48. package/dist/commands/logs.d.ts.map +1 -1
  49. package/dist/commands/logs.js +9 -5
  50. package/dist/commands/logs.js.map +1 -1
  51. package/dist/commands/retry.d.ts +19 -1
  52. package/dist/commands/retry.d.ts.map +1 -1
  53. package/dist/commands/retry.js +5 -2
  54. package/dist/commands/retry.js.map +1 -1
  55. package/dist/commands/rollback.d.ts +23 -4
  56. package/dist/commands/rollback.d.ts.map +1 -1
  57. package/dist/commands/rollback.js +18 -5
  58. package/dist/commands/rollback.js.map +1 -1
  59. package/dist/commands/skill.d.ts +32 -0
  60. package/dist/commands/skill.d.ts.map +1 -0
  61. package/dist/commands/skill.js +50 -0
  62. package/dist/commands/skill.js.map +1 -0
  63. package/dist/commands/spec.d.ts +42 -0
  64. package/dist/commands/spec.d.ts.map +1 -0
  65. package/dist/commands/spec.js +123 -0
  66. package/dist/commands/spec.js.map +1 -0
  67. package/dist/commands/start.d.ts +28 -0
  68. package/dist/commands/start.d.ts.map +1 -0
  69. package/dist/commands/start.js +18 -0
  70. package/dist/commands/start.js.map +1 -0
  71. package/dist/commands/status.d.ts +20 -1
  72. package/dist/commands/status.d.ts.map +1 -1
  73. package/dist/commands/status.js +11 -4
  74. package/dist/commands/status.js.map +1 -1
  75. package/dist/commands/stop.d.ts +28 -0
  76. package/dist/commands/stop.d.ts.map +1 -0
  77. package/dist/commands/stop.js +18 -0
  78. package/dist/commands/stop.js.map +1 -0
  79. package/dist/create-workspace.d.ts +67 -0
  80. package/dist/create-workspace.d.ts.map +1 -0
  81. package/dist/create-workspace.js +631 -0
  82. package/dist/create-workspace.js.map +1 -0
  83. package/dist/studio-events.d.ts +9 -0
  84. package/dist/studio-events.d.ts.map +1 -0
  85. package/dist/studio-events.js +30 -0
  86. package/dist/studio-events.js.map +1 -0
  87. package/dist/toolchain-capsule.d.ts +4 -0
  88. package/dist/toolchain-capsule.d.ts.map +1 -0
  89. package/dist/toolchain-capsule.js +116 -0
  90. package/dist/toolchain-capsule.js.map +1 -0
  91. package/package.json +17 -10
  92. package/template/.dockerignore +23 -0
  93. package/template/.env.example +3 -0
  94. package/template/AGENTS.md +26 -0
  95. package/template/README.md +45 -0
  96. package/template/_gitignore +14 -0
  97. package/template/apps/web/data-api-live.e2e.html +15 -0
  98. package/template/apps/web/e2e/client-fixture.ts +5 -0
  99. package/template/apps/web/e2e/component-defaults-fixture.css +15 -0
  100. package/template/apps/web/e2e/data-api-live-fixture.tsx +162 -0
  101. package/template/apps/web/e2e/data-api-live.spec.ts +144 -0
  102. package/template/apps/web/e2e/field-protocol-fixture.tsx +387 -0
  103. package/template/apps/web/e2e/field-protocol.spec.ts +285 -0
  104. package/template/apps/web/e2e/mobile-files.spec.ts +96 -0
  105. package/template/apps/web/e2e/mobile-reference-fixture.tsx +314 -0
  106. package/template/apps/web/e2e/mobile-reference.spec.ts +360 -0
  107. package/template/apps/web/e2e/mobile-selection.spec.ts +199 -0
  108. package/template/apps/web/e2e/resource-experience-fixture.tsx +491 -0
  109. package/template/apps/web/e2e/resource-platform-mock.ts +202 -0
  110. package/template/apps/web/e2e/resources.spec.ts +488 -0
  111. package/template/apps/web/e2e/standard-admin.spec.ts +511 -0
  112. package/template/apps/web/e2e/workflow-entry-fixture.tsx +107 -0
  113. package/template/apps/web/e2e/workflow-entry.spec.ts +556 -0
  114. package/template/apps/web/e2e/workflow-experience-fixture.tsx +338 -0
  115. package/template/apps/web/e2e/workflow.spec.ts +1265 -0
  116. package/template/apps/web/field-protocol.e2e.html +22 -0
  117. package/template/apps/web/index.html +12 -0
  118. package/template/apps/web/mobile-reference.e2e.html +27 -0
  119. package/template/apps/web/package.json +34 -0
  120. package/template/apps/web/playwright.config.ts +47 -0
  121. package/template/apps/web/resource-experience.e2e.html +16 -0
  122. package/template/apps/web/scripts/check.mjs +43 -0
  123. package/template/apps/web/scripts/verify-build.mjs +6 -0
  124. package/template/apps/web/src/document.css +5 -0
  125. package/template/apps/web/src/main.tsx +66 -0
  126. package/template/apps/web/src/user-end/tailwind.css +5 -0
  127. package/template/apps/web/test/contracts.test.ts +163 -0
  128. package/template/apps/web/test/loopback.test.ts +68 -0
  129. package/template/apps/web/test/standard-user-surfaces.typecheck.tsx +27 -0
  130. package/template/apps/web/tsconfig.json +12 -0
  131. package/template/apps/web/vite.config.ts +43 -0
  132. package/template/apps/web/workflow-entry.e2e.html +15 -0
  133. package/template/apps/web/workflow-experience.e2e.html +15 -0
  134. package/template/appspec/app.md +52 -0
  135. package/template/openxiangda.config.ts +55 -0
  136. package/template/package.json +28 -0
  137. package/template/packages/contracts/package.json +23 -0
  138. package/template/packages/contracts/src/generated.ts +265 -0
  139. package/template/packages/contracts/src/index.ts +1 -0
  140. package/template/packages/contracts/tsconfig.json +9 -0
  141. package/template/pnpm-workspace.yaml +7 -0
  142. package/template/tsconfig.base.json +13 -0
  143. package/dist/commands/app/create.d.ts +0 -15
  144. package/dist/commands/app/create.d.ts.map +0 -1
  145. package/dist/commands/app/create.js +0 -42
  146. package/dist/commands/app/create.js.map +0 -1
  147. package/dist/commands/app/info.d.ts +0 -9
  148. package/dist/commands/app/info.d.ts.map +0 -1
  149. package/dist/commands/app/info.js +0 -10
  150. package/dist/commands/app/info.js.map +0 -1
  151. package/dist/commands/app/link.d.ts +0 -10
  152. package/dist/commands/app/link.d.ts.map +0 -1
  153. package/dist/commands/app/link.js +0 -16
  154. package/dist/commands/app/link.js.map +0 -1
  155. package/dist/commands/app/provision.d.ts +0 -9
  156. package/dist/commands/app/provision.d.ts.map +0 -1
  157. package/dist/commands/app/provision.js +0 -10
  158. package/dist/commands/app/provision.js.map +0 -1
  159. package/dist/commands/auth/login.d.ts +0 -11
  160. package/dist/commands/auth/login.d.ts.map +0 -1
  161. package/dist/commands/auth/login.js.map +0 -1
  162. package/dist/commands/auth/logout.d.ts +0 -6
  163. package/dist/commands/auth/logout.d.ts.map +0 -1
  164. package/dist/commands/auth/logout.js +0 -30
  165. package/dist/commands/auth/logout.js.map +0 -1
  166. package/dist/commands/auth/status.d.ts +0 -6
  167. package/dist/commands/auth/status.d.ts.map +0 -1
  168. package/dist/commands/auth/status.js +0 -60
  169. package/dist/commands/auth/status.js.map +0 -1
  170. package/dist/commands/build.d.ts +0 -11
  171. package/dist/commands/build.d.ts.map +0 -1
  172. package/dist/commands/build.js +0 -23
  173. package/dist/commands/build.js.map +0 -1
  174. package/dist/commands/doctor.d.ts +0 -9
  175. package/dist/commands/doctor.d.ts.map +0 -1
  176. package/dist/commands/doctor.js +0 -7
  177. package/dist/commands/doctor.js.map +0 -1
  178. package/dist/commands/event/delivery/list.d.ts +0 -10
  179. package/dist/commands/event/delivery/list.d.ts.map +0 -1
  180. package/dist/commands/event/delivery/list.js +0 -14
  181. package/dist/commands/event/delivery/list.js.map +0 -1
  182. package/dist/commands/event/delivery/replay.d.ts +0 -13
  183. package/dist/commands/event/delivery/replay.d.ts.map +0 -1
  184. package/dist/commands/event/delivery/replay.js +0 -16
  185. package/dist/commands/event/delivery/replay.js.map +0 -1
  186. package/dist/commands/event/subscription/list.d.ts +0 -9
  187. package/dist/commands/event/subscription/list.d.ts.map +0 -1
  188. package/dist/commands/event/subscription/list.js +0 -10
  189. package/dist/commands/event/subscription/list.js.map +0 -1
  190. package/dist/commands/event/subscription/rotate-secret.d.ts +0 -15
  191. package/dist/commands/event/subscription/rotate-secret.d.ts.map +0 -1
  192. package/dist/commands/event/subscription/rotate-secret.js +0 -33
  193. package/dist/commands/event/subscription/rotate-secret.js.map +0 -1
  194. package/dist/commands/event/subscription/status.d.ts +0 -14
  195. package/dist/commands/event/subscription/status.d.ts.map +0 -1
  196. package/dist/commands/event/subscription/status.js +0 -19
  197. package/dist/commands/event/subscription/status.js.map +0 -1
  198. package/dist/commands/event/timer/list.d.ts +0 -9
  199. package/dist/commands/event/timer/list.d.ts.map +0 -1
  200. package/dist/commands/event/timer/list.js +0 -10
  201. package/dist/commands/event/timer/list.js.map +0 -1
  202. package/dist/commands/event/timer/status.d.ts +0 -14
  203. package/dist/commands/event/timer/status.d.ts.map +0 -1
  204. package/dist/commands/event/timer/status.js +0 -19
  205. package/dist/commands/event/timer/status.js.map +0 -1
  206. package/dist/commands/generate.d.ts +0 -10
  207. package/dist/commands/generate.d.ts.map +0 -1
  208. package/dist/commands/generate.js +0 -8
  209. package/dist/commands/generate.js.map +0 -1
  210. package/dist/commands/oauth/audit/list.d.ts +0 -12
  211. package/dist/commands/oauth/audit/list.d.ts.map +0 -1
  212. package/dist/commands/oauth/audit/list.js +0 -20
  213. package/dist/commands/oauth/audit/list.js.map +0 -1
  214. package/dist/commands/oauth/client/create.d.ts +0 -13
  215. package/dist/commands/oauth/client/create.d.ts.map +0 -1
  216. package/dist/commands/oauth/client/create.js +0 -34
  217. package/dist/commands/oauth/client/create.js.map +0 -1
  218. package/dist/commands/oauth/client/list.d.ts +0 -9
  219. package/dist/commands/oauth/client/list.d.ts.map +0 -1
  220. package/dist/commands/oauth/client/list.js +0 -10
  221. package/dist/commands/oauth/client/list.js.map +0 -1
  222. package/dist/commands/oauth/client/revoke.d.ts +0 -12
  223. package/dist/commands/oauth/client/revoke.d.ts.map +0 -1
  224. package/dist/commands/oauth/client/revoke.js +0 -12
  225. package/dist/commands/oauth/client/revoke.js.map +0 -1
  226. package/dist/commands/oauth/client/rotate.d.ts +0 -13
  227. package/dist/commands/oauth/client/rotate.d.ts.map +0 -1
  228. package/dist/commands/oauth/client/rotate.js +0 -20
  229. package/dist/commands/oauth/client/rotate.js.map +0 -1
  230. package/dist/commands/oauth/client/update.d.ts +0 -15
  231. package/dist/commands/oauth/client/update.d.ts.map +0 -1
  232. package/dist/commands/oauth/client/update.js +0 -23
  233. package/dist/commands/oauth/client/update.js.map +0 -1
  234. package/dist/commands/oauth/runtime/rotate.d.ts +0 -12
  235. package/dist/commands/oauth/runtime/rotate.d.ts.map +0 -1
  236. package/dist/commands/oauth/runtime/rotate.js +0 -31
  237. package/dist/commands/oauth/runtime/rotate.js.map +0 -1
  238. package/dist/commands/oauth/runtime/status.d.ts +0 -10
  239. package/dist/commands/oauth/runtime/status.d.ts.map +0 -1
  240. package/dist/commands/oauth/runtime/status.js +0 -17
  241. package/dist/commands/oauth/runtime/status.js.map +0 -1
  242. package/dist/commands/promote.d.ts +0 -13
  243. package/dist/commands/promote.d.ts.map +0 -1
  244. package/dist/commands/promote.js +0 -9
  245. package/dist/commands/promote.js.map +0 -1
  246. package/dist/commands/secret/audit/list.d.ts +0 -14
  247. package/dist/commands/secret/audit/list.d.ts.map +0 -1
  248. package/dist/commands/secret/audit/list.js +0 -16
  249. package/dist/commands/secret/audit/list.js.map +0 -1
  250. package/dist/commands/secret/create.d.ts +0 -18
  251. package/dist/commands/secret/create.d.ts.map +0 -1
  252. package/dist/commands/secret/create.js +0 -33
  253. package/dist/commands/secret/create.js.map +0 -1
  254. package/dist/commands/secret/delete.d.ts +0 -15
  255. package/dist/commands/secret/delete.d.ts.map +0 -1
  256. package/dist/commands/secret/delete.js +0 -20
  257. package/dist/commands/secret/delete.js.map +0 -1
  258. package/dist/commands/secret/list.d.ts +0 -10
  259. package/dist/commands/secret/list.d.ts.map +0 -1
  260. package/dist/commands/secret/list.js +0 -14
  261. package/dist/commands/secret/list.js.map +0 -1
  262. package/dist/commands/secret/rotate.d.ts +0 -18
  263. package/dist/commands/secret/rotate.d.ts.map +0 -1
  264. package/dist/commands/secret/rotate.js +0 -30
  265. package/dist/commands/secret/rotate.js.map +0 -1
  266. package/dist/commands/secret/update.d.ts +0 -17
  267. package/dist/commands/secret/update.d.ts.map +0 -1
  268. package/dist/commands/secret/update.js +0 -37
  269. package/dist/commands/secret/update.js.map +0 -1
  270. package/dist/commands/skill/install.d.ts +0 -11
  271. package/dist/commands/skill/install.d.ts.map +0 -1
  272. package/dist/commands/skill/install.js +0 -52
  273. package/dist/commands/skill/install.js.map +0 -1
  274. package/dist/commands/skill/validate.d.ts +0 -9
  275. package/dist/commands/skill/validate.d.ts.map +0 -1
  276. package/dist/commands/skill/validate.js +0 -32
  277. package/dist/commands/skill/validate.js.map +0 -1
  278. package/dist/commands/test.d.ts +0 -9
  279. package/dist/commands/test.d.ts.map +0 -1
  280. package/dist/commands/test.js +0 -7
  281. package/dist/commands/test.js.map +0 -1
  282. package/dist/commands/workflow/provider/list.d.ts +0 -10
  283. package/dist/commands/workflow/provider/list.d.ts.map +0 -1
  284. package/dist/commands/workflow/provider/list.js +0 -14
  285. package/dist/commands/workflow/provider/list.js.map +0 -1
  286. package/dist/commands/workflow/provider/rotate.d.ts +0 -15
  287. package/dist/commands/workflow/provider/rotate.d.ts.map +0 -1
  288. package/dist/commands/workflow/provider/rotate.js +0 -24
  289. package/dist/commands/workflow/provider/rotate.js.map +0 -1
  290. package/dist/secret-value.d.ts +0 -5
  291. package/dist/secret-value.d.ts.map +0 -1
  292. package/dist/secret-value.js +0 -15
  293. package/dist/secret-value.js.map +0 -1
@@ -0,0 +1,199 @@
1
+ import { expect, test, type Page } from '@playwright/test';
2
+
3
+ const field = (page: Page, label: string) => page.locator(`[data-field-code="${label}"]`);
4
+ const picker = (page: Page, label: string) => field(page, label).getByRole('dialog', { name: `选择${label}` });
5
+ const open = async (page: Page, label: string) => {
6
+ await field(page, label).getByRole('button', { name: `选择${label}`, exact: true }).click();
7
+ await expect(picker(page, label)).toBeVisible();
8
+ };
9
+ const submit = async (page: Page) => {
10
+ await page.getByRole('button', { name: '验证表单', exact: true }).click();
11
+ await expect(page.locator('[data-saved-values]')).not.toBeEmpty();
12
+ return JSON.parse(await page.locator('[data-saved-values]').innerText());
13
+ };
14
+ const department = (id: string, label: string, hasChildren = false, selectable = true) => ({
15
+ kind: 'department', id, label, hasChildren, selectable,
16
+ snapshot: { value: id, label, fullPath: `学校 / ${label}` },
17
+ });
18
+ const member = (id: string, label: string, selectable = true) => ({
19
+ kind: 'user', id, label, selectable,
20
+ snapshot: { value: id, label, employeeNo: `E-${id}`, departments: [{ value: 'college-a', label: '理学院' }] },
21
+ });
22
+
23
+ test.describe('mobile platform selection fields', () => {
24
+ test.beforeEach(async ({ page }) => {
25
+ await page.setViewportSize({ width: 390, height: 844 });
26
+ await page.route('**/china-divisions/**', route => route.fulfill({ json: [] }));
27
+ await page.route('**/files/*/content?**', route => route.fulfill({ status: 204, body: '' }));
28
+ });
29
+
30
+ test('member browsing, paging and search retain staged snapshots until confirmation', async ({ page }) => {
31
+ const errors: string[] = [];
32
+ page.on('pageerror', error => errors.push(error.message));
33
+ const queries: string[] = [];
34
+ await page.route('**/directory/**', async route => {
35
+ const url = new URL(route.request().url());
36
+ queries.push(url.pathname + url.search);
37
+ const tree = url.pathname.endsWith('/tree');
38
+ const browse = url.pathname.endsWith('/users') && url.pathname.includes('/departments/');
39
+ const items = tree ? [department('college-a', '理学院')]
40
+ : browse ? (url.searchParams.get('page') === '2' ? [member('user-3', '李老师')] : [member('user-2', '王老师'), member('blocked', '不可选成员', false)])
41
+ : [member('user-4', '陈老师')];
42
+ await route.fulfill({ json: { schemaVersion: 'openxiangda.directory-entry-page/v2', kind: tree ? 'department' : 'user', items,
43
+ nextCursor: browse && url.searchParams.get('page') === '1' ? '2' : null } });
44
+ });
45
+ await page.goto('/field-protocol.e2e.html?mode=mobile');
46
+ await open(page, '成员多选');
47
+ const sheet = picker(page, '成员多选');
48
+ await sheet.getByRole('button', { name: '进入理学院' }).click();
49
+ await expect(sheet.getByText('不可选成员', { exact: true })).toBeVisible();
50
+ await expect(sheet.locator('.adm-check-list-item').filter({ hasText: '不可选成员' })).toHaveAttribute('aria-disabled', 'true');
51
+ await sheet.getByText('不可选成员', { exact: true }).click({ force: true });
52
+ await expect(sheet.getByLabel('已选项目')).not.toContainText('不可选成员');
53
+ await sheet.getByText('王老师', { exact: true }).click();
54
+ await sheet.getByRole('button', { name: '更多成员' }).click();
55
+ await sheet.getByText('李老师', { exact: true }).click();
56
+ await sheet.getByPlaceholder('搜索成员多选').fill('陈老师');
57
+ await sheet.getByText('陈老师', { exact: true }).click();
58
+ await expect(sheet.getByLabel('已选项目')).toContainText('王老师');
59
+ await expect(sheet.getByLabel('已选项目')).toContainText('李老师');
60
+ await sheet.getByRole('button', { name: '关闭', exact: true }).click();
61
+ await expect(field(page, '成员多选').getByRole('button', { name: '选择成员多选', exact: true })).toHaveText('张三');
62
+
63
+ await open(page, '成员多选');
64
+ await sheet.getByRole('button', { name: '清空', exact: true }).click();
65
+ await sheet.getByRole('button', { name: '进入理学院' }).click();
66
+ await sheet.getByText('王老师', { exact: true }).click();
67
+ await sheet.getByPlaceholder('搜索成员多选').fill('陈老师');
68
+ await sheet.getByText('陈老师', { exact: true }).click();
69
+ await sheet.getByRole('button', { name: '确定(2)', exact: true }).click();
70
+ const saved = await submit(page);
71
+ expect(saved['成员多选']).toEqual([member('user-2', '王老师').snapshot, member('user-4', '陈老师').snapshot]);
72
+ await page.getByRole('button', { name: '重新载入已保存值' }).click();
73
+ await expect(field(page, '成员多选').getByRole('button', { name: '选择成员多选', exact: true })).toHaveText('王老师、陈老师');
74
+ expect(queries.some(query => query.includes('page=2'))).toBe(true);
75
+ expect(errors).toEqual([]);
76
+ await open(page, '成员多选');
77
+ await sheet.getByRole('button', { name: '进入理学院' }).click();
78
+ await expect(sheet.getByText('王老师', { exact: true }).last()).toBeVisible();
79
+ await page.screenshot({ path: 'test-results/mobile-member-picker.png' });
80
+ });
81
+
82
+ test('explicit mobile department control browses disabled ancestors and retries failed pages', async ({ page }) => {
83
+ await page.setViewportSize({ width: 1200, height: 900 });
84
+ let failures = 1;
85
+ await page.route('**/directory/**', async route => {
86
+ const url = new URL(route.request().url());
87
+ const parent = url.searchParams.get('parentId');
88
+ if (parent === 'root' && failures-- > 0) { await route.fulfill({ status: 503, json: { code: 'OPENXIANGDA_DIRECTORY_REQUEST_FAILED', message: '目录暂时不可用' } }); return; }
89
+ await route.fulfill({ json: { schemaVersion: 'openxiangda.directory-entry-page/v2', kind: 'department',
90
+ items: parent === 'root' ? [department('college-a', '理学院'), department('college-b', '文学院')]
91
+ : url.searchParams.get('offset') ? [department('office', '办公室')]
92
+ : [department('root', '学校', true, false)], nextCursor: !parent && !url.searchParams.get('offset') ? '100' : null } });
93
+ });
94
+ await page.goto('/field-protocol.e2e.html?mode=mobile');
95
+ await open(page, '部门多选');
96
+ const sheet = picker(page, '部门多选');
97
+ await expect(sheet.locator('.ant-tree, .ant-select, .ant-drawer')).toHaveCount(0);
98
+ await sheet.getByRole('button', { name: '清空', exact: true }).click();
99
+ await sheet.getByRole('button', { name: '更多部门' }).click();
100
+ await sheet.getByText('办公室', { exact: true }).click();
101
+ await sheet.getByRole('button', { name: '进入学校' }).click();
102
+ await expect(sheet.getByRole('alert')).toContainText('目录暂时不可用');
103
+ await sheet.getByRole('button', { name: '重试', exact: true }).click();
104
+ await sheet.getByText('文学院', { exact: true }).click();
105
+ await sheet.getByRole('button', { name: '确定(2)', exact: true }).click();
106
+ expect((await submit(page))['部门多选']).toEqual([department('office', '办公室').snapshot, department('college-b', '文学院').snapshot]);
107
+ await open(page, '部门单选');
108
+ await picker(page, '部门单选').getByRole('button', { name: '进入学校' }).click();
109
+ await picker(page, '部门单选').getByText('文学院', { exact: true }).click();
110
+ await picker(page, '部门单选').getByRole('button', { name: '确定(1)', exact: true }).click();
111
+ expect((await submit(page))['部门单选']).toEqual(department('college-b', '文学院').snapshot);
112
+ await page.setViewportSize({ width: 390, height: 844 });
113
+ await open(page, '部门多选');
114
+ await sheet.getByRole('button', { name: '进入学校' }).click();
115
+ await expect(sheet.getByText('文学院', { exact: true }).last()).toBeVisible();
116
+ await page.screenshot({ path: 'test-results/mobile-department-picker.png' });
117
+ });
118
+
119
+ test('reference search ignores stale pages, preserves off-page choices and retries without losing them', async ({ page }) => {
120
+ let releaseOld: (() => void) | undefined;
121
+ let oldFinished = false;
122
+ let failed = false;
123
+ const reference = (value: string, label: string) => ({ value, label, resourceCode: 'field-lookups', snapshot: { code: `CODE-${value}` } });
124
+ await page.route('**/source/query', async route => {
125
+ const request = route.request().postDataJSON();
126
+ if (request.cursor === 'old-next') { await new Promise<void>(resolve => { releaseOld = resolve; }); }
127
+ if (request.keyword === '故障' && !failed) { failed = true; await route.fulfill({ status: 503, json: { code: 'OPENXIANGDA_DATA_SOURCE_FAILED', message: '候选加载失败' } }); return; }
128
+ const items = request.cursor ? [reference('stale', '过期候选')] : request.keyword ? [reference('b', '测量服务')] : [reference('a', '检测设备')];
129
+ await route.fulfill({ json: { schemaVersion: 'openxiangda.data-field-source-page/v2',
130
+ environment: { id: 'env', key: 'production', activeAppVersionId: 'version', headRevision: 1, authzRevisionId: 'authz' },
131
+ resourceCode: 'field-records', fieldCode: request.fieldCode, operation: request.operation, sourceResourceCode: 'field-lookups',
132
+ items, nextCursor: request.keyword || request.cursor ? null : 'old-next' } });
133
+ if (request.cursor) oldFinished = true;
134
+ });
135
+ await page.goto('/field-protocol.e2e.html?mode=mobile');
136
+ await open(page, '动态多选下拉');
137
+ const sheet = picker(page, '动态多选下拉');
138
+ await sheet.getByRole('button', { name: '清空', exact: true }).click();
139
+ await sheet.getByText('检测设备', { exact: true }).click();
140
+ await sheet.getByRole('button', { name: '加载更多', exact: true }).click();
141
+ await expect.poll(() => Boolean(releaseOld)).toBe(true);
142
+ await sheet.getByPlaceholder('搜索动态多选下拉').fill('测量');
143
+ await sheet.getByText('测量服务', { exact: true }).click();
144
+ releaseOld!();
145
+ await expect.poll(() => oldFinished).toBe(true);
146
+ await expect(sheet.getByText('过期候选')).toHaveCount(0);
147
+ await expect(sheet.getByRole('button', { name: '加载更多', exact: true })).toHaveCount(0);
148
+ await expect(sheet.getByLabel('已选项目')).toContainText('检测设备');
149
+ await page.screenshot({ path: 'test-results/mobile-reference-picker.png' });
150
+ await sheet.getByPlaceholder('搜索动态多选下拉').fill('故障');
151
+ await expect(sheet.getByRole('alert')).toContainText('候选加载失败');
152
+ await sheet.getByRole('button', { name: '重试', exact: true }).click();
153
+ await expect(sheet.getByText('测量服务', { exact: true }).last()).toBeVisible();
154
+ await sheet.getByPlaceholder('搜索动态多选下拉').press('Enter');
155
+ await expect(page.locator('[data-saved-values]')).toBeEmpty();
156
+ await sheet.getByRole('button', { name: '确定(2)', exact: true }).click();
157
+ expect((await submit(page))['动态多选下拉']).toEqual([reference('a', '检测设备'), reference('b', '测量服务')]);
158
+ await page.getByRole('button', { name: '重新载入已保存值' }).click();
159
+ await open(page, '动态多选下拉');
160
+ await sheet.getByRole('button', { name: '移除检测设备', exact: true }).click();
161
+ await sheet.getByRole('button', { name: '确定(1)', exact: true }).click();
162
+ expect((await submit(page))['动态多选下拉']).toEqual([reference('b', '测量服务')]);
163
+ });
164
+
165
+ test('cascade drilldown and path search commit complete single/multiple snapshots', async ({ page }) => {
166
+ await page.goto('/field-protocol.e2e.html?mode=mobile');
167
+ await open(page, '级联多选');
168
+ const sheet = picker(page, '级联多选');
169
+ await sheet.getByRole('button', { name: '清空', exact: true }).click();
170
+ await sheet.getByRole('button', { name: '进入设备' }).click();
171
+ await sheet.getByText('光谱仪', { exact: true }).click();
172
+ await sheet.getByRole('button', { name: '首页', exact: true }).click();
173
+ await sheet.getByPlaceholder('搜索选项或路径').fill('服务');
174
+ await sheet.getByText('培训', { exact: true }).click();
175
+ await expect(sheet.getByLabel('已选项目')).toContainText('设备 / 光谱仪');
176
+ await expect(sheet.getByLabel('已选项目')).toContainText('服务 / 培训');
177
+ await page.screenshot({ path: 'test-results/mobile-cascade-picker.png' });
178
+ await sheet.getByRole('button', { name: '关闭', exact: true }).click();
179
+ await expect(field(page, '级联多选').getByRole('button', { name: '选择级联多选', exact: true })).toHaveText('设备 / 显微镜');
180
+ await open(page, '级联多选');
181
+ await sheet.getByRole('button', { name: '进入设备' }).click();
182
+ await sheet.getByText('光谱仪', { exact: true }).click();
183
+ await sheet.getByRole('button', { name: '确定(2)', exact: true }).click();
184
+ const expected = [
185
+ [{ label: '设备', value: 'equipment' }, { label: '显微镜', value: 'microscope' }],
186
+ [{ label: '设备', value: 'equipment' }, { label: '光谱仪', value: 'spectrometer' }],
187
+ ];
188
+ expect((await submit(page))['级联多选']).toEqual(expected);
189
+ await open(page, '级联单选');
190
+ await picker(page, '级联单选').getByRole('button', { name: '首页', exact: true }).click();
191
+ await picker(page, '级联单选').getByRole('button', { name: '进入服务' }).click();
192
+ await picker(page, '级联单选').getByText('培训', { exact: true }).click();
193
+ await picker(page, '级联单选').getByRole('button', { name: '确定', exact: true }).click();
194
+ expect((await submit(page))['级联单选']).toEqual([{ label: '服务', value: 'service' }, { label: '培训', value: 'training' }]);
195
+ await page.getByRole('button', { name: '重新载入已保存值' }).click();
196
+ await expect(field(page, '级联单选').getByRole('button', { name: '选择级联单选', exact: true })).toHaveText('服务 / 培训');
197
+ await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(0);
198
+ });
199
+ });