Skip to content

Commit 01a8e76

Browse files
committed
test(jq): strengthen string key shorthand tests with AST assertions and edge cases
- Assert entry count, key strings, and Field node shape in parser tests - Add non-identifier key tests: hyphenated ("a-b"), numeric ("1"), empty ("") - Add regression test for explicit key-value with string key ("name": .x) - Add comparison tests for non-identifier key shorthands - Extract expectShorthandEntry() helper to reduce duplication
1 parent 003b696 commit 01a8e76

3 files changed

Lines changed: 142 additions & 10 deletions

File tree

src/commands/jq/jq.string-key-shorthand.test.ts

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,104 @@
11
import { describe, expect, it } from "vitest";
22
import { Bash } from "../../Bash.js";
33
import { parse } from "../query-engine/parser.js";
4+
import type { ObjectNode } from "../query-engine/parser-types.js";
5+
6+
function expectShorthandEntry(
7+
entry: ObjectNode["entries"][number],
8+
keyName: string,
9+
) {
10+
expect(entry.key).toBe(keyName);
11+
expect(entry.value).toEqual({ type: "Field", name: keyName });
12+
}
413

514
describe("jq string key shorthand in object construction", () => {
615
describe("parser: quoted string keys without colon", () => {
7-
it('should parse {"name"} as shorthand', () => {
8-
const ast = parse('{"name"}');
16+
it('should parse {"name"} as shorthand with correct AST', () => {
17+
const ast = parse('{"name"}') as ObjectNode;
918
expect(ast.type).toBe("Object");
19+
expect(ast.entries).toHaveLength(1);
20+
expectShorthandEntry(ast.entries[0], "name");
1021
});
1122

12-
it('should parse {"name", "label"} as shorthand', () => {
13-
const ast = parse('{"name", "label"}');
23+
it('should parse {"name", "label"} as shorthand with correct AST', () => {
24+
const ast = parse('{"name", "label"}') as ObjectNode;
1425
expect(ast.type).toBe("Object");
26+
expect(ast.entries).toHaveLength(2);
27+
expectShorthandEntry(ast.entries[0], "name");
28+
expectShorthandEntry(ast.entries[1], "label");
1529
});
1630

1731
it('should parse {"if"} as shorthand (keyword as string key)', () => {
18-
const ast = parse('{"if"}');
32+
const ast = parse('{"if"}') as ObjectNode;
1933
expect(ast.type).toBe("Object");
34+
expect(ast.entries).toHaveLength(1);
35+
expectShorthandEntry(ast.entries[0], "if");
2036
});
2137

2238
it('should parse {"as"} as shorthand (keyword as string key)', () => {
23-
const ast = parse('{"as"}');
39+
const ast = parse('{"as"}') as ObjectNode;
2440
expect(ast.type).toBe("Object");
41+
expect(ast.entries).toHaveLength(1);
42+
expectShorthandEntry(ast.entries[0], "as");
2543
});
2644

2745
it('should parse {"try"} as shorthand (keyword as string key)', () => {
28-
const ast = parse('{"try"}');
46+
const ast = parse('{"try"}') as ObjectNode;
2947
expect(ast.type).toBe("Object");
48+
expect(ast.entries).toHaveLength(1);
49+
expectShorthandEntry(ast.entries[0], "try");
3050
});
3151

3252
it('should parse {"true"} as shorthand (keyword as string key)', () => {
33-
const ast = parse('{"true"}');
53+
const ast = parse('{"true"}') as ObjectNode;
3454
expect(ast.type).toBe("Object");
55+
expect(ast.entries).toHaveLength(1);
56+
expectShorthandEntry(ast.entries[0], "true");
3557
});
3658

3759
it('should parse {"null"} as shorthand (keyword as string key)', () => {
38-
const ast = parse('{"null"}');
60+
const ast = parse('{"null"}') as ObjectNode;
3961
expect(ast.type).toBe("Object");
62+
expect(ast.entries).toHaveLength(1);
63+
expectShorthandEntry(ast.entries[0], "null");
4064
});
4165

4266
it('should parse mixed: {"name", "label": .x}', () => {
43-
const ast = parse('{"name", "label": .x}');
67+
const ast = parse('{"name", "label": .x}') as ObjectNode;
68+
expect(ast.type).toBe("Object");
69+
expect(ast.entries).toHaveLength(2);
70+
expectShorthandEntry(ast.entries[0], "name");
71+
expect(ast.entries[1].key).toBe("label");
72+
expect(ast.entries[1].value).toEqual({ type: "Field", name: "x" });
73+
});
74+
75+
it('should parse non-identifier key {"a-b"} as shorthand', () => {
76+
const ast = parse('{"a-b"}') as ObjectNode;
77+
expect(ast.type).toBe("Object");
78+
expect(ast.entries).toHaveLength(1);
79+
expectShorthandEntry(ast.entries[0], "a-b");
80+
});
81+
82+
it('should parse numeric string key {"1"} as shorthand', () => {
83+
const ast = parse('{"1"}') as ObjectNode;
84+
expect(ast.type).toBe("Object");
85+
expect(ast.entries).toHaveLength(1);
86+
expectShorthandEntry(ast.entries[0], "1");
87+
});
88+
89+
it('should parse empty string key {""} as shorthand', () => {
90+
const ast = parse('{""}') as ObjectNode;
91+
expect(ast.type).toBe("Object");
92+
expect(ast.entries).toHaveLength(1);
93+
expectShorthandEntry(ast.entries[0], "");
94+
});
95+
96+
it("should still parse explicit key-value with string key", () => {
97+
const ast = parse('{"name": .x}') as ObjectNode;
4498
expect(ast.type).toBe("Object");
99+
expect(ast.entries).toHaveLength(1);
100+
expect(ast.entries[0].key).toBe("name");
101+
expect(ast.entries[0].value).toEqual({ type: "Field", name: "x" });
45102
});
46103
});
47104

@@ -104,5 +161,32 @@ describe("jq string key shorthand in object construction", () => {
104161
expect(result.exitCode).toBe(0);
105162
expect(result.stdout).toBe('{"name":"foo","label":"bar"}\n');
106163
});
164+
165+
it('should evaluate non-identifier key {"a-b"} shorthand', async () => {
166+
const env = new Bash();
167+
const result = await env.exec(
168+
`echo '{"a-b":"val","extra":"x"}' | jq -c '{"a-b"}'`,
169+
);
170+
expect(result.exitCode).toBe(0);
171+
expect(result.stdout).toBe('{"a-b":"val"}\n');
172+
});
173+
174+
it('should evaluate numeric string key {"1"} shorthand', async () => {
175+
const env = new Bash();
176+
const result = await env.exec(
177+
`echo '{"1":"val","extra":"x"}' | jq -c '{"1"}'`,
178+
);
179+
expect(result.exitCode).toBe(0);
180+
expect(result.stdout).toBe('{"1":"val"}\n');
181+
});
182+
183+
it('should evaluate empty string key {""} shorthand', async () => {
184+
const env = new Bash();
185+
const result = await env.exec(
186+
`echo '{"":"val","extra":"x"}' | jq -c '{""}'`,
187+
);
188+
expect(result.exitCode).toBe(0);
189+
expect(result.stdout).toBe('{"":"val"}\n');
190+
});
107191
});
108192
});

src/comparison-tests/fixtures/jq.comparison.fixtures.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@
6262
"stderr": "",
6363
"exitCode": 0
6464
},
65+
"348c7a17b8cc0a03": {
66+
"command": "jq -c '{\"\"}' data.json",
67+
"files": {
68+
"data.json": "{\"\":\"val\",\"extra\":\"x\"}"
69+
},
70+
"stdout": "{\"\":\"val\"}\n",
71+
"stderr": "",
72+
"exitCode": 0
73+
},
6574
"3a5bd13032387ef1": {
6675
"command": "jq 'reverse' data.json",
6776
"files": {
@@ -116,6 +125,15 @@
116125
"stderr": "",
117126
"exitCode": 0
118127
},
128+
"889ffd5c41c5b865": {
129+
"command": "jq -c '{\"a-b\"}' data.json",
130+
"files": {
131+
"data.json": "{\"a-b\":\"val\",\"extra\":\"x\"}"
132+
},
133+
"stdout": "{\"a-b\":\"val\"}\n",
134+
"stderr": "",
135+
"exitCode": 0
136+
},
119137
"91c885f22f4323a6": {
120138
"command": "jq '.' data.json",
121139
"files": {
@@ -265,6 +283,15 @@
265283
"stderr": "",
266284
"exitCode": 0
267285
},
286+
"f3069be0bd4e62b6": {
287+
"command": "jq -c '{\"1\"}' data.json",
288+
"files": {
289+
"data.json": "{\"1\":\"val\",\"extra\":\"x\"}"
290+
},
291+
"stdout": "{\"1\":\"val\"}\n",
292+
"stderr": "",
293+
"exitCode": 0
294+
},
268295
"f9870901c3117349": {
269296
"command": "jq -c '{\"if\"}' data.json",
270297
"files": {

src/comparison-tests/jq.comparison.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,27 @@ describe("jq command - Real Bash Comparison", () => {
268268
`jq -c '{"name", "v": .value}' data.json`,
269269
);
270270
});
271+
272+
it('should handle non-identifier key {"a-b"} shorthand', async () => {
273+
const env = await setupFiles(testDir, {
274+
"data.json": '{"a-b":"val","extra":"x"}',
275+
});
276+
await compareOutputs(env, testDir, `jq -c '{"a-b"}' data.json`);
277+
});
278+
279+
it('should handle numeric string key {"1"} shorthand', async () => {
280+
const env = await setupFiles(testDir, {
281+
"data.json": '{"1":"val","extra":"x"}',
282+
});
283+
await compareOutputs(env, testDir, `jq -c '{"1"}' data.json`);
284+
});
285+
286+
it('should handle empty string key {""} shorthand', async () => {
287+
const env = await setupFiles(testDir, {
288+
"data.json": '{"":"val","extra":"x"}',
289+
});
290+
await compareOutputs(env, testDir, `jq -c '{""}' data.json`);
291+
});
271292
});
272293

273294
describe("string functions", () => {

0 commit comments

Comments
 (0)