cx 22.3.4 → 22.3.5

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.
Files changed (45) hide show
  1. package/dist/data.js +94 -25
  2. package/dist/manifest.js +474 -468
  3. package/dist/ui.js +27 -6
  4. package/dist/widgets.js +1 -1
  5. package/package.json +1 -1
  6. package/src/core.d.ts +41 -6
  7. package/src/data/Binding.d.ts +4 -3
  8. package/src/data/Binding.js +12 -8
  9. package/src/data/Store.d.ts +5 -5
  10. package/src/data/Store.js +2 -2
  11. package/src/data/StoreRef.js +2 -0
  12. package/src/data/StructuredSelector.js +43 -57
  13. package/src/data/StructuredSelector.spec.js +54 -39
  14. package/src/data/View.d.ts +40 -16
  15. package/src/data/View.js +27 -19
  16. package/src/data/comparer.js +15 -10
  17. package/src/data/computable.d.ts +27 -4
  18. package/src/data/computable.js +2 -1
  19. package/src/data/computable.spec.js +8 -0
  20. package/src/data/createAccessorModelProxy.d.ts +6 -0
  21. package/src/data/createAccessorModelProxy.js +24 -0
  22. package/src/data/createAccessorModelProxy.spec.tsx +23 -0
  23. package/src/data/createStructuredSelector.js +9 -8
  24. package/src/data/getAccessor.js +24 -15
  25. package/src/data/getSelector.js +4 -1
  26. package/src/data/getSelector.spec.js +7 -0
  27. package/src/data/index.d.ts +2 -0
  28. package/src/data/index.js +2 -0
  29. package/src/ui/Controller.d.ts +13 -8
  30. package/src/ui/Instance.d.ts +6 -6
  31. package/src/ui/Instance.js +6 -5
  32. package/src/ui/Repeater.d.ts +25 -15
  33. package/src/ui/Restate.spec.js +0 -1
  34. package/src/ui/adapter/ArrayAdapter.d.ts +9 -10
  35. package/src/ui/adapter/ArrayAdapter.js +30 -37
  36. package/src/ui/bind.d.ts +3 -2
  37. package/src/ui/bind.js +4 -3
  38. package/src/ui/expr.d.ts +23 -2
  39. package/src/ui/expr.js +16 -4
  40. package/src/widgets/AccessorBindings.spec.tsx +66 -0
  41. package/src/widgets/HtmlElement.d.ts +1 -1
  42. package/src/widgets/List.d.ts +36 -24
  43. package/src/widgets/form/ValidationGroup.spec.js +1 -1
  44. package/src/widgets/grid/Grid.d.ts +86 -68
  45. package/src/widgets/grid/Grid.js +1 -1
@@ -11,6 +11,7 @@ import { isArray } from "../util/isArray";
11
11
  import { isObject } from "../util/isObject";
12
12
  import { isNonEmptyArray } from "../util/isNonEmptyArray";
13
13
  import { isUndefined } from "../util/isUndefined";
14
+ import { isAccessorChain } from "../data/createAccessorModelProxy";
14
15
 
15
16
  let instanceId = 1000;
16
17
 
@@ -116,8 +117,8 @@ export class Instance {
116
117
  ins = ins.widget.isContent
117
118
  ? ins.contentPlaceholder
118
119
  : ins.parent.outerLayout === ins
119
- ? ins.parent.parent
120
- : ins.parent;
120
+ ? ins.parent.parent
121
+ : ins.parent;
121
122
  }
122
123
  renderList.reverse();
123
124
  }
@@ -416,10 +417,10 @@ export class Instance {
416
417
  let action = p.action(value, this);
417
418
  this.store.dispatch(action);
418
419
  changed = true;
419
- } else if (isString(p.bind)) {
420
+ } else if (isString(p.bind) || isAccessorChain(p.bind)) {
420
421
  changed = this.store.set(p.bind, value);
421
422
  }
422
- }
423
+ } else if (isAccessorChain(p)) changed = this.store.set(p.toString(), value);
423
424
  });
424
425
  return changed;
425
426
  }
@@ -544,7 +545,7 @@ export class InstanceCache {
544
545
  }
545
546
 
