@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 CHANGED
@@ -1 +1 @@
1
- 0.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.1.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 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.",
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
+ }