1 | const {compile, chain, root, arg0, arg1, setter, splice, withName} = require('../../index');
|
2 | const {
|
3 | describeCompilers,
|
4 | evalOrLoad,
|
5 | currentValues,
|
6 | funcLibrary,
|
7 | expectTapFunctionToHaveBeenCalled,
|
8 | rand
|
9 | } = require('../test-utils');
|
10 | const _ = require('lodash');
|
11 | const path = require('path')
|
12 |
|
13 | describe('Tests for usability and debugging carmi', () => {
|
14 | describeCompilers(['simple', 'optimizing'], compiler => {
|
15 | it('should store source files and ast in debug mode', () => {
|
16 | const makeSureThisCanBeFound = root.map(item => item.mult(2));
|
17 | const res = makeSureThisCanBeFound.map(item => item.plus(80));
|
18 | const model = {res, set: setter(arg0)}
|
19 | const optCode = evalOrLoad(compile(model, {compiler, debug: true}));
|
20 | const inst = optCode([1, 2, 3], funcLibrary);
|
21 | expect(inst.res).toEqual([82, 84, 86]);
|
22 | const sources = JSON.stringify(inst.$source());
|
23 | const ast = JSON.stringify(inst.$ast());
|
24 |
|
25 | expect(ast.indexOf('80')).toBeGreaterThan(-1)
|
26 | });
|
27 |
|
28 | it('withName', () => {
|
29 | const negated = withName('negated', root.map(val => val.not()));
|
30 | const model = {doubleNegated: negated.map(val => val.not().call('tap')), set: setter(arg0)};
|
31 | const optCode = evalOrLoad(compile(model, {compiler, debug: true}));
|
32 | const inst = optCode([true, 1, 0, false, null], funcLibrary);
|
33 | expect(inst.doubleNegated).toEqual([true, true, false, false, false]);
|
34 | expectTapFunctionToHaveBeenCalled(inst.$model.length, compiler);
|
35 | inst.set(1, null);
|
36 | const nameGiven = Object.keys(inst).find(k => k.indexOf('negated') !== -1);
|
37 | expect(nameGiven).toContain('negated');
|
38 | expect(inst.doubleNegated).toEqual([true, false, false, false, false]);
|
39 | expectTapFunctionToHaveBeenCalled(1, compiler);
|
40 | });
|
41 | it('chain should work in loop and on primitives', () => {
|
42 | const model = {
|
43 | test1: chain({test: true}),
|
44 | test2: chain({test: chain(true)}),
|
45 | test3: chain({test: chain(true).not()})
|
46 | }
|
47 | const optCode = evalOrLoad(compile(model, {compiler}));
|
48 | const inst = optCode([], funcLibrary);
|
49 | expect(inst.test1).toEqual({test: true});
|
50 | expect(inst.test2).toEqual({test: true});
|
51 | expect(inst.test3).toEqual({test: false});
|
52 | });
|
53 | it('throw on invalid arguments in setter function', () => {
|
54 | const args = ['store', arg0, true]
|
55 | expect(() => setter(...args)).toThrowError(`Invalid arguments for setter/splice/push - can only accept path (use arg0/arg1/arg2 - to define placeholders in the path), received [${args}]`);
|
56 | });
|
57 | it('throw on invalid arguments in splice function', () => {
|
58 | const args = ['store', arg0, true]
|
59 | expect(() => splice(...args)).toThrowError(`Invalid arguments for setter/splice/push - can only accept path (use arg0/arg1/arg2 - to define placeholders in the path), received [${args}]`);
|
60 | });
|
61 | it('throw on invalids reuse of key/val/loop/context inside other functions', () => {
|
62 | expect(() => {
|
63 | root.map(item => item.map(child => child.eq(item)))
|
64 | }).toThrowError();
|
65 | expect(() => {
|
66 | root.map((item, val) => item.map(child => child.eq(val)))
|
67 | }).toThrowError();
|
68 | expect(() => {
|
69 | root.map((item, val, context) => item.map(child => child.eq(context)), root.get(1))
|
70 | }).toThrowError();
|
71 | })
|
72 | it('expect to hoist shared expressions', () => {
|
73 | const once = root.map(val => val.call('tap'));
|
74 | const twice = root.map(val => val.call('tap')).filter(val => val);
|
75 | const model = {once, twice, set: setter(arg0)};
|
76 | const optCode = evalOrLoad(compile(model, {compiler}));
|
77 | const inst = optCode([false, 1, 0], funcLibrary);
|
78 | expect(inst.once).toEqual([false, 1, 0]);
|
79 | expect(inst.twice).toEqual([1]);
|
80 | expectTapFunctionToHaveBeenCalled(inst.$model.length, compiler);
|
81 | inst.set(2, true);
|
82 | expect(inst.once).toEqual([false, 1, true]);
|
83 | expect(inst.twice).toEqual([1, true]);
|
84 | expectTapFunctionToHaveBeenCalled(1, compiler);
|
85 | })
|
86 | it('passing item between functions should throw nicer error message', () => {
|
87 | expect(() => root.mapValues(item =>
|
88 | root.filterBy(innerItem => innerItem.eq(item))
|
89 | )).toThrow(/eq(.|\n)+filterBy/gm)
|
90 | })
|
91 |
|
92 | it('when using non-numbers with number functions, throw a nicer error', () => {
|
93 | const model = {three: chain({a: 1}).ceil()}
|
94 | const optCode = evalOrLoad(compile(model, {compiler, debug: true}));
|
95 | expect(() => optCode([], funcLibrary)).toThrow('}.ceil')
|
96 | })
|
97 |
|
98 | it('throw more readable error when trying to chain an object with underfined', () => {
|
99 | expect(() => chain({a: {b: [1, undefined]}})).toThrow('a.b[1]')
|
100 | })
|
101 |
|
102 | it('when calling a non-existent function, throw a readable error', () => {
|
103 | const model = {three: chain({a: 1}).call('nonExistentFunction')}
|
104 | const optCode = evalOrLoad(compile(model, {compiler, debug: true}));
|
105 |
|
106 | expect(() => optCode([], funcLibrary)).toThrow('nonExistentFunction')
|
107 | })
|
108 |
|
109 | it('when calling a function with undefined args, throw a readable error', () => {
|
110 | const model = {three: chain({a: () => 123}).call('func')}
|
111 | expect(() => compile(model, {compiler, debug: true})).toThrow('() => 123')
|
112 | })
|
113 |
|
114 | it('allow primitives on the model', () => {
|
115 | const model = {three: chain(3)}
|
116 | const optCode = evalOrLoad(compile(model, {compiler}));
|
117 | const inst = optCode([], funcLibrary);
|
118 | expect(inst.three).toEqual(3);
|
119 | })
|
120 |
|
121 | it('should include relative paths in code', () => {
|
122 | const model = {three: chain(3).mapValues('func').call('func')}
|
123 | const src = compile(model, {compiler, debug: true});
|
124 | expect(src).not.toContain(__dirname)
|
125 | })
|
126 | });
|
127 |
|
128 | describeCompilers(['optimizing'], compiler => {
|
129 | it('when using non-objects with object functions, throw a nicer error', () => {
|
130 | const model = {three: chain(3).mapValues(a => a)}
|
131 | const src = compile(model, {compiler, debug: true, cwd: path.resolve(__dirname, '../..')});
|
132 | const optCode = evalOrLoad(src)
|
133 | expect(() => optCode([], funcLibrary)).toThrow('3.mapValues')
|
134 | })
|
135 |
|
136 | it('when using arrays with object functions, throw an error', () => {
|
137 | const model = {bad: root.get('data').mapValues(a => a)}
|
138 | const src = compile(model, {compiler, debug: true});
|
139 | const optCode = evalOrLoad(src)
|
140 |
|
141 | expect(() => optCode({data: [0]}, funcLibrary)).toThrow('[0].mapValues')
|
142 | })
|
143 |
|
144 | it('values should only work woth object', () => {
|
145 | const model = {
|
146 | original: root.get('list'),
|
147 | valued: root.get('list').values()
|
148 | }
|
149 |
|
150 | const src = compile(model, {compiler, debug: true})
|
151 | const optModel = evalOrLoad(src)
|
152 | const initialData = {list: [1, 2, 3, 4]}
|
153 |
|
154 | expect(() => optModel(initialData)).toThrow('values expects object. valued at')
|
155 | })
|
156 |
|
157 | it('when using objects with array functions, throw an error', () => {
|
158 | const model = {bad: root.get('data').filter(a => a)}
|
159 | const src = compile(model, {compiler, debug: true});
|
160 | const optCode = evalOrLoad(src)
|
161 |
|
162 | expect(() => optCode({data: {a: 0}}, funcLibrary)).toThrow('0}.filter')
|
163 | })
|
164 | })
|
165 | });
|