546
547
  getChild(widget, store, key) {
547
- let k = this.keyPrefix + (key != null ? key : (widget.vdomKey || widget.widgetId));
548
+ let k = this.keyPrefix + (key != null ? key : widget.vdomKey || widget.widgetId);
548
549
  let instance = this.children[k];
549
550
 
550
551
  if (
@@ -1,12 +1,22 @@
1
- import * as Cx from "../core";
1
+ import {
2
+ RecordsProp,
3
+ RecordAlias,
4
+ StringProp,
5
+ SortersProp,
6
+ StructuredProp,
7
+ CollatorOptions,
8
+ Widget,
9
+ PureContainerProps,
10
+ Record,
11
+ } from "../core";
2
12
  import { Instance } from "./Instance";
3
13
 
4
- interface RepeaterProps extends Cx.PureContainerProps {
5
- records: Cx.RecordsProp;
6
- recordName?: string;
7
- recordAlias?: string;
8
- indexName?: string;
9
- indexAlias?: string;
14
+ interface RepeaterProps extends PureContainerProps {
15
+ records: RecordsProp;
16
+ recordName?: RecordAlias;
17
+ recordAlias?: RecordAlias;
18
+ indexName?: RecordAlias;
19
+ indexAlias?: RecordAlias;
10
20
  cached?: boolean;
11
21
 
12
22
  /** Indicate that parent store data should not be mutated. */
@@ -15,22 +25,22 @@ interface RepeaterProps extends Cx.PureContainerProps {
15
25
  /** Indicate that record stores should not be mutated. */
16
26
  sealed?: boolean;
17
27
 
18
- sorters?: Cx.SortersProp;
28
+ sorters?: SortersProp;
19
29
 
20
30
  /** A binding used to store the name of the field used for sorting the collection. Available only if `sorters` are not used. */
21
- sortField?: Cx.StringProp;
31
+ sortField?: StringProp;
22
32
 
23
- /** A binding used to store the sort direction. Available only if `sorters` are not used. Possible values are `"ASC"` and `"DESC"`. Deafults to `"ASC"`. */
24
- sortDirection?: Cx.StringProp;
33
+ /** A binding used to store the sort direction. Available only if `sorters` are not used. Possible values are `"ASC"` and `"DESC"`. Defaults to `"ASC"`. */
34
+ sortDirection?: StringProp;
25
35
 
26
36
  /** Parameters that affect filtering */
27
- filterParams?: Cx.StructuredProp;
37
+ filterParams?: StructuredProp;
28
38
 
29
39
  /** Callback to create a filter function for given filter params. */
30
- onCreateFilter?: (filterParams: any, instance: Instance) => (record: Cx.Record) => boolean;
40
+ onCreateFilter?: (filterParams: any, instance: Instance) => (record: Record) => boolean;
31
41
 
32
42
  /** Options for data sorting. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator */
33
- sortOptions?: Cx.CollatorOptions
43
+ sortOptions?: CollatorOptions;
34
44
  }
35
45
 
36
- export class Repeater extends Cx.Widget<RepeaterProps> {}
46
+ export class Repeater extends Widget<RepeaterProps> {}
@@ -350,7 +350,6 @@ describe("Restate", () => {
350
350
  let store = new Store();
351
351
  store.subscribe(() => {
352
352
  changed = true;
353
- console.log("CHANGED");
354
353
  });
355
354
 
356
355
  const component = renderer.create(<Cx widget={widget} store={store} subscribe immediate />);
@@ -1,17 +1,16 @@
1
- import {DataAdapter} from './DataAdapter';
2
- import {Binding} from "../../data/Binding";
3
- import {Ref} from "../../data/Ref";
1
+ import { AccessorChain, Prop } from "../../core";
2
+ import { Accessor } from "../../data/getAccessor";
3
+ import { DataAdapter } from "./DataAdapter";
4
4
 
5
- interface Accessor {
6
- binding: Binding,
7
- ref: Ref
8
- }
9
-
10
- interface Config {
5
+ interface ArrayAdapterConfig {
11
6
  immutable?: boolean;
12
7
  sealed?: boolean;
8
+ recordsBinding?: Prop<any[]>;
9
+ recordsAccessor?: Accessor;
10
+ recordName: string | AccessorChain<any>;
11
+ indexName: string | AccessorChain<any>;
13
12
  }
14
13
 
15
14
  export class ArrayAdapter extends DataAdapter {
16
- constructor(config: Config);
15
+ constructor(config: ArrayAdapterConfig);
17
16
  }
@@ -1,22 +1,23 @@
1
- import { DataAdapter } from './DataAdapter';
2
- import { ReadOnlyDataView } from '../../data/ReadOnlyDataView';
3
- import { sorter } from '../../data/comparer';
4
- import { isArray } from '../../util/isArray';
1
+ import { DataAdapter } from "./DataAdapter";
2
+ import { ReadOnlyDataView } from "../../data/ReadOnlyDataView";
3
+ import { sorter } from "../../data/comparer";
4
+ import { isArray } from "../../util/isArray";
5
5
  import { ArrayElementView } from "../../data/ArrayElementView";
6
6
  import { getAccessor } from "../../data/getAccessor";
7
7
  import { Culture } from "../Culture";
8
- import { isDefined } from '../../util';
9
-
8
+ import { isDefined } from "../../util";
10
9
 
11
10
  export class ArrayAdapter extends DataAdapter {
12
-
13
11
  init() {
14
12
  this.recordsAccessor = getAccessor(this.recordsBinding ? this.recordsBinding : this.recordsAccessor);
13
+
14
+ //resolve accessor chains
15
+ this.recordName = this.recordName.toString();
16
+ this.indexName = this.indexName.toString();
15
17
  }
16
18
 
17
19
  initInstance(context, instance) {
18
- if (!instance.recordStoreCache)
19
- instance.recordStoreCache = new WeakMap();
20
+ if (!instance.recordStoreCache) instance.recordStoreCache = new WeakMap();
20
21
  if (!instance.recordsAccessor && this.recordsAccessor) {
21
22
  instance.recordsAccessor = this.recordsAccessor.bindInstance
22
23
  ? this.recordsAccessor.bindInstance(instance)
@@ -25,8 +26,7 @@ export class ArrayAdapter extends DataAdapter {
25
26
  }
26
27
 
27
28
  getRecords(context, instance, records, parentStore) {
28
- if (!instance.recordStoreCache)
29
- this.initInstance(context, instance);
29
+ if (!instance.recordStoreCache) this.initInstance(context, instance);
30
30
 
31
31
  return this.mapRecords(context, instance, records, parentStore, instance.recordsAccessor);
32
32
  }
@@ -34,22 +34,18 @@ export class ArrayAdapter extends DataAdapter {
34
34
  mapRecords(context, instance, records, parentStore, recordsAccessor) {
35
35
  let result = [];
36
36
 
37
- if (!instance.recordStoreCache)
38
- this.initInstance(context, instance);
37
+ if (!instance.recordStoreCache) this.initInstance(context, instance);
39
38
 
40
39
  if (isArray(records))
41
40
  records.forEach((data, index) => {
42
-
43
- if (this.filterFn && !this.filterFn(data))
44
- return;
41
+ if (this.filterFn && !this.filterFn(data)) return;
45
42
 
46
43
  let record = this.mapRecord(context, instance, data, parentStore, recordsAccessor, index);
47
44
 
48
45
  result.push(record);
49
46
  });
50
47
 
51
- if (this.sorter)
52
- result = this.sorter(result);
48
+ if (this.sorter) result = this.sorter(result);
53
49
 
54
50
  return result;
55
51
  }
@@ -66,7 +62,7 @@ export class ArrayAdapter extends DataAdapter {
66
62
  recordAlias: this.recordName,
67
63
  indexAlias: this.indexName,
68
64
  immutable: this.immutable,
69
- sealed: this.sealed
65
+ sealed: this.sealed,
70
66
  });
71
67
  else {
72
68
  recordStore.setStore(parentStore);
@@ -78,25 +74,24 @@ export class ArrayAdapter extends DataAdapter {
78
74
  store: parentStore,
79
75
  data: {
80
76
  [this.recordName]: data,
81
- [this.indexName]: index
77
+ [this.indexName]: index,
82
78
  },
83
79
  immutable: this.immutable,
84
- sealed: this.sealed
80
+ sealed: this.sealed,
85
81
  });
86
82
  else {
87
83
  recordStore.setStore(parentStore);
88
84
  }
89
85
  }
90
86
 
91
- if (typeof data == 'object')
92
- instance.recordStoreCache.set(data, recordStore);
87
+ if (typeof data == "object") instance.recordStoreCache.set(data, recordStore);
93
88
 
94
89
  return {
95
90
  store: recordStore,
96
91
  index: index,
97
92
  data: data,
98
- type: 'data',
99
- key: this.keyField ? data[this.keyField] : index
93
+ type: "data",
94
+ key: this.keyField ? data[this.keyField] : index,
100
95
  };
101
96
  }
102
97
 
@@ -114,21 +109,19 @@ export class ArrayAdapter extends DataAdapter {
114
109
  let dataAccessor;
115
110
 
116
111
  //if all sorters are based on record fields access data directly (faster)
117
- if (sorters.every(x => x.field && x.value == null)) {
118
- dataAccessor = x => x.data;
119
- fieldValueMapper = x => ({ bind: x.field });
120
- }
121
- else {
122
- dataAccessor = x => x.store.getData();
123
- fieldValueMapper = x => ({ bind: this.recordName + '.' + x.field });
112
+ if (sorters.every((x) => x.field && x.value == null)) {
113
+ dataAccessor = (x) => x.data;
114
+ fieldValueMapper = (x) => ({ bind: x.field });
115
+ } else {
116
+ dataAccessor = (x) => x.store.getData();
117
+ fieldValueMapper = (x) => ({ bind: this.recordName + "." + x.field });
124
118
  }
125
119
  this.sorter = sorter(
126
- sorters.map(x => {
120
+ sorters.map((x) => {
127
121
  let s = Object.assign({}, x);
128
- if (s.field && s.value == null)
129
- s.value = fieldValueMapper(s);
122
+ if (s.field && s.value == null) s.value = fieldValueMapper(s);
130
123
  if (!s.comparer)
131
- s.comparer = this.getComparer(isDefined(s.sortOptions) ? s.sortOptions : this.sortOptions)
124
+ s.comparer = this.getComparer(isDefined(s.sortOptions) ? s.sortOptions : this.sortOptions);
132
125
  return s;
133
126
  }),
134
127
  dataAccessor
@@ -146,4 +139,4 @@ export class ArrayAdapter extends DataAdapter {
146
139
  ArrayAdapter.prototype.immutable = false;
147
140
  ArrayAdapter.prototype.sealed = false;
148
141
 
149
- ArrayAdapter.autoInit = true;
142
+ ArrayAdapter.autoInit = true;
package/src/ui/bind.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- import * as Cx from '../core';
1
+ import { Binding, AccessorChain } from "../core";
2
2
 
3
- export function bind(path: string, defaultValue?: any) : Cx.Binding;
3
+ export function bind(path: string, defaultValue?: any): Binding;
4
+ export function bind(chain: AccessorChain<any>, defaultValue?: any): Binding;
package/src/ui/bind.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export function bind(path, defaultValue) {
2
2
  return {
3
- bind: path,
4
- defaultValue
5
- }
3
+ //toString will ensure chain accessors are converted to strings
4
+ bind: path.toString(),
5
+ defaultValue,
6
+ };
6
7
  }
package/src/ui/expr.d.ts CHANGED
@@ -1,3 +1,24 @@
1
- import * as Cx from '../core';
1
+ import { AccessorChain, Selector, Expr } from "../core";
2
2
 
3
- export function expr(code: string) : Cx.Binding;
3
+ export function expr(code: string): Expr;
4
+ export function expr<V1, R>(arg1: AccessorChain<V1>, compute: (v1: V1) => R): Selector<R>;
5
+ export function expr<V1, V2, R>(
6
+ arg1: AccessorChain<V1>,
7
+ arg2: AccessorChain<V2>,
8
+ compute: (v1: V1, v2: V2) => R
9
+ ): Selector<R>;
10
+
11
+ export function expr<V1, V2, V3, R>(
12
+ arg1: AccessorChain<V1>,
13
+ arg2: AccessorChain<V2>,
14
+ arg3: AccessorChain<V3>,
15
+ compute: (v1: V1, v2: V2, v3: V3) => R
16
+ ): Selector<R>;
17
+
18
+ export function expr<V1, V2, V3, V4, R>(
19
+ arg1: AccessorChain<V1>,
20
+ arg2: AccessorChain<V2>,
21
+ arg3: AccessorChain<V3>,
22
+ arg4: AccessorChain<V4>,
23
+ compute: (v1: V1, v2: V2, v3: V3, v4: V4) => R
24
+ ): Selector<R>;
package/src/ui/expr.js CHANGED
@@ -1,5 +1,17 @@
1
+ import { Binding } from "../data/Binding";
2
+ import { isString } from "../util/isString";
3
+
1
4
  export function expr(text) {
2
- return {
3
- expr: text
4
- }
5
- }
5
+ if (isString(text))
6
+ return {
7
+ expr: text,
8
+ };
9
+
10
+ let getters = [];
11
+ let compute = arguments[arguments.length - 1];
12
+ for (let i = 0; i < arguments.length - 1; i++) getters.push(Binding.get(arguments[i]).value);
13
+ return (data) => {
14
+ let argv = getters.map((g) => g(data));
15
+ return compute.apply(this, argv);
16
+ };
17
+ }
@@ -0,0 +1,66 @@
1
+ import assert from "assert";
2
+ import reactTestRenderer from "react-test-renderer";
3
+ import { createAccessorModelProxy } from "../data/createAccessorModelProxy";
4
+ import { Store } from "../data/Store";
5
+ import { expr } from "../ui";
6
+ import { Cx } from "../ui/Cx";
7
+ import { HtmlElement } from "./HtmlElement";
8
+
9
+ let DummyHack = HtmlElement;
10
+
11
+ interface Model {
12
+ $page: {
13
+ text?: string;
14
+ a?: number;
15
+ b?: number;
16
+ };
17
+ }
18
+
19
+ let { $page } = createAccessorModelProxy<Model>();
20
+
21
+ describe("Accessors", () => {
22
+ it("work as regular bindings", () => {
23
+ let widget = (
24
+ <cx>
25
+ <div text={$page.text} />
26
+ </cx>
27
+ );
28
+
29
+ let store = new Store<Model>({
30
+ data: {
31
+ $page: {
32
+ text: "Test",
33
+ },
34
+ },
35
+ });
36
+
37
+ const component = reactTestRenderer.create(<Cx widget={widget} store={store} />);
38
+
39
+ let tree = component.toJSON();
40
+ assert(tree.type === "div");
41
+ assert.deepStrictEqual(tree.children, ["Test"]);
42
+ });
43
+
44
+ it("support expressions", () => {
45
+ let widget = (
46
+ <cx>
47
+ <div text={expr($page.a, $page.b, (a, b) => a + b)} />
48
+ </cx>
49
+ );
50
+
51
+ let store = new Store<Model>({
52
+ data: {
53
+ $page: {
54
+ a: 1,
55
+ b: 3,
56
+ },
57
+ },
58
+ });
59
+
60
+ const component = reactTestRenderer.create(<Cx widget={widget} store={store} />);
61
+
62
+ let tree = component.toJSON();
63
+ assert(tree.type === "div");
64
+ assert.deepStrictEqual(tree.children, ["4"]);
65
+ });
66
+ });
@@ -11,7 +11,7 @@ interface HtmlElementProps extends Cx.HtmlElementProps {
11
11
  tag?: string;
12
12
 
13
13
  /** HTML to be injected into the element. */
14
- html?: string;
14
+ html?: Cx.StringProp;
15
15
 
16
16
  styled?: boolean;
17
17
 
@@ -1,37 +1,49 @@
1
- import { Instance } from "./../ui/Instance.d";
2
- import * as Cx from "../core";
3
- import { PropertySelection, KeySelection } from "../ui/selection";
4
- import { Instance } from "../ui/Instance";
5
-
6
- interface ListProps extends Cx.StyledContainerProps {
1
+ import {
2
+ BooleanProp,
3
+ ClassProp,
4
+ CollatorOptions,
5
+ Config,
6
+ Record,
7
+ RecordAlias,
8
+ RecordsProp,
9
+ SortersProp,
10
+ StringProp,
11
+ StructuredProp,
12
+ StyledContainerProps,
13
+ StyleProp,
14
+ Widget,
15
+ } from "../core";
16
+ import { Instance } from "./../ui/Instance";
17
+
18
+ interface ListProps extends StyledContainerProps {
7
19
  /** An array of records to be displayed in the list. */
8
- records?: Cx.RecordsProp;
20
+ records?: RecordsProp;
9
21
 
10
22
  /** Used for sorting the list. */
11
- sorters?: Cx.SortersProp;
23
+ sorters?: SortersProp;
12
24
 
13
25
  /** A binding used to store the name of the field used for sorting the collection. Available only if `sorters` are not used. */
14
- sortField?: Cx.StringProp;
26
+ sortField?: StringProp;
15
27
 
16
- /** A binding used to store the sort direction. Available only if `sorters` are not used. Possible values are `"ASC"` and `"DESC"`. Deafults to `"ASC"`. */
17
- sortDirection?: Cx.StringProp;
28
+ /** A binding used to store the sort direction. Available only if `sorters` are not used. Possible values are `"ASC"` and `"DESC"`. Defaults to `"ASC"`. */
29
+ sortDirection?: StringProp;
18
30
 
19
31
  /** CSS style that will be applied to all list items. */
20
- itemStyle?: Cx.StyleProp;
32
+ itemStyle?: StyleProp;
21
33
 
22
34
  /** CSS class that will be applied to all list items. */
23
- itemClass?: Cx.ClassProp;
35
+ itemClass?: ClassProp;
24
36
 
25
37
  /** CSS class that will be applied to all list items. */
26
- itemClassName?: Cx.ClassProp;
38
+ itemClassName?: ClassProp;
27
39
 
28
- emptyText?: Cx.StringProp;
40
+ emptyText?: StringProp;
29
41
 
30
42
  /** Grouping configuration. */
31
- grouping?: Cx.Config;
43
+ grouping?: Config;
32
44
 
33
- recordName?: string;
34
- indexName?: string;
45
+ recordName?: RecordAlias;
46
+ indexName?: RecordAlias;
35
47
 
36
48
  /** Base CSS class to be applied to the element. Defaults to 'list'. */
37
49
  baseClass?: string;
@@ -42,22 +54,22 @@ interface ListProps extends Cx.StyledContainerProps {
42
54
  cached?: boolean;
43
55
 
44
56
  /** Selection configuration. */
45
- selection?: Cx.Config;
57
+ selection?: Config;
46
58
 
47
59
  /** Parameters that affect filtering */
48
- filterParams?: Cx.StructuredProp;
60
+ filterParams?: StructuredProp;
49
61
 
50
62
  /** Callback to create a filter function for given filter params. */
51
- onCreateFilter?: (filterParams: any, instance: Instance) => (record: Cx.Record) => boolean;
63
+ onCreateFilter?: (filterParams: any, instance: Instance) => (record: Record) => boolean;
52
64
 
53
65
  /** Scrolls selection into the view. Default value is false. */
54
66
  scrollSelectionIntoView?: boolean;
55
67
 
56
68
  /** Options for data sorting. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator */
57
- sortOptions?: Cx.CollatorOptions;
69
+ sortOptions?: CollatorOptions;
58
70
 
59
71
  /** Parameter used for disabling specific items in the list. */
60
- itemDisabled?: Cx.BooleanProp;
72
+ itemDisabled?: BooleanProp;
61
73
 
62
74
  /** Lists in this mode perform selection automatically without offering cursor navigation. */
63
75
  selectMode?: boolean;
@@ -69,4 +81,4 @@ interface ListProps extends Cx.StyledContainerProps {
69
81
  onScroll?: (event: Event, instance: Instance) => void;
70
82
  }
71
83
 
72
- export class List extends Cx.Widget<ListProps> {}
84
+ export class List extends Widget<ListProps> {}
@@ -21,7 +21,7 @@ describe("ValidationGroup", () => {
21
21
 
22
22
  let store = new Store();
23
23
 
24
- const component = renderer.create(<Cx widget={widget} store={store} subscribe />);
24
+ const component = renderer.create(<Cx widget={widget} store={store} subscribe immediate />);
25
25
 
26
26
  let tree = component.toJSON();
27
27
  assert.equal(tree.type, "div");