openxiangda-cli 2.0.0-alpha.177 → 2.0.0-alpha.178
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
CHANGED
|
@@ -17,7 +17,7 @@ const correctionFields = {
|
|
|
17
17
|
async function workflowEntryPlatform(page: Page) {
|
|
18
18
|
await mockPlatform(page, false, undefined, true);
|
|
19
19
|
const state = {
|
|
20
|
-
admin: true,
|
|
20
|
+
admin: true, instanceStatus: 'approved',
|
|
21
21
|
record: { id: 'record-1', revision: 8, title: '原业务申请', amount: 28600, decision: '已同意', internalNote: '不得显示', systemValue: '内部标识', attachments: [], richText: '<p>原说明</p>' },
|
|
22
22
|
complexFields: false, uploads: [] as any[], childQueries: [] as any[], auditReads: 0,
|
|
23
23
|
children: [
|
|
@@ -61,7 +61,7 @@ async function workflowEntryPlatform(page: Page) {
|
|
|
61
61
|
if (path.endsWith(`/workflow/records/${resourceCode}/${state.record.id}/detail`)) {
|
|
62
62
|
const childFields = { name: { label: '品名', type: 'text.short', widget: 'text', ...fieldCapabilities }, quantity: { label: '数量', type: 'number.integer', widget: 'number', ...fieldCapabilities }, detailNote: { label: '仅详情说明', type: 'text.short', widget: 'text', ...fieldCapabilities } };
|
|
63
63
|
const fields = { ...correctionFields, decision: { label: '审批结论', type: 'text.short', widget: 'readonly', ...fieldCapabilities }, ...(state.complexFields ? { items: { label: '采购明细', type: 'subtable', widget: 'subtable', subtable: { resourceCode: 'purchase-lines', foreignKey: 'purchaseId', orderField: 'sortOrder', maxRows: 20 }, ...fieldCapabilities } } : {}) };
|
|
64
|
-
const instance = { id: instanceId, appCode, environmentKey: 'preproduction', status:
|
|
64
|
+
const instance = { id: instanceId, appCode, environmentKey: 'preproduction', status: state.instanceStatus, outcome: state.instanceStatus === 'approved' ? 'approved' : null, startedAt: '2026-09-06T01:00:00Z', completedAt: state.instanceStatus === 'approved' ? '2026-09-06T02:38:00Z' : null, dataRef: { resourceCode, recordId: state.record.id } };
|
|
65
65
|
const surface = { schemaVersion: 'openxiangda.workflow-surface/v2', protocolVersion: 'workflow_surface_v2', engineVersion: '2.0', surfaceRevision: 'a'.repeat(64), instanceSequence: 4, instance, task: null, operations: [],
|
|
66
66
|
detailNavigation: { desktopPath: `/admin/purchases/${state.record.id}`, mobilePath: `/m/admin/purchases/${state.record.id}`, custom: false },
|
|
67
67
|
presentation: { summary: { title: '采购申请', initiatorDisplayName: '陈晨', departmentDisplayName: '办公室', submittedAt: instance.startedAt }, businessDetail: {
|
|
@@ -538,3 +538,19 @@ test('mobile child editing selects form fields while readonly details retain the
|
|
|
538
538
|
{ operation: 'update', resourceCode: 'purchase-lines', id: 'line-1', expectedRevision: 3, data: { name: '移动明细修改' } },
|
|
539
539
|
]);
|
|
540
540
|
});
|
|
541
|
+
|
|
542
|
+
for (const mobile of [false, true]) {
|
|
543
|
+
test(`${mobile ? 'mobile' : 'desktop'} workflow editing is only offered after completion`, async ({ page }) => {
|
|
544
|
+
const state = await workflowEntryPlatform(page);
|
|
545
|
+
await page.setViewportSize(mobile ? { width: 390, height: 600 } : { width: 1440, height: 900 });
|
|
546
|
+
for (const status of ['running', 'returned', 'approved']) {
|
|
547
|
+
state.instanceStatus = status;
|
|
548
|
+
await page.goto(`/workflow-entry.e2e.html?mode=${mobile ? 'mobile' : 'desktop'}-detail`);
|
|
549
|
+
await expect(page.locator('.oxa-record-detail-header')).toBeVisible();
|
|
550
|
+
await expect(page.locator('.oxa-record-detail-header').getByRole('button', { name: '编辑', exact: true })).toHaveCount(status === 'approved' ? 1 : 0);
|
|
551
|
+
await expect(page.locator('.oxa-record-detail-footer').getByRole('button', { name: /关\s*闭/ })).toHaveCount(0);
|
|
552
|
+
await expect(page.locator('.oxa-workflow-current-node')).toHaveCount(0);
|
|
553
|
+
}
|
|
554
|
+
expect(state.correctionReads).toBe(0);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
@@ -816,46 +816,88 @@ test('renders a paged desktop work center from the current-user role union', asy
|
|
|
816
816
|
}
|
|
817
817
|
});
|
|
818
818
|
|
|
819
|
-
|
|
819
|
+
for (const mobile of [false, true]) {
|
|
820
|
+
for (const multiple of [false, true]) {
|
|
821
|
+
test(`${mobile ? 'mobile' : 'desktop'} ${multiple ? 'CC' : 'transfer'} uses the standard member picker`, async ({ page }) => {
|
|
822
|
+
await page.setViewportSize(mobile ? { width: 320, height: 480 } : { width: 1440, height: 900 });
|
|
820
823
|
await mockWorkflow(page);
|
|
821
824
|
const current = detail();
|
|
825
|
+
const key = multiple ? 'cc' : 'transfer';
|
|
826
|
+
const label = multiple ? '抄送' : '转交';
|
|
827
|
+
const field = multiple ? 'userIds' : 'userId';
|
|
822
828
|
const cc = {
|
|
823
|
-
key
|
|
829
|
+
key, kind: 'workflow_command', label, group: 'instance_control', audience: 'participant',
|
|
824
830
|
placement: 'overflow', emphasis: 'neutral', tone: 'neutral', visible: true, enabled: true,
|
|
825
|
-
inputSchema: { type: 'object', additionalProperties: false, required: [
|
|
826
|
-
|
|
831
|
+
inputSchema: { type: 'object', additionalProperties: false, required: [field], properties: {
|
|
832
|
+
[field]: multiple ? { type: 'array', title: '抄送给', minItems: 1, maxItems: 20, uniqueItems: true, items: { type: 'string' } } : { type: 'string', title: '转交给' },
|
|
827
833
|
comment: { type: 'string', title: '留言', maxLength: 4000 },
|
|
828
834
|
} },
|
|
829
|
-
uiSchema: { presentation: 'dialog', confirmText:
|
|
830
|
-
execute: { method: 'POST', href: `/openxiangda-api/v2/applications/${appCode}/workflow
|
|
835
|
+
uiSchema: { presentation: 'dialog', confirmText: `确认${label}`, properties: { [field]: { component: 'user_select', mode: multiple ? 'multiple' : 'single' } } },
|
|
836
|
+
execute: { method: 'POST', href: `/openxiangda-api/v2/applications/${appCode}/workflow/${multiple ? `instances/${instanceId}` : `tasks/${taskId}`}/commands/${key}`, idempotencyRequired: true },
|
|
831
837
|
refresh: ['surface', 'timeline', 'work_center'],
|
|
832
838
|
};
|
|
833
|
-
const augmented = { ...current, operations: [...current.operations, cc], surface: { ...current.surface, operations: [...current.surface.operations, cc] } };
|
|
839
|
+
const augmented = { ...current, operations: [...current.operations.filter(item => item.key !== key), cc], surface: { ...current.surface, operations: [...current.surface.operations.filter(item => item.key !== key), cc] } };
|
|
834
840
|
await page.route('**/workflow/tasks/*/detail', route => route.fulfill({ json: envelope(augmented) }));
|
|
835
|
-
await page.route('**/directory
|
|
841
|
+
await page.route('**/directory/**', route => {
|
|
842
|
+
const tree = new URL(route.request().url()).pathname.endsWith('/tree');
|
|
843
|
+
const items = tree ? [] : ['陈老师', '李老师'].map((label, index) => ({ kind: 'user', id: `cc-user-${index + 1}`, label, selectable: true, snapshot: { value: `cc-user-${index + 1}`, label } }));
|
|
844
|
+
return route.fulfill({ json: envelope({ schemaVersion: 'openxiangda.directory-entry-page/v2', kind: tree ? 'department' : 'user', items, nextCursor: null }) });
|
|
845
|
+
});
|
|
836
846
|
let writes = 0;
|
|
837
|
-
await page.route(
|
|
847
|
+
await page.route(`**/commands/${key}`, async route => {
|
|
838
848
|
writes += 1;
|
|
839
|
-
expect(route.request().postDataJSON()).toMatchObject({ commandToken: current.surface.commandToken, input: {
|
|
849
|
+
expect(route.request().postDataJSON()).toMatchObject({ commandToken: current.surface.commandToken, input: { [field]: multiple ? ['cc-user-1', 'cc-user-2'] : 'cc-user-1', comment: '请查阅' } });
|
|
840
850
|
expect(route.request().postDataJSON().idempotencyKey).toBeTruthy();
|
|
841
851
|
expect(route.request().headers()['x-openxiangda-csrf-token']).toBe('workflow-csrf-1');
|
|
842
852
|
await route.fulfill({ json: envelope({ status: 'completed', advanced: false, instanceId }) });
|
|
843
853
|
});
|
|
844
|
-
await page.goto(workflowFixtureUrl(
|
|
854
|
+
await page.goto(workflowFixtureUrl(`${mobile ? '/m' : ''}/tasks/${taskId}`));
|
|
855
|
+
const footer = page.locator('.oxa-record-detail-footer');
|
|
856
|
+
await expect(footer).toBeVisible();
|
|
857
|
+
await expect(footer.getByRole('button', { name: /关\s*闭|编\s*辑/ })).toHaveCount(0);
|
|
858
|
+
if (mobile) {
|
|
859
|
+
const boxes = await footer.locator('.oxa-workflow-actions > button').evaluateAll(elements => elements.map(element => element.getBoundingClientRect().y));
|
|
860
|
+
expect(boxes).toHaveLength(3);
|
|
861
|
+
expect(Math.max(...boxes) - Math.min(...boxes)).toBeLessThan(2);
|
|
862
|
+
}
|
|
845
863
|
await page.getByRole('button', { name: '更多操作' }).click();
|
|
846
|
-
await page.getByRole('button', { name:
|
|
847
|
-
await page.getByRole('button', { name:
|
|
864
|
+
await page.getByRole('button', { name: label, exact: true }).click();
|
|
865
|
+
await page.getByRole('button', { name: `确认${label}` }).click();
|
|
866
|
+
expect(writes).toBe(0);
|
|
867
|
+
await expect(page.getByRole('combobox')).toHaveCount(0);
|
|
868
|
+
if (mobile) {
|
|
869
|
+
const bounds = await page.locator('.oxa-workflow-operation-drawer .ant-drawer-content-wrapper').boundingBox();
|
|
870
|
+
expect(bounds!.y).toBeGreaterThanOrEqual(0);
|
|
871
|
+
expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(481);
|
|
872
|
+
await expect(page.getByRole('button', { name: `确认${label}` })).toBeInViewport();
|
|
873
|
+
}
|
|
874
|
+
const trigger = page.locator(mobile ? '.oxa-mobile-choice-field button' : '.oxa-directory-trigger').first();
|
|
875
|
+
await trigger.click();
|
|
876
|
+
if (mobile) {
|
|
877
|
+
await expect(page.locator('.oxa-mobile-selection-sheet')).toBeInViewport();
|
|
878
|
+
await expect(page.getByRole('button', { name: '关闭', exact: true }).last()).toBeInViewport();
|
|
879
|
+
await expect(page.getByRole('button', { name: '确定', exact: true })).toBeInViewport();
|
|
880
|
+
}
|
|
881
|
+
await page.getByPlaceholder(mobile ? /搜索/ : '搜索成员姓名或工号').fill('老师');
|
|
882
|
+
await page.getByText('陈老师', { exact: true }).click();
|
|
883
|
+
await page.getByRole('button', { name: mobile ? '关闭' : /取\s*消/, exact: true }).last().click();
|
|
884
|
+
await page.getByRole('button', { name: `确认${label}` }).click();
|
|
848
885
|
expect(writes).toBe(0);
|
|
849
|
-
|
|
850
|
-
await
|
|
851
|
-
await page.
|
|
852
|
-
await page.
|
|
886
|
+
await trigger.click();
|
|
887
|
+
await page.getByPlaceholder(mobile ? /搜索/ : '搜索成员姓名或工号').fill('老师');
|
|
888
|
+
await page.getByText('陈老师', { exact: true }).click();
|
|
889
|
+
if (multiple) await page.getByText('李老师', { exact: true }).click();
|
|
890
|
+
await page.getByRole('button', { name: mobile ? `确定(${multiple ? 2 : 1})` : /确\s*定/, exact: true }).last().click();
|
|
853
891
|
await page.getByLabel('留言').fill('请查阅');
|
|
854
|
-
await page.getByRole('button', { name:
|
|
892
|
+
await page.getByRole('button', { name: `确认${label}` }).click();
|
|
855
893
|
await expect.poll(() => writes).toBe(1);
|
|
856
894
|
await expect(page.getByRole('button', { name: /同\s*意/ })).toBeVisible();
|
|
857
895
|
});
|
|
858
896
|
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
}
|
|
900
|
+
|
|
859
901
|
test('renders the generic desktop and mobile application todo center without a second router', async ({
|
|
860
902
|
page,
|
|
861
903
|
}) => {
|