@nocobase/client-v2 2.1.0-alpha.31 → 2.1.0-alpha.32
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/es/components/form/JsonTextArea.d.ts +18 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/actions/dateRangeLimit.d.ts +9 -0
- package/es/flow/actions/index.d.ts +1 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +4 -0
- package/es/flow/models/base/PageModel/RootPageModel.d.ts +9 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +7 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +2 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +20 -0
- package/es/flow/models/fields/JSEditableFieldModel.d.ts +4 -0
- package/es/index.mjs +72 -65
- package/lib/index.js +61 -54
- package/package.json +6 -5
- package/src/components/form/JsonTextArea.tsx +129 -0
- package/src/components/index.ts +1 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +478 -0
- package/src/flow/actions/__tests__/pattern.test.ts +190 -0
- package/src/flow/actions/dateRangeLimit.tsx +66 -0
- package/src/flow/actions/index.ts +1 -0
- package/src/flow/actions/linkageRules.tsx +117 -19
- package/src/flow/actions/openView.tsx +2 -1
- package/src/flow/actions/pattern.tsx +25 -2
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +7 -1
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +117 -0
- package/src/flow/models/base/PageModel/PageModel.tsx +15 -3
- package/src/flow/models/base/PageModel/RootPageModel.tsx +37 -2
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +73 -0
- package/src/flow/models/base/PageModel/__tests__/RootPageModel.test.ts +116 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +167 -1
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +103 -11
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +27 -3
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +47 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +42 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableField.refresh.test.tsx +122 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +2 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +21 -9
- package/src/flow/models/fields/DateTimeFieldModel/DateOnlyFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeFieldModel.tsx +4 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeNoTzFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeTzFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +242 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +152 -0
- package/src/flow/models/fields/JSEditableFieldModel.tsx +110 -14
- package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +87 -0
- package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +210 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
12
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
13
|
+
import { FlowEngine, FlowEngineProvider, FlowModel, FlowModelRenderer } from '@nocobase/flow-engine';
|
|
14
|
+
import { JSEditableFieldModel } from '../JSEditableFieldModel';
|
|
15
|
+
|
|
16
|
+
function createField(props?: Record<string, any>, code = '') {
|
|
17
|
+
const engine = new FlowEngine();
|
|
18
|
+
engine.registerModels({ JSEditableFieldModel });
|
|
19
|
+
return engine.createModel<JSEditableFieldModel>({
|
|
20
|
+
use: 'JSEditableFieldModel',
|
|
21
|
+
uid: `js-field-${props?.pattern || 'editable'}`,
|
|
22
|
+
props,
|
|
23
|
+
stepParams: {
|
|
24
|
+
jsSettings: {
|
|
25
|
+
runJs: {
|
|
26
|
+
code,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderField(props?: Record<string, any>, code?: string) {
|
|
34
|
+
const field = createField(props, code);
|
|
35
|
+
|
|
36
|
+
render(<>{field.render()}</>);
|
|
37
|
+
|
|
38
|
+
return field;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const EDITABLE_CODE = `
|
|
42
|
+
function JsEditableField() {
|
|
43
|
+
const React = ctx.React;
|
|
44
|
+
const { Input } = ctx.antd;
|
|
45
|
+
const [value, setValue] = React.useState(ctx.getValue?.() ?? '');
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
const handler = (ev) => setValue(ev?.detail ?? '');
|
|
49
|
+
ctx.element?.addEventListener('js-field:value-change', handler);
|
|
50
|
+
return () => ctx.element?.removeEventListener('js-field:value-change', handler);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const onChange = (e) => {
|
|
54
|
+
const v = e?.target?.value ?? '';
|
|
55
|
+
setValue(v);
|
|
56
|
+
ctx.setValue?.(v);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Input
|
|
61
|
+
{...ctx.model.props}
|
|
62
|
+
value={value}
|
|
63
|
+
onChange={onChange}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ctx.render(<JsEditableField />);
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const READONLY_AWARE_CODE = `
|
|
72
|
+
const React = ctx.React;
|
|
73
|
+
ctx.render(<span data-testid="js-readonly-state">{String(ctx.readOnly)}</span>);
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
class ParentModel extends FlowModel<any> {
|
|
77
|
+
render() {
|
|
78
|
+
return <FlowModelRenderer model={this.subModels.field} />;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderParentFieldWithFlowRenderer(
|
|
83
|
+
fieldProps?: Record<string, any>,
|
|
84
|
+
parentProps?: Record<string, any>,
|
|
85
|
+
code = EDITABLE_CODE,
|
|
86
|
+
) {
|
|
87
|
+
const engine = new FlowEngine();
|
|
88
|
+
engine.registerModels({ JSEditableFieldModel, ParentModel });
|
|
89
|
+
const parent = engine.createModel<ParentModel>({
|
|
90
|
+
use: ParentModel,
|
|
91
|
+
uid: 'js-field-parent',
|
|
92
|
+
props: parentProps,
|
|
93
|
+
subModels: {
|
|
94
|
+
field: {
|
|
95
|
+
use: 'JSEditableFieldModel',
|
|
96
|
+
uid: 'js-field-with-parent',
|
|
97
|
+
props: fieldProps,
|
|
98
|
+
stepParams: {
|
|
99
|
+
jsSettings: {
|
|
100
|
+
runJs: {
|
|
101
|
+
code,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
render(
|
|
110
|
+
<FlowEngineProvider engine={engine}>
|
|
111
|
+
<FlowModelRenderer model={parent} />
|
|
112
|
+
</FlowEngineProvider>,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return parent;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
describe('JSEditableFieldModel', () => {
|
|
119
|
+
it('renders configured JavaScript in display only mode', async () => {
|
|
120
|
+
const field = renderParentFieldWithFlowRenderer(
|
|
121
|
+
{ pattern: 'readPretty', value: 'hello' },
|
|
122
|
+
undefined,
|
|
123
|
+
READONLY_AWARE_CODE,
|
|
124
|
+
).subModels.field as JSEditableFieldModel;
|
|
125
|
+
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
expect(screen.getByTestId('js-readonly-state')).toHaveTextContent('true');
|
|
128
|
+
expect(field.context.ref.current).toBeInstanceOf(HTMLSpanElement);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('renders fallback as text in display only mode when code is empty', () => {
|
|
133
|
+
renderField({ pattern: 'readPretty', value: 'hello' });
|
|
134
|
+
|
|
135
|
+
expect(screen.getByText('hello')).toBeInTheDocument();
|
|
136
|
+
expect(screen.queryByDisplayValue('hello')).not.toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('renders fallback input for editable mode', () => {
|
|
140
|
+
renderField({ value: 'hello' });
|
|
141
|
+
|
|
142
|
+
expect(screen.getByDisplayValue('hello')).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('rerenders as text when owner form item switches to display only', async () => {
|
|
146
|
+
const parent = renderParentFieldWithFlowRenderer({ value: 'hello' }, undefined, '');
|
|
147
|
+
|
|
148
|
+
await act(async () => {
|
|
149
|
+
parent.setProps({ pattern: 'readPretty' });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await waitFor(() => {
|
|
153
|
+
expect(screen.getByText('hello')).toBeInTheDocument();
|
|
154
|
+
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('does not rerun JavaScript settings when field value changes', async () => {
|
|
159
|
+
const parent = renderParentFieldWithFlowRenderer({ value: 'hello' });
|
|
160
|
+
const field = parent.subModels.field as JSEditableFieldModel;
|
|
161
|
+
const applyFlowSpy = vi.spyOn(field, 'applyFlow');
|
|
162
|
+
|
|
163
|
+
await waitFor(() => {
|
|
164
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
applyFlowSpy.mockClear();
|
|
168
|
+
|
|
169
|
+
await act(async () => {
|
|
170
|
+
field.setProps({ value: 'hello1' });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(applyFlowSpy).not.toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('coalesces script and pattern changes into one JavaScript settings run', async () => {
|
|
177
|
+
const parent = renderParentFieldWithFlowRenderer({ value: 'hello' }, undefined, '');
|
|
178
|
+
const field = parent.subModels.field as JSEditableFieldModel;
|
|
179
|
+
const applyFlowSpy = vi.spyOn(field, 'applyFlow');
|
|
180
|
+
|
|
181
|
+
await act(async () => {
|
|
182
|
+
field.setStepParams('jsSettings', 'runJs', { code: READONLY_AWARE_CODE });
|
|
183
|
+
parent.setProps({ pattern: 'readPretty' });
|
|
184
|
+
field.scheduleApplyJsSettings();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(screen.getByTestId('js-readonly-state')).toHaveTextContent('true');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(applyFlowSpy).toHaveBeenCalledTimes(1);
|
|
192
|
+
expect(applyFlowSpy).toHaveBeenCalledWith('jsSettings');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('applies JavaScript settings once on initial render', async () => {
|
|
196
|
+
const applyFlowSpy = vi.spyOn(FlowModel.prototype, 'applyFlow');
|
|
197
|
+
renderParentFieldWithFlowRenderer({ value: 'hello' });
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(applyFlowSpy).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(applyFlowSpy.mock.calls).toEqual([['jsSettings']]);
|
|
206
|
+
} finally {
|
|
207
|
+
applyFlowSpy.mockRestore();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|