objectmodel 4.3.1 → 4.4.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/.eslintignore +8 -8
- package/.eslintrc.json +25 -25
- package/.travis.yml +2 -2
- package/LICENSE +22 -22
- package/README.md +67 -67
- package/build/add-banner.js +13 -13
- package/build/bundle-entry.dev.js +2 -2
- package/build/bundle-entry.js +11 -11
- package/dist/object-model.cjs +462 -469
- package/dist/object-model.js +462 -469
- package/dist/object-model.js.map +1 -1
- package/dist/object-model.min.js +2 -2
- package/dist/object-model.min.js.map +1 -1
- package/index.html +4 -4
- package/package.json +9 -3
- package/rollup.config.js +13 -13
- package/src/array-model.d.ts +16 -0
- package/src/array-model.js +68 -68
- package/src/devtool-formatter.js +198 -198
- package/src/function-model.d.ts +24 -0
- package/src/function-model.js +58 -65
- package/src/helpers.js +43 -43
- package/src/index.js +4 -4
- package/src/list-model.js +43 -43
- package/src/map-model.d.ts +18 -0
- package/src/map-model.js +48 -48
- package/src/object-model.d.ts +74 -0
- package/src/set-model.d.ts +16 -0
- package/src/set-model.js +41 -41
- package/test/array-model.spec.cjs +291 -291
- package/test/array-model.test-d.ts +24 -0
- package/test/basic-model.spec.cjs +263 -263
- package/test/basic-model.test-d.ts +30 -0
- package/test/bench/array.html +51 -51
- package/test/bench/bench-lib.js +49 -49
- package/test/bench/map-no-cast.html +53 -53
- package/test/bench/map-set.html +52 -52
- package/test/bench/map.html +51 -51
- package/test/bench/object-models.html +87 -87
- package/test/function-model.spec.cjs +161 -162
- package/test/function-model.test-d.ts +18 -0
- package/test/index.cjs +13 -13
- package/test/map-model.spec.cjs +224 -224
- package/test/map-model.test-d.ts +21 -0
- package/test/model.spec.cjs +30 -30
- package/test/object-model.spec.cjs +1345 -1346
- package/test/object-model.test-d.ts +53 -0
- package/test/set-model.spec.cjs +213 -213
- package/test/set-model.test-d.ts +17 -0
- package/test/umd.html +25 -25
- package/types/definitions.d.ts +43 -0
- package/types/helpers.d.ts +4 -0
- package/types/index.d.ts +6 -128
|
@@ -1,292 +1,292 @@
|
|
|
1
|
-
/* global QUnit, ArrayModel, ObjectModel */
|
|
2
|
-
|
|
3
|
-
QUnit.module("Array Models");
|
|
4
|
-
|
|
5
|
-
QUnit.test("constructor && proto", function (assert) {
|
|
6
|
-
|
|
7
|
-
assert.ok(ArrayModel instanceof Function, "ArrayModel instanceof Function");
|
|
8
|
-
|
|
9
|
-
const Arr = ArrayModel(Number);
|
|
10
|
-
|
|
11
|
-
assert.ok(Arr instanceof ArrayModel, "Array models can be declared");
|
|
12
|
-
|
|
13
|
-
assert.ok(typeof Arr.extend === "function", "test Array model method extend");
|
|
14
|
-
assert.ok(typeof Arr.assert === "function", "test Array model method assert");
|
|
15
|
-
assert.ok(typeof Arr.test === "function", "test Array model method test");
|
|
16
|
-
assert.ok(Arr.definition === Number, "test Array model prop definition");
|
|
17
|
-
assert.ok(typeof Arr.assertions === "object", "test Array model prop assertions");
|
|
18
|
-
|
|
19
|
-
assert.ok(ArrayModel(undefined) instanceof ArrayModel, "ArrayModel can receive undefined as argument");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
QUnit.test("instanciation && mutation methods watchers", function (assert) {
|
|
23
|
-
|
|
24
|
-
const Arr = ArrayModel(Number);
|
|
25
|
-
const a = Arr([]);
|
|
26
|
-
|
|
27
|
-
assert.ok(a instanceof Arr && a instanceof Array, "Array models can be instanciated");
|
|
28
|
-
|
|
29
|
-
assert.equal(a.push.name, "push", "proxyfied methods keep original properties");
|
|
30
|
-
|
|
31
|
-
a.push(1);
|
|
32
|
-
a[0] = 42;
|
|
33
|
-
a.splice(1, 0, 5, 6, Infinity);
|
|
34
|
-
assert.throws(function () {
|
|
35
|
-
a.push("toto");
|
|
36
|
-
}, /TypeError/, "push calls are catched");
|
|
37
|
-
assert.throws(function () {
|
|
38
|
-
a[0] = {};
|
|
39
|
-
}, /TypeError/, "array keys set are catched");
|
|
40
|
-
assert.throws(function () {
|
|
41
|
-
a.splice(1, 0, 7, 'oups', 9);
|
|
42
|
-
}, /TypeError/, "splice calls are catched");
|
|
43
|
-
assert.equal(a.length, 4, "array length change is ok");
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
QUnit.test("validation in constructor", function (assert) {
|
|
48
|
-
|
|
49
|
-
const Arr = ArrayModel(Number);
|
|
50
|
-
const b = Arr([1, 2, 3]);
|
|
51
|
-
assert.equal(b.length, 3, "array.length is ok");
|
|
52
|
-
|
|
53
|
-
assert.throws(function () {
|
|
54
|
-
Arr([1, false, 3]);
|
|
55
|
-
}, /TypeError/, "validation in array model constructor 1/2");
|
|
56
|
-
|
|
57
|
-
assert.throws(function () {
|
|
58
|
-
Arr([1, 2, 3, function () {
|
|
59
|
-
}]);
|
|
60
|
-
}, /TypeError/, "validation in array model constructor 2/2");
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
QUnit.test("union types & submodels", function (assert) {
|
|
65
|
-
|
|
66
|
-
const Question = ObjectModel({
|
|
67
|
-
answer: Number
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const Arr = ArrayModel([Question, String, Boolean]);
|
|
71
|
-
const a = Arr(["test"]);
|
|
72
|
-
a.unshift(true);
|
|
73
|
-
a.push(Question({ answer: 42 }));
|
|
74
|
-
a.push({ answer: 43 });
|
|
75
|
-
assert.throws(function () {
|
|
76
|
-
a.unshift(42);
|
|
77
|
-
}, /TypeError/, "unshift multiple types");
|
|
78
|
-
assert.throws(function () {
|
|
79
|
-
a[0] = null;
|
|
80
|
-
}, /TypeError/, "set index multiple types");
|
|
81
|
-
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
QUnit.test("union types & fixed values", function (assert) {
|
|
85
|
-
|
|
86
|
-
const Arr = ArrayModel([true, 2, "3"]);
|
|
87
|
-
assert.throws(function () {
|
|
88
|
-
Arr(["3", 2, true, 1]);
|
|
89
|
-
}, /TypeError[\s\S]*Array\[3]/, "ArrayModel fixed values");
|
|
90
|
-
|
|
91
|
-
const Cards = ArrayModel([Number, "J", "Q", "K"]); // array of Numbers, J, Q or K
|
|
92
|
-
const Hand = Cards.extend().assert(cards => cards.length === 2);
|
|
93
|
-
const pokerHand = new Hand(["K", 10]);
|
|
94
|
-
|
|
95
|
-
assert.ok(Object.getPrototypeOf(Hand.prototype) === Cards.prototype, "extension respect prototypal chain");
|
|
96
|
-
assert.ok(pokerHand instanceof Hand && pokerHand instanceof Cards, "array model inheritance");
|
|
97
|
-
Cards(["K", 10]).push(7);
|
|
98
|
-
assert.throws(function () {
|
|
99
|
-
Hand(["K", 10]).push(7);
|
|
100
|
-
}, /TypeError/, "min/max of inherit array model");
|
|
101
|
-
|
|
102
|
-
const CheaterHand = Cards.extend("joker");
|
|
103
|
-
CheaterHand(["K", 10, "joker"]);
|
|
104
|
-
assert.throws(function () {
|
|
105
|
-
Hand("K", 10, "joker");
|
|
106
|
-
}, /TypeError/, "array model type extension");
|
|
107
|
-
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
QUnit.test("Child array models in object models", function (assert) {
|
|
111
|
-
|
|
112
|
-
const Child = ObjectModel({ arr: ArrayModel(String) });
|
|
113
|
-
const Parent = ObjectModel({ child: Child });
|
|
114
|
-
|
|
115
|
-
const childO = Child({ arr: ["a", "b", "c"] });
|
|
116
|
-
assert.ok(childO.arr instanceof Array, "child array model is array");
|
|
117
|
-
const parentO = Parent({ child: childO });
|
|
118
|
-
assert.ok(parentO.child.arr instanceof Array, "child array model from parent is array");
|
|
119
|
-
|
|
120
|
-
childO.arr.push("a");
|
|
121
|
-
assert.throws(function () {
|
|
122
|
-
childO.arr.push(false);
|
|
123
|
-
}, /TypeError/, "child array model catches push calls");
|
|
124
|
-
assert.throws(function () {
|
|
125
|
-
childO.arr[0] = 1;
|
|
126
|
-
}, /TypeError/, "child array model catches set index");
|
|
127
|
-
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
QUnit.test("default values", function (assert) {
|
|
131
|
-
|
|
132
|
-
const ArrModel = ArrayModel([Number, String]).defaultTo([]);
|
|
133
|
-
const a = ArrModel();
|
|
134
|
-
|
|
135
|
-
assert.ok(a instanceof Array && a.length === 0, "Array model default value");
|
|
136
|
-
a.push(1, 2);
|
|
137
|
-
|
|
138
|
-
const b = ArrModel();
|
|
139
|
-
assert.ok(b instanceof Array && b.length === 0, "Avoid default value common reference issue for Array models");
|
|
140
|
-
|
|
141
|
-
ArrModel.default.push(1, 2, 3);
|
|
142
|
-
|
|
143
|
-
const c = ArrModel();
|
|
144
|
-
|
|
145
|
-
assert.ok(c.length === 3 && c.join(";") === "1;2;3", "array model default value is mutable array");
|
|
146
|
-
|
|
147
|
-
ArrModel.default = "nope";
|
|
148
|
-
|
|
149
|
-
assert.throws(function () {
|
|
150
|
-
ArrModel()
|
|
151
|
-
}, /TypeError.*got String "nope"/, "invalid default property still throws TypeError for array models");
|
|
152
|
-
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
QUnit.test("Assertions", function (assert) {
|
|
156
|
-
|
|
157
|
-
const ArrayMax3 = ArrayModel(Number).assert(function maxRange(arr) { return arr.length <= 3; });
|
|
158
|
-
let arr = ArrayMax3([1, 2]);
|
|
159
|
-
|
|
160
|
-
arr.push(3);
|
|
161
|
-
assert.throws(function () { arr.push(4); }, /TypeError[\s\S]*maxRange/, "test assertion after array method");
|
|
162
|
-
|
|
163
|
-
const ArraySumMax10 = ArrayModel(Number).assert(function (arr) {
|
|
164
|
-
return arr.reduce(function (a, b) { return a + b; }, 0) <= 10;
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
arr = ArraySumMax10([2, 3, 4]);
|
|
168
|
-
assert.throws(function () { arr[1] = 7; }, /TypeError/, "test assertion after array key assignment");
|
|
169
|
-
|
|
170
|
-
const AssertArray = ArrayModel(Number).assert(v => v.length >= 0, "may throw exception");
|
|
171
|
-
|
|
172
|
-
new AssertArray([]);
|
|
173
|
-
|
|
174
|
-
assert.throws(function () { new AssertArray(); },
|
|
175
|
-
/assertion "may throw exception" returned TypeError.*for value undefined/,
|
|
176
|
-
"assertions catch exceptions on Array models");
|
|
177
|
-
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
QUnit.test("Automatic model casting", function (assert) {
|
|
181
|
-
|
|
182
|
-
const N = ObjectModel({ x: Number, y: [Number] }).defaultTo({ x: 5, y: 7 });
|
|
183
|
-
const Arr = ArrayModel(N);
|
|
184
|
-
const a = Arr([{ x: 9 }]);
|
|
185
|
-
|
|
186
|
-
assert.ok(a[0] instanceof N, "test automatic model casting with array init 1/2")
|
|
187
|
-
assert.equal(a[0].x * a[0].y, 63, "test automatic model casting with array init 2/2")
|
|
188
|
-
|
|
189
|
-
a.push({ x: 3 });
|
|
190
|
-
|
|
191
|
-
assert.ok(a[1] instanceof N, "test automatic model casting with array push method 1/2")
|
|
192
|
-
assert.equal(a[1].x * a[1].y, 21, "test automatic model casting with array push method 2/2")
|
|
193
|
-
|
|
194
|
-
a.splice(1, 1, { x: 4 });
|
|
195
|
-
|
|
196
|
-
assert.ok(a[1] instanceof N, "test automatic model casting with array splice method 1/2")
|
|
197
|
-
assert.equal(a[1].x * a[1].y, 28, "test automatic model casting with array splice method 2/2")
|
|
198
|
-
|
|
199
|
-
a.fill({ x: 5 })
|
|
200
|
-
|
|
201
|
-
assert.ok(a[1] instanceof N, "test automatic model casting with array fill method 1/2")
|
|
202
|
-
assert.equal(a[1].x * a[1].y, 35, "test automatic model casting with array fill method 2/2")
|
|
203
|
-
|
|
204
|
-
a.unshift({ x: 1 }, { x: 6 })
|
|
205
|
-
|
|
206
|
-
assert.ok(a[1] instanceof N, "test automatic model casting with array unshift method 1/2")
|
|
207
|
-
assert.equal(a[1].x * a[1].y, 42, "test automatic model casting with array unshift method 2/2")
|
|
208
|
-
|
|
209
|
-
a[0] = { x: 10 };
|
|
210
|
-
|
|
211
|
-
assert.ok(a[0] instanceof N, "test automatic model casting with array set index 1/2")
|
|
212
|
-
assert.equal(a[0].x * a[0].y, 70, "test automatic model casting with array set index 2/2");
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
QUnit.test("Other traps", function (assert) {
|
|
217
|
-
|
|
218
|
-
const Arr = ArrayModel(Number);
|
|
219
|
-
const a = Arr([1, 2, 3])
|
|
220
|
-
|
|
221
|
-
delete a.unknownProperty;
|
|
222
|
-
delete a[3];
|
|
223
|
-
|
|
224
|
-
assert.throws(function () {
|
|
225
|
-
delete a[2]
|
|
226
|
-
}, /TypeError/, "deleteProperty trap block array holes if def != undefined")
|
|
227
|
-
|
|
228
|
-
const ArrB = ArrayModel([Number]);
|
|
229
|
-
const b = ArrB([1, 2, 3])
|
|
230
|
-
|
|
231
|
-
delete b[2]
|
|
232
|
-
assert.equal(b[2], undefined, "deleteProperty trap does not block when def is optional")
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
QUnit.test("toString", function (assert) {
|
|
236
|
-
assert.equal(ArrayModel(Number).toString(), "Array of Number", "ArrayModels toString for basic elements")
|
|
237
|
-
assert.equal(ArrayModel([String, 42]).toString(), "Array of String or 42", "ArrayModels toString for union type elements")
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
QUnit.test("Dynamic definition", function (assert) {
|
|
241
|
-
let A = ArrayModel(String);
|
|
242
|
-
let a1 = A(["hello", "world"])
|
|
243
|
-
A.definition = Number;
|
|
244
|
-
let a2 = A([1, 2, 3])
|
|
245
|
-
assert.equal(A.test(a1), false, "definition can be dynamically changed 1/4")
|
|
246
|
-
assert.equal(A.test(a2), true, "definition can be dynamically changed 2/4")
|
|
247
|
-
a1.splice(0, a1.length);
|
|
248
|
-
assert.throws(() => a1.push("hello"), /TypeError/, "definition can be dynamically changed 3/4")
|
|
249
|
-
a1.push(42);
|
|
250
|
-
assert.equal(a1[0], 42, "definition can be dynamically changed 4/4")
|
|
251
|
-
|
|
252
|
-
let OM = ObjectModel({ n: Number });
|
|
253
|
-
A.definition = OM;
|
|
254
|
-
a1.splice(0, a1.length);
|
|
255
|
-
a1.push({ n: 42 });
|
|
256
|
-
assert.ok(a1[0] instanceof OM, "autocast still works after definition dynamically changed")
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
QUnit.test("delete key", function (assert) {
|
|
260
|
-
let A = ArrayModel([Number]);
|
|
261
|
-
let a = A([1, 2, 3])
|
|
262
|
-
delete a[2]
|
|
263
|
-
assert.equal(3 in a, false, "delete should remove own array key")
|
|
264
|
-
assert.equal(a.length, 3, "delete should not update length")
|
|
265
|
-
assert.equal(Object.keys(a).length, 2, "delete should leave an empty hole")
|
|
266
|
-
|
|
267
|
-
const ArrayDense = ArrayModel([Number]).assert(function hasNoHoles(arr) {
|
|
268
|
-
return arr.filter(() => true).length === arr.length
|
|
269
|
-
}).as("ArrayDense");
|
|
270
|
-
|
|
271
|
-
A = ArrayDense.extend([Number]);
|
|
272
|
-
a = A([4, 5, 6])
|
|
273
|
-
assert.throws(() => {
|
|
274
|
-
delete a[2]
|
|
275
|
-
}, /TypeError.*hasNoHoles/, "ArrayDense should prevent deleting keys")
|
|
276
|
-
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
QUnit.test("Arraymodel as ObjectModel default prop", function (assert) {
|
|
280
|
-
class OM extends ObjectModel({
|
|
281
|
-
a: ArrayModel(Number)
|
|
282
|
-
}).defaultTo({
|
|
283
|
-
a: []
|
|
284
|
-
}) { }
|
|
285
|
-
|
|
286
|
-
let o = new OM();
|
|
287
|
-
|
|
288
|
-
assert.ok(Array.isArray(o.a), "ArrayModel works as ObjectModel default prop")
|
|
289
|
-
o.a.push(1, 2, 3)
|
|
290
|
-
assert.equal(o.a.length, 3, "ArrayModel as ObjectModel default prop can be mutated")
|
|
291
|
-
|
|
1
|
+
/* global QUnit, ArrayModel, ObjectModel */
|
|
2
|
+
|
|
3
|
+
QUnit.module("Array Models");
|
|
4
|
+
|
|
5
|
+
QUnit.test("constructor && proto", function (assert) {
|
|
6
|
+
|
|
7
|
+
assert.ok(ArrayModel instanceof Function, "ArrayModel instanceof Function");
|
|
8
|
+
|
|
9
|
+
const Arr = ArrayModel(Number);
|
|
10
|
+
|
|
11
|
+
assert.ok(Arr instanceof ArrayModel, "Array models can be declared");
|
|
12
|
+
|
|
13
|
+
assert.ok(typeof Arr.extend === "function", "test Array model method extend");
|
|
14
|
+
assert.ok(typeof Arr.assert === "function", "test Array model method assert");
|
|
15
|
+
assert.ok(typeof Arr.test === "function", "test Array model method test");
|
|
16
|
+
assert.ok(Arr.definition === Number, "test Array model prop definition");
|
|
17
|
+
assert.ok(typeof Arr.assertions === "object", "test Array model prop assertions");
|
|
18
|
+
|
|
19
|
+
assert.ok(ArrayModel(undefined) instanceof ArrayModel, "ArrayModel can receive undefined as argument");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
QUnit.test("instanciation && mutation methods watchers", function (assert) {
|
|
23
|
+
|
|
24
|
+
const Arr = ArrayModel(Number);
|
|
25
|
+
const a = Arr([]);
|
|
26
|
+
|
|
27
|
+
assert.ok(a instanceof Arr && a instanceof Array, "Array models can be instanciated");
|
|
28
|
+
|
|
29
|
+
assert.equal(a.push.name, "push", "proxyfied methods keep original properties");
|
|
30
|
+
|
|
31
|
+
a.push(1);
|
|
32
|
+
a[0] = 42;
|
|
33
|
+
a.splice(1, 0, 5, 6, Infinity);
|
|
34
|
+
assert.throws(function () {
|
|
35
|
+
a.push("toto");
|
|
36
|
+
}, /TypeError/, "push calls are catched");
|
|
37
|
+
assert.throws(function () {
|
|
38
|
+
a[0] = {};
|
|
39
|
+
}, /TypeError/, "array keys set are catched");
|
|
40
|
+
assert.throws(function () {
|
|
41
|
+
a.splice(1, 0, 7, 'oups', 9);
|
|
42
|
+
}, /TypeError/, "splice calls are catched");
|
|
43
|
+
assert.equal(a.length, 4, "array length change is ok");
|
|
44
|
+
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
QUnit.test("validation in constructor", function (assert) {
|
|
48
|
+
|
|
49
|
+
const Arr = ArrayModel(Number);
|
|
50
|
+
const b = Arr([1, 2, 3]);
|
|
51
|
+
assert.equal(b.length, 3, "array.length is ok");
|
|
52
|
+
|
|
53
|
+
assert.throws(function () {
|
|
54
|
+
Arr([1, false, 3]);
|
|
55
|
+
}, /TypeError/, "validation in array model constructor 1/2");
|
|
56
|
+
|
|
57
|
+
assert.throws(function () {
|
|
58
|
+
Arr([1, 2, 3, function () {
|
|
59
|
+
}]);
|
|
60
|
+
}, /TypeError/, "validation in array model constructor 2/2");
|
|
61
|
+
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
QUnit.test("union types & submodels", function (assert) {
|
|
65
|
+
|
|
66
|
+
const Question = ObjectModel({
|
|
67
|
+
answer: Number
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const Arr = ArrayModel([Question, String, Boolean]);
|
|
71
|
+
const a = Arr(["test"]);
|
|
72
|
+
a.unshift(true);
|
|
73
|
+
a.push(Question({ answer: 42 }));
|
|
74
|
+
a.push({ answer: 43 });
|
|
75
|
+
assert.throws(function () {
|
|
76
|
+
a.unshift(42);
|
|
77
|
+
}, /TypeError/, "unshift multiple types");
|
|
78
|
+
assert.throws(function () {
|
|
79
|
+
a[0] = null;
|
|
80
|
+
}, /TypeError/, "set index multiple types");
|
|
81
|
+
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
QUnit.test("union types & fixed values", function (assert) {
|
|
85
|
+
|
|
86
|
+
const Arr = ArrayModel([true, 2, "3"]);
|
|
87
|
+
assert.throws(function () {
|
|
88
|
+
Arr(["3", 2, true, 1]);
|
|
89
|
+
}, /TypeError[\s\S]*Array\[3]/, "ArrayModel fixed values");
|
|
90
|
+
|
|
91
|
+
const Cards = ArrayModel([Number, "J", "Q", "K"]); // array of Numbers, J, Q or K
|
|
92
|
+
const Hand = Cards.extend().assert(cards => cards.length === 2);
|
|
93
|
+
const pokerHand = new Hand(["K", 10]);
|
|
94
|
+
|
|
95
|
+
assert.ok(Object.getPrototypeOf(Hand.prototype) === Cards.prototype, "extension respect prototypal chain");
|
|
96
|
+
assert.ok(pokerHand instanceof Hand && pokerHand instanceof Cards, "array model inheritance");
|
|
97
|
+
Cards(["K", 10]).push(7);
|
|
98
|
+
assert.throws(function () {
|
|
99
|
+
Hand(["K", 10]).push(7);
|
|
100
|
+
}, /TypeError/, "min/max of inherit array model");
|
|
101
|
+
|
|
102
|
+
const CheaterHand = Cards.extend("joker");
|
|
103
|
+
CheaterHand(["K", 10, "joker"]);
|
|
104
|
+
assert.throws(function () {
|
|
105
|
+
Hand("K", 10, "joker");
|
|
106
|
+
}, /TypeError/, "array model type extension");
|
|
107
|
+
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
QUnit.test("Child array models in object models", function (assert) {
|
|
111
|
+
|
|
112
|
+
const Child = ObjectModel({ arr: ArrayModel(String) });
|
|
113
|
+
const Parent = ObjectModel({ child: Child });
|
|
114
|
+
|
|
115
|
+
const childO = Child({ arr: ["a", "b", "c"] });
|
|
116
|
+
assert.ok(childO.arr instanceof Array, "child array model is array");
|
|
117
|
+
const parentO = Parent({ child: childO });
|
|
118
|
+
assert.ok(parentO.child.arr instanceof Array, "child array model from parent is array");
|
|
119
|
+
|
|
120
|
+
childO.arr.push("a");
|
|
121
|
+
assert.throws(function () {
|
|
122
|
+
childO.arr.push(false);
|
|
123
|
+
}, /TypeError/, "child array model catches push calls");
|
|
124
|
+
assert.throws(function () {
|
|
125
|
+
childO.arr[0] = 1;
|
|
126
|
+
}, /TypeError/, "child array model catches set index");
|
|
127
|
+
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
QUnit.test("default values", function (assert) {
|
|
131
|
+
|
|
132
|
+
const ArrModel = ArrayModel([Number, String]).defaultTo([]);
|
|
133
|
+
const a = ArrModel();
|
|
134
|
+
|
|
135
|
+
assert.ok(a instanceof Array && a.length === 0, "Array model default value");
|
|
136
|
+
a.push(1, 2);
|
|
137
|
+
|
|
138
|
+
const b = ArrModel();
|
|
139
|
+
assert.ok(b instanceof Array && b.length === 0, "Avoid default value common reference issue for Array models");
|
|
140
|
+
|
|
141
|
+
ArrModel.default.push(1, 2, 3);
|
|
142
|
+
|
|
143
|
+
const c = ArrModel();
|
|
144
|
+
|
|
145
|
+
assert.ok(c.length === 3 && c.join(";") === "1;2;3", "array model default value is mutable array");
|
|
146
|
+
|
|
147
|
+
ArrModel.default = "nope";
|
|
148
|
+
|
|
149
|
+
assert.throws(function () {
|
|
150
|
+
ArrModel()
|
|
151
|
+
}, /TypeError.*got String "nope"/, "invalid default property still throws TypeError for array models");
|
|
152
|
+
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
QUnit.test("Assertions", function (assert) {
|
|
156
|
+
|
|
157
|
+
const ArrayMax3 = ArrayModel(Number).assert(function maxRange(arr) { return arr.length <= 3; });
|
|
158
|
+
let arr = ArrayMax3([1, 2]);
|
|
159
|
+
|
|
160
|
+
arr.push(3);
|
|
161
|
+
assert.throws(function () { arr.push(4); }, /TypeError[\s\S]*maxRange/, "test assertion after array method");
|
|
162
|
+
|
|
163
|
+
const ArraySumMax10 = ArrayModel(Number).assert(function (arr) {
|
|
164
|
+
return arr.reduce(function (a, b) { return a + b; }, 0) <= 10;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
arr = ArraySumMax10([2, 3, 4]);
|
|
168
|
+
assert.throws(function () { arr[1] = 7; }, /TypeError/, "test assertion after array key assignment");
|
|
169
|
+
|
|
170
|
+
const AssertArray = ArrayModel(Number).assert(v => v.length >= 0, "may throw exception");
|
|
171
|
+
|
|
172
|
+
new AssertArray([]);
|
|
173
|
+
|
|
174
|
+
assert.throws(function () { new AssertArray(); },
|
|
175
|
+
/assertion "may throw exception" returned TypeError.*for value undefined/,
|
|
176
|
+
"assertions catch exceptions on Array models");
|
|
177
|
+
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
QUnit.test("Automatic model casting", function (assert) {
|
|
181
|
+
|
|
182
|
+
const N = ObjectModel({ x: Number, y: [Number] }).defaultTo({ x: 5, y: 7 });
|
|
183
|
+
const Arr = ArrayModel(N);
|
|
184
|
+
const a = Arr([{ x: 9 }]);
|
|
185
|
+
|
|
186
|
+
assert.ok(a[0] instanceof N, "test automatic model casting with array init 1/2")
|
|
187
|
+
assert.equal(a[0].x * a[0].y, 63, "test automatic model casting with array init 2/2")
|
|
188
|
+
|
|
189
|
+
a.push({ x: 3 });
|
|
190
|
+
|
|
191
|
+
assert.ok(a[1] instanceof N, "test automatic model casting with array push method 1/2")
|
|
192
|
+
assert.equal(a[1].x * a[1].y, 21, "test automatic model casting with array push method 2/2")
|
|
193
|
+
|
|
194
|
+
a.splice(1, 1, { x: 4 });
|
|
195
|
+
|
|
196
|
+
assert.ok(a[1] instanceof N, "test automatic model casting with array splice method 1/2")
|
|
197
|
+
assert.equal(a[1].x * a[1].y, 28, "test automatic model casting with array splice method 2/2")
|
|
198
|
+
|
|
199
|
+
a.fill({ x: 5 })
|
|
200
|
+
|
|
201
|
+
assert.ok(a[1] instanceof N, "test automatic model casting with array fill method 1/2")
|
|
202
|
+
assert.equal(a[1].x * a[1].y, 35, "test automatic model casting with array fill method 2/2")
|
|
203
|
+
|
|
204
|
+
a.unshift({ x: 1 }, { x: 6 })
|
|
205
|
+
|
|
206
|
+
assert.ok(a[1] instanceof N, "test automatic model casting with array unshift method 1/2")
|
|
207
|
+
assert.equal(a[1].x * a[1].y, 42, "test automatic model casting with array unshift method 2/2")
|
|
208
|
+
|
|
209
|
+
a[0] = { x: 10 };
|
|
210
|
+
|
|
211
|
+
assert.ok(a[0] instanceof N, "test automatic model casting with array set index 1/2")
|
|
212
|
+
assert.equal(a[0].x * a[0].y, 70, "test automatic model casting with array set index 2/2");
|
|
213
|
+
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
QUnit.test("Other traps", function (assert) {
|
|
217
|
+
|
|
218
|
+
const Arr = ArrayModel(Number);
|
|
219
|
+
const a = Arr([1, 2, 3])
|
|
220
|
+
|
|
221
|
+
delete a.unknownProperty;
|
|
222
|
+
delete a[3];
|
|
223
|
+
|
|
224
|
+
assert.throws(function () {
|
|
225
|
+
delete a[2]
|
|
226
|
+
}, /TypeError/, "deleteProperty trap block array holes if def != undefined")
|
|
227
|
+
|
|
228
|
+
const ArrB = ArrayModel([Number]);
|
|
229
|
+
const b = ArrB([1, 2, 3])
|
|
230
|
+
|
|
231
|
+
delete b[2]
|
|
232
|
+
assert.equal(b[2], undefined, "deleteProperty trap does not block when def is optional")
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
QUnit.test("toString", function (assert) {
|
|
236
|
+
assert.equal(ArrayModel(Number).toString(), "Array of Number", "ArrayModels toString for basic elements")
|
|
237
|
+
assert.equal(ArrayModel([String, 42]).toString(), "Array of String or 42", "ArrayModels toString for union type elements")
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
QUnit.test("Dynamic definition", function (assert) {
|
|
241
|
+
let A = ArrayModel(String);
|
|
242
|
+
let a1 = A(["hello", "world"])
|
|
243
|
+
A.definition = Number;
|
|
244
|
+
let a2 = A([1, 2, 3])
|
|
245
|
+
assert.equal(A.test(a1), false, "definition can be dynamically changed 1/4")
|
|
246
|
+
assert.equal(A.test(a2), true, "definition can be dynamically changed 2/4")
|
|
247
|
+
a1.splice(0, a1.length);
|
|
248
|
+
assert.throws(() => a1.push("hello"), /TypeError/, "definition can be dynamically changed 3/4")
|
|
249
|
+
a1.push(42);
|
|
250
|
+
assert.equal(a1[0], 42, "definition can be dynamically changed 4/4")
|
|
251
|
+
|
|
252
|
+
let OM = ObjectModel({ n: Number });
|
|
253
|
+
A.definition = OM;
|
|
254
|
+
a1.splice(0, a1.length);
|
|
255
|
+
a1.push({ n: 42 });
|
|
256
|
+
assert.ok(a1[0] instanceof OM, "autocast still works after definition dynamically changed")
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
QUnit.test("delete key", function (assert) {
|
|
260
|
+
let A = ArrayModel([Number]);
|
|
261
|
+
let a = A([1, 2, 3])
|
|
262
|
+
delete a[2]
|
|
263
|
+
assert.equal(3 in a, false, "delete should remove own array key")
|
|
264
|
+
assert.equal(a.length, 3, "delete should not update length")
|
|
265
|
+
assert.equal(Object.keys(a).length, 2, "delete should leave an empty hole")
|
|
266
|
+
|
|
267
|
+
const ArrayDense = ArrayModel([Number]).assert(function hasNoHoles(arr) {
|
|
268
|
+
return arr.filter(() => true).length === arr.length
|
|
269
|
+
}).as("ArrayDense");
|
|
270
|
+
|
|
271
|
+
A = ArrayDense.extend([Number]);
|
|
272
|
+
a = A([4, 5, 6])
|
|
273
|
+
assert.throws(() => {
|
|
274
|
+
delete a[2]
|
|
275
|
+
}, /TypeError.*hasNoHoles/, "ArrayDense should prevent deleting keys")
|
|
276
|
+
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
QUnit.test("Arraymodel as ObjectModel default prop", function (assert) {
|
|
280
|
+
class OM extends ObjectModel({
|
|
281
|
+
a: ArrayModel(Number)
|
|
282
|
+
}).defaultTo({
|
|
283
|
+
a: []
|
|
284
|
+
}) { }
|
|
285
|
+
|
|
286
|
+
let o = new OM();
|
|
287
|
+
|
|
288
|
+
assert.ok(Array.isArray(o.a), "ArrayModel works as ObjectModel default prop")
|
|
289
|
+
o.a.push(1, 2, 3)
|
|
290
|
+
assert.equal(o.a.length, 3, "ArrayModel as ObjectModel default prop can be mutated")
|
|
291
|
+
|
|
292
292
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {expectType} from 'tsd';
|
|
2
|
+
import { ArrayModel } from '../src/array-model';
|
|
3
|
+
import { Model, BasicModel, ObjectModel } from "../src/object-model"
|
|
4
|
+
|
|
5
|
+
expectType<number[]>(ArrayModel(Number)([1,2,3]))
|
|
6
|
+
|
|
7
|
+
const Cards = new ArrayModel([Number, "J" as const,"Q" as const,"K" as const]);
|
|
8
|
+
expectType<(number | "J" | "Q" | "K")[]>(Cards([]))
|
|
9
|
+
|
|
10
|
+
const Integer = BasicModel(Number).assert(Number.isInteger)
|
|
11
|
+
const Size = BasicModel(/^X{0,2}[SL]$/)
|
|
12
|
+
const Sizes = ArrayModel([Integer, Size])
|
|
13
|
+
const sizes = Sizes([12,"L"])
|
|
14
|
+
expectType<(string | number)[]>(sizes)
|
|
15
|
+
|
|
16
|
+
const Person = ObjectModel({ name: String, female: Boolean })
|
|
17
|
+
const Group = ArrayModel(Person)
|
|
18
|
+
expectType<{ name: string, female: boolean}[]>(Group([]))
|
|
19
|
+
|
|
20
|
+
const ModelA = Model({ a: Number, c: Number })
|
|
21
|
+
const ModelB = Model({ b: String, c: String })
|
|
22
|
+
const AorBs = ArrayModel([ModelA, ModelB])
|
|
23
|
+
const aorbs = AorBs([ { a: 1, c:0 }, { b: "2", c: ""}])
|
|
24
|
+
expectType<({ a: number, c: number } | { b: string, c: string })[]>(aorbs)
|