@valbuild/core 0.12.0 → 0.13.1
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 +25 -37
- package/jest.config.js +4 -0
- package/package.json +1 -1
- package/src/Json.ts +4 -0
- package/src/expr/README.md +193 -0
- package/src/expr/eval.test.ts +202 -0
- package/src/expr/eval.ts +248 -0
- package/src/expr/expr.ts +91 -0
- package/src/expr/index.ts +3 -0
- package/src/expr/parser.test.ts +158 -0
- package/src/expr/parser.ts +229 -0
- package/src/expr/repl.ts +93 -0
- package/src/expr/tokenizer.test.ts +539 -0
- package/src/expr/tokenizer.ts +117 -0
- package/src/fetchVal.test.ts +164 -0
- package/src/fetchVal.ts +211 -0
- package/src/fp/array.ts +30 -0
- package/src/fp/index.ts +3 -0
- package/src/fp/result.ts +214 -0
- package/src/fp/util.ts +52 -0
- package/src/index.ts +55 -0
- package/src/initSchema.ts +45 -0
- package/src/initVal.ts +96 -0
- package/src/module.test.ts +170 -0
- package/src/module.ts +333 -0
- package/src/patch/deref.test.ts +300 -0
- package/src/patch/deref.ts +128 -0
- package/src/patch/index.ts +11 -0
- package/src/patch/json.test.ts +583 -0
- package/src/patch/json.ts +304 -0
- package/src/patch/operation.ts +74 -0
- package/src/patch/ops.ts +83 -0
- package/src/patch/parse.test.ts +202 -0
- package/src/patch/parse.ts +187 -0
- package/src/patch/patch.ts +46 -0
- package/src/patch/util.ts +67 -0
- package/src/schema/array.ts +52 -0
- package/src/schema/boolean.ts +38 -0
- package/src/schema/i18n.ts +65 -0
- package/src/schema/image.ts +70 -0
- package/src/schema/index.ts +46 -0
- package/src/schema/literal.ts +42 -0
- package/src/schema/number.ts +45 -0
- package/src/schema/object.ts +67 -0
- package/src/schema/oneOf.ts +60 -0
- package/src/schema/richtext.ts +417 -0
- package/src/schema/string.ts +49 -0
- package/src/schema/union.ts +62 -0
- package/src/selector/ExprProxy.test.ts +203 -0
- package/src/selector/ExprProxy.ts +209 -0
- package/src/selector/SelectorProxy.test.ts +172 -0
- package/src/selector/SelectorProxy.ts +237 -0
- package/src/selector/array.ts +37 -0
- package/src/selector/boolean.ts +4 -0
- package/src/selector/file.ts +14 -0
- package/src/selector/i18n.ts +13 -0
- package/src/selector/index.ts +159 -0
- package/src/selector/number.ts +4 -0
- package/src/selector/object.ts +22 -0
- package/src/selector/primitive.ts +17 -0
- package/src/selector/remote.ts +9 -0
- package/src/selector/selector.test.ts +453 -0
- package/src/selector/selectorOf.ts +7 -0
- package/src/selector/string.ts +4 -0
- package/src/source/file.ts +45 -0
- package/src/source/i18n.ts +60 -0
- package/src/source/index.ts +50 -0
- package/src/source/remote.ts +54 -0
- package/src/val/array.ts +10 -0
- package/src/val/index.ts +90 -0
- package/src/val/object.ts +13 -0
- package/src/val/primitive.ts +8 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { convertLiteralProxy, newExprSelectorProxy } from "./ExprProxy";
|
3
|
+
import * as expr from "../expr/expr";
|
4
|
+
import { GenericSelector, SelectorSource, SourceOrExpr } from ".";
|
5
|
+
import { Source } from "../source";
|
6
|
+
|
7
|
+
const ExprSelectorTestCases: any[] = [
|
8
|
+
{
|
9
|
+
description: "basic module",
|
10
|
+
input: newExprSelectorProxy<string>(root("/app/foo")),
|
11
|
+
expected: "(val '/app/foo')",
|
12
|
+
},
|
13
|
+
{
|
14
|
+
description: "basic prop",
|
15
|
+
input: newExprSelectorProxy<string[]>(root("/app/foo"))[0],
|
16
|
+
expected: "('0' (val '/app/foo'))",
|
17
|
+
},
|
18
|
+
{
|
19
|
+
description: "noop andThen",
|
20
|
+
input: newExprSelectorProxy<string>(root("/app/foo")).andThen((v) => v),
|
21
|
+
expected: "!(andThen (val '/app/foo') @[0,0])",
|
22
|
+
},
|
23
|
+
{
|
24
|
+
description: "noop map",
|
25
|
+
input: newExprSelectorProxy<string[]>(root("/app/foo")).map((v) => v),
|
26
|
+
expected: "!(map (val '/app/foo') @[0,0])",
|
27
|
+
},
|
28
|
+
{
|
29
|
+
description: "eq string",
|
30
|
+
input: newExprSelectorProxy<string>(root("/app/foo")).eq("hei"),
|
31
|
+
expected: "(eq (val '/app/foo') 'hei')",
|
32
|
+
},
|
33
|
+
{
|
34
|
+
description: "eq undefined",
|
35
|
+
input: newExprSelectorProxy<null>(root("/app/foo")).eq(null),
|
36
|
+
expected: "(eq (val '/app/foo') ())",
|
37
|
+
},
|
38
|
+
{
|
39
|
+
description: "eq number",
|
40
|
+
input: newExprSelectorProxy<number>(root("/app/foo")).eq(1),
|
41
|
+
expected: "(eq (val '/app/foo') (json '1'))",
|
42
|
+
},
|
43
|
+
{
|
44
|
+
description: "eq boolean",
|
45
|
+
input: newExprSelectorProxy<boolean>(root("/app/foo")).eq(true),
|
46
|
+
expected: "(eq (val '/app/foo') (json 'true'))",
|
47
|
+
},
|
48
|
+
{
|
49
|
+
description: "filter string",
|
50
|
+
input: newExprSelectorProxy<string[]>(root("/app/foo")).filter((v) =>
|
51
|
+
v.eq("hei")
|
52
|
+
),
|
53
|
+
expected: "!(filter (val '/app/foo') (eq @[0,0] 'hei'))",
|
54
|
+
},
|
55
|
+
{
|
56
|
+
description: "filter number",
|
57
|
+
input: newExprSelectorProxy<number[]>(root("/app/foo")).filter((v) =>
|
58
|
+
v.eq(1)
|
59
|
+
),
|
60
|
+
expected: "!(filter (val '/app/foo') (eq @[0,0] (json '1')))",
|
61
|
+
},
|
62
|
+
{
|
63
|
+
description: "filter optional",
|
64
|
+
input: newExprSelectorProxy<number[]>(root("/app/foo")).filter((v) =>
|
65
|
+
v.eq(null)
|
66
|
+
),
|
67
|
+
expected: "!(filter (val '/app/foo') (eq @[0,0] ()))",
|
68
|
+
},
|
69
|
+
{
|
70
|
+
description: "basic projection",
|
71
|
+
input: newExprSelectorProxy<string[]>(root("/app/foo")).map((v) => ({
|
72
|
+
foo: v,
|
73
|
+
})),
|
74
|
+
expected: "!(map (val '/app/foo') (json '{\"foo\": ${@[0,0]}}'))",
|
75
|
+
},
|
76
|
+
{
|
77
|
+
description: "nested projection",
|
78
|
+
input: newExprSelectorProxy<string[]>(root("/app/foo")).map((v) => ({
|
79
|
+
foo: {
|
80
|
+
bar: v,
|
81
|
+
},
|
82
|
+
})),
|
83
|
+
expected:
|
84
|
+
// TODO: this could be more readable
|
85
|
+
// Example: "!(map (val '/app/foo') (json '{\"foo\": {\"bar\": {${@[0,0]}}'}'))"
|
86
|
+
"!(map (val '/app/foo') (json '{\"foo\": ${'{\"bar\": ${@[0,0]}}'}}'))",
|
87
|
+
},
|
88
|
+
{
|
89
|
+
description: "multi module",
|
90
|
+
input: newExprSelectorProxy<string>(root("/app/foo")).andThen(() =>
|
91
|
+
newExprSelectorProxy<string[]>(root("/app/bar"))
|
92
|
+
),
|
93
|
+
expected: "!(andThen (val '/app/foo') (val '/app/bar'))",
|
94
|
+
},
|
95
|
+
];
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Useful test cases for literal conversion
|
99
|
+
* There somewhat of an overlap between these cases and the selector cases,
|
100
|
+
* we could prune some of these away if we want to
|
101
|
+
**/
|
102
|
+
const LiteralConversionTestCases: {
|
103
|
+
input: SelectorSource;
|
104
|
+
expected: string;
|
105
|
+
description: string;
|
106
|
+
}[] = [
|
107
|
+
{ description: "basic string", input: "foo", expected: "(json '\"foo\"')" },
|
108
|
+
{ description: "basic number", input: 1, expected: "(json '1')" },
|
109
|
+
{ description: "basic boolean", input: true, expected: "(json 'true')" },
|
110
|
+
{ description: "basic array", input: [1], expected: "(json '[1]')" },
|
111
|
+
{ description: "basic null", input: null, expected: "()" },
|
112
|
+
{
|
113
|
+
description: "array with 2 different",
|
114
|
+
input: [1, "foo"],
|
115
|
+
expected: "(json '[1, \"foo\"]')",
|
116
|
+
},
|
117
|
+
{
|
118
|
+
description: "array with undefined",
|
119
|
+
input: [1, null],
|
120
|
+
expected: "(json '[1, ${()}]')",
|
121
|
+
},
|
122
|
+
{
|
123
|
+
description: "basic object",
|
124
|
+
input: { foo: "one", bar: 1 },
|
125
|
+
expected: '(json \'{"foo": "one", "bar": 1}\')',
|
126
|
+
},
|
127
|
+
{
|
128
|
+
description: "nested array",
|
129
|
+
input: [1, [1]],
|
130
|
+
expected: "(json '[1, [1]]')",
|
131
|
+
},
|
132
|
+
{
|
133
|
+
description: "nested object",
|
134
|
+
input: { foo: "one", bar: { zoo: 1 } },
|
135
|
+
expected: '(json \'{"foo": "one", "bar": {"zoo": 1}}\')',
|
136
|
+
},
|
137
|
+
{
|
138
|
+
description: "nested object with array",
|
139
|
+
input: {
|
140
|
+
foo: "one",
|
141
|
+
bar: { zoo: [1, "inner"] },
|
142
|
+
},
|
143
|
+
expected: '(json \'{"foo": "one", "bar": {"zoo": [1, "inner"]}}\')',
|
144
|
+
},
|
145
|
+
{
|
146
|
+
description: "basic interpolation",
|
147
|
+
input: [1, newExprSelectorProxy<string[]>(root("/app/foo"))],
|
148
|
+
expected: "(json '[1, ${(val '/app/foo')}]')",
|
149
|
+
},
|
150
|
+
{
|
151
|
+
description: "advanced interpolation",
|
152
|
+
input: [
|
153
|
+
123,
|
154
|
+
newExprSelectorProxy<string[]>(root("/app/foo")).map((v) =>
|
155
|
+
v.andThen((a) => ({ bar: a.eq("bar") }))
|
156
|
+
),
|
157
|
+
],
|
158
|
+
expected:
|
159
|
+
"(json '[123, ${!(map (val '/app/foo') !(andThen @[0,0] (json '{\"bar\": ${(eq @[1,0] 'bar')}}')))}]')",
|
160
|
+
},
|
161
|
+
{
|
162
|
+
description: "interpolation with multiple selectors",
|
163
|
+
input: [
|
164
|
+
123,
|
165
|
+
newExprSelectorProxy<string[]>(root("/app/foo")).map((v) =>
|
166
|
+
v.andThen((a) => ({
|
167
|
+
bar: a.eq("bar"),
|
168
|
+
more: newExprSelectorProxy<string[]>(root("/app/foo")),
|
169
|
+
}))
|
170
|
+
),
|
171
|
+
],
|
172
|
+
expected:
|
173
|
+
"(json '[123, ${!(map (val '/app/foo') !(andThen @[0,0] (json '{\"bar\": ${(eq @[1,0] 'bar')}, \"more\": ${(val '/app/foo')}}')))}]')",
|
174
|
+
},
|
175
|
+
];
|
176
|
+
|
177
|
+
describe("expr", () => {
|
178
|
+
test.each(ExprSelectorTestCases)(
|
179
|
+
'expr selector ($description): "$expected"',
|
180
|
+
({ input, expected }) => {
|
181
|
+
const valOrExpr = (input as unknown as GenericSelector<Source>)[
|
182
|
+
SourceOrExpr
|
183
|
+
];
|
184
|
+
if (valOrExpr instanceof expr.Expr) {
|
185
|
+
expect(valOrExpr.transpile()).toBe(expected);
|
186
|
+
} else {
|
187
|
+
expect(valOrExpr).toBe(expect.any(expr.Expr));
|
188
|
+
}
|
189
|
+
}
|
190
|
+
);
|
191
|
+
test.each(LiteralConversionTestCases)(
|
192
|
+
'literal conversion ($description): "$expected"',
|
193
|
+
({ input, expected }) =>
|
194
|
+
expect(convertLiteralProxy(input).transpile()).toBe(expected)
|
195
|
+
);
|
196
|
+
});
|
197
|
+
|
198
|
+
function root(sourcePath: string) {
|
199
|
+
return new expr.Call(
|
200
|
+
[new expr.Sym("val"), new expr.StringLiteral(sourcePath)],
|
201
|
+
false
|
202
|
+
);
|
203
|
+
}
|
@@ -0,0 +1,209 @@
|
|
1
|
+
import { Selector, SelectorSource, SourceOrExpr } from ".";
|
2
|
+
import * as expr from "../expr/expr";
|
3
|
+
import { Source, SourcePrimitive } from "../source";
|
4
|
+
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6
|
+
type AnyFun = (...args: any[]) => any;
|
7
|
+
|
8
|
+
export function newExprSelectorProxy<T extends Source>(
|
9
|
+
root: expr.Expr,
|
10
|
+
depth = 0
|
11
|
+
): Selector<T> {
|
12
|
+
return new Proxy(new GenericExprSelector(root, depth), {
|
13
|
+
get: (target, prop) => {
|
14
|
+
if (prop === SourceOrExpr) {
|
15
|
+
return target[SourceOrExpr];
|
16
|
+
}
|
17
|
+
if (!hasOwn(target, prop)) {
|
18
|
+
return newExprSelectorProxy(
|
19
|
+
new expr.Call([new expr.StringLiteral(prop.toString()), root], false),
|
20
|
+
depth
|
21
|
+
);
|
22
|
+
}
|
23
|
+
return Reflect.get(target, prop);
|
24
|
+
},
|
25
|
+
}) as unknown as Selector<T>;
|
26
|
+
}
|
27
|
+
|
28
|
+
class GenericExprSelector {
|
29
|
+
[SourceOrExpr]: expr.Expr;
|
30
|
+
constructor(private readonly root: expr.Expr, private readonly depth = 0) {
|
31
|
+
this[SourceOrExpr] = root;
|
32
|
+
}
|
33
|
+
|
34
|
+
andThen = (f: AnyFun) => {
|
35
|
+
return genericHigherOrderFunction(this.root, "andThen", f, 1, this.depth);
|
36
|
+
};
|
37
|
+
|
38
|
+
map = (f: AnyFun) => {
|
39
|
+
return genericHigherOrderFunction(this.root, "map", f, 2, this.depth);
|
40
|
+
};
|
41
|
+
|
42
|
+
filter = (f: AnyFun) => {
|
43
|
+
return genericHigherOrderFunction(this.root, "filter", f, 1, this.depth);
|
44
|
+
};
|
45
|
+
|
46
|
+
eq = (other: SourcePrimitive) => {
|
47
|
+
return newExprSelectorProxy(
|
48
|
+
new expr.Call(
|
49
|
+
[
|
50
|
+
new expr.Sym("eq"),
|
51
|
+
this.root,
|
52
|
+
typeof other === "string"
|
53
|
+
? new expr.StringLiteral(other)
|
54
|
+
: typeof other === "undefined"
|
55
|
+
? expr.NilSym
|
56
|
+
: convertLiteralProxy(other),
|
57
|
+
],
|
58
|
+
false
|
59
|
+
),
|
60
|
+
this.depth
|
61
|
+
);
|
62
|
+
};
|
63
|
+
}
|
64
|
+
|
65
|
+
function genericHigherOrderFunction(
|
66
|
+
root: expr.Expr,
|
67
|
+
name: string,
|
68
|
+
f: AnyFun,
|
69
|
+
args: number,
|
70
|
+
depth: number
|
71
|
+
) {
|
72
|
+
const argsExprs: Selector<Source>[] = [];
|
73
|
+
for (let i = 0; i < args; i++) {
|
74
|
+
argsExprs.push(
|
75
|
+
newExprSelectorProxy(new expr.Sym(`@[${depth},${i}]`), depth + 1)
|
76
|
+
);
|
77
|
+
}
|
78
|
+
return newExprSelectorProxy(
|
79
|
+
new expr.Call(
|
80
|
+
[new expr.Sym(name), root, convertLiteralProxy(f(...argsExprs))],
|
81
|
+
true
|
82
|
+
),
|
83
|
+
depth
|
84
|
+
);
|
85
|
+
}
|
86
|
+
|
87
|
+
function hasOwn<T extends PropertyKey>(obj: object, prop: T): boolean {
|
88
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
89
|
+
}
|
90
|
+
|
91
|
+
export function convertLiteralProxy(
|
92
|
+
source: expr.Expr | SelectorSource
|
93
|
+
): expr.Expr {
|
94
|
+
const [convertedLiteral] = convertObjectToStringExpr(source, true);
|
95
|
+
return withJsonCall(convertedLiteral, false)[0];
|
96
|
+
}
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Add a json call if the literal must be parsed
|
100
|
+
* Happens at the very least at the top level, but may also happen inside e.g. a string template
|
101
|
+
*
|
102
|
+
*/
|
103
|
+
function withJsonCall(
|
104
|
+
e: expr.Expr,
|
105
|
+
isLiteralScope: boolean
|
106
|
+
): [e: expr.Expr, isJson: boolean] {
|
107
|
+
if (
|
108
|
+
!isLiteralScope &&
|
109
|
+
(e instanceof expr.StringLiteral || e instanceof expr.StringTemplate)
|
110
|
+
) {
|
111
|
+
return [new expr.Call([new expr.Sym("json"), e], false), true];
|
112
|
+
}
|
113
|
+
return [e, false];
|
114
|
+
}
|
115
|
+
|
116
|
+
function convertObjectToStringExpr(
|
117
|
+
source: expr.Expr | SelectorSource,
|
118
|
+
isLiteralScope: boolean
|
119
|
+
): [e: expr.Expr, isJson: boolean] {
|
120
|
+
if (source === null || source === undefined) {
|
121
|
+
return [expr.NilSym, true];
|
122
|
+
} else if (typeof source === "string") {
|
123
|
+
return [new expr.StringLiteral(JSON.stringify(source)), false];
|
124
|
+
} else if (typeof source === "number" || typeof source === "boolean") {
|
125
|
+
return withJsonCall(
|
126
|
+
new expr.StringLiteral(source.toString()),
|
127
|
+
isLiteralScope
|
128
|
+
);
|
129
|
+
} else if (typeof source === "object" && SourceOrExpr in source) {
|
130
|
+
const selector = source;
|
131
|
+
const valOrExpr = selector[SourceOrExpr];
|
132
|
+
if (valOrExpr instanceof expr.Expr) {
|
133
|
+
return [valOrExpr, true];
|
134
|
+
} else {
|
135
|
+
// use Val literal - UNTESTED - may happen if we are referencing local content
|
136
|
+
return convertObjectToStringExpr(valOrExpr, isLiteralScope);
|
137
|
+
}
|
138
|
+
} else if (source instanceof expr.Expr) {
|
139
|
+
return [source, true];
|
140
|
+
} else if (typeof source === "object") {
|
141
|
+
// source is a literal object or array, might have nested selectors
|
142
|
+
const isArray = Array.isArray(source);
|
143
|
+
const entries = isArray
|
144
|
+
? Array.from(source.entries())
|
145
|
+
: Object.entries(source);
|
146
|
+
|
147
|
+
let isStringTemplate = false;
|
148
|
+
const converted = entries.map(([key, v]) => {
|
149
|
+
const [converted, mustInterpolate] =
|
150
|
+
typeof v === "string"
|
151
|
+
? withJsonCall(
|
152
|
+
new expr.StringLiteral(JSON.stringify(v)),
|
153
|
+
isLiteralScope
|
154
|
+
)
|
155
|
+
: convertObjectToStringExpr(v, isLiteralScope);
|
156
|
+
|
157
|
+
isStringTemplate = isStringTemplate || mustInterpolate;
|
158
|
+
return [key, converted] as const;
|
159
|
+
});
|
160
|
+
|
161
|
+
if (!isStringTemplate) {
|
162
|
+
const value = isArray
|
163
|
+
? `[${converted.map(([, v]) => getStringLiteralValue(v)).join(", ")}]`
|
164
|
+
: `{${converted
|
165
|
+
.map(
|
166
|
+
([key, v]) =>
|
167
|
+
`${JSON.stringify(key)}: ${getStringLiteralValue(v)}`
|
168
|
+
)
|
169
|
+
.join(", ")}}`;
|
170
|
+
|
171
|
+
return [new expr.StringLiteral(value), false];
|
172
|
+
}
|
173
|
+
|
174
|
+
return [
|
175
|
+
new expr.StringTemplate([
|
176
|
+
isArray ? new expr.StringLiteral("[") : new expr.StringLiteral("{"),
|
177
|
+
...converted.flatMap(([key, entry], i) => {
|
178
|
+
const convertedEntry =
|
179
|
+
typeof entry === "string"
|
180
|
+
? new expr.StringLiteral(JSON.stringify(entry))
|
181
|
+
: convertObjectToStringExpr(entry, isLiteralScope)[0];
|
182
|
+
const maybeComma =
|
183
|
+
i < converted.length - 1 ? [new expr.StringLiteral(", ")] : [];
|
184
|
+
if (isArray) {
|
185
|
+
return [convertedEntry].concat(maybeComma);
|
186
|
+
} else {
|
187
|
+
return [
|
188
|
+
new expr.StringLiteral(JSON.stringify(key) + ": "),
|
189
|
+
convertedEntry,
|
190
|
+
].concat(maybeComma);
|
191
|
+
}
|
192
|
+
}),
|
193
|
+
isArray ? new expr.StringLiteral("]") : new expr.StringLiteral("}"),
|
194
|
+
]),
|
195
|
+
true,
|
196
|
+
];
|
197
|
+
}
|
198
|
+
throw new Error(
|
199
|
+
`Unsupported type '${typeof source}': ${JSON.stringify(source)}`
|
200
|
+
);
|
201
|
+
}
|
202
|
+
|
203
|
+
function getStringLiteralValue(e: expr.Expr): string {
|
204
|
+
if (e instanceof expr.StringLiteral) {
|
205
|
+
return e.value;
|
206
|
+
} else {
|
207
|
+
throw new Error(`expected a string literal but found: ${e.transpile()}`);
|
208
|
+
}
|
209
|
+
}
|
@@ -0,0 +1,172 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { Selector, GenericSelector, Path } from ".";
|
3
|
+
import { number } from "../schema/number";
|
4
|
+
import { string } from "../schema/string";
|
5
|
+
import { Source } from "../source";
|
6
|
+
import { SourcePath } from "../val";
|
7
|
+
import { newSelectorProxy, selectorToVal } from "./SelectorProxy";
|
8
|
+
|
9
|
+
describe("SelectorProxy", () => {
|
10
|
+
test("string eq andThen", () => {
|
11
|
+
const sourcePath = "/app/text" as SourcePath;
|
12
|
+
const string1 = newSelectorProxy("foo", sourcePath) as Selector<string>;
|
13
|
+
expectValOrExpr(string1.eq("foo")).toStrictEqual({
|
14
|
+
val: true,
|
15
|
+
[Path]: undefined,
|
16
|
+
});
|
17
|
+
expectValOrExpr(string1.eq("foo").andThen(() => string1)).toStrictEqual({
|
18
|
+
val: "foo",
|
19
|
+
[Path]: sourcePath,
|
20
|
+
});
|
21
|
+
});
|
22
|
+
|
23
|
+
test("array filter eq", () => {
|
24
|
+
const sourcePath = "/app/texts" as SourcePath;
|
25
|
+
const string1 = newSelectorProxy(["foo", "bar"], sourcePath) as Selector<
|
26
|
+
string[]
|
27
|
+
>;
|
28
|
+
expectValOrExpr(
|
29
|
+
string1.map((a) => a).filter((a) => a.eq("foo"))
|
30
|
+
).toStrictEqual({
|
31
|
+
val: ["foo"],
|
32
|
+
[Path]: "/app/texts",
|
33
|
+
});
|
34
|
+
});
|
35
|
+
|
36
|
+
test("array numbers", () => {
|
37
|
+
const sourcePath = "/app/numbers" as SourcePath;
|
38
|
+
const numbersVal = newSelectorProxy([1, 2, 3], sourcePath) as Selector<
|
39
|
+
string[]
|
40
|
+
>;
|
41
|
+
expectValOrExpr(numbersVal).toStrictEqual({
|
42
|
+
val: [1, 2, 3],
|
43
|
+
[Path]: "/app/numbers",
|
44
|
+
});
|
45
|
+
});
|
46
|
+
|
47
|
+
test("array length", () => {
|
48
|
+
const sourcePath = "/app/numbers" as SourcePath;
|
49
|
+
const numbersVal = newSelectorProxy([1, 2, 3], sourcePath) as Selector<
|
50
|
+
string[]
|
51
|
+
>;
|
52
|
+
expectValOrExpr(numbersVal.length).toStrictEqual({
|
53
|
+
val: 3,
|
54
|
+
[Path]: undefined,
|
55
|
+
});
|
56
|
+
});
|
57
|
+
|
58
|
+
test("array filter match number", () => {
|
59
|
+
const sourcePath = "/app/numbers" as SourcePath;
|
60
|
+
const numbersVal = newSelectorProxy([1, 2, undefined], sourcePath) as
|
61
|
+
| Selector<string[]>
|
62
|
+
| Selector<null[]>;
|
63
|
+
expectValOrExpr(numbersVal.filter(number())).toStrictEqual({
|
64
|
+
val: [1, 2],
|
65
|
+
[Path]: "/app/numbers",
|
66
|
+
});
|
67
|
+
});
|
68
|
+
|
69
|
+
test("array filter match string / undefined / null", () => {
|
70
|
+
const sourcePath = "/app/numbers" as SourcePath;
|
71
|
+
const numbersVal = newSelectorProxy(
|
72
|
+
[1, 2, undefined, null, "test"],
|
73
|
+
sourcePath
|
74
|
+
) as Selector<(number | string | null)[]>;
|
75
|
+
expectValOrExpr(numbersVal.filter(string().optional())).toStrictEqual({
|
76
|
+
val: [null, null, "test"],
|
77
|
+
[Path]: "/app/numbers",
|
78
|
+
});
|
79
|
+
});
|
80
|
+
|
81
|
+
test("object lookup", () => {
|
82
|
+
const sourcePath = "/app/blog" as SourcePath;
|
83
|
+
const blogsVal = newSelectorProxy(
|
84
|
+
{ title: "title1" },
|
85
|
+
sourcePath
|
86
|
+
) as Selector<{ title: string }>;
|
87
|
+
expectValOrExpr(blogsVal).toStrictEqual({
|
88
|
+
val: { title: "title1" },
|
89
|
+
[Path]: "/app/blog",
|
90
|
+
});
|
91
|
+
});
|
92
|
+
|
93
|
+
test("object prop", () => {
|
94
|
+
const sourcePath = "/app/blog" as SourcePath;
|
95
|
+
const blogsVal = newSelectorProxy(
|
96
|
+
{ title: "title1" },
|
97
|
+
sourcePath
|
98
|
+
) as Selector<{ title: string }>;
|
99
|
+
expectValOrExpr(blogsVal.title).toStrictEqual({
|
100
|
+
val: "title1",
|
101
|
+
[Path]: '/app/blog."title"',
|
102
|
+
});
|
103
|
+
});
|
104
|
+
|
105
|
+
test("array object index", () => {
|
106
|
+
const sourcePath = "/app/blogs" as SourcePath;
|
107
|
+
const blogsVal = newSelectorProxy(
|
108
|
+
[{ title: "title1" }],
|
109
|
+
sourcePath
|
110
|
+
) as Selector<{ title: string }[]>;
|
111
|
+
expectValOrExpr(blogsVal[0]).toStrictEqual({
|
112
|
+
val: {
|
113
|
+
title: "title1",
|
114
|
+
},
|
115
|
+
[Path]: "/app/blogs.0",
|
116
|
+
});
|
117
|
+
});
|
118
|
+
|
119
|
+
test("array map object index", () => {
|
120
|
+
const sourcePath = "/app/blogs" as SourcePath;
|
121
|
+
const blogsVal = newSelectorProxy(
|
122
|
+
[{ title: "title1" }],
|
123
|
+
sourcePath
|
124
|
+
) as Selector<{ title: string }[]>;
|
125
|
+
expectValOrExpr(blogsVal.map((v) => v)[0]).toStrictEqual({
|
126
|
+
val: {
|
127
|
+
title: "title1",
|
128
|
+
},
|
129
|
+
[Path]: "/app/blogs.0",
|
130
|
+
});
|
131
|
+
});
|
132
|
+
|
133
|
+
test("map object then index", () => {
|
134
|
+
const sourcePath = "/app/blogs" as SourcePath;
|
135
|
+
const blogsVal = newSelectorProxy(
|
136
|
+
[{ title: "title1" }],
|
137
|
+
sourcePath
|
138
|
+
) as Selector<{ title: string }[]>;
|
139
|
+
expectValOrExpr(blogsVal.map((blog) => blog)).toStrictEqual({
|
140
|
+
val: [{ title: "title1" }],
|
141
|
+
[Path]: "/app/blogs",
|
142
|
+
});
|
143
|
+
});
|
144
|
+
|
145
|
+
test("map object then index then prop", () => {
|
146
|
+
const sourcePath = "/app/blogs" as SourcePath;
|
147
|
+
const blogsVal = newSelectorProxy(
|
148
|
+
[{ title: "title1" }, { title: "title2" }],
|
149
|
+
sourcePath
|
150
|
+
) as Selector<{ title: string }[]>;
|
151
|
+
expectValOrExpr(blogsVal.map((blog) => blog)[0].title).toStrictEqual({
|
152
|
+
val: "title1",
|
153
|
+
[Path]: '/app/blogs.0."title"',
|
154
|
+
});
|
155
|
+
});
|
156
|
+
|
157
|
+
test("map object then prop then index", () => {
|
158
|
+
const sourcePath = "/app/blogs" as SourcePath;
|
159
|
+
const blogsVal = newSelectorProxy(
|
160
|
+
[{ title: "title1" }, { title: "title2" }],
|
161
|
+
sourcePath
|
162
|
+
) as Selector<{ title: string }[]>;
|
163
|
+
expectValOrExpr(blogsVal.map((blog) => blog.title)[0]).toStrictEqual({
|
164
|
+
val: "title1",
|
165
|
+
[Path]: '/app/blogs.0."title"',
|
166
|
+
});
|
167
|
+
});
|
168
|
+
});
|
169
|
+
|
170
|
+
function expectValOrExpr<T extends Source>(selector: GenericSelector<T>) {
|
171
|
+
return expect(selectorToVal(selector));
|
172
|
+
}
|