@joist/templating 4.2.4-next.3 → 4.2.4-next.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.
- package/README.md +43 -164
- package/package.json +1 -1
- package/src/lib/bind.test.ts +72 -0
- package/src/lib/bind.ts +7 -4
- package/src/lib/elements/async.element.ts +2 -2
- package/src/lib/elements/bind.element.test.ts +2 -2
- package/src/lib/elements/bind.element.ts +4 -4
- package/src/lib/elements/for.element.ts +16 -9
- package/src/lib/elements/if.element.test.ts +266 -56
- package/src/lib/elements/if.element.ts +5 -5
- package/src/lib/elements/scope.ts +1 -1
- package/src/lib/elements/value.element.ts +8 -4
- package/src/lib/events.ts +5 -4
- package/src/lib/expression.test.ts +204 -0
- package/src/lib/expression.ts +179 -0
- package/target/lib/bind.js +6 -4
- package/target/lib/bind.js.map +1 -1
- package/target/lib/bind.test.js +76 -0
- package/target/lib/bind.test.js.map +1 -0
- package/target/lib/elements/async.element.js +2 -2
- package/target/lib/elements/async.element.js.map +1 -1
- package/target/lib/elements/bind.element.d.ts +2 -2
- package/target/lib/elements/bind.element.js +3 -3
- package/target/lib/elements/bind.element.js.map +1 -1
- package/target/lib/elements/bind.element.test.js +2 -2
- package/target/lib/elements/bind.element.test.js.map +1 -1
- package/target/lib/elements/for.element.d.ts +1 -1
- package/target/lib/elements/for.element.js +13 -7
- package/target/lib/elements/for.element.js.map +1 -1
- package/target/lib/elements/if.element.js +5 -5
- package/target/lib/elements/if.element.js.map +1 -1
- package/target/lib/elements/if.element.test.js +12 -56
- package/target/lib/elements/if.element.test.js.map +1 -1
- package/target/lib/elements/scope.js +1 -1
- package/target/lib/elements/scope.js.map +1 -1
- package/target/lib/elements/value.element.js +6 -4
- package/target/lib/elements/value.element.js.map +1 -1
- package/target/lib/events.d.ts +4 -3
- package/target/lib/events.js +3 -3
- package/target/lib/events.js.map +1 -1
- package/target/lib/expression.d.ts +13 -0
- package/target/lib/expression.js +87 -0
- package/target/lib/expression.js.map +1 -0
- package/target/lib/expression.test.d.ts +1 -0
- package/target/lib/expression.test.js +171 -0
- package/target/lib/expression.test.js.map +1 -0
- package/src/lib/token.test.ts +0 -68
- package/src/lib/token.ts +0 -36
- package/target/lib/token.d.ts +0 -8
- package/target/lib/token.js +0 -29
- package/target/lib/token.js.map +0 -1
- package/target/lib/token.test.js +0 -56
- package/target/lib/token.test.js.map +0 -1
- /package/target/lib/{token.test.d.ts → bind.test.d.ts} +0 -0
|
@@ -5,86 +5,296 @@ import { assert } from "chai";
|
|
|
5
5
|
|
|
6
6
|
import type { JoistValueEvent } from "../events.js";
|
|
7
7
|
|
|
8
|
-
it("should render content when the bind value is truthy", () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
// it("should render content when the bind value is truthy", () => {
|
|
9
|
+
// const element = fixtureSync(html`
|
|
10
|
+
// <div
|
|
11
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
12
|
+
// e.update({ oldValue: null, newValue: true });
|
|
13
|
+
// }}
|
|
14
|
+
// >
|
|
15
|
+
// <j-if bind="test">
|
|
16
|
+
// <template>Visible Content</template>
|
|
17
|
+
// </j-if>
|
|
18
|
+
// </div>
|
|
19
|
+
// `);
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
});
|
|
21
|
+
// assert.equal(element.textContent?.trim(), "Visible Content");
|
|
22
|
+
// });
|
|
23
23
|
|
|
24
|
-
it("should not render content when the bind value is falsy", () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
// it("should not render content when the bind value is falsy", () => {
|
|
25
|
+
// const element = fixtureSync(html`
|
|
26
|
+
// <div
|
|
27
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
28
|
+
// e.update({ oldValue: null, newValue: true });
|
|
29
|
+
// e.update({ oldValue: null, newValue: false });
|
|
30
|
+
// }}
|
|
31
|
+
// >
|
|
32
|
+
// <j-if bind="test">
|
|
33
|
+
// <template>Visible Content</template>
|
|
34
|
+
// </j-if>
|
|
35
|
+
// </div>
|
|
36
|
+
// `);
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
});
|
|
38
|
+
// assert.equal(element.textContent?.trim(), "");
|
|
39
|
+
// });
|
|
40
40
|
|
|
41
|
-
it("should handle negated tokens correctly", () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
// it("should handle negated tokens correctly", () => {
|
|
42
|
+
// const element = fixtureSync(html`
|
|
43
|
+
// <div
|
|
44
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
45
|
+
// e.update({ oldValue: null, newValue: false });
|
|
46
|
+
// }}
|
|
47
|
+
// >
|
|
48
|
+
// <j-if bind="!test">
|
|
49
|
+
// <template>Visible Content</template>
|
|
50
|
+
// </j-if>
|
|
51
|
+
// </div>
|
|
52
|
+
// `);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
});
|
|
54
|
+
// assert.equal(element.textContent?.trim(), "Visible Content");
|
|
55
|
+
// });
|
|
56
|
+
|
|
57
|
+
// it("should render else template when condition is falsy", () => {
|
|
58
|
+
// const element = fixtureSync(html`
|
|
59
|
+
// <div
|
|
60
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
61
|
+
// e.update({ oldValue: null, newValue: false });
|
|
62
|
+
// }}
|
|
63
|
+
// >
|
|
64
|
+
// <j-if bind="test">
|
|
65
|
+
// <template>If Content</template>
|
|
66
|
+
// <template else>Else Content</template>
|
|
67
|
+
// </j-if>
|
|
68
|
+
// </div>
|
|
69
|
+
// `);
|
|
70
|
+
|
|
71
|
+
// assert.equal(element.textContent?.trim(), "Else Content");
|
|
72
|
+
// });
|
|
73
|
+
|
|
74
|
+
// it("should switch between if and else templates", () => {
|
|
75
|
+
// const element = fixtureSync(html`
|
|
76
|
+
// <div
|
|
77
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
78
|
+
// e.update({ oldValue: null, newValue: false });
|
|
79
|
+
// e.update({ oldValue: false, newValue: true });
|
|
80
|
+
// }}
|
|
81
|
+
// >
|
|
82
|
+
// <j-if bind="test">
|
|
83
|
+
// <template>If Content</template>
|
|
84
|
+
// <template else>Else Content</template>
|
|
85
|
+
// </j-if>
|
|
86
|
+
// </div>
|
|
87
|
+
// `);
|
|
88
|
+
|
|
89
|
+
// assert.equal(element.textContent?.trim(), "If Content");
|
|
90
|
+
// });
|
|
91
|
+
|
|
92
|
+
// it("should handle equality comparison", () => {
|
|
93
|
+
// const element = fixtureSync(html`
|
|
94
|
+
// <div
|
|
95
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
96
|
+
// e.update({ oldValue: null, newValue: { status: "active" } });
|
|
97
|
+
// }}
|
|
98
|
+
// >
|
|
99
|
+
// <j-if bind="example.status == active">
|
|
100
|
+
// <template>Status is Active</template>
|
|
101
|
+
// </j-if>
|
|
102
|
+
// </div>
|
|
103
|
+
// `);
|
|
104
|
+
|
|
105
|
+
// assert.equal(element.textContent?.trim(), "Status is Active");
|
|
106
|
+
// });
|
|
107
|
+
|
|
108
|
+
// it("should handle greater than comparison", () => {
|
|
109
|
+
// const element = fixtureSync(html`
|
|
110
|
+
// <div
|
|
111
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
112
|
+
// e.update({ oldValue: null, newValue: { count: 10 } });
|
|
113
|
+
// }}
|
|
114
|
+
// >
|
|
115
|
+
// <j-if bind="example.count > 5">
|
|
116
|
+
// <template>Count is Greater Than 5</template>
|
|
117
|
+
// </j-if>
|
|
118
|
+
// </div>
|
|
119
|
+
// `);
|
|
120
|
+
|
|
121
|
+
// assert.equal(element.textContent?.trim(), "Count is Greater Than 5");
|
|
122
|
+
// });
|
|
123
|
+
|
|
124
|
+
// it("should handle less than comparison", () => {
|
|
125
|
+
// const element = fixtureSync(html`
|
|
126
|
+
// <div
|
|
127
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
128
|
+
// e.update({ oldValue: null, newValue: { score: 75 } });
|
|
129
|
+
// }}
|
|
130
|
+
// >
|
|
131
|
+
// <j-if bind="example.score < 100">
|
|
132
|
+
// <template>Score is Less Than 100</template>
|
|
133
|
+
// </j-if>
|
|
134
|
+
// </div>
|
|
135
|
+
// `);
|
|
136
|
+
|
|
137
|
+
// assert.equal(element.textContent?.trim(), "Score is Less Than 100");
|
|
138
|
+
// });
|
|
139
|
+
|
|
140
|
+
// it("should handle nested path comparisons", () => {
|
|
141
|
+
// const element = fixtureSync(html`
|
|
142
|
+
// <div
|
|
143
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
144
|
+
// e.update({ oldValue: null, newValue: { user: { score: 150 } } });
|
|
145
|
+
// }}
|
|
146
|
+
// >
|
|
147
|
+
// <j-if bind="example.user.score > 100">
|
|
148
|
+
// <template>User Score is Above 100</template>
|
|
149
|
+
// </j-if>
|
|
150
|
+
// </div>
|
|
151
|
+
// `);
|
|
152
|
+
|
|
153
|
+
// assert.equal(element.textContent?.trim(), "User Score is Above 100");
|
|
154
|
+
// });
|
|
155
|
+
|
|
156
|
+
// it("should handle negated comparisons", () => {
|
|
157
|
+
// const element = fixtureSync(html`
|
|
158
|
+
// <div
|
|
159
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
160
|
+
// e.update({ oldValue: null, newValue: { status: "inactive" } });
|
|
161
|
+
// }}
|
|
162
|
+
// >
|
|
163
|
+
// <j-if bind="!example.status == active">
|
|
164
|
+
// <template>Status is Not Active</template>
|
|
165
|
+
// </j-if>
|
|
166
|
+
// </div>
|
|
167
|
+
// `);
|
|
168
|
+
|
|
169
|
+
// assert.equal(element.textContent?.trim(), "Status is Not Active");
|
|
170
|
+
// });
|
|
171
|
+
|
|
172
|
+
// it("should handle string number comparisons", () => {
|
|
173
|
+
// const element = fixtureSync(html`
|
|
174
|
+
// <div
|
|
175
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
176
|
+
// e.update({ oldValue: null, newValue: { count: "10" } });
|
|
177
|
+
// }}
|
|
178
|
+
// >
|
|
179
|
+
// <j-if bind="example.count > 5">
|
|
180
|
+
// <template>String Count is Greater Than 5</template>
|
|
181
|
+
// </j-if>
|
|
182
|
+
// </div>
|
|
183
|
+
// `);
|
|
184
|
+
|
|
185
|
+
// assert.equal(element.textContent?.trim(), "String Count is Greater Than 5");
|
|
186
|
+
// });
|
|
187
|
+
|
|
188
|
+
// it("should handle undefined values in comparisons", () => {
|
|
189
|
+
// const element = fixtureSync(html`
|
|
190
|
+
// <div
|
|
191
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
192
|
+
// e.update({ oldValue: null, newValue: { count: undefined } });
|
|
193
|
+
// }}
|
|
194
|
+
// >
|
|
195
|
+
// <j-if bind="example.count > 5">
|
|
196
|
+
// <template>Count is Greater Than 5</template>
|
|
197
|
+
// </j-if>
|
|
198
|
+
// </div>
|
|
199
|
+
// `);
|
|
200
|
+
|
|
201
|
+
// assert.equal(element.textContent?.trim(), "");
|
|
202
|
+
// });
|
|
203
|
+
|
|
204
|
+
// it("should handle not equal comparison", () => {
|
|
205
|
+
// const element = fixtureSync(html`
|
|
206
|
+
// <div
|
|
207
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
208
|
+
// e.update({ oldValue: null, newValue: { status: "inactive" } });
|
|
209
|
+
// }}
|
|
210
|
+
// >
|
|
211
|
+
// <j-if bind="example.status != active">
|
|
212
|
+
// <template>Status is Not Active</template>
|
|
213
|
+
// </j-if>
|
|
214
|
+
// </div>
|
|
215
|
+
// `);
|
|
216
|
+
|
|
217
|
+
// assert.equal(element.textContent?.trim(), "Status is Not Active");
|
|
218
|
+
// });
|
|
219
|
+
|
|
220
|
+
// it("should handle not equal comparison with matching value", () => {
|
|
221
|
+
// const element = fixtureSync(html`
|
|
222
|
+
// <div
|
|
223
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
224
|
+
// e.update({ oldValue: null, newValue: { status: "active" } });
|
|
225
|
+
// }}
|
|
226
|
+
// >
|
|
227
|
+
// <j-if bind="example.status != active">
|
|
228
|
+
// <template>Status is Not Active</template>
|
|
229
|
+
// </j-if>
|
|
230
|
+
// </div>
|
|
231
|
+
// `);
|
|
232
|
+
|
|
233
|
+
// assert.equal(element.textContent?.trim(), "");
|
|
234
|
+
// });
|
|
235
|
+
|
|
236
|
+
// it("should handle not equal comparison with string numbers", () => {
|
|
237
|
+
// const element = fixtureSync(html`
|
|
238
|
+
// <div
|
|
239
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
240
|
+
// e.update({ oldValue: null, newValue: { count: "10" } });
|
|
241
|
+
// }}
|
|
242
|
+
// >
|
|
243
|
+
// <j-if bind="example.count != 5">
|
|
244
|
+
// <template>Count is Not 5</template>
|
|
245
|
+
// </j-if>
|
|
246
|
+
// </div>
|
|
247
|
+
// `);
|
|
248
|
+
|
|
249
|
+
// assert.equal(element.textContent?.trim(), "Count is Not 5");
|
|
250
|
+
// });
|
|
251
|
+
|
|
252
|
+
// it("should handle not equal comparison with undefined", () => {
|
|
253
|
+
// const element = fixtureSync(html`
|
|
254
|
+
// <div
|
|
255
|
+
// @joist::value=${(e: JoistValueEvent) => {
|
|
256
|
+
// e.update({ oldValue: null, newValue: { status: undefined } });
|
|
257
|
+
// }}
|
|
258
|
+
// >
|
|
259
|
+
// <j-if bind="example.status != active">
|
|
260
|
+
// <template>Status is Not Active</template>
|
|
261
|
+
// </j-if>
|
|
262
|
+
// </div>
|
|
263
|
+
// `);
|
|
264
|
+
|
|
265
|
+
// assert.equal(element.textContent?.trim(), "Status is Not Active");
|
|
266
|
+
// });
|
|
56
267
|
|
|
57
|
-
it("should
|
|
268
|
+
it("should handle array length", () => {
|
|
58
269
|
const element = fixtureSync(html`
|
|
59
270
|
<div
|
|
60
271
|
@joist::value=${(e: JoistValueEvent) => {
|
|
61
|
-
e.update({ oldValue: null, newValue:
|
|
272
|
+
e.update({ oldValue: null, newValue: [0, 1, 2] });
|
|
62
273
|
}}
|
|
63
274
|
>
|
|
64
|
-
<j-if bind="
|
|
65
|
-
<template>
|
|
66
|
-
<template else>
|
|
275
|
+
<j-if bind="example.length">
|
|
276
|
+
<template>Array has length</template>
|
|
277
|
+
<template else>Array has no length</template>
|
|
67
278
|
</j-if>
|
|
68
279
|
</div>
|
|
69
280
|
`);
|
|
70
281
|
|
|
71
|
-
assert.equal(element.textContent?.trim(), "
|
|
282
|
+
assert.equal(element.textContent?.trim(), "Array has length");
|
|
72
283
|
});
|
|
73
284
|
|
|
74
|
-
it("should
|
|
285
|
+
it("should handle a first change even if the value is the same", () => {
|
|
75
286
|
const element = fixtureSync(html`
|
|
76
287
|
<div
|
|
77
288
|
@joist::value=${(e: JoistValueEvent) => {
|
|
78
|
-
e.update({ oldValue: null, newValue:
|
|
79
|
-
e.update({ oldValue: false, newValue: true });
|
|
289
|
+
e.update({ oldValue: null, newValue: null, firstChange: true });
|
|
80
290
|
}}
|
|
81
291
|
>
|
|
82
|
-
<j-if bind="
|
|
83
|
-
<template>
|
|
84
|
-
<template else>
|
|
292
|
+
<j-if bind="example.length">
|
|
293
|
+
<template>Array has length</template>
|
|
294
|
+
<template else>Array has no length</template>
|
|
85
295
|
</j-if>
|
|
86
296
|
</div>
|
|
87
297
|
`);
|
|
88
298
|
|
|
89
|
-
assert.equal(element.textContent?.trim(), "
|
|
299
|
+
assert.equal(element.textContent?.trim(), "Array has no length");
|
|
90
300
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { attr, element, queryAll, css, html } from "@joist/element";
|
|
2
2
|
|
|
3
3
|
import { JoistValueEvent } from "../events.js";
|
|
4
|
-
import {
|
|
4
|
+
import { JExpression } from "../expression.js";
|
|
5
5
|
|
|
6
6
|
declare global {
|
|
7
7
|
interface HTMLElementTagNameMap {
|
|
@@ -43,12 +43,12 @@ export class JoistIfElement extends HTMLElement {
|
|
|
43
43
|
// make sure there are no other nodes after the template
|
|
44
44
|
this.#clean();
|
|
45
45
|
|
|
46
|
-
const token = new
|
|
46
|
+
const token = new JExpression(this.bind);
|
|
47
47
|
|
|
48
48
|
this.dispatchEvent(
|
|
49
|
-
new JoistValueEvent(token, ({ newValue, oldValue }) => {
|
|
50
|
-
if (newValue !== oldValue) {
|
|
51
|
-
this.apply(token.
|
|
49
|
+
new JoistValueEvent(token, ({ newValue, oldValue, firstChange }) => {
|
|
50
|
+
if (firstChange || newValue !== oldValue) {
|
|
51
|
+
this.apply(token.readBoundValueFrom(newValue), token.isNegated);
|
|
52
52
|
}
|
|
53
53
|
}),
|
|
54
54
|
);
|
|
@@ -24,7 +24,7 @@ export class JoistScopeElement extends HTMLElement {
|
|
|
24
24
|
|
|
25
25
|
@listen("joist::value")
|
|
26
26
|
onJoistValueFound(e: JoistValueEvent): void {
|
|
27
|
-
if (e.
|
|
27
|
+
if (e.expression.bindTo === this.name) {
|
|
28
28
|
e.stopPropagation();
|
|
29
29
|
|
|
30
30
|
this.#binding = e;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { attr, element, css, html } from "@joist/element";
|
|
2
2
|
import { JoistValueEvent } from "../events.js";
|
|
3
|
-
import {
|
|
3
|
+
import { JExpression } from "../expression.js";
|
|
4
4
|
|
|
5
5
|
declare global {
|
|
6
6
|
interface HTMLElementTagNameMap {
|
|
@@ -18,13 +18,17 @@ export class JoistValueElement extends HTMLElement {
|
|
|
18
18
|
accessor bind = "";
|
|
19
19
|
|
|
20
20
|
connectedCallback(): void {
|
|
21
|
-
const token = new
|
|
21
|
+
const token = new JExpression(this.bind);
|
|
22
22
|
|
|
23
23
|
this.dispatchEvent(
|
|
24
24
|
new JoistValueEvent(token, (value) => {
|
|
25
|
-
const valueToWrite = String(token.
|
|
25
|
+
const valueToWrite = String(token.readBoundValueFrom(value.newValue));
|
|
26
26
|
|
|
27
|
-
if (
|
|
27
|
+
if (
|
|
28
|
+
valueToWrite !== null &&
|
|
29
|
+
valueToWrite !== undefined &&
|
|
30
|
+
this.textContent !== valueToWrite
|
|
31
|
+
) {
|
|
28
32
|
this.textContent = valueToWrite;
|
|
29
33
|
}
|
|
30
34
|
}),
|
package/src/lib/events.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Change } from "@joist/observable";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { JExpression } from "./expression.js";
|
|
4
4
|
|
|
5
5
|
declare global {
|
|
6
6
|
interface HTMLElementEventMap {
|
|
@@ -10,16 +10,17 @@ declare global {
|
|
|
10
10
|
|
|
11
11
|
export interface BindChange<T> extends Change<T> {
|
|
12
12
|
alwaysUpdate?: boolean;
|
|
13
|
+
firstChange?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export class JoistValueEvent extends Event {
|
|
16
|
-
readonly
|
|
17
|
+
readonly expression: JExpression;
|
|
17
18
|
readonly update: (value: BindChange<unknown>) => void;
|
|
18
19
|
|
|
19
|
-
constructor(
|
|
20
|
+
constructor(expression: JExpression, update: (value: BindChange<unknown>) => void) {
|
|
20
21
|
super("joist::value", { bubbles: true, composed: true });
|
|
21
22
|
|
|
22
|
-
this.
|
|
23
|
+
this.expression = expression;
|
|
23
24
|
this.update = update;
|
|
24
25
|
}
|
|
25
26
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
|
|
3
|
+
import { JExpression } from "./expression.js";
|
|
4
|
+
|
|
5
|
+
describe("JExpression", () => {
|
|
6
|
+
describe("constructor", () => {
|
|
7
|
+
it("should initialize with a raw token", () => {
|
|
8
|
+
const token = new JExpression("example.token");
|
|
9
|
+
assert.equal(token.rawToken, "example.token");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should set isNegated to true if the token starts with '!'", () => {
|
|
13
|
+
const token = new JExpression("!example.token");
|
|
14
|
+
assert.isTrue(token.isNegated);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should set isNegated to false if the token does not start with '!'", () => {
|
|
18
|
+
const token = new JExpression("example.token");
|
|
19
|
+
assert.isFalse(token.isNegated);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should correctly parse the bindTo property", () => {
|
|
23
|
+
const token = new JExpression("example.token");
|
|
24
|
+
assert.equal(token.bindTo, "example");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should correctly parse the path property", () => {
|
|
28
|
+
const token = new JExpression("example.token.part");
|
|
29
|
+
assert.deepEqual(token.path, ["token", "part"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should remove '!' from bindTo if present", () => {
|
|
33
|
+
const token = new JExpression("!example.token");
|
|
34
|
+
assert.equal(token.bindTo, "example");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should parse equals operator value", () => {
|
|
38
|
+
const token = new JExpression("example==value");
|
|
39
|
+
assert.equal(token.equalsValue, "value");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should handle equals operator with negation", () => {
|
|
43
|
+
const token = new JExpression("!example == value");
|
|
44
|
+
assert.equal(token.equalsValue, "value");
|
|
45
|
+
assert.isTrue(token.isNegated);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle equals operator with nested paths", () => {
|
|
49
|
+
const token = new JExpression("example.nested == value");
|
|
50
|
+
assert.equal(token.equalsValue, "value");
|
|
51
|
+
assert.deepEqual(token.path, ["nested"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should parse greater than operator value", () => {
|
|
55
|
+
const token = new JExpression("example > 5");
|
|
56
|
+
assert.equal(token.gtValue, "5");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should parse less than operator value", () => {
|
|
60
|
+
const token = new JExpression("example < 10");
|
|
61
|
+
assert.equal(token.ltValue, "10");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should handle greater than operator with negation", () => {
|
|
65
|
+
const token = new JExpression("!example > 5");
|
|
66
|
+
assert.equal(token.gtValue, "5");
|
|
67
|
+
assert.isTrue(token.isNegated);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should handle less than operator with nested paths", () => {
|
|
71
|
+
const token = new JExpression("example.count < 10");
|
|
72
|
+
assert.equal(token.ltValue, "10");
|
|
73
|
+
assert.deepEqual(token.path, ["count"]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("readTokenValueFrom", () => {
|
|
78
|
+
it("should read the value from a nested object", () => {
|
|
79
|
+
const token = new JExpression("example.token.part");
|
|
80
|
+
const obj = { token: { part: 42 } };
|
|
81
|
+
const value = token.readBoundValueFrom<number>(obj);
|
|
82
|
+
assert.equal(value, 42);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should return undefined if the path does not exist", () => {
|
|
86
|
+
const token = new JExpression("example.nonexistent.path");
|
|
87
|
+
const obj = { token: { part: 42 } };
|
|
88
|
+
const value = token.readBoundValueFrom(obj);
|
|
89
|
+
assert.isUndefined(value);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should handle empty paths gracefully", () => {
|
|
93
|
+
const token = new JExpression("example");
|
|
94
|
+
const obj = { foo: 42 };
|
|
95
|
+
const value = token.readBoundValueFrom(obj);
|
|
96
|
+
|
|
97
|
+
assert.deepEqual(value, { foo: 42 });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should parse values from strings", () => {
|
|
101
|
+
const token = new JExpression("example.length");
|
|
102
|
+
const value = token.readBoundValueFrom("42");
|
|
103
|
+
|
|
104
|
+
assert.equal(value, 2);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should return true when equals against primative", () => {
|
|
108
|
+
const token = new JExpression("example == active");
|
|
109
|
+
const value = token.readBoundValueFrom<boolean>("active");
|
|
110
|
+
assert.isTrue(value);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should return true when equals comparison matches", () => {
|
|
114
|
+
const token = new JExpression("example.status==active");
|
|
115
|
+
const obj = { status: "active" };
|
|
116
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
117
|
+
assert.isTrue(value);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return false when equals comparison does not match", () => {
|
|
121
|
+
const token = new JExpression("example.status==active");
|
|
122
|
+
const obj = { status: "inactive" };
|
|
123
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
124
|
+
assert.isFalse(value);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should handle equals comparison with numbers", () => {
|
|
128
|
+
const token = new JExpression("example.count == 5");
|
|
129
|
+
const obj = { count: 5 };
|
|
130
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
131
|
+
assert.isTrue(value);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should handle equals comparison with nested paths", () => {
|
|
135
|
+
const token = new JExpression("example.user.status == active");
|
|
136
|
+
const obj = { user: { status: "active" } };
|
|
137
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
138
|
+
assert.isTrue(value);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should handle equals comparison with undefined values", () => {
|
|
142
|
+
const token = new JExpression("example.status == active");
|
|
143
|
+
const obj = { status: undefined };
|
|
144
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
145
|
+
assert.isFalse(value);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should return true when greater than comparison matches", () => {
|
|
149
|
+
const token = new JExpression("example.count > 5");
|
|
150
|
+
const obj = { count: 10 };
|
|
151
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
152
|
+
assert.isTrue(value);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should return false when greater than comparison does not match", () => {
|
|
156
|
+
const token = new JExpression("example.count > 5");
|
|
157
|
+
const obj = { count: 3 };
|
|
158
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
159
|
+
assert.isFalse(value);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should return true when less than comparison matches", () => {
|
|
163
|
+
const token = new JExpression("example.count < 10");
|
|
164
|
+
const obj = { count: 5 };
|
|
165
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
166
|
+
assert.isTrue(value);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should return false when less than comparison does not match", () => {
|
|
170
|
+
const token = new JExpression("example.count < 10");
|
|
171
|
+
const obj = { count: 15 };
|
|
172
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
173
|
+
assert.isFalse(value);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should handle greater than comparison with string numbers", () => {
|
|
177
|
+
const token = new JExpression("example.count > 5");
|
|
178
|
+
const obj = { count: "10" };
|
|
179
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
180
|
+
assert.isTrue(value);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle less than comparison with string numbers", () => {
|
|
184
|
+
const token = new JExpression("example.count < 10");
|
|
185
|
+
const obj = { count: "5" };
|
|
186
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
187
|
+
assert.isTrue(value);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should handle greater than comparison with undefined values", () => {
|
|
191
|
+
const token = new JExpression("example.count > 5");
|
|
192
|
+
const obj = { count: undefined };
|
|
193
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
194
|
+
assert.isFalse(value);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should handle less than comparison with undefined values", () => {
|
|
198
|
+
const token = new JExpression("example.count < 10");
|
|
199
|
+
const obj = { count: undefined };
|
|
200
|
+
const value = token.readBoundValueFrom<boolean>(obj);
|
|
201
|
+
assert.isFalse(value);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|