@neovici/cosmoz-form 2.4.0 → 2.5.0
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/dist/async-rule.d.ts +21 -11
- package/dist/async-rule.d.ts.map +1 -1
- package/dist/inputs/autocomplete.d.ts +1 -1
- package/dist/inputs/autocomplete.d.ts.map +1 -1
- package/dist/inputs/base.d.ts +4 -4
- package/dist/inputs/base.d.ts.map +1 -1
- package/dist/inputs/base.js +15 -11
- package/dist/inputs/basic.d.ts +1 -1
- package/dist/inputs/basic.d.ts.map +1 -1
- package/dist/inputs/common.d.ts +8 -8
- package/dist/inputs/common.d.ts.map +1 -1
- package/dist/inputs/common.js +3 -3
- package/dist/inputs/date-range.d.ts +1 -1
- package/dist/inputs/date-range.d.ts.map +1 -1
- package/dist/inputs/file-drop.d.ts +1 -1
- package/dist/inputs/file-drop.d.ts.map +1 -1
- package/dist/inputs/file.d.ts +1 -1
- package/dist/inputs/file.d.ts.map +1 -1
- package/dist/inputs/inline-file.d.ts +1 -1
- package/dist/inputs/inline-file.d.ts.map +1 -1
- package/dist/inputs/inline-file.js +2 -1
- package/dist/inputs/read-only-number.d.ts +1 -1
- package/dist/inputs/read-only-number.d.ts.map +1 -1
- package/dist/inputs/read-only-number.js +3 -3
- package/dist/inputs/toggle.d.ts +1 -1
- package/dist/inputs/toggle.d.ts.map +1 -1
- package/dist/make-debounce-runner.d.ts +2 -2
- package/dist/make-debounce-runner.d.ts.map +1 -1
- package/dist/make-debounce-runner.js +10 -2
- package/dist/make-take-latest-runner.d.ts +2 -2
- package/dist/make-take-latest-runner.d.ts.map +1 -1
- package/dist/make-take-latest-runner.js +1 -0
- package/dist/render/fields.d.ts +11 -11
- package/dist/render/fields.d.ts.map +1 -1
- package/dist/render/fields.js +2 -3
- package/dist/render/items.d.ts +12 -8
- package/dist/render/items.d.ts.map +1 -1
- package/dist/render/items.js +5 -4
- package/dist/test/apply-rules.test.d.ts +2 -0
- package/dist/test/apply-rules.test.d.ts.map +1 -0
- package/dist/test/apply-rules.test.js +295 -0
- package/dist/test/use-items.test.js +69 -2
- package/dist/test/use-validated-form.test.js +120 -1
- package/dist/types/index.d.ts +27 -27
- package/dist/types/index.d.ts.map +1 -1
- package/dist/use-async-form-core.d.ts +6 -6
- package/dist/use-async-form-core.d.ts.map +1 -1
- package/dist/use-async-form-core.js +9 -7
- package/dist/use-form-core.d.ts +3 -2
- package/dist/use-form-core.d.ts.map +1 -1
- package/dist/use-form-core.js +30 -7
- package/dist/use-form.d.ts +1 -1
- package/dist/use-form.d.ts.map +1 -1
- package/dist/use-form.js +1 -1
- package/dist/use-items/apply-rules.d.ts +10 -5
- package/dist/use-items/apply-rules.d.ts.map +1 -1
- package/dist/use-items/apply-rules.js +3 -3
- package/dist/use-items/use-async-rules.d.ts +4 -3
- package/dist/use-items/use-async-rules.d.ts.map +1 -1
- package/dist/use-items/use-async-rules.js +6 -5
- package/dist/use-items/use-items.d.ts +6 -4
- package/dist/use-items/use-items.d.ts.map +1 -1
- package/dist/use-items/use-items.js +44 -11
- package/dist/use-items/use-validated-items.d.ts +6 -4
- package/dist/use-items/use-validated-items.d.ts.map +1 -1
- package/dist/use-items/use-validated-items.js +5 -3
- package/dist/use-validated-form$.d.ts +7 -6
- package/dist/use-validated-form$.d.ts.map +1 -1
- package/dist/use-validated-form$.js +2 -2
- package/dist/use-validated-form-core.d.ts +12 -10
- package/dist/use-validated-form-core.d.ts.map +1 -1
- package/dist/use-validated-form-core.js +5 -5
- package/dist/use-validated-form.d.ts +7 -5
- package/dist/use-validated-form.d.ts.map +1 -1
- package/dist/use-validated-form.js +3 -3
- package/dist/validation/index.d.ts +10 -10
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +12 -12
- package/dist/validation/rules.d.ts +6 -6
- package/dist/validation/rules.d.ts.map +1 -1
- package/dist/validation/rules.js +8 -6
- package/package.json +1 -1
package/dist/render/items.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
|
|
2
|
+
import { cancelIcon } from '@neovici/cosmoz-icons';
|
|
2
3
|
import { tagged as css } from '@neovici/cosmoz-utils';
|
|
4
|
+
import { invoke, noop } from '@neovici/cosmoz-utils/function';
|
|
3
5
|
import { html } from 'lit-html';
|
|
4
6
|
import { when } from 'lit-html/directives/when.js';
|
|
5
7
|
import { renderFields, renderHeaders, renderStyles } from './fields';
|
|
6
|
-
import { cancelIcon } from '@neovici/cosmoz-icons';
|
|
7
8
|
import { styles } from './styles';
|
|
8
|
-
import { invoke, noop } from '@neovici/cosmoz-utils/function';
|
|
9
9
|
const key = Symbol('key');
|
|
10
10
|
const mkDefaults = (defaults) => {
|
|
11
11
|
const newly = { ...defaults };
|
|
@@ -14,12 +14,14 @@ const mkDefaults = (defaults) => {
|
|
|
14
14
|
export const renderRemove = (remove) => html `<button class="remove" ?disabled=${!remove} @click=${remove}>
|
|
15
15
|
${cancelIcon()}
|
|
16
16
|
</button>`;
|
|
17
|
-
export const renderItem = (values, index, { update, remove, fields, ...thru }) => html `<div class="item" data-index=${index}>
|
|
17
|
+
export const renderItem = (values, index, { update, remove, fields, context, touched = false, ...thru }) => html `<div class="item" data-index=${index}>
|
|
18
18
|
${[
|
|
19
19
|
renderFields({
|
|
20
20
|
...thru,
|
|
21
21
|
values,
|
|
22
22
|
fields,
|
|
23
|
+
context: context ?? {},
|
|
24
|
+
touched,
|
|
23
25
|
onChange: (changes) => update(index, {
|
|
24
26
|
...invoke(changes, values),
|
|
25
27
|
[key]: values?.[key] ?? values,
|
|
@@ -28,7 +30,6 @@ export const renderItem = (values, index, { update, remove, fields, ...thru }) =
|
|
|
28
30
|
load: noop,
|
|
29
31
|
onReset: noop,
|
|
30
32
|
onValues: noop,
|
|
31
|
-
touched: false,
|
|
32
33
|
}),
|
|
33
34
|
when(remove, (remove) => renderRemove(values && remove && (() => remove(index)))),
|
|
34
35
|
]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-rules.test.d.ts","sourceRoot":"","sources":["../../src/test/apply-rules.test.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { assert } from '@open-wc/testing';
|
|
2
|
+
import { applyRules } from '../use-items/apply-rules';
|
|
3
|
+
suite('applyRules', () => {
|
|
4
|
+
// ── Basic mechanics ───────────────────────────────────────────────────────
|
|
5
|
+
test('returns newItem unchanged when rules is undefined', () => {
|
|
6
|
+
const item = { name: 'Alice', age: 30 };
|
|
7
|
+
const result = applyRules({ newItem: item, rules: undefined });
|
|
8
|
+
assert.strictEqual(result, item);
|
|
9
|
+
});
|
|
10
|
+
test('returns newItem unchanged when rules is empty array', () => {
|
|
11
|
+
const item = { name: 'Alice', age: 30 };
|
|
12
|
+
const result = applyRules({ newItem: item, rules: [] });
|
|
13
|
+
assert.deepEqual(result, item);
|
|
14
|
+
});
|
|
15
|
+
test('rule with no depsFn always runs and merges patch into item', () => {
|
|
16
|
+
// Realistic: compute a derived `total` field from `qty * price`
|
|
17
|
+
const totalRule = [({ qty, price }) => ({ total: qty * price })];
|
|
18
|
+
const result = applyRules({
|
|
19
|
+
newItem: { qty: 3, price: 10 },
|
|
20
|
+
rules: [totalRule],
|
|
21
|
+
});
|
|
22
|
+
assert.equal(result.total, 30);
|
|
23
|
+
assert.equal(result.qty, 3); // original fields survive
|
|
24
|
+
assert.equal(result.price, 10);
|
|
25
|
+
});
|
|
26
|
+
test('rule result is spread — original fields not replaced', () => {
|
|
27
|
+
// Rule only sets one field; others must survive
|
|
28
|
+
const setStatus = [() => ({ status: 'active' })];
|
|
29
|
+
const result = applyRules({
|
|
30
|
+
newItem: { id: 1, name: 'Bob', status: 'pending' },
|
|
31
|
+
rules: [setStatus],
|
|
32
|
+
});
|
|
33
|
+
assert.equal(result.id, 1);
|
|
34
|
+
assert.equal(result.name, 'Bob');
|
|
35
|
+
assert.equal(result.status, 'active');
|
|
36
|
+
});
|
|
37
|
+
test('multiple rules applied in sequence — later rule sees result of earlier rule', () => {
|
|
38
|
+
// Mirrors the use-items fixture: nameLength → doubledNameLength
|
|
39
|
+
const computesNameLength = [
|
|
40
|
+
({ name }) => ({ nameLength: name.length }),
|
|
41
|
+
({ name }) => [name],
|
|
42
|
+
];
|
|
43
|
+
const doublesNameLength = [
|
|
44
|
+
({ nameLength }) => ({ doubledNameLength: nameLength * 2 }),
|
|
45
|
+
({ nameLength }) => [nameLength],
|
|
46
|
+
];
|
|
47
|
+
const result = applyRules({
|
|
48
|
+
newItem: { name: 'hello' },
|
|
49
|
+
rules: [computesNameLength, doublesNameLength],
|
|
50
|
+
});
|
|
51
|
+
assert.equal(result.nameLength, 5);
|
|
52
|
+
assert.equal(result.doubledNameLength, 10);
|
|
53
|
+
});
|
|
54
|
+
// ── depsFn skip logic ─────────────────────────────────────────────────────
|
|
55
|
+
test('rule with depsFn is skipped when deps unchanged', () => {
|
|
56
|
+
let runCount = 0;
|
|
57
|
+
const rule = [
|
|
58
|
+
({ qty, price }) => {
|
|
59
|
+
runCount++;
|
|
60
|
+
return { total: qty * price };
|
|
61
|
+
},
|
|
62
|
+
({ qty, price }) => [qty, price],
|
|
63
|
+
];
|
|
64
|
+
const old = { qty: 2, price: 5, total: 10 };
|
|
65
|
+
const next = { qty: 2, price: 5, total: 10 }; // same values
|
|
66
|
+
applyRules({ oldItem: old, newItem: next, rules: [rule] });
|
|
67
|
+
assert.equal(runCount, 0, 'rule should not run when deps unchanged');
|
|
68
|
+
});
|
|
69
|
+
test('rule with depsFn runs when deps change', () => {
|
|
70
|
+
let runCount = 0;
|
|
71
|
+
const rule = [
|
|
72
|
+
({ qty, price }) => {
|
|
73
|
+
runCount++;
|
|
74
|
+
return { total: qty * price };
|
|
75
|
+
},
|
|
76
|
+
({ qty, price }) => [qty, price],
|
|
77
|
+
];
|
|
78
|
+
const old = { qty: 2, price: 5, total: 10 };
|
|
79
|
+
const next = { qty: 3, price: 5 }; // qty changed
|
|
80
|
+
const result = applyRules({ oldItem: old, newItem: next, rules: [rule] });
|
|
81
|
+
assert.equal(runCount, 1);
|
|
82
|
+
assert.equal(result.total, 15);
|
|
83
|
+
});
|
|
84
|
+
test('rule with depsFn always runs when no oldItem (initial apply)', () => {
|
|
85
|
+
let runCount = 0;
|
|
86
|
+
const rule = [
|
|
87
|
+
({ qty, price }) => {
|
|
88
|
+
runCount++;
|
|
89
|
+
return { total: qty * price };
|
|
90
|
+
},
|
|
91
|
+
({ qty, price }) => [qty, price],
|
|
92
|
+
];
|
|
93
|
+
// No oldItem — dep check is bypassed, rule always runs
|
|
94
|
+
applyRules({ newItem: { qty: 2, price: 5 }, rules: [rule] });
|
|
95
|
+
assert.equal(runCount, 1);
|
|
96
|
+
});
|
|
97
|
+
test('rule without depsFn always runs even when oldItem is present', () => {
|
|
98
|
+
let runCount = 0;
|
|
99
|
+
// No depsFn — always re-compute (e.g. addIndex pattern)
|
|
100
|
+
const addIndex = [
|
|
101
|
+
(_, __, index) => {
|
|
102
|
+
runCount++;
|
|
103
|
+
return { index };
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
const old = { name: 'A', index: 0 };
|
|
107
|
+
const next = { name: 'A' }; // name unchanged
|
|
108
|
+
applyRules({ oldItem: old, newItem: next, rules: [addIndex], index: 0 });
|
|
109
|
+
assert.equal(runCount, 1, 'no-depsFn rule must always run');
|
|
110
|
+
});
|
|
111
|
+
// ── index / oldIndex ──────────────────────────────────────────────────────
|
|
112
|
+
test('passes index and oldIndex to computeFn', () => {
|
|
113
|
+
// Realistic: track move history when an item changes position
|
|
114
|
+
let capturedIndex, capturedOldIndex;
|
|
115
|
+
const trackMove = [
|
|
116
|
+
(current, old, index, oldIndex) => {
|
|
117
|
+
capturedIndex = index;
|
|
118
|
+
capturedOldIndex = oldIndex;
|
|
119
|
+
return {};
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
applyRules({
|
|
123
|
+
oldItem: { name: 'A' },
|
|
124
|
+
newItem: { name: 'A' },
|
|
125
|
+
rules: [trackMove],
|
|
126
|
+
index: 2,
|
|
127
|
+
oldIndex: 5,
|
|
128
|
+
});
|
|
129
|
+
assert.equal(capturedIndex, 2);
|
|
130
|
+
assert.equal(capturedOldIndex, 5);
|
|
131
|
+
});
|
|
132
|
+
test('oldIndex defaults to index when not provided', () => {
|
|
133
|
+
let capturedOldIndex;
|
|
134
|
+
const rule = [
|
|
135
|
+
(current, old, index, oldIndex) => {
|
|
136
|
+
capturedOldIndex = oldIndex;
|
|
137
|
+
return {};
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
applyRules({
|
|
141
|
+
oldItem: { name: 'A' },
|
|
142
|
+
newItem: { name: 'A' },
|
|
143
|
+
rules: [rule],
|
|
144
|
+
index: 3,
|
|
145
|
+
});
|
|
146
|
+
assert.equal(capturedOldIndex, 3);
|
|
147
|
+
});
|
|
148
|
+
test('depsFn receives index — rule skips when item and index both unchanged', () => {
|
|
149
|
+
let runCount = 0;
|
|
150
|
+
// Realistic: rule that re-computes when name or position changes
|
|
151
|
+
const rule = [
|
|
152
|
+
({ name }, old, index) => {
|
|
153
|
+
runCount++;
|
|
154
|
+
return { label: `#${index}: ${name}` };
|
|
155
|
+
},
|
|
156
|
+
({ name }, index) => [name, index],
|
|
157
|
+
];
|
|
158
|
+
const item = { name: 'Alice' };
|
|
159
|
+
applyRules({
|
|
160
|
+
oldItem: item,
|
|
161
|
+
newItem: item,
|
|
162
|
+
rules: [rule],
|
|
163
|
+
index: 0,
|
|
164
|
+
oldIndex: 0,
|
|
165
|
+
});
|
|
166
|
+
assert.equal(runCount, 0, 'rule should skip when both name and index unchanged');
|
|
167
|
+
});
|
|
168
|
+
test('depsFn receives index — rule runs when only index changes (item moved)', () => {
|
|
169
|
+
// Realistic: item reordered — index changed, content unchanged
|
|
170
|
+
const results = [];
|
|
171
|
+
const rule = [
|
|
172
|
+
({ name }, old, index) => {
|
|
173
|
+
results.push({ name, index });
|
|
174
|
+
return { label: `#${index}: ${name}` };
|
|
175
|
+
},
|
|
176
|
+
({ name }, index) => [name, index],
|
|
177
|
+
];
|
|
178
|
+
const item = { name: 'Alice' };
|
|
179
|
+
const result = applyRules({
|
|
180
|
+
oldItem: item,
|
|
181
|
+
newItem: item,
|
|
182
|
+
rules: [rule],
|
|
183
|
+
index: 1, // moved to position 1
|
|
184
|
+
oldIndex: 0, // was at position 0
|
|
185
|
+
});
|
|
186
|
+
assert.equal(results.length, 1);
|
|
187
|
+
assert.equal(result.label, '#1: Alice');
|
|
188
|
+
});
|
|
189
|
+
// ── context / oldContext ──────────────────────────────────────────────────
|
|
190
|
+
test('passes context to computeFn', () => {
|
|
191
|
+
// Realistic: compute totalWithVat using a VAT rate from context
|
|
192
|
+
const vatRule = [
|
|
193
|
+
({ price }, _old, _index, _oldIndex, context) => ({
|
|
194
|
+
total: price * (1 + (context?.vatRate ?? 0) / 100),
|
|
195
|
+
}),
|
|
196
|
+
];
|
|
197
|
+
const result = applyRules({
|
|
198
|
+
newItem: { price: 100 },
|
|
199
|
+
rules: [vatRule],
|
|
200
|
+
context: { vatRate: 20 },
|
|
201
|
+
});
|
|
202
|
+
assert.equal(result.total, 120);
|
|
203
|
+
});
|
|
204
|
+
test('passes context to depsFn — rule skips when context and item unchanged', () => {
|
|
205
|
+
let runCount = 0;
|
|
206
|
+
const rule = [
|
|
207
|
+
() => {
|
|
208
|
+
runCount++;
|
|
209
|
+
return {};
|
|
210
|
+
},
|
|
211
|
+
({ price }, _index, context) => [price, context?.vatRate],
|
|
212
|
+
];
|
|
213
|
+
const item = { price: 100 };
|
|
214
|
+
const ctx = { vatRate: 20 };
|
|
215
|
+
applyRules({
|
|
216
|
+
oldItem: item,
|
|
217
|
+
newItem: item,
|
|
218
|
+
rules: [rule],
|
|
219
|
+
context: ctx,
|
|
220
|
+
oldContext: ctx,
|
|
221
|
+
});
|
|
222
|
+
assert.equal(runCount, 0, 'rule should skip when price and vatRate both unchanged');
|
|
223
|
+
});
|
|
224
|
+
test('rule re-runs when context changes — depsFn detects vatRate change', () => {
|
|
225
|
+
// Realistic: VAT rate changed on parent form — all items must re-compute
|
|
226
|
+
const vatRule = [
|
|
227
|
+
({ price }, _old, _index, _oldIndex, context) => ({
|
|
228
|
+
total: price * (1 + (context?.vatRate ?? 0) / 100),
|
|
229
|
+
}),
|
|
230
|
+
({ price }, _index, context) => [price, context?.vatRate],
|
|
231
|
+
];
|
|
232
|
+
const item = { price: 100, total: 110 };
|
|
233
|
+
const result = applyRules({
|
|
234
|
+
oldItem: item,
|
|
235
|
+
newItem: item,
|
|
236
|
+
rules: [vatRule],
|
|
237
|
+
context: { vatRate: 25 },
|
|
238
|
+
oldContext: { vatRate: 10 },
|
|
239
|
+
});
|
|
240
|
+
assert.equal(result.total, 125);
|
|
241
|
+
});
|
|
242
|
+
test('rule skips when context and item both unchanged (oldContext same as context)', () => {
|
|
243
|
+
let runCount = 0;
|
|
244
|
+
const rule = [
|
|
245
|
+
() => {
|
|
246
|
+
runCount++;
|
|
247
|
+
return {};
|
|
248
|
+
},
|
|
249
|
+
({ price }, _index, context) => [price, context?.vatRate],
|
|
250
|
+
];
|
|
251
|
+
const item = { price: 50 };
|
|
252
|
+
applyRules({
|
|
253
|
+
oldItem: item,
|
|
254
|
+
newItem: item,
|
|
255
|
+
rules: [rule],
|
|
256
|
+
context: { vatRate: 20 },
|
|
257
|
+
oldContext: { vatRate: 20 },
|
|
258
|
+
});
|
|
259
|
+
assert.equal(runCount, 0);
|
|
260
|
+
});
|
|
261
|
+
test('oldContext defaults to context — no spurious re-runs when called without oldContext', () => {
|
|
262
|
+
// Callers that do not provide oldContext (normal mutations) should not
|
|
263
|
+
// cause context-change re-runs
|
|
264
|
+
let runCount = 0;
|
|
265
|
+
const rule = [
|
|
266
|
+
() => {
|
|
267
|
+
runCount++;
|
|
268
|
+
return {};
|
|
269
|
+
},
|
|
270
|
+
({ qty }, _index, context) => [qty, context?.discount],
|
|
271
|
+
];
|
|
272
|
+
const old = { qty: 2, discount: 0 };
|
|
273
|
+
const next = { qty: 3 }; // qty changed, no oldContext provided
|
|
274
|
+
applyRules({
|
|
275
|
+
oldItem: old,
|
|
276
|
+
newItem: next,
|
|
277
|
+
rules: [rule],
|
|
278
|
+
context: { discount: 10 },
|
|
279
|
+
// no oldContext — defaults to context, so discount appears unchanged
|
|
280
|
+
});
|
|
281
|
+
// Only qty changed in deps; rule runs due to qty change not context change
|
|
282
|
+
assert.equal(runCount, 1);
|
|
283
|
+
});
|
|
284
|
+
test('backward compat — rule with no context arg works unchanged', () => {
|
|
285
|
+
const rule = [
|
|
286
|
+
({ qty, price }) => ({ total: qty * price }),
|
|
287
|
+
({ qty, price }) => [qty, price],
|
|
288
|
+
];
|
|
289
|
+
const result = applyRules({
|
|
290
|
+
newItem: { qty: 4, price: 25 },
|
|
291
|
+
rules: [rule],
|
|
292
|
+
});
|
|
293
|
+
assert.equal(result.total, 100);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* eslint-disable mocha/max-top-level-suites */
|
|
2
|
-
import { assert } from '@open-wc/testing';
|
|
3
2
|
import { renderHook } from '@neovici/testing';
|
|
4
|
-
import {
|
|
3
|
+
import { assert, waitUntil } from '@open-wc/testing';
|
|
5
4
|
import { TOUCHED } from '../touch';
|
|
5
|
+
import { useItems } from '../use-items';
|
|
6
6
|
suite('useItems', () => {
|
|
7
7
|
let result;
|
|
8
8
|
let nextUpdate;
|
|
@@ -326,3 +326,70 @@ suite('useItems', () => {
|
|
|
326
326
|
});
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
|
+
suite('useItems with context', () => {
|
|
330
|
+
// Rule: label = org/name. depsFn: [name, org] so re-runs when either changes.
|
|
331
|
+
const labelRule = [
|
|
332
|
+
({ name }, _old, _index, _oldIndex, context) => ({
|
|
333
|
+
label: `${context?.org ?? ''}/${name}`,
|
|
334
|
+
}),
|
|
335
|
+
({ name }, _index, context) => [name, context?.org],
|
|
336
|
+
];
|
|
337
|
+
test('rule computes derived field using context on initialization', async () => {
|
|
338
|
+
const ctx = { org: 'neovici' };
|
|
339
|
+
const { result } = await renderHook(() => useItems({
|
|
340
|
+
initial: [{ name: 'foo' }],
|
|
341
|
+
rules: [labelRule],
|
|
342
|
+
context: ctx,
|
|
343
|
+
}));
|
|
344
|
+
assert.equal(result.current.items[0].label, 'neovici/foo');
|
|
345
|
+
});
|
|
346
|
+
test('rule re-computes derived field using context on update', async () => {
|
|
347
|
+
const ctx = { org: 'neovici' };
|
|
348
|
+
const { result, nextUpdate } = await renderHook(() => useItems({
|
|
349
|
+
initial: [{ name: 'foo' }],
|
|
350
|
+
rules: [labelRule],
|
|
351
|
+
context: ctx,
|
|
352
|
+
}));
|
|
353
|
+
result.current.update(0, { name: 'bar' });
|
|
354
|
+
await nextUpdate();
|
|
355
|
+
assert.equal(result.current.items[0].label, 'neovici/bar');
|
|
356
|
+
});
|
|
357
|
+
test('context change re-runs rule and updates derived field', async () => {
|
|
358
|
+
const scoreRule = [
|
|
359
|
+
({ value }, _old, _index, _oldIndex, context) => ({
|
|
360
|
+
score: value * (context?.threshold ?? 1),
|
|
361
|
+
}),
|
|
362
|
+
({ value }, _index, context) => [value, context?.threshold],
|
|
363
|
+
];
|
|
364
|
+
const { result, rerender } = await renderHook(({ ctx }) => useItems({
|
|
365
|
+
initial: [{ value: 5 }],
|
|
366
|
+
rules: [scoreRule],
|
|
367
|
+
context: ctx,
|
|
368
|
+
}), { initialProps: { ctx: { threshold: 10 } } });
|
|
369
|
+
assert.equal(result.current.items[0].score, 50);
|
|
370
|
+
await rerender({ ctx: { threshold: 20 } });
|
|
371
|
+
await waitUntil(() => result.current.items[0].score === 100, 'score should update when context threshold changes');
|
|
372
|
+
assert.equal(result.current.items[0].score, 100);
|
|
373
|
+
});
|
|
374
|
+
test('append applies context-aware rule to new items', async () => {
|
|
375
|
+
const ctx = { org: 'test' };
|
|
376
|
+
const { result, nextUpdate } = await renderHook(() => useItems({
|
|
377
|
+
initial: [],
|
|
378
|
+
rules: [labelRule],
|
|
379
|
+
context: ctx,
|
|
380
|
+
}));
|
|
381
|
+
result.current.append([{ name: 'new' }]);
|
|
382
|
+
await nextUpdate();
|
|
383
|
+
assert.equal(result.current.items[0].label, 'test/new');
|
|
384
|
+
});
|
|
385
|
+
test('backward compat — rules without context arg work unchanged', async () => {
|
|
386
|
+
const rules = [
|
|
387
|
+
[({ name }) => ({ nameLength: name.length }), ({ name }) => [name]],
|
|
388
|
+
];
|
|
389
|
+
const { result } = await renderHook(() => useItems({
|
|
390
|
+
initial: [{ name: 'hello' }],
|
|
391
|
+
rules,
|
|
392
|
+
}));
|
|
393
|
+
assert.equal(result.current.items[0].nameLength, 5);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable mocha/max-top-level-suites */
|
|
2
|
-
import { assert } from '@open-wc/testing';
|
|
3
2
|
import { renderHook } from '@neovici/testing';
|
|
3
|
+
import { assert, waitUntil } from '@open-wc/testing';
|
|
4
4
|
import { useValidatedForm } from '../use-validated-form';
|
|
5
5
|
import { required, tooLong, tooShort } from '../validation';
|
|
6
6
|
suite('useValidatedForm', () => {
|
|
@@ -97,3 +97,122 @@ suite('useValidatedForm with field rules', () => {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
+
suite('useValidatedForm with context', () => {
|
|
101
|
+
test('validate uses context to determine error', async () => {
|
|
102
|
+
const fields = [
|
|
103
|
+
{
|
|
104
|
+
id: 'amount',
|
|
105
|
+
validate: (value, _values, _field, context) => value < (context?.minAmount ?? 0) ? 'Too low' : false,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
const ctx = { minAmount: 200 };
|
|
109
|
+
const { result } = await renderHook(() => useValidatedForm({
|
|
110
|
+
fields,
|
|
111
|
+
initial: { amount: 100 },
|
|
112
|
+
context: ctx,
|
|
113
|
+
}));
|
|
114
|
+
assert.isTrue(result.current.invalid);
|
|
115
|
+
assert.equal(result.current.fields[0].error, 'Too low');
|
|
116
|
+
});
|
|
117
|
+
test('validate error uses context constraint', async () => {
|
|
118
|
+
const fields = [
|
|
119
|
+
{
|
|
120
|
+
id: 'deliveryDate',
|
|
121
|
+
validate: (value, _values, _field, context) => value < (context?.minDate ?? '') ? 'Too early' : false,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
const ctx = { minDate: '2024-06-01' };
|
|
125
|
+
const { result } = await renderHook(() => useValidatedForm({
|
|
126
|
+
fields,
|
|
127
|
+
initial: { deliveryDate: '2024-01-01' },
|
|
128
|
+
context: ctx,
|
|
129
|
+
}));
|
|
130
|
+
assert.isTrue(result.current.invalid);
|
|
131
|
+
assert.equal(result.current.fields[0].error, 'Too early');
|
|
132
|
+
});
|
|
133
|
+
test('validation passes when value satisfies context constraint', async () => {
|
|
134
|
+
const fields = [
|
|
135
|
+
{
|
|
136
|
+
id: 'deliveryDate',
|
|
137
|
+
validate: (value, _values, _field, context) => value < (context?.minDate ?? '') ? 'Too early' : false,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
const ctx = { minDate: '2024-06-01' };
|
|
141
|
+
const { result } = await renderHook(() => useValidatedForm({
|
|
142
|
+
fields,
|
|
143
|
+
initial: { deliveryDate: '2024-12-01' },
|
|
144
|
+
context: ctx,
|
|
145
|
+
}));
|
|
146
|
+
assert.isFalse(result.current.invalid);
|
|
147
|
+
});
|
|
148
|
+
test('rule computes derived field using context', async () => {
|
|
149
|
+
const rules = [
|
|
150
|
+
[
|
|
151
|
+
({ name }, _old, _index, _oldIndex, context) => ({
|
|
152
|
+
displayName: `${context?.prefix ?? ''} ${name}`.trim(),
|
|
153
|
+
}),
|
|
154
|
+
({ name }, _index, context) => [name, context?.prefix],
|
|
155
|
+
],
|
|
156
|
+
];
|
|
157
|
+
const ctx = { prefix: 'Dr' };
|
|
158
|
+
const { result } = await renderHook(() => useValidatedForm({
|
|
159
|
+
fields: [{ id: 'name' }, { id: 'displayName' }],
|
|
160
|
+
initial: { name: 'Alice', displayName: '' },
|
|
161
|
+
rules,
|
|
162
|
+
context: ctx,
|
|
163
|
+
}));
|
|
164
|
+
assert.equal(result.current.values.displayName, 'Dr Alice');
|
|
165
|
+
});
|
|
166
|
+
test('context is exposed on the returned form object', async () => {
|
|
167
|
+
const ctx = { tenant: 'acme' };
|
|
168
|
+
const { result } = await renderHook(() => useValidatedForm({
|
|
169
|
+
fields: [{ id: 'name' }],
|
|
170
|
+
initial: { name: 'Alice' },
|
|
171
|
+
context: ctx,
|
|
172
|
+
}));
|
|
173
|
+
assert.deepEqual(result.current.context, { tenant: 'acme' });
|
|
174
|
+
});
|
|
175
|
+
test('context defaults to empty object when not provided', async () => {
|
|
176
|
+
const { result } = await renderHook(() => useValidatedForm({
|
|
177
|
+
fields: [{ id: 'name' }],
|
|
178
|
+
initial: { name: 'Alice' },
|
|
179
|
+
}));
|
|
180
|
+
assert.deepEqual(result.current.context, {});
|
|
181
|
+
});
|
|
182
|
+
test('context change causes validate to see new context', async () => {
|
|
183
|
+
const fields = [
|
|
184
|
+
{
|
|
185
|
+
id: 'deliveryDate',
|
|
186
|
+
validate: (value, _values, _field, context) => value < (context?.minDate ?? '') ? 'Too early' : false,
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
const { result, rerender } = await renderHook(({ ctx }) => useValidatedForm({
|
|
190
|
+
fields,
|
|
191
|
+
initial: { deliveryDate: '2024-01-01' },
|
|
192
|
+
context: ctx,
|
|
193
|
+
}), { initialProps: { ctx: { minDate: '2024-06-01' } } });
|
|
194
|
+
assert.isTrue(result.current.invalid);
|
|
195
|
+
await rerender({ ctx: { minDate: '2023-01-01' } });
|
|
196
|
+
assert.isFalse(result.current.invalid);
|
|
197
|
+
});
|
|
198
|
+
test('context change triggers rule re-run on form', async () => {
|
|
199
|
+
const rules = [
|
|
200
|
+
[
|
|
201
|
+
(current, _old, _index, _oldIndex, context) => ({
|
|
202
|
+
total: current.price * (1 + (context?.vat ?? 0) / 100),
|
|
203
|
+
}),
|
|
204
|
+
(_current, _index, context) => [context?.vat],
|
|
205
|
+
],
|
|
206
|
+
];
|
|
207
|
+
const { result, rerender } = await renderHook(({ ctx }) => useValidatedForm({
|
|
208
|
+
fields: [{ id: 'price' }, { id: 'total' }],
|
|
209
|
+
initial: { price: 100, total: 0 },
|
|
210
|
+
rules,
|
|
211
|
+
context: ctx,
|
|
212
|
+
}), { initialProps: { ctx: { vat: 0 } } });
|
|
213
|
+
assert.equal(result.current.values.total, 100);
|
|
214
|
+
await rerender({ ctx: { vat: 20 } });
|
|
215
|
+
await waitUntil(() => result.current.values.total === 120, 'total should update when vat context changes');
|
|
216
|
+
assert.equal(result.current.values.total, 120);
|
|
217
|
+
});
|
|
218
|
+
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,56 +2,56 @@ import { AutocompleteProps, CommonFieldProps, FileProps, TextareaProps } from '.
|
|
|
2
2
|
import { ReadOnlyNumberProps } from '../inputs/read-only-number';
|
|
3
3
|
import { UseForm } from '../use-form-core';
|
|
4
4
|
import { ItemRule } from '../use-items';
|
|
5
|
-
export type Invoked<T, F, V, R> = (value: V, values: T, field: F) => R;
|
|
6
|
-
export type Invokable<T, F, V, R> = R | Invoked<T, F, V, R>;
|
|
5
|
+
export type Invoked<T, F, V, R, C extends object = object> = (value: V, values: T, field: F, context?: C) => R;
|
|
6
|
+
export type Invokable<T, F, V, R, C extends object = object> = R | Invoked<T, F, V, R, C>;
|
|
7
7
|
export type Resolvable<T, A extends unknown[] = []> = T | PromiseLike<T> | ((...args: A) => T | PromiseLike<T>);
|
|
8
8
|
export type OnFocusFn<T, F, V> = (onChange: (value: V, touched?: boolean) => void, value: V, values: T, field: F) => (event: FocusEvent) => void;
|
|
9
9
|
export type OnChange<T, K, V> = (update: (changes: Partial<T>, touched?: boolean) => void, id: K, value: V, values: T) => void;
|
|
10
|
-
export type Rule<T extends object, K extends keyof T, V extends T[K]> = (value: V, values?: T, field?: Field<T, K, V
|
|
10
|
+
export type Rule<T extends object, K extends keyof T, V extends T[K], C extends object = object> = (value: V, values?: T, field?: Field<T, K, V, C>, context?: C) => false | string;
|
|
11
11
|
export type Errors = {
|
|
12
12
|
[key: string]: string | true | undefined;
|
|
13
13
|
};
|
|
14
|
-
export type Validate<T extends object, K extends keyof T, V extends T[K]> = Rule<T, K, V> | Rule<T, K, V>[];
|
|
14
|
+
export type Validate<T extends object, K extends keyof T, V extends T[K], C extends object = object> = Rule<T, K, V, C> | Rule<T, K, V, C>[];
|
|
15
15
|
export type Renderable = null | undefined | {
|
|
16
16
|
toString(): string;
|
|
17
17
|
} | ReadonlyArray<Renderable>;
|
|
18
|
-
export interface InputProps<T extends object, K extends keyof T, V extends T[K]> extends UseForm<T> {
|
|
18
|
+
export interface InputProps<T extends object, K extends keyof T, V extends T[K], C extends object = object> extends UseForm<T, C> {
|
|
19
19
|
value: V;
|
|
20
|
-
field: Field<T, K, V>;
|
|
20
|
+
field: Field<T, K, V, C>;
|
|
21
21
|
error: false | string;
|
|
22
22
|
invalid: boolean;
|
|
23
23
|
touched: boolean;
|
|
24
24
|
}
|
|
25
|
-
export interface Input<T extends object, K extends keyof T, V extends T[K]> {
|
|
26
|
-
(opts: InputProps<T, K, V>): Renderable;
|
|
25
|
+
export interface Input<T extends object, K extends keyof T, V extends T[K], C extends object = object> {
|
|
26
|
+
(opts: InputProps<T, K, V, C>): Renderable;
|
|
27
27
|
}
|
|
28
|
-
export type Codec<T extends object, K extends keyof T, V extends T[K]> = readonly [
|
|
29
|
-
Invoked<T, Field<T, K, V>, V, unknown>,
|
|
30
|
-
Invoked<T, Field<T, K, V>, unknown, V>
|
|
28
|
+
export type Codec<T extends object, K extends keyof T, V extends T[K], C extends object = object> = readonly [
|
|
29
|
+
Invoked<T, Field<T, K, V, C>, V, unknown, C>,
|
|
30
|
+
Invoked<T, Field<T, K, V, C>, unknown, V, C>
|
|
31
31
|
];
|
|
32
|
-
export interface Field<T extends object, K extends keyof T, V extends T[K] = T[K]> extends CommonFieldProps<T, K, V>, TextareaProps, AutocompleteProps<T, K, V>, FileProps<T, K, V>, ReadOnlyNumberProps {
|
|
32
|
+
export interface Field<T extends object, K extends keyof T, V extends T[K] = T[K], C extends object = object> extends CommonFieldProps<T, K, V, C>, TextareaProps, AutocompleteProps<T, K, V>, FileProps<T, K, V>, ReadOnlyNumberProps {
|
|
33
33
|
id: K;
|
|
34
34
|
path?: keyof T;
|
|
35
|
-
label?: Invokable<T, Field<T, K, V>, V, string>;
|
|
35
|
+
label?: Invokable<T, Field<T, K, V, C>, V, string, C>;
|
|
36
36
|
description?: string;
|
|
37
|
-
placeholder?: Invokable<T, Field<T, K, V>, V, string>;
|
|
38
|
-
validate?: Validate<T, K, V>;
|
|
39
|
-
mandatory?: Invokable<T, Field<T, K, V>, V, boolean>;
|
|
40
|
-
disabled?: Invokable<T, Field<T, K, V>, V, boolean>;
|
|
41
|
-
hidden?: Invokable<T, Field<T, K, V>, V, boolean>;
|
|
42
|
-
warning?: Invokable<T, Field<T, K, V>, V, Renderable>;
|
|
43
|
-
prefix?: Invokable<T, Field<T, K, V>, V, Renderable>;
|
|
44
|
-
suffix?: Invokable<T, Field<T, K, V>, V, Renderable>;
|
|
45
|
-
value?: Codec<T, K, V>;
|
|
37
|
+
placeholder?: Invokable<T, Field<T, K, V, C>, V, string, C>;
|
|
38
|
+
validate?: Validate<T, K, V, C>;
|
|
39
|
+
mandatory?: Invokable<T, Field<T, K, V, C>, V, boolean, C>;
|
|
40
|
+
disabled?: Invokable<T, Field<T, K, V, C>, V, boolean, C>;
|
|
41
|
+
hidden?: Invokable<T, Field<T, K, V, C>, V, boolean, C>;
|
|
42
|
+
warning?: Invokable<T, Field<T, K, V, C>, V, Renderable, C>;
|
|
43
|
+
prefix?: Invokable<T, Field<T, K, V, C>, V, Renderable, C>;
|
|
44
|
+
suffix?: Invokable<T, Field<T, K, V, C>, V, Renderable, C>;
|
|
45
|
+
value?: Codec<T, K, V, C>;
|
|
46
46
|
styles?: Record<string, string>;
|
|
47
|
-
onFocus?: OnFocusFn<T, Field<T, K, V>, V>;
|
|
47
|
+
onFocus?: OnFocusFn<T, Field<T, K, V, C>, V>;
|
|
48
48
|
onChange?: OnChange<T, K, V>;
|
|
49
49
|
rules?: ItemRule<T>[];
|
|
50
|
-
header?: Invokable<T, Field<T, K, V>, V, string>;
|
|
51
|
-
input?: Input<T, K, V>;
|
|
50
|
+
header?: Invokable<T, Field<T, K, V, C>, V, string, C>;
|
|
51
|
+
input?: Input<T, K, V, C>;
|
|
52
52
|
}
|
|
53
|
-
export type Fields<T extends object> = readonly {
|
|
54
|
-
[K in keyof T]-?: Readonly<Field<T, K, T[K]>>;
|
|
53
|
+
export type Fields<T extends object, C extends object = object> = readonly {
|
|
54
|
+
[K in keyof T]-?: Readonly<Field<T, K, T[K], C>>;
|
|
55
55
|
}[keyof T][];
|
|
56
56
|
export { InputBaseProps as InputBaseOpts } from '../inputs/base';
|
|
57
57
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|