~repos /atoms-element
git clone https://pyrossh.dev/repos/atoms-element.git
A simple web component library for defining your custom elements. It works on both client and server.
8ff74592
—
Peter John 4 years ago
make typings work
- element.js +34 -59
- element.test.js +29 -24
- example/app-counter.js +10 -4
element.js
CHANGED
|
@@ -210,68 +210,43 @@ const logError = (msg) => {
|
|
|
210
210
|
}
|
|
211
211
|
};
|
|
212
212
|
|
|
213
|
-
const checkRequired = (context, data) => {
|
|
214
|
-
if (data === null || typeof data === 'undefined') {
|
|
215
|
-
logError(`'${context}' Field is required`);
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const
|
|
213
|
+
const validator = (type, validate) => (innerType) => {
|
|
214
|
+
const isPrimitiveType = ['number', 'string', 'boolean'].includes(type);
|
|
220
215
|
const common = {
|
|
221
|
-
type:
|
|
216
|
+
type: type,
|
|
222
|
-
parse: (attr) => attr,
|
|
217
|
+
parse: isPrimitiveType ? (attr) => attr : (attr) => (attr ? JSON.parse(attr.replace(/'/g, `"`)) : null),
|
|
223
|
-
};
|
|
224
|
-
|
|
218
|
+
validate: (context, data) => {
|
|
225
|
-
|
|
219
|
+
if (data === null || typeof data === 'undefined') {
|
|
220
|
+
if (common.__required) {
|
|
221
|
+
logError(`'${context}' Field is required`);
|
|
222
|
+
}
|
|
226
|
-
|
|
223
|
+
return;
|
|
227
|
-
|
|
224
|
+
}
|
|
225
|
+
if (!isPrimitiveType) {
|
|
226
|
+
validate(innerType, context, data);
|
|
227
|
+
} else {
|
|
228
|
-
|
|
228
|
+
const dataType = typeof data;
|
|
229
|
-
|
|
229
|
+
if (dataType !== type) {
|
|
230
|
-
|
|
230
|
+
logError(`'${context}' Expected type '${type}' got type '${dataType}'`);
|
|
231
|
-
|
|
231
|
+
}
|
|
232
|
-
|
|
232
|
+
}
|
|
233
|
-
return {
|
|
234
|
-
validate,
|
|
235
|
-
...common,
|
|
236
|
-
isRequired: {
|
|
237
|
-
...common,
|
|
238
|
-
validate: (context, data) => {
|
|
239
|
-
checkRequired(context, data);
|
|
240
|
-
validate(context, data);
|
|
241
|
-
},
|
|
242
233
|
},
|
|
243
234
|
};
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
|
|
235
|
+
common.required = () => {
|
|
236
|
+
common.__required = true;
|
|
247
|
-
|
|
237
|
+
return common;
|
|
248
|
-
type: complexType,
|
|
249
|
-
parse: (attr) => (attr ? JSON.parse(attr.replace(/'/g, `"`)) : null),
|
|
250
238
|
};
|
|
251
|
-
|
|
239
|
+
common.default = (fnOrValue) => {
|
|
240
|
+
common.__default = fnOrValue;
|
|
252
|
-
return
|
|
241
|
+
return common;
|
|
253
|
-
...common,
|
|
254
|
-
validate: (context, data) => {
|
|
255
|
-
if (!data) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
validate(innerType, context, data);
|
|
259
|
-
},
|
|
260
|
-
isRequired: {
|
|
261
|
-
...common,
|
|
262
|
-
validate: (context, data) => {
|
|
263
|
-
checkRequired(context, data);
|
|
264
|
-
validate(innerType, context, data);
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
242
|
};
|
|
243
|
+
return common;
|
|
269
244
|
};
|
|
270
245
|
|
|
271
|
-
export const number =
|
|
246
|
+
export const number = validator('number');
|
|
272
|
-
export const string =
|
|
247
|
+
export const string = validator('string');
|
|
273
|
-
export const boolean =
|
|
248
|
+
export const boolean = validator('boolean');
|
|
274
|
-
export const object =
|
|
249
|
+
export const object = validator('object', (innerType, context, data) => {
|
|
275
250
|
if (data.constructor !== Object) {
|
|
276
251
|
logError(`'${context}' Expected object literal '{}' got '${typeof data}'`);
|
|
277
252
|
}
|
|
@@ -281,7 +256,7 @@ export const object = checkComplex('object', (innerType, context, data) => {
|
|
|
281
256
|
fieldValidator.validate(`${context}.${key}`, item);
|
|
282
257
|
}
|
|
283
258
|
});
|
|
284
|
-
export const array =
|
|
259
|
+
export const array = validator('array', (innerType, context, data) => {
|
|
285
260
|
if (!Array.isArray(data)) {
|
|
286
261
|
logError(`Expected Array got ${data}`);
|
|
287
262
|
}
|
|
@@ -290,7 +265,6 @@ export const array = checkComplex('array', (innerType, context, data) => {
|
|
|
290
265
|
innerType.validate(`${context}[${i}]`, item);
|
|
291
266
|
}
|
|
292
267
|
});
|
|
293
|
-
export const func = checkComplex('function', (innerType, context, data) => {});
|
|
294
268
|
|
|
295
269
|
// const depsChanged = (prev, next) => prev == null || next.some((f, i) => !Object.is(f, prev[i]));
|
|
296
270
|
// useEffect(handler, deps) {
|
|
@@ -430,8 +404,9 @@ export default class AtomsElement extends BaseElement {
|
|
|
430
404
|
|
|
431
405
|
get state() {
|
|
432
406
|
return Object.keys(this.constructor.stateTypes).reduceRight((acc, key) => {
|
|
433
|
-
if (!this._state[key]) {
|
|
434
|
-
|
|
407
|
+
const stateType = this.constructor.stateTypes[key];
|
|
408
|
+
if (!this._state[key] && typeof stateType.__default !== 'undefined') {
|
|
409
|
+
this._state[key] = typeof stateType.__default === 'function' ? stateType.__default(this.attrs, this._state) : stateType.__default;
|
|
435
410
|
}
|
|
436
411
|
acc[key] = this._state[key];
|
|
437
412
|
acc[`set${key[0].toUpperCase()}${key.slice(1)}`] = (v) => () => {
|
element.test.js
CHANGED
|
@@ -6,39 +6,44 @@ global.__DEV = true;
|
|
|
6
6
|
const primitives = [
|
|
7
7
|
{
|
|
8
8
|
type: 'number',
|
|
9
|
-
validator: number,
|
|
10
9
|
valid: [123, 40.5, 6410],
|
|
11
10
|
invalid: ['123', false, {}, [], new Date()],
|
|
12
11
|
},
|
|
13
12
|
{
|
|
14
13
|
type: 'boolean',
|
|
15
|
-
validator: boolean,
|
|
16
14
|
valid: [false, true],
|
|
17
15
|
invalid: ['123', 123, {}, [], new Date()],
|
|
18
16
|
},
|
|
19
17
|
{
|
|
20
18
|
type: 'string',
|
|
21
|
-
validator: string,
|
|
22
19
|
valid: ['', '123'],
|
|
23
20
|
invalid: [123, false, {}, [], new Date()],
|
|
24
21
|
},
|
|
25
22
|
];
|
|
23
|
+
const primitiveTypes = {
|
|
24
|
+
number: number,
|
|
25
|
+
boolean: boolean,
|
|
26
|
+
string: string,
|
|
27
|
+
};
|
|
26
28
|
|
|
27
29
|
primitives.forEach((value) =>
|
|
28
30
|
it(`${value.type}`, () => {
|
|
29
31
|
const spy = jest.spyOn(global.console, 'warn').mockImplementation();
|
|
30
32
|
const context = 'key';
|
|
33
|
+
const validator = primitiveTypes[value.type]();
|
|
34
|
+
const validatorReq = primitiveTypes[value.type]().required();
|
|
31
|
-
expect(
|
|
35
|
+
expect(validator.type).toEqual(value.type);
|
|
32
|
-
expect(
|
|
36
|
+
expect(validator.__required).toEqual(undefined);
|
|
37
|
+
expect(validatorReq.__required).toEqual(true);
|
|
33
|
-
|
|
38
|
+
validator.validate(context);
|
|
34
39
|
for (const v of value.valid) {
|
|
35
|
-
|
|
40
|
+
validator.validate(context, v);
|
|
36
|
-
|
|
41
|
+
validatorReq.validate(context, v);
|
|
37
42
|
}
|
|
38
|
-
|
|
43
|
+
validatorReq.validate(context);
|
|
39
44
|
expect(console.warn).toHaveBeenCalledWith(`'key' Field is required`);
|
|
40
45
|
for (const v of value.invalid) {
|
|
41
|
-
|
|
46
|
+
validator.validate(context, v);
|
|
42
47
|
expect(console.warn).toHaveBeenCalledWith(`'key' Expected type '${value.type}' got type '${typeof v}'`);
|
|
43
48
|
}
|
|
44
49
|
spy.mockRestore();
|
|
@@ -49,14 +54,14 @@ test('object', () => {
|
|
|
49
54
|
const spy = jest.spyOn(global.console, 'warn').mockImplementation();
|
|
50
55
|
const context = 'data';
|
|
51
56
|
object({}).validate(context, { name: '123' });
|
|
52
|
-
object({ name: string }).validate(context, { name: '123' });
|
|
57
|
+
object({ name: string() }).validate(context, { name: '123' });
|
|
53
|
-
object({ name: string.
|
|
58
|
+
object({ name: string().required() }).validate(context, { name: '' });
|
|
54
|
-
object({ name: string.
|
|
59
|
+
object({ name: string().required() }).validate(context, {});
|
|
55
60
|
expect(console.warn).toHaveBeenCalledWith(`'data.name' Field is required`);
|
|
56
61
|
|
|
57
62
|
const schema = object({
|
|
58
63
|
address: object({
|
|
59
|
-
street: string,
|
|
64
|
+
street: string(),
|
|
60
65
|
}),
|
|
61
66
|
});
|
|
62
67
|
schema.validate(context, {});
|
|
@@ -83,7 +88,7 @@ test('object', () => {
|
|
|
83
88
|
|
|
84
89
|
const schema2 = object({
|
|
85
90
|
address: object({
|
|
86
|
-
street: string.
|
|
91
|
+
street: string().required(),
|
|
87
92
|
}),
|
|
88
93
|
});
|
|
89
94
|
schema2.validate(context, {});
|
|
@@ -102,15 +107,15 @@ test('object', () => {
|
|
|
102
107
|
test('array', () => {
|
|
103
108
|
const spy = jest.spyOn(global.console, 'warn').mockImplementation();
|
|
104
109
|
const context = 'items';
|
|
105
|
-
array(string).validate(context, ['123']);
|
|
110
|
+
array(string()).validate(context, ['123']);
|
|
106
|
-
array(string).validate(context, [123]);
|
|
111
|
+
array(string()).validate(context, [123]);
|
|
107
112
|
expect(console.warn).toHaveBeenCalledWith(`'items[0]' Expected type 'string' got type 'number'`);
|
|
108
|
-
array(array(string)).validate(context, [['123']]);
|
|
113
|
+
array(array(string())).validate(context, [['123']]);
|
|
109
|
-
array(array(string)).validate(context, [[123]]);
|
|
114
|
+
array(array(string())).validate(context, [[123]]);
|
|
110
115
|
expect(console.warn).toHaveBeenCalledWith(`'items[0][0]' Expected type 'string' got type 'number'`);
|
|
111
116
|
|
|
112
117
|
const schema = object({
|
|
113
|
-
street: string.
|
|
118
|
+
street: string().required(),
|
|
114
119
|
});
|
|
115
120
|
array(schema).validate(context, []);
|
|
116
121
|
array(schema).validate(context, [{ street: '123' }, { street: '456' }, { street: '789' }]);
|
|
@@ -235,14 +240,14 @@ test('AtomsElement', async () => {
|
|
|
235
240
|
static name = 'app-item';
|
|
236
241
|
|
|
237
242
|
static attrTypes = {
|
|
238
|
-
perPage: string.
|
|
243
|
+
perPage: string().required(),
|
|
239
244
|
address: object({
|
|
240
|
-
street: string.
|
|
245
|
+
street: string().required(),
|
|
241
|
-
}).
|
|
246
|
+
}).required(),
|
|
242
247
|
};
|
|
243
248
|
|
|
244
249
|
static stateTypes = {
|
|
245
|
-
count: number.
|
|
250
|
+
count: number().required().default(0),
|
|
246
251
|
};
|
|
247
252
|
|
|
248
253
|
static effects = {
|
example/app-counter.js
CHANGED
|
@@ -4,20 +4,26 @@ class Counter extends AtomsElement {
|
|
|
4
4
|
static name = 'app-counter';
|
|
5
5
|
|
|
6
6
|
static attrTypes = {
|
|
7
|
-
name: string.
|
|
7
|
+
name: string().required(),
|
|
8
8
|
meta: object({
|
|
9
|
-
start: number,
|
|
9
|
+
start: number(),
|
|
10
10
|
}),
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
static stateTypes = {
|
|
14
|
+
count: number()
|
|
15
|
+
.required()
|
|
16
|
+
.default((attrs) => attrs.meta?.start || 0),
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
static styles = css`
|
|
14
20
|
.container {
|
|
15
21
|
}
|
|
16
22
|
`;
|
|
17
23
|
|
|
18
24
|
render() {
|
|
19
|
-
const { name
|
|
25
|
+
const { name } = this.attrs;
|
|
20
|
-
const
|
|
26
|
+
const { count, setCount } = this.state;
|
|
21
27
|
|
|
22
28
|
return html`
|
|
23
29
|
<div>
|