@particle-academy/fancy-conformance 0.1.0 → 0.2.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/VERSION +1 -1
- package/package.json +2 -2
- package/suites/shared/expr/cases.json +186 -0
- package/suites/shared/expr/manifest.json +22 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.0
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/fancy-conformance",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared cross-language conformance fixtures for the Fancy suite. One contract, N implementations, and a single table that every implementation asserts in its own CI
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared cross-language conformance fixtures for the Fancy suite. One contract, N implementations, and a single table that every implementation asserts in its own CI \u2014 so 'parity' is a test result rather than a claim. Ships the fixture data itself, so a Rust, Go or Python runner can consume it without a JavaScript toolchain.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/Particle-Academy/fancy-conformance.git"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../schema/case-table.schema.json",
|
|
3
|
+
"suite": "shared/expr",
|
|
4
|
+
"cases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "0001-whole-string-keeps-type",
|
|
7
|
+
"title": "A whole-string expression returns the resolved VALUE, not its text",
|
|
8
|
+
"since": "0.2.0",
|
|
9
|
+
"tags": ["evaluate", "types"],
|
|
10
|
+
"fn": "evaluateExpression",
|
|
11
|
+
"input": { "template": "{{ $json.count }}", "context": { "in": { "count": 3 } } },
|
|
12
|
+
"expected": 3
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "0002-mixed-text-interpolates",
|
|
16
|
+
"title": "Anything other than a whole-string expression interpolates as text",
|
|
17
|
+
"since": "0.2.0",
|
|
18
|
+
"tags": ["evaluate"],
|
|
19
|
+
"fn": "evaluateExpression",
|
|
20
|
+
"input": { "template": "Hi {{ $json.name }}, you have {{ $json.count }}", "context": { "in": { "name": "Ada", "count": 3 } } },
|
|
21
|
+
"expected": "Hi Ada, you have 3"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "0003-input-alias",
|
|
25
|
+
"title": "$input is an alias for $json",
|
|
26
|
+
"since": "0.2.0",
|
|
27
|
+
"tags": ["evaluate", "alias"],
|
|
28
|
+
"fn": "evaluateExpression",
|
|
29
|
+
"input": { "template": "{{ $input.name }}", "context": { "in": { "name": "Ada" } } },
|
|
30
|
+
"expected": "Ada"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "0004-alias-falls-back-to-whole-context",
|
|
34
|
+
"title": "With no `in` key, $json addresses the whole context",
|
|
35
|
+
"since": "0.2.0",
|
|
36
|
+
"tags": ["evaluate", "alias"],
|
|
37
|
+
"fn": "evaluateExpression",
|
|
38
|
+
"input": { "template": "{{ $json.answer }}", "context": { "answer": "42" } },
|
|
39
|
+
"expected": "42"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "0005-bare-top-level-key",
|
|
43
|
+
"title": "A bare path addresses the context, not the input",
|
|
44
|
+
"since": "0.2.0",
|
|
45
|
+
"tags": ["evaluate"],
|
|
46
|
+
"fn": "evaluateExpression",
|
|
47
|
+
"input": { "template": "{{ answer }}", "context": { "answer": "yes", "in": { "answer": "no" } } },
|
|
48
|
+
"expected": "yes"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "0006-nested-dot-path",
|
|
52
|
+
"title": "Dot-paths nest",
|
|
53
|
+
"since": "0.2.0",
|
|
54
|
+
"tags": ["evaluate"],
|
|
55
|
+
"fn": "evaluateExpression",
|
|
56
|
+
"input": { "template": "{{ $json.user.email }}", "context": { "in": { "user": { "email": "a@b.c" } } } },
|
|
57
|
+
"expected": "a@b.c"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "0007-missing-path-is-null",
|
|
61
|
+
"title": "An unresolvable path is null, not an error and not the literal text",
|
|
62
|
+
"since": "0.2.0",
|
|
63
|
+
"tags": ["evaluate", "regression"],
|
|
64
|
+
"fn": "evaluateExpression",
|
|
65
|
+
"input": { "template": "{{ $json.nope }}", "context": { "in": { "yes": 1 } } },
|
|
66
|
+
"expected": null
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "0008-missing-path-interpolates-empty",
|
|
70
|
+
"title": "A missing path interpolates to empty, never to \"null\"",
|
|
71
|
+
"since": "0.2.0",
|
|
72
|
+
"tags": ["evaluate", "regression"],
|
|
73
|
+
"fn": "evaluateExpression",
|
|
74
|
+
"input": { "template": "[{{ $json.nope }}]", "context": { "in": {} } },
|
|
75
|
+
"expected": "[]"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "0009-non-string-passes-through",
|
|
79
|
+
"title": "A non-string template is returned untouched",
|
|
80
|
+
"since": "0.2.0",
|
|
81
|
+
"tags": ["evaluate"],
|
|
82
|
+
"fn": "evaluateExpression",
|
|
83
|
+
"input": { "template": 7, "context": { "in": {} } },
|
|
84
|
+
"expected": 7
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "0010-plain-string-untouched",
|
|
88
|
+
"title": "A string with no braces is returned as-is",
|
|
89
|
+
"since": "0.2.0",
|
|
90
|
+
"tags": ["evaluate"],
|
|
91
|
+
"fn": "evaluateExpression",
|
|
92
|
+
"input": { "template": "just text", "context": { "in": {} } },
|
|
93
|
+
"expected": "just text"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "0011-object-interpolates-as-json",
|
|
97
|
+
"title": "Interpolating an object stringifies it as JSON",
|
|
98
|
+
"since": "0.2.0",
|
|
99
|
+
"tags": ["evaluate", "stringify"],
|
|
100
|
+
"fn": "evaluateExpression",
|
|
101
|
+
"input": { "template": "payload: {{ $json.body }}", "context": { "in": { "body": { "a": 1 } } } },
|
|
102
|
+
"expected": "payload: {\"a\":1}"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "0012-boolean-interpolates-lowercase",
|
|
106
|
+
"title": "Booleans interpolate as true/false, not 1/empty",
|
|
107
|
+
"since": "0.2.0",
|
|
108
|
+
"tags": ["evaluate", "stringify", "regression"],
|
|
109
|
+
"fn": "evaluateExpression",
|
|
110
|
+
"input": { "template": "ok={{ $json.ok }}", "context": { "in": { "ok": false } } },
|
|
111
|
+
"expected": "ok=false"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"id": "0013-truthy-string-false",
|
|
115
|
+
"title": "The string \"false\" is FALSY",
|
|
116
|
+
"since": "0.2.0",
|
|
117
|
+
"tags": ["truthy", "regression"],
|
|
118
|
+
"fn": "truthy",
|
|
119
|
+
"input": { "value": "false" },
|
|
120
|
+
"expected": false
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "0014-truthy-string-zero",
|
|
124
|
+
"title": "The string \"0\" is FALSY",
|
|
125
|
+
"since": "0.2.0",
|
|
126
|
+
"tags": ["truthy", "regression"],
|
|
127
|
+
"fn": "truthy",
|
|
128
|
+
"input": { "value": "0" },
|
|
129
|
+
"expected": false
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": "0015-truthy-empty-array",
|
|
133
|
+
"title": "An empty array is FALSY",
|
|
134
|
+
"since": "0.2.0",
|
|
135
|
+
"tags": ["truthy", "regression"],
|
|
136
|
+
"fn": "truthy",
|
|
137
|
+
"input": { "value": [] },
|
|
138
|
+
"expected": false
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "0016-truthy-nonempty-array",
|
|
142
|
+
"title": "A non-empty array is truthy",
|
|
143
|
+
"since": "0.2.0",
|
|
144
|
+
"tags": ["truthy"],
|
|
145
|
+
"fn": "truthy",
|
|
146
|
+
"input": { "value": [0] },
|
|
147
|
+
"expected": true
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"id": "0017-truthy-zero",
|
|
151
|
+
"title": "Zero is falsy",
|
|
152
|
+
"since": "0.2.0",
|
|
153
|
+
"tags": ["truthy"],
|
|
154
|
+
"fn": "truthy",
|
|
155
|
+
"input": { "value": 0 },
|
|
156
|
+
"expected": false
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"id": "0018-truthy-null",
|
|
160
|
+
"title": "null is falsy",
|
|
161
|
+
"since": "0.2.0",
|
|
162
|
+
"tags": ["truthy"],
|
|
163
|
+
"fn": "truthy",
|
|
164
|
+
"input": { "value": null },
|
|
165
|
+
"expected": false
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "0019-truthy-off",
|
|
169
|
+
"title": "\"off\", \"no\" and \"null\" are falsy strings too",
|
|
170
|
+
"since": "0.2.0",
|
|
171
|
+
"tags": ["truthy"],
|
|
172
|
+
"fn": "truthy",
|
|
173
|
+
"input": { "value": "OFF" },
|
|
174
|
+
"expected": false
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"id": "0020-truthy-ordinary-string",
|
|
178
|
+
"title": "Any other non-empty string is truthy",
|
|
179
|
+
"since": "0.2.0",
|
|
180
|
+
"tags": ["truthy"],
|
|
181
|
+
"fn": "truthy",
|
|
182
|
+
"input": { "value": "no thanks" },
|
|
183
|
+
"expected": true
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../schema/suite-manifest.schema.json",
|
|
3
|
+
"suite": "shared/expr",
|
|
4
|
+
"title": "{{ }} expression resolution for fancy-flow node config",
|
|
5
|
+
"since": "0.2.0",
|
|
6
|
+
"caseFormat": "table",
|
|
7
|
+
"cases": "cases.json",
|
|
8
|
+
"contract": {
|
|
9
|
+
"function": "evaluateExpression(template: unknown, context: object): unknown | truthy(value: unknown): boolean",
|
|
10
|
+
"summary": "Resolve a dot-path template against an executor's inputs, and decide branch truthiness. Deliberately NOT a general expression language: dot-paths only, no arithmetic, comparisons or calls.",
|
|
11
|
+
"implementations": [
|
|
12
|
+
{ "language": "php", "package": "particle-academy/fancy-flow-php", "symbol": "FancyFlow\\Nodes\\Support\\Expr" },
|
|
13
|
+
{ "language": "node", "package": "@particle-academy/fancy-flow", "symbol": "evaluateExpression / truthy" }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"notes": [
|
|
17
|
+
"This suite exists because of what conformance could NOT catch. `Expr` shipped in PHP with no TypeScript twin at all, and a fixture table compares two implementations — it is structurally unable to report a MISSING one. The gap surfaced only when someone asked for editor autocomplete over a grammar and the grammar turned out to live on one side.",
|
|
18
|
+
"`truthy` is where the two languages disagree by default, so those rows carry the weight. `\"0\"`, `\"false\"` and `[]` are all truthy in JavaScript and falsy here; a branch node reading a form value or a JSON body hits every one of them. An implementation that forwards to native truthiness fails cases 0013-0015 and nothing else, which is exactly the signal wanted.",
|
|
19
|
+
"Case 0008 pins that a missing path interpolates to an empty string rather than the text \"null\" — the difference between a quietly-blank message and one that ships the word null to a customer.",
|
|
20
|
+
"fancy-flow's own `runFlow` does not call this. The PHP runtime resolves expressions inside its executors; the JS runtime hands config to the host's executor verbatim, and every host that uses `{{ }}` today already resolves it itself. Turning it on inside `runFlow` would interpolate a second time over values those hosts had already substituted. The TS side is therefore an opt-in export, and this suite pins its SEMANTICS, not where it is called from."
|
|
21
|
+
]
|
|
22
|
+
}
|