openxiangda-cli 2.0.0-alpha.163 → 2.0.0-alpha.165
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.
- package/package.json +5 -5
- package/template/AGENTS.md +1 -0
- package/template/README.md +8 -0
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/e2e/workflow-experience-fixture.tsx +92 -1
- package/template/apps/web/e2e/workflow.spec.ts +138 -31
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/test/standard-user-surfaces.typecheck.tsx +27 -0
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.165",
|
|
4
4
|
"description": "Thin application-level CLI for OpenXiangda 2.0.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@oclif/core": "4.13.3",
|
|
24
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
25
|
-
"openxiangda-devkit-core": "2.0.0-alpha.
|
|
26
|
-
"openxiangda-mcp": "2.0.0-alpha.
|
|
27
|
-
"openxiangda-skill-kit": "2.0.0-alpha.
|
|
24
|
+
"openxiangda-contracts": "2.0.0-alpha.83",
|
|
25
|
+
"openxiangda-devkit-core": "2.0.0-alpha.106",
|
|
26
|
+
"openxiangda-mcp": "2.0.0-alpha.106",
|
|
27
|
+
"openxiangda-skill-kit": "2.0.0-alpha.131"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsx": "4.23.12",
|
package/template/AGENTS.md
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
admin shell, and do not make user-end utilities depend on `--oxa-antd-*`.
|
|
25
25
|
- Bind every generated `appRoutes` entry to its local page with `defineApplicationContributions`; desktop `admin` routes stay inside the platform Shell, while `user` routes render without an admin Shell for independent mobile/user experiences. Generated resource CRUD routes are compiler-owned under `/admin/resources/<resourceCode>...` and `/m/admin/resources/<resourceCode>...`; never recreate root resource paths, aliases or redirects. Explicit routes that have the same canonical shape as another explicit or generated route fail compilation, even when dynamic parameter names differ. Use only the typed `toolbar`, `row` and `detail` resource slots for generated resource actions. Declare the complete editable admin menu with `defineAdminNavigation` and its page/group helpers; the Shell renders only generated `adminNavigation` references and permissions only filter them. Do not create another router, menu store, layout, identity provider, permission store or copied CRUD page; route/action access uses capability or `allOf`/`anyOf`, while Data/App API and Workflow authorization remain server-owned.
|
|
26
26
|
- Application login is optional `frontend.authentication`: existing platform users only, registration rejected, exact desktop `/login` and mobile `/m/login`. Bind generated `authenticationSurfaces` to separate PC/mobile renderers through `defineApplicationContributions`; renderers own only brand visuals and call `ApplicationLoginSurfaceProps`. The platform alone owns passwords, providers, OAuth state/callbacks, secure cookies, current identity and authorization. Never put login in protected `appRoutes`, call a v1 auth route, store tokens, create users/roles, or add another Router/identity provider. Keep QA in generated `platformAuthManifest`, outside the protected route denominator.
|
|
27
|
+
- Branded standard user pages use the optional exact `standardUserSurfaces` contribution from `openxiangda/react`: provide separate desktop/mobile `frame` and `applicationTodoCenter` renderers or omit the property entirely. The platform still owns `/todos`, `/m/todos`, Workflow routes, `RuntimeBoundary`, current-user Notification Hub data, query/load/interaction callbacks and navigation. Never add a second Router, Todo API client, identity store or token prop.
|
|
27
28
|
- External users without platform accounts use only an exact static `surface: 'user'` route declared through `frontend.publicAccess`. Declare the one resource, bounded field sets, required operations, draft limits and named duplicate validations; consume only generated `anonymousPublicAccess` and `createAnonymousPublicClient`. `own.list`/`own.read` mean records submitted by the same platform-issued HttpOnly browser credential, not a verified natural person, and another browser or cleared cookie intentionally loses access. Never create a guest role/user, call the general Native Data API, expose an anonymous upload path, store identity locally or derive ownership from IP, user-agent or fingerprint.
|
|
28
29
|
- Declare each resource once in `openxiangda.config.ts` with only `code`, `name`, `fields` and optional `mutationOwner`, generated/list/layout/data-policy settings. Each field owns type, label, required state, Surface flags, reference/file metadata and access. Resource codes are lower kebab-case. Use `native` for direct Data API mutations, `action`, `readonly` or `workflow` for non-Native ownership; never grant or generate Native mutation for a non-Native owner.
|
|
29
30
|
- Never write resource-level `schemaVersion`, `appCode`, `schema`, `surface`, `capabilities`, `fieldPolicies` or `platform/data` modules. The compiler derives the strict DataResource, CRUD capabilities, Surface and AI Schema. The application manifest still starts with its one top-level `schemaVersion: 3`.
|
package/template/README.md
CHANGED
|
@@ -48,6 +48,14 @@ renderer 只使用 `ApplicationLoginSurfaceProps` 回调,不能验证密码、
|
|
|
48
48
|
OAuth callback 或创建第二个 Router/身份状态。`platformAuthManifest` 单独记录登录面 QA,
|
|
49
49
|
不会改变用户路由覆盖数。
|
|
50
50
|
|
|
51
|
+
如需统一品牌化普通用户页面,在应用入口给
|
|
52
|
+
`defineApplicationContributions` 增加完整的 `standardUserSurfaces`:必须同时提供
|
|
53
|
+
`frame.desktop/mobile` 与 `applicationTodoCenter.desktop/mobile`。外框只使用
|
|
54
|
+
`StandardUserPageFrameProps` 的页面类型、安全路由元数据、`children` 和平台返回回调;
|
|
55
|
+
待办 renderer 只使用 `StandardApplicationTodoCenterProps` 的当前用户 items、加载/错误、
|
|
56
|
+
查询、刷新、增量加载、交互和跳转回调。省略该属性继续使用平台默认页面。应用不得接管
|
|
57
|
+
`/todos`、`/m/todos`、Workflow 路由、`RuntimeBoundary`、Token 或 Notification Hub API。
|
|
58
|
+
|
|
51
59
|
生成式资源后台路径固定为 `/admin/resources/<resourceCode>...`,移动后台面为
|
|
52
60
|
`/m/admin/resources/<resourceCode>...`;普通根路径和 `/m/...` 产品路径可由显式
|
|
53
61
|
`user` route 使用。生成页内部跳转来自编译后的 `adminPages`,不要手写旧根路径、别名或
|
|
@@ -5,11 +5,17 @@ import {
|
|
|
5
5
|
OpenXiangdaApplication,
|
|
6
6
|
type AdminNavigationInput,
|
|
7
7
|
type AdminPagesInput,
|
|
8
|
+
type StandardApplicationTodoCenterProps,
|
|
9
|
+
type StandardUserPageFrameProps,
|
|
10
|
+
type StandardUserSurfaceContributions,
|
|
8
11
|
} from 'openxiangda/react';
|
|
9
12
|
import { useParams, useSearchParams } from 'react-router-dom';
|
|
10
13
|
import { appCode } from '../../../packages/contracts/src/generated.js';
|
|
11
14
|
import 'openxiangda/react/styles.css';
|
|
12
15
|
|
|
16
|
+
const fixtureParams = new URLSearchParams(window.location.search);
|
|
17
|
+
const standardSurfaceMode = fixtureParams.get('surfaces');
|
|
18
|
+
|
|
13
19
|
const workflowDefinitions = [
|
|
14
20
|
{
|
|
15
21
|
code: 'purchase-approval',
|
|
@@ -83,11 +89,97 @@ function CustomWorkflowDetail() {
|
|
|
83
89
|
);
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
function StandardFrame({
|
|
93
|
+
back,
|
|
94
|
+
canGoBack,
|
|
95
|
+
children,
|
|
96
|
+
device,
|
|
97
|
+
pageKind,
|
|
98
|
+
route,
|
|
99
|
+
}: StandardUserPageFrameProps) {
|
|
100
|
+
return (
|
|
101
|
+
<section
|
|
102
|
+
data-can-go-back={String(canGoBack)}
|
|
103
|
+
data-page-kind={pageKind}
|
|
104
|
+
data-route-code={route.routeCode}
|
|
105
|
+
data-route-params={JSON.stringify(route.params)}
|
|
106
|
+
data-testid={`standard-frame-${device}`}
|
|
107
|
+
>
|
|
108
|
+
<button onClick={back} type="button">返回</button>
|
|
109
|
+
{children}
|
|
110
|
+
</section>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function CustomTodo({
|
|
115
|
+
device,
|
|
116
|
+
error,
|
|
117
|
+
hasMore,
|
|
118
|
+
items,
|
|
119
|
+
loadMore,
|
|
120
|
+
loading,
|
|
121
|
+
loadingMore,
|
|
122
|
+
openItem,
|
|
123
|
+
query,
|
|
124
|
+
refresh,
|
|
125
|
+
setQuery,
|
|
126
|
+
}: StandardApplicationTodoCenterProps) {
|
|
127
|
+
return (
|
|
128
|
+
<main data-testid={`custom-todo-${device}`}>
|
|
129
|
+
<h1>{device === 'mobile' ? '移动应用待办' : '桌面应用待办'}</h1>
|
|
130
|
+
<output data-testid="custom-todo-query">
|
|
131
|
+
{query.view}/{String(query.unread)}/{query.keyword || 'all'}
|
|
132
|
+
</output>
|
|
133
|
+
{error ? <div role="alert">{error}</div> : null}
|
|
134
|
+
<button onClick={() => void refresh()} type="button">刷新待办</button>
|
|
135
|
+
<button
|
|
136
|
+
onClick={() => setQuery({ unread: !query.unread })}
|
|
137
|
+
type="button"
|
|
138
|
+
>
|
|
139
|
+
切换未读
|
|
140
|
+
</button>
|
|
141
|
+
<ul aria-busy={loading || loadingMore}>
|
|
142
|
+
{items.map(item => (
|
|
143
|
+
<li key={item.messageId}>
|
|
144
|
+
<button onClick={() => void openItem(item)} type="button">
|
|
145
|
+
{item.title}
|
|
146
|
+
</button>
|
|
147
|
+
</li>
|
|
148
|
+
))}
|
|
149
|
+
</ul>
|
|
150
|
+
<button
|
|
151
|
+
disabled={!hasMore || loadingMore}
|
|
152
|
+
onClick={() => void loadMore()}
|
|
153
|
+
type="button"
|
|
154
|
+
>
|
|
155
|
+
加载更多
|
|
156
|
+
</button>
|
|
157
|
+
</main>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function BrokenTodo(_props: StandardApplicationTodoCenterProps) {
|
|
162
|
+
throw new Error('fixture standard user renderer failure');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const standardUserSurfaces: StandardUserSurfaceContributions | undefined =
|
|
166
|
+
standardSurfaceMode
|
|
167
|
+
? {
|
|
168
|
+
frame: { desktop: StandardFrame, mobile: StandardFrame },
|
|
169
|
+
applicationTodoCenter: {
|
|
170
|
+
desktop:
|
|
171
|
+
standardSurfaceMode === 'error' ? BrokenTodo : CustomTodo,
|
|
172
|
+
mobile: standardSurfaceMode === 'error' ? BrokenTodo : CustomTodo,
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
176
|
+
|
|
86
177
|
const applicationContributions = defineApplicationContributions(detailRoutes, {
|
|
87
178
|
pages: {
|
|
88
179
|
desktop: CustomWorkflowDetail,
|
|
89
180
|
mobile: CustomWorkflowDetail,
|
|
90
181
|
},
|
|
182
|
+
...(standardUserSurfaces ? { standardUserSurfaces } : {}),
|
|
91
183
|
});
|
|
92
184
|
|
|
93
185
|
const adminPages = [] as const satisfies AdminPagesInput;
|
|
@@ -203,7 +295,6 @@ const routeManifest = {
|
|
|
203
295
|
|
|
204
296
|
const adminNavigation = [] as const satisfies AdminNavigationInput;
|
|
205
297
|
|
|
206
|
-
const fixtureParams = new URLSearchParams(window.location.search);
|
|
207
298
|
const initialPath = fixtureParams.get('initial') || '/work-center';
|
|
208
299
|
const runtimeBase = fixtureParams.get('base')?.trim();
|
|
209
300
|
if (runtimeBase) {
|
|
@@ -24,11 +24,12 @@ function detailNavigation(custom = false) {
|
|
|
24
24
|
|
|
25
25
|
function workflowFixtureUrl(
|
|
26
26
|
initialPath: string,
|
|
27
|
-
options: { base?: string; state?: string } = {},
|
|
27
|
+
options: { base?: string; state?: string; surfaces?: 'custom' | 'error' } = {},
|
|
28
28
|
) {
|
|
29
29
|
const params = new URLSearchParams({ initial: initialPath });
|
|
30
30
|
if (options.base) params.set('base', options.base);
|
|
31
31
|
if (options.state) params.set('state', options.state);
|
|
32
|
+
if (options.surfaces) params.set('surfaces', options.surfaces);
|
|
32
33
|
return `/workflow-experience.e2e.html?${params.toString()}`;
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -525,42 +526,47 @@ function processCommandSurface() {
|
|
|
525
526
|
};
|
|
526
527
|
}
|
|
527
528
|
|
|
528
|
-
function todoPage() {
|
|
529
|
+
function todoPage(offset = 0) {
|
|
530
|
+
const first = {
|
|
531
|
+
messageId: '33333333-3333-4333-8333-333333333333',
|
|
532
|
+
recipientId: '44444444-4444-4444-8444-444444444444',
|
|
533
|
+
state: 'action_required',
|
|
534
|
+
interactionState: 'unread',
|
|
535
|
+
title: '设备采购申请待审批',
|
|
536
|
+
summary: '张三提交了一笔办公设备采购申请',
|
|
537
|
+
fields: [
|
|
538
|
+
{ code: 'amount', label: '申请金额', value: '¥28,600' },
|
|
539
|
+
{ code: 'department', label: '申请部门', value: '理学院' },
|
|
540
|
+
],
|
|
541
|
+
actions: [{ code: 'open', label: '去处理' }],
|
|
542
|
+
sourceKind: 'PLATFORM_EVENT',
|
|
543
|
+
sourceLabel: '流程中心',
|
|
544
|
+
occurredAt: '2026-08-28T01:00:00.000Z',
|
|
545
|
+
updatedAt: '2026-08-28T01:01:00.000Z',
|
|
546
|
+
navigation: {
|
|
547
|
+
desktopPath: `/admin/operations/purchases/${instanceId}?taskId=${taskId}`,
|
|
548
|
+
mobilePath: `/m/purchases/${instanceId}?taskId=${taskId}`,
|
|
549
|
+
external: false,
|
|
550
|
+
navigationUnavailable: false,
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
const second = {
|
|
554
|
+
...first,
|
|
555
|
+
messageId: '77777777-7777-4777-8777-777777777777',
|
|
556
|
+
title: '采购结果通知',
|
|
557
|
+
interactionState: 'read',
|
|
558
|
+
};
|
|
529
559
|
return {
|
|
530
560
|
schemaVersion: 'openxiangda.application-todo-center/v2',
|
|
531
561
|
appCode,
|
|
532
562
|
environmentKey: 'preproduction',
|
|
533
563
|
view: 'pending',
|
|
534
564
|
counts: { pending: 1, informational: 2, completed: 3, unread: 1 },
|
|
535
|
-
items: [
|
|
536
|
-
|
|
537
|
-
messageId: '33333333-3333-4333-8333-333333333333',
|
|
538
|
-
recipientId: '44444444-4444-4444-8444-444444444444',
|
|
539
|
-
state: 'action_required',
|
|
540
|
-
interactionState: 'unread',
|
|
541
|
-
title: '设备采购申请待审批',
|
|
542
|
-
summary: '张三提交了一笔办公设备采购申请',
|
|
543
|
-
fields: [
|
|
544
|
-
{ code: 'amount', label: '申请金额', value: '¥28,600' },
|
|
545
|
-
{ code: 'department', label: '申请部门', value: '理学院' },
|
|
546
|
-
],
|
|
547
|
-
actions: [{ code: 'open', label: '去处理' }],
|
|
548
|
-
sourceKind: 'PLATFORM_EVENT',
|
|
549
|
-
sourceLabel: '流程中心',
|
|
550
|
-
occurredAt: '2026-08-28T01:00:00.000Z',
|
|
551
|
-
updatedAt: '2026-08-28T01:01:00.000Z',
|
|
552
|
-
navigation: {
|
|
553
|
-
desktopPath: `/admin/operations/purchases/${instanceId}?taskId=${taskId}`,
|
|
554
|
-
mobilePath: `/m/purchases/${instanceId}?taskId=${taskId}`,
|
|
555
|
-
external: false,
|
|
556
|
-
navigationUnavailable: false,
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
],
|
|
560
|
-
total: 1,
|
|
565
|
+
items: [offset > 0 ? second : first],
|
|
566
|
+
total: 2,
|
|
561
567
|
limit: 12,
|
|
562
|
-
offset
|
|
563
|
-
nextOffset: null,
|
|
568
|
+
offset,
|
|
569
|
+
nextOffset: offset > 0 ? null : 1,
|
|
564
570
|
};
|
|
565
571
|
}
|
|
566
572
|
|
|
@@ -642,7 +648,9 @@ async function mockWorkflow(
|
|
|
642
648
|
if (path.endsWith('/todos') && route.request().method() === 'GET') {
|
|
643
649
|
return route.fulfill({
|
|
644
650
|
contentType: 'application/json',
|
|
645
|
-
body: JSON.stringify(
|
|
651
|
+
body: JSON.stringify(
|
|
652
|
+
envelope(todoPage(Number(url.searchParams.get('offset') || 0))),
|
|
653
|
+
),
|
|
646
654
|
});
|
|
647
655
|
}
|
|
648
656
|
if (path.includes('/todos/') && path.endsWith('/interactions')) {
|
|
@@ -806,6 +814,7 @@ test('renders the generic desktop and mobile application todo center without a s
|
|
|
806
814
|
await mockWorkflow(page);
|
|
807
815
|
await page.goto('/workflow-experience.e2e.html?initial=/todos');
|
|
808
816
|
await expect(page.getByRole('heading', { name: '待办中心' })).toBeVisible();
|
|
817
|
+
await expect(page.getByTestId('standard-frame-desktop')).toHaveCount(0);
|
|
809
818
|
await expect(page.locator('.oxa-todo-table')).toBeVisible();
|
|
810
819
|
await expect(page.locator('.oxa-todo-preview-card')).toHaveCount(0);
|
|
811
820
|
await expect(page.getByText('设备采购申请待审批').first()).toBeVisible();
|
|
@@ -838,6 +847,104 @@ test('renders the generic desktop and mobile application todo center without a s
|
|
|
838
847
|
);
|
|
839
848
|
});
|
|
840
849
|
|
|
850
|
+
test('selects paired application todo renderers while the platform owns data, paging and navigation', async ({
|
|
851
|
+
page,
|
|
852
|
+
}) => {
|
|
853
|
+
await mockWorkflow(page);
|
|
854
|
+
await page.goto(
|
|
855
|
+
workflowFixtureUrl('/todos', { surfaces: 'custom' }),
|
|
856
|
+
);
|
|
857
|
+
const desktopFrame = page.getByTestId('standard-frame-desktop');
|
|
858
|
+
await expect(desktopFrame).toHaveAttribute(
|
|
859
|
+
'data-page-kind',
|
|
860
|
+
'application-todo-center',
|
|
861
|
+
);
|
|
862
|
+
await expect(desktopFrame).toHaveAttribute(
|
|
863
|
+
'data-route-code',
|
|
864
|
+
'application.todo-center.desktop',
|
|
865
|
+
);
|
|
866
|
+
await expect(page.getByTestId('custom-todo-desktop')).toBeVisible();
|
|
867
|
+
await expect(page.getByText('设备采购申请待审批')).toBeVisible();
|
|
868
|
+
|
|
869
|
+
const nextPage = page.waitForRequest(request => {
|
|
870
|
+
const url = new URL(request.url());
|
|
871
|
+
return url.pathname.endsWith('/todos') && url.searchParams.get('offset') === '1';
|
|
872
|
+
});
|
|
873
|
+
await page.getByRole('button', { name: '加载更多' }).click();
|
|
874
|
+
await nextPage;
|
|
875
|
+
await expect(page.getByText('采购结果通知')).toBeVisible();
|
|
876
|
+
|
|
877
|
+
const unreadRequest = page.waitForRequest(request => {
|
|
878
|
+
const url = new URL(request.url());
|
|
879
|
+
return url.pathname.endsWith('/todos') && url.searchParams.get('unread') === 'true';
|
|
880
|
+
});
|
|
881
|
+
await page.getByRole('button', { name: '切换未读' }).click();
|
|
882
|
+
await unreadRequest;
|
|
883
|
+
await expect(page.getByTestId('custom-todo-query')).toContainText(
|
|
884
|
+
'pending/true/all',
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
await page.getByRole('button', { name: '设备采购申请待审批' }).click();
|
|
888
|
+
await expect(page).toHaveURL(
|
|
889
|
+
new RegExp(`/admin/operations/purchases/${instanceId}\\?taskId=${taskId}`),
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
await page.setViewportSize({ width: 390, height: 844 });
|
|
893
|
+
await page.goto(
|
|
894
|
+
workflowFixtureUrl('/m/todos', { surfaces: 'custom' }),
|
|
895
|
+
);
|
|
896
|
+
await expect(page.getByTestId('custom-todo-mobile')).toBeVisible();
|
|
897
|
+
await expect(page.getByTestId('standard-frame-mobile')).toHaveAttribute(
|
|
898
|
+
'data-route-code',
|
|
899
|
+
'application.todo-center.mobile',
|
|
900
|
+
);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
test('frames platform-owned workflow launch, task and instance routes with safe metadata', async ({
|
|
904
|
+
page,
|
|
905
|
+
}) => {
|
|
906
|
+
await mockWorkflow(page);
|
|
907
|
+
for (const [path, kind, routeCode, params] of [
|
|
908
|
+
[
|
|
909
|
+
'/workflows/purchase-approval/start',
|
|
910
|
+
'workflow-launch',
|
|
911
|
+
'workflow.purchase-approval.launch.desktop',
|
|
912
|
+
'purchase-approval',
|
|
913
|
+
],
|
|
914
|
+
[
|
|
915
|
+
`/tasks/${taskId}`,
|
|
916
|
+
'workflow-task',
|
|
917
|
+
'workflow.task.desktop',
|
|
918
|
+
taskId,
|
|
919
|
+
],
|
|
920
|
+
[
|
|
921
|
+
`/workflows/${instanceId}`,
|
|
922
|
+
'workflow-instance',
|
|
923
|
+
'workflow.instance.desktop',
|
|
924
|
+
instanceId,
|
|
925
|
+
],
|
|
926
|
+
] as const) {
|
|
927
|
+
await page.goto(workflowFixtureUrl(path, { surfaces: 'custom' }));
|
|
928
|
+
const frame = page.getByTestId('standard-frame-desktop');
|
|
929
|
+
await expect(frame).toHaveAttribute('data-page-kind', kind);
|
|
930
|
+
await expect(frame).toHaveAttribute('data-route-code', routeCode);
|
|
931
|
+
await expect(frame).toHaveAttribute(
|
|
932
|
+
'data-route-params',
|
|
933
|
+
new RegExp(params),
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
test('contains an application standard renderer failure inside the platform boundary', async ({
|
|
939
|
+
page,
|
|
940
|
+
}) => {
|
|
941
|
+
await mockWorkflow(page);
|
|
942
|
+
await page.goto(workflowFixtureUrl('/todos', { surfaces: 'error' }));
|
|
943
|
+
await expect(page.getByText('页面显示失败')).toBeVisible();
|
|
944
|
+
await expect(page.getByText(/平台登录态和业务数据未受影响/)).toBeVisible();
|
|
945
|
+
await expect(page.getByRole('button', { name: '重新加载页面' })).toBeVisible();
|
|
946
|
+
});
|
|
947
|
+
|
|
841
948
|
test('launches the same workflow from the canonical mobile Surface and follows custom detail routing', async ({
|
|
842
949
|
page,
|
|
843
950
|
}) => {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineApplicationContributions,
|
|
3
|
+
type StandardApplicationTodoCenterProps,
|
|
4
|
+
type StandardUserPageFrameProps,
|
|
5
|
+
type StandardUserSurfaceContributions,
|
|
6
|
+
} from 'openxiangda/react';
|
|
7
|
+
|
|
8
|
+
function Frame({ children }: StandardUserPageFrameProps) {
|
|
9
|
+
return <>{children}</>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function Todo({ items }: StandardApplicationTodoCenterProps) {
|
|
13
|
+
return <>{items.length}</>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const standardUserSurfaces = {
|
|
17
|
+
frame: { desktop: Frame, mobile: Frame },
|
|
18
|
+
applicationTodoCenter: { desktop: Todo, mobile: Todo },
|
|
19
|
+
} satisfies StandardUserSurfaceContributions;
|
|
20
|
+
|
|
21
|
+
const contributions = defineApplicationContributions({}, {
|
|
22
|
+
pages: {},
|
|
23
|
+
standardUserSurfaces,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const exportedFrame = contributions.standardUserSurfaces?.frame.desktop;
|
|
27
|
+
void exportedFrame;
|