~repos /atoms-element

#js

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
Files changed (3) hide show
  1. element.js +34 -59
  2. element.test.js +29 -24
  3. 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 checkPrimitive = (primitiveType) => {
213
+ const validator = (type, validate) => (innerType) => {
214
+ const isPrimitiveType = ['number', 'string', 'boolean'].includes(type);
220
215
  const common = {
221
- type: primitiveType,
216
+ type: type,
222
- parse: (attr) => attr,
217
+ parse: isPrimitiveType ? (attr) => attr : (attr) => (attr ? JSON.parse(attr.replace(/'/g, `"`)) : null),
223
- };
224
- const validate = (context, data) => {
218
+ validate: (context, data) => {
225
- if (data === null || typeof data === 'undefined') {
219
+ if (data === null || typeof data === 'undefined') {
220
+ if (common.__required) {
221
+ logError(`'${context}' Field is required`);
222
+ }
226
- return;
223
+ return;
227
- }
224
+ }
225
+ if (!isPrimitiveType) {
226
+ validate(innerType, context, data);
227
+ } else {
228
- const dataType = typeof data;
228
+ const dataType = typeof data;
229
- if (dataType !== primitiveType) {
229
+ if (dataType !== type) {
230
- logError(`'${context}' Expected type '${primitiveType}' got type '${dataType}'`);
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
- const checkComplex = (complexType, validate) => {
235
+ common.required = () => {
236
+ common.__required = true;
247
- const common = {
237
+ return common;
248
- type: complexType,
249
- parse: (attr) => (attr ? JSON.parse(attr.replace(/'/g, `"`)) : null),
250
238
  };
251
- return (innerType) => {
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 = checkPrimitive('number');
246
+ export const number = validator('number');
272
- export const string = checkPrimitive('string');
247
+ export const string = validator('string');
273
- export const boolean = checkPrimitive('boolean');
248
+ export const boolean = validator('boolean');
274
- export const object = checkComplex('object', (innerType, context, data) => {
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 = checkComplex('array', (innerType, context, data) => {
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
- this._state[key] = 0; // TODO: default
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(value.validator.type).toEqual(value.type);
35
+ expect(validator.type).toEqual(value.type);
32
- expect(value.validator.isRequired.type).toEqual(value.type);
36
+ expect(validator.__required).toEqual(undefined);
37
+ expect(validatorReq.__required).toEqual(true);
33
- value.validator.validate(context);
38
+ validator.validate(context);
34
39
  for (const v of value.valid) {
35
- value.validator.validate(context, v);
40
+ validator.validate(context, v);
36
- value.validator.isRequired.validate(context, v);
41
+ validatorReq.validate(context, v);
37
42
  }
38
- value.validator.isRequired.validate(context);
43
+ validatorReq.validate(context);
39
44
  expect(console.warn).toHaveBeenCalledWith(`'key' Field is required`);
40
45
  for (const v of value.invalid) {
41
- value.validator.validate(context, v);
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.isRequired }).validate(context, { name: '' });
58
+ object({ name: string().required() }).validate(context, { name: '' });
54
- object({ name: string.isRequired }).validate(context, {});
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.isRequired,
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.isRequired,
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.isRequired,
243
+ perPage: string().required(),
239
244
  address: object({
240
- street: string.isRequired,
245
+ street: string().required(),
241
- }).isRequired,
246
+ }).required(),
242
247
  };
243
248
 
244
249
  static stateTypes = {
245
- count: number.isRequired,
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.isRequired,
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, meta } = this.attrs;
25
+ const { name } = this.attrs;
20
- const [count, setCount] = this.useState(meta?.start || 0);
26
+ const { count, setCount } = this.state;
21
27
 
22
28
  return html`
23
29
  <div>