@penpair/marwes-ui 0.2.0 → 0.3.0
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/dist/build-kit.js +24 -6
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +500 -94
- package/dist/components.js +375 -74
- package/dist/config.js +53 -16
- package/dist/index.js +21 -3
- package/dist/marwes.js +45 -12
- package/dist/mode-trial.js +325 -75
- package/dist/ownership.js +162 -37
- package/dist/penpot.js +447 -114
- package/dist/reconciliation.js +112 -37
- package/dist/resolved-values.d.ts +3 -8
- package/dist/resolved-values.js +99 -103
- package/dist/snapshot-file.js +255 -50
- package/dist/theme-input.js +12 -4
- package/dist/theme-lint.js +113 -27
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +207 -52
- package/package.json +2 -2
package/dist/reconciliation.js
CHANGED
|
@@ -1,104 +1,179 @@
|
|
|
1
1
|
import { normalizeValue } from "./tokens.js";
|
|
2
2
|
const set = (live, name) => live.sets.filter((entry) => entry.name === name);
|
|
3
3
|
const theme = (live, group, name) => live.themes.filter((entry) => entry.group === group && entry.name === name);
|
|
4
|
-
const tokenState = (token) => token
|
|
4
|
+
const tokenState = (token) => token
|
|
5
|
+
? JSON.stringify([
|
|
6
|
+
token.type,
|
|
7
|
+
normalizeValue(token.type, token.value),
|
|
8
|
+
token.description || "",
|
|
9
|
+
])
|
|
10
|
+
: null;
|
|
5
11
|
export function actionSatisfied(action, live) {
|
|
6
12
|
switch (action.kind) {
|
|
7
|
-
case "createSet":
|
|
13
|
+
case "createSet":
|
|
14
|
+
return set(live, action.set).length === 1;
|
|
8
15
|
case "addTokens":
|
|
9
16
|
case "updateTokens":
|
|
10
17
|
case "replaceTokens": {
|
|
11
18
|
const sets = set(live, action.set);
|
|
12
|
-
return sets.length === 1 &&
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
return (sets.length === 1 &&
|
|
20
|
+
action.tokens.every((wanted) => {
|
|
21
|
+
const actual = sets[0].tokens.filter((token) => token.name === wanted.name);
|
|
22
|
+
return actual.length === 1 && tokenState(actual[0]) === tokenState(wanted);
|
|
23
|
+
}));
|
|
16
24
|
}
|
|
17
|
-
case "removeTokens":
|
|
25
|
+
case "removeTokens":
|
|
26
|
+
return (set(live, action.set).length === 1 &&
|
|
27
|
+
action.names.every((name) => !set(live, action.set)[0].tokens.some((token) => token.name === name)));
|
|
18
28
|
case "createTheme": {
|
|
19
29
|
const found = theme(live, action.group, action.name);
|
|
20
|
-
return found.length === 1 &&
|
|
30
|
+
return (found.length === 1 &&
|
|
31
|
+
found[0].sets.length === action.sets.length &&
|
|
32
|
+
action.sets.every((name) => found[0].sets.includes(name)));
|
|
21
33
|
}
|
|
22
34
|
case "syncThemeSets": {
|
|
23
35
|
const found = theme(live, action.group, action.name);
|
|
24
|
-
return found.length === 1 &&
|
|
36
|
+
return (found.length === 1 &&
|
|
37
|
+
action.add.every((name) => found[0].sets.includes(name)) &&
|
|
38
|
+
action.remove.every((name) => !found[0].sets.includes(name)));
|
|
25
39
|
}
|
|
26
|
-
case "activateTheme":
|
|
40
|
+
case "activateTheme":
|
|
41
|
+
return (theme(live, action.group, action.name).some((entry) => entry.active) &&
|
|
42
|
+
live.themes.every((entry) => !entry.active || (entry.group === action.group && entry.name === action.name)));
|
|
27
43
|
}
|
|
28
44
|
}
|
|
29
45
|
/** Only old or explicitly desired states can be our partial write; a third value needs owner reconciliation. */
|
|
30
46
|
export function reconcileAction(action, before, current) {
|
|
31
|
-
if (actionSatisfied(action, current))
|
|
47
|
+
if (actionSatisfied(action, current)) {
|
|
32
48
|
return "complete";
|
|
49
|
+
}
|
|
33
50
|
switch (action.kind) {
|
|
34
|
-
case "createSet":
|
|
51
|
+
case "createSet":
|
|
52
|
+
return set(current, action.set).length === 0 ? "pending" : "conflict";
|
|
35
53
|
case "addTokens":
|
|
36
54
|
case "updateTokens":
|
|
37
55
|
case "replaceTokens": {
|
|
38
|
-
const oldSets = set(before, action.set)
|
|
39
|
-
|
|
56
|
+
const oldSets = set(before, action.set);
|
|
57
|
+
const nowSets = set(current, action.set);
|
|
58
|
+
if (nowSets.length !== 1 || oldSets.length > 1) {
|
|
40
59
|
return "conflict";
|
|
60
|
+
}
|
|
41
61
|
for (const wanted of action.tokens) {
|
|
42
62
|
const oldTokens = oldSets[0]?.tokens.filter((token) => token.name === wanted.name) ?? [];
|
|
43
63
|
const nowTokens = nowSets[0].tokens.filter((token) => token.name === wanted.name);
|
|
44
|
-
if (oldTokens.length > 1 || nowTokens.length > 1)
|
|
64
|
+
if (oldTokens.length > 1 || nowTokens.length > 1) {
|
|
45
65
|
return "conflict";
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
}
|
|
67
|
+
const old = tokenState(oldTokens[0]);
|
|
68
|
+
const actual = tokenState(nowTokens[0]);
|
|
69
|
+
const desired = tokenState(wanted);
|
|
70
|
+
if (action.kind === "addTokens" && old !== null && old !== desired) {
|
|
48
71
|
return "conflict";
|
|
49
|
-
|
|
72
|
+
}
|
|
73
|
+
if (action.kind !== "addTokens" && old === null) {
|
|
50
74
|
return "conflict";
|
|
51
|
-
|
|
75
|
+
}
|
|
76
|
+
if (actual !== old &&
|
|
77
|
+
actual !== desired &&
|
|
78
|
+
!(action.kind === "replaceTokens" && actual === null)) {
|
|
52
79
|
return "conflict";
|
|
80
|
+
}
|
|
53
81
|
}
|
|
54
82
|
return "pending";
|
|
55
83
|
}
|
|
56
84
|
case "removeTokens": {
|
|
57
|
-
const oldSets = set(before, action.set)
|
|
58
|
-
|
|
85
|
+
const oldSets = set(before, action.set);
|
|
86
|
+
const nowSets = set(current, action.set);
|
|
87
|
+
if (oldSets.length !== 1 || nowSets.length !== 1) {
|
|
59
88
|
return "conflict";
|
|
89
|
+
}
|
|
60
90
|
return action.names.every((name) => {
|
|
61
91
|
const old = oldSets[0].tokens.filter((token) => token.name === name);
|
|
62
92
|
const actual = nowSets[0].tokens.filter((token) => token.name === name);
|
|
63
|
-
return actual.length <= 1 &&
|
|
64
|
-
|
|
93
|
+
return (actual.length <= 1 &&
|
|
94
|
+
(actual.length === 0 ||
|
|
95
|
+
(old.length === 1 && tokenState(old[0]) === tokenState(actual[0]))));
|
|
96
|
+
})
|
|
97
|
+
? "pending"
|
|
98
|
+
: "conflict";
|
|
65
99
|
}
|
|
66
100
|
case "createTheme": {
|
|
67
|
-
const old = theme(before, action.group, action.name)
|
|
68
|
-
|
|
101
|
+
const old = theme(before, action.group, action.name);
|
|
102
|
+
const actual = theme(current, action.group, action.name);
|
|
103
|
+
return old.length === 0 &&
|
|
104
|
+
actual.length <= 1 &&
|
|
105
|
+
(actual.length === 0 || actual[0].sets.every((name) => action.sets.includes(name)))
|
|
106
|
+
? "pending"
|
|
107
|
+
: "conflict";
|
|
69
108
|
}
|
|
70
109
|
case "syncThemeSets": {
|
|
71
|
-
const old = theme(before, action.group, action.name)
|
|
72
|
-
|
|
110
|
+
const old = theme(before, action.group, action.name);
|
|
111
|
+
const actual = theme(current, action.group, action.name);
|
|
112
|
+
if (old.length !== 1 || actual.length !== 1) {
|
|
73
113
|
return "conflict";
|
|
74
|
-
|
|
75
|
-
const
|
|
114
|
+
}
|
|
115
|
+
const touched = new Set([
|
|
116
|
+
...action.add,
|
|
117
|
+
...action.remove,
|
|
118
|
+
]);
|
|
119
|
+
const unchanged = (sets) => sets
|
|
120
|
+
.filter((name) => !touched.has(name))
|
|
121
|
+
.sort()
|
|
122
|
+
.join("\n");
|
|
76
123
|
return unchanged(old[0].sets) === unchanged(actual[0].sets) ? "pending" : "conflict";
|
|
77
124
|
}
|
|
78
125
|
case "activateTheme": {
|
|
79
|
-
const active = (live) => live.themes
|
|
126
|
+
const active = (live) => live.themes
|
|
127
|
+
.filter((entry) => entry.active)
|
|
128
|
+
.map((entry) => JSON.stringify([
|
|
129
|
+
entry.group,
|
|
130
|
+
entry.name,
|
|
131
|
+
]))
|
|
132
|
+
.sort()
|
|
133
|
+
.join("\n");
|
|
80
134
|
return active(current) === active(before) || active(current) === "" ? "pending" : "conflict";
|
|
81
135
|
}
|
|
82
136
|
}
|
|
83
137
|
}
|
|
84
138
|
/** Recompute residual work from actual state so completed token replacements are never removed/recreated again. */
|
|
85
139
|
export function remainingAction(action, current) {
|
|
86
|
-
if (actionSatisfied(action, current))
|
|
140
|
+
if (actionSatisfied(action, current)) {
|
|
87
141
|
return undefined;
|
|
142
|
+
}
|
|
88
143
|
if ("tokens" in action) {
|
|
89
144
|
const actual = set(current, action.set)[0];
|
|
90
|
-
const tokens = action.tokens.filter((wanted) => tokenState(actual?.tokens.find((token) => token.name === wanted.name)) !==
|
|
91
|
-
|
|
145
|
+
const tokens = action.tokens.filter((wanted) => tokenState(actual?.tokens.find((token) => token.name === wanted.name)) !==
|
|
146
|
+
tokenState(wanted));
|
|
147
|
+
return tokens.length
|
|
148
|
+
? {
|
|
149
|
+
...action,
|
|
150
|
+
tokens,
|
|
151
|
+
}
|
|
152
|
+
: undefined;
|
|
153
|
+
}
|
|
154
|
+
if (action.kind === "removeTokens") {
|
|
155
|
+
return {
|
|
156
|
+
...action,
|
|
157
|
+
names: action.names.filter((name) => set(current, action.set)[0]?.tokens.some((token) => token.name === name)),
|
|
158
|
+
};
|
|
92
159
|
}
|
|
93
|
-
if (action.kind === "removeTokens")
|
|
94
|
-
return { ...action, names: action.names.filter((name) => set(current, action.set)[0]?.tokens.some((token) => token.name === name)) };
|
|
95
160
|
if (action.kind === "createTheme" && theme(current, action.group, action.name).length === 1) {
|
|
96
161
|
const actual = theme(current, action.group, action.name)[0];
|
|
97
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
kind: "syncThemeSets",
|
|
164
|
+
group: action.group,
|
|
165
|
+
name: action.name,
|
|
166
|
+
add: action.sets.filter((name) => !actual.sets.includes(name)),
|
|
167
|
+
remove: [],
|
|
168
|
+
};
|
|
98
169
|
}
|
|
99
170
|
if (action.kind === "syncThemeSets") {
|
|
100
171
|
const actual = theme(current, action.group, action.name)[0];
|
|
101
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
...action,
|
|
174
|
+
add: action.add.filter((name) => !actual.sets.includes(name)),
|
|
175
|
+
remove: action.remove.filter((name) => actual.sets.includes(name)),
|
|
176
|
+
};
|
|
102
177
|
}
|
|
103
178
|
return action;
|
|
104
179
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { type TokenSnapshot } from "./tokens.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
value?: unknown;
|
|
6
|
-
unknown?: string;
|
|
7
|
-
}
|
|
8
|
-
/** A small arithmetic grammar, never JavaScript evaluation. Unsupported units/functions are explicit unknowns. */
|
|
9
|
-
export declare function numericExpression(source: string): number;
|
|
2
|
+
import { type ResolvedDeclaredToken } from "@penpair/core";
|
|
3
|
+
export { numericExpression } from "@penpair/core";
|
|
4
|
+
export type ResolvedExpectation = ResolvedDeclaredToken;
|
|
10
5
|
export declare function expectedResolved(snapshot: TokenSnapshot, theme: {
|
|
11
6
|
sets: string[];
|
|
12
7
|
}): ResolvedExpectation[];
|
package/dist/resolved-values.js
CHANGED
|
@@ -1,128 +1,124 @@
|
|
|
1
1
|
import { canonical, normalizeValue, toHexColor } from "./tokens.js";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
value = sum();
|
|
17
|
-
if (tokens[index++] !== ")")
|
|
18
|
-
throw new Error("Unclosed expression");
|
|
19
|
-
}
|
|
20
|
-
else if (token === "-")
|
|
21
|
-
value = -atom();
|
|
22
|
-
else if (token === "+")
|
|
23
|
-
value = atom();
|
|
24
|
-
else {
|
|
25
|
-
if (!token || !/^[\d.]/.test(token))
|
|
26
|
-
throw new Error("Missing number");
|
|
27
|
-
value = Number(token);
|
|
28
|
-
}
|
|
29
|
-
depth--;
|
|
30
|
-
if (!Number.isFinite(value))
|
|
31
|
-
throw new Error("Nonfinite expression");
|
|
32
|
-
return value;
|
|
33
|
-
};
|
|
34
|
-
const product = () => { let value = atom(); while (["*", "/"].includes(tokens[index] ?? "")) {
|
|
35
|
-
const op = tokens[index++], right = atom();
|
|
36
|
-
value = op === "*" ? value * right : value / right;
|
|
37
|
-
} return value; };
|
|
38
|
-
const sum = () => { let value = product(); while (["+", "-"].includes(tokens[index] ?? "")) {
|
|
39
|
-
const op = tokens[index++], right = product();
|
|
40
|
-
value = op === "+" ? value + right : value - right;
|
|
41
|
-
} return value; };
|
|
42
|
-
const value = sum();
|
|
43
|
-
if (index !== tokens.length || !Number.isFinite(value))
|
|
44
|
-
throw new Error("Invalid numeric expression");
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
|
-
const numericTypes = new Set(["borderRadius", "dimension", "fontSizes", "letterSpacing", "number", "opacity", "rotation", "sizing", "spacing", "borderWidth"]);
|
|
2
|
+
import { numericExpression, resolveDeclaredTokens, } from "@penpair/core";
|
|
3
|
+
export { numericExpression } from "@penpair/core";
|
|
4
|
+
const numericTypes = new Set([
|
|
5
|
+
"borderRadius",
|
|
6
|
+
"dimension",
|
|
7
|
+
"fontSizes",
|
|
8
|
+
"letterSpacing",
|
|
9
|
+
"number",
|
|
10
|
+
"opacity",
|
|
11
|
+
"rotation",
|
|
12
|
+
"sizing",
|
|
13
|
+
"spacing",
|
|
14
|
+
"borderWidth",
|
|
15
|
+
]);
|
|
48
16
|
export function expectedResolved(snapshot, theme) {
|
|
49
|
-
|
|
50
|
-
const resolve = (name, visiting = new Set()) => {
|
|
51
|
-
if (visiting.has(name) || visiting.size >= 64)
|
|
52
|
-
throw new Error("Cyclic or over-deep reference");
|
|
53
|
-
const token = effective.get(name);
|
|
54
|
-
if (!token)
|
|
55
|
-
throw new Error("Missing referenced token: " + name);
|
|
56
|
-
const next = new Set(visiting);
|
|
57
|
-
next.add(name);
|
|
58
|
-
const value = (input) => {
|
|
59
|
-
if (typeof input === "string") {
|
|
60
|
-
const alias = /^\{([^{}]+)\}$/.exec(input);
|
|
61
|
-
if (alias)
|
|
62
|
-
return resolve(alias[1], next);
|
|
63
|
-
return input.replace(/\{([^{}]+)\}/g, (_match, reference) => {
|
|
64
|
-
const resolved = resolve(reference, next);
|
|
65
|
-
if (typeof resolved !== "number" && typeof resolved !== "string")
|
|
66
|
-
throw new Error("Composite interpolation unsupported");
|
|
67
|
-
return String(resolved);
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
if (input && typeof input === "object" && !Array.isArray(input))
|
|
71
|
-
return Object.fromEntries(Object.entries(input).map(([key, entry]) => [key, value(entry)]));
|
|
72
|
-
return input;
|
|
73
|
-
};
|
|
74
|
-
const resolved = value(token.value);
|
|
75
|
-
return numericTypes.has(token.type) ? typeof resolved === "number" ? resolved : numericExpression(String(resolved)) : resolved;
|
|
76
|
-
};
|
|
77
|
-
return [...effective.values()].map(token => { try {
|
|
78
|
-
return { name: token.name, type: token.type, value: resolve(token.name) };
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
return { name: token.name, type: token.type, unknown: String(error.message) };
|
|
82
|
-
} });
|
|
17
|
+
return resolveDeclaredTokens(snapshot, theme.sets);
|
|
83
18
|
}
|
|
84
19
|
export function compareResolved(wanted, actual) {
|
|
85
|
-
if (wanted.unknown)
|
|
86
|
-
return {
|
|
87
|
-
|
|
88
|
-
|
|
20
|
+
if (wanted.unknown) {
|
|
21
|
+
return {
|
|
22
|
+
unknown: wanted.unknown,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (actual === undefined || actual === null) {
|
|
26
|
+
return {
|
|
27
|
+
unknown: "API resolvedValue is unavailable",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
89
30
|
try {
|
|
90
31
|
if (numericTypes.has(wanted.type)) {
|
|
91
32
|
const value = typeof actual === "number" ? actual : numericExpression(String(actual));
|
|
92
|
-
return {
|
|
33
|
+
return {
|
|
34
|
+
equal: typeof wanted.value === "number" &&
|
|
35
|
+
Math.abs(value - wanted.value) <= 1e-9 * Math.max(1, Math.abs(wanted.value)),
|
|
36
|
+
};
|
|
93
37
|
}
|
|
94
38
|
if (wanted.type === "color") {
|
|
95
|
-
const color = (value) => {
|
|
96
|
-
|
|
97
|
-
|
|
39
|
+
const color = (value) => {
|
|
40
|
+
if (typeof value !== "string") {
|
|
41
|
+
throw new Error("Unsupported resolved color");
|
|
42
|
+
}
|
|
43
|
+
const expanded = /^#[0-9a-f]{3,4}$/i.test(value)
|
|
44
|
+
? "#" +
|
|
45
|
+
value
|
|
46
|
+
.slice(1)
|
|
47
|
+
.split("")
|
|
48
|
+
.map((c) => c + c)
|
|
49
|
+
.join("")
|
|
50
|
+
: value;
|
|
51
|
+
const hex = toHexColor(expanded);
|
|
52
|
+
return hex.length === 9 && hex.endsWith("FF") ? hex.slice(0, -2) : hex;
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
equal: color(wanted.value) === color(actual),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (wanted.type === "fontFamilies" &&
|
|
59
|
+
Array.isArray(actual) &&
|
|
60
|
+
!actual.every((value) => typeof value === "string")) {
|
|
61
|
+
return {
|
|
62
|
+
unknown: "Unsupported nested font resolution",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (wanted.type === "shadow") {
|
|
66
|
+
return {
|
|
67
|
+
unknown: "Composite shadow resolution has no supported comparison model",
|
|
68
|
+
};
|
|
98
69
|
}
|
|
99
|
-
if (wanted.type === "fontFamilies" && Array.isArray(actual) && !actual.every(value => typeof value === "string"))
|
|
100
|
-
return { unknown: "Unsupported nested font resolution" };
|
|
101
|
-
if (wanted.type === "shadow")
|
|
102
|
-
return { unknown: "Composite shadow resolution has no supported comparison model" };
|
|
103
70
|
if (wanted.type === "typography") {
|
|
104
71
|
const normalize = (input) => {
|
|
105
|
-
const object = Array.isArray(input) ? input.length === 1 ? input[0] : undefined : input;
|
|
106
|
-
if (!object || typeof object !== "object" || Array.isArray(object))
|
|
72
|
+
const object = Array.isArray(input) ? (input.length === 1 ? input[0] : undefined) : input;
|
|
73
|
+
if (!object || typeof object !== "object" || Array.isArray(object)) {
|
|
107
74
|
throw new Error("Unsupported typography resolution");
|
|
108
|
-
|
|
75
|
+
}
|
|
76
|
+
const aliases = {
|
|
77
|
+
fontFamilies: "fontFamily",
|
|
78
|
+
fontSizes: "fontSize",
|
|
79
|
+
fontWeights: "fontWeight",
|
|
80
|
+
};
|
|
109
81
|
return Object.fromEntries(Object.entries(object).map(([key, value]) => {
|
|
110
82
|
key = aliases[key] ?? key;
|
|
111
83
|
if (key === "fontFamily") {
|
|
112
|
-
const family = Array.isArray(value)
|
|
113
|
-
|
|
84
|
+
const family = Array.isArray(value)
|
|
85
|
+
? value
|
|
86
|
+
: [
|
|
87
|
+
value,
|
|
88
|
+
];
|
|
89
|
+
if (!family.every((v) => typeof v === "string")) {
|
|
114
90
|
throw new Error("Unsupported nested font resolution");
|
|
115
|
-
|
|
91
|
+
}
|
|
92
|
+
return [
|
|
93
|
+
key,
|
|
94
|
+
family,
|
|
95
|
+
];
|
|
116
96
|
}
|
|
117
|
-
return [
|
|
97
|
+
return [
|
|
98
|
+
key,
|
|
99
|
+
[
|
|
100
|
+
"fontSize",
|
|
101
|
+
"letterSpacing",
|
|
102
|
+
"lineHeight",
|
|
103
|
+
].includes(key)
|
|
104
|
+
? String(numericExpression(String(value)))
|
|
105
|
+
: String(value),
|
|
106
|
+
];
|
|
118
107
|
}));
|
|
119
108
|
};
|
|
120
|
-
const expected = normalize(wanted.value)
|
|
121
|
-
|
|
109
|
+
const expected = normalize(wanted.value);
|
|
110
|
+
const observed = normalize(actual);
|
|
111
|
+
return {
|
|
112
|
+
equal: Object.keys(expected).every((key) => canonical(expected[key]) === canonical(observed[key])),
|
|
113
|
+
};
|
|
122
114
|
}
|
|
123
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
equal: normalizeValue(wanted.type, wanted.value) === normalizeValue(wanted.type, actual),
|
|
117
|
+
};
|
|
124
118
|
}
|
|
125
119
|
catch (error) {
|
|
126
|
-
return {
|
|
120
|
+
return {
|
|
121
|
+
unknown: String(error.message),
|
|
122
|
+
};
|
|
127
123
|
}
|
|
128
124
|
}
|