Skip to content

Commit ae89ef7

Browse files
committed
Add integration test suite for GraphQL emitter
Expands the test coverage of the component-based emitter to exercise the full surface area of TypeSpec-to-GraphQL translation. New test files: - nullability.test.ts: required vs. optional vs. T | null, input and output side semantics, list element nullability - input-output-splitting.test.ts: models used as input, output, or both; verifies correct suffixing and separation - unions.test.ts: named union types, nullable variants, wrapper models - enums.test.ts: enum declarations, enum values, enum references in model properties and operations - arrays.test.ts: array element non-null semantics and nested arrays - circular-references.test.ts: self-referential and mutually recursive model definitions - deprecation.test.ts: @deprecated decorator propagation to SDL - doc-comments.test.ts: doc-comment propagation to SDL descriptions Extends e2e.test.ts beyond the single smoke test into a full scenario suite (complete API schema, custom scalars, union output types, models used only as input, mutations, empty schema handling, operation parameters, and complex SDL validation). Extends test/main.tsp with the fixture models, operations, and helpers needed by the e2e and integration tests. No production code changes — this PR is tests only.
1 parent cf1eb14 commit ae89ef7

11 files changed

Lines changed: 3280 additions & 19 deletions
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { describe, expect, it } from "vitest";
2+
import { emitSingleSchema } from "./test-host.js";
3+
4+
describe("arrays", () => {
5+
it("supports array types", async () => {
6+
const code = `
7+
@schema
8+
namespace TestNamespace {
9+
model Tag {
10+
name: string;
11+
color: string;
12+
}
13+
14+
model Article {
15+
id: string;
16+
title: string;
17+
tags: Tag[];
18+
categories: string[];
19+
}
20+
21+
@query
22+
op getArticle(id: string): Article;
23+
24+
@query
25+
op listArticles(): Article[];
26+
}
27+
`;
28+
29+
const result = await emitSingleSchema(code, {});
30+
31+
expect(result).toMatchInlineSnapshot(`
32+
"type Tag {
33+
name: String!
34+
color: String!
35+
}
36+
37+
type Article {
38+
id: String!
39+
title: String!
40+
tags: [Tag!]!
41+
categories: [String!]!
42+
}
43+
44+
type Query {
45+
getArticle(id: String!): Article!
46+
listArticles: [Article!]!
47+
}
48+
49+
"
50+
`);
51+
});
52+
53+
it("emits list types for array properties", async () => {
54+
const code = `
55+
@schema
56+
namespace TestNamespace {
57+
model User {
58+
id: string;
59+
tags: string[];
60+
}
61+
62+
@query
63+
op getUser(): User;
64+
}
65+
`;
66+
67+
const result = await emitSingleSchema(code, {});
68+
expect(result).toMatchInlineSnapshot(`
69+
"type User {
70+
id: String!
71+
tags: [String!]!
72+
}
73+
74+
type Query {
75+
getUser: User!
76+
}
77+
78+
"
79+
`);
80+
});
81+
82+
it("emits nullable list items for optional element types", async () => {
83+
const code = `
84+
@schema
85+
namespace TestNamespace {
86+
model User {
87+
id: string;
88+
tags: (string | null)[];
89+
}
90+
91+
@query
92+
op getUser(): User;
93+
}
94+
`;
95+
96+
const result = await emitSingleSchema(code, {});
97+
expect(result).toMatchInlineSnapshot(`
98+
"type User {
99+
id: String!
100+
tags: [String]!
101+
}
102+
103+
type Query {
104+
getUser: User!
105+
}
106+
107+
"
108+
`);
109+
});
110+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, expect, it } from "vitest";
2+
import { emitSingleSchema } from "./test-host.js";
3+
4+
describe("circular references", () => {
5+
it("handles circular references between models", async () => {
6+
const code = `
7+
@schema
8+
namespace TestNamespace {
9+
model User {
10+
id: string;
11+
name: string;
12+
posts: Post[];
13+
}
14+
15+
model Post {
16+
id: string;
17+
title: string;
18+
author: User;
19+
comments: Comment[];
20+
}
21+
22+
model Comment {
23+
id: string;
24+
text: string;
25+
author: User;
26+
post: Post;
27+
}
28+
29+
@query
30+
op getUser(id: string): User;
31+
32+
@query
33+
op getPost(id: string): Post;
34+
}
35+
`;
36+
37+
const result = await emitSingleSchema(code, {});
38+
39+
expect(result).toMatchInlineSnapshot(`
40+
"type User {
41+
id: String!
42+
name: String!
43+
posts: [Post!]!
44+
}
45+
46+
type Post {
47+
id: String!
48+
title: String!
49+
author: User!
50+
comments: [Comment!]!
51+
}
52+
53+
type Comment {
54+
id: String!
55+
text: String!
56+
author: User!
57+
post: Post!
58+
}
59+
60+
type Query {
61+
getUser(id: String!): User!
62+
getPost(id: String!): Post!
63+
}
64+
65+
"
66+
`);
67+
});
68+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, it } from "vitest";
2+
import { emitSingleSchema } from "./test-host.js";
3+
4+
describe("deprecation", () => {
5+
it("supports @deprecated directive", async () => {
6+
const code = `
7+
@schema
8+
namespace TestNamespace {
9+
model User {
10+
id: string;
11+
name: string;
12+
#deprecated "Use email instead"
13+
username: string;
14+
}
15+
16+
@query
17+
op getUser(id: string): User;
18+
19+
#deprecated "Use getUserById instead"
20+
@query
21+
op findUser(id: string): User;
22+
}
23+
`;
24+
25+
const result = await emitSingleSchema(code, {});
26+
27+
expect(result).toMatchInlineSnapshot(`
28+
"type User {
29+
id: String!
30+
name: String!
31+
username: String! @deprecated(reason: "Use email instead")
32+
}
33+
34+
type Query {
35+
getUser(id: String!): User!
36+
findUser(id: String!): User! @deprecated(reason: "Use getUserById instead")
37+
}
38+
39+
"
40+
`);
41+
});
42+
});
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { describe, expect, it } from "vitest";
2+
import { emitSingleSchema } from "./test-host.js";
3+
4+
describe("doc comments", () => {
5+
it("propagates doc comments to model descriptions", async () => {
6+
const code = `
7+
@schema
8+
namespace TestNamespace {
9+
/** A user in the system */
10+
model User {
11+
id: string;
12+
name: string;
13+
}
14+
15+
@query
16+
op getUser(id: string): User;
17+
}
18+
`;
19+
20+
const result = await emitSingleSchema(code, {});
21+
expect(result).toMatchInlineSnapshot(`
22+
""""A user in the system"""
23+
type User {
24+
id: String!
25+
name: String!
26+
}
27+
28+
type Query {
29+
getUser(id: String!): User!
30+
}
31+
32+
"
33+
`);
34+
});
35+
36+
it("propagates doc comments to field descriptions", async () => {
37+
const code = `
38+
@schema
39+
namespace TestNamespace {
40+
model User {
41+
/** The unique identifier */
42+
id: string;
43+
/** The user's display name */
44+
name: string;
45+
}
46+
47+
@query
48+
op getUser(id: string): User;
49+
}
50+
`;
51+
52+
const result = await emitSingleSchema(code, {});
53+
expect(result).toMatchInlineSnapshot(`
54+
"type User {
55+
"""The unique identifier"""
56+
id: String!
57+
58+
"""The user's display name"""
59+
name: String!
60+
}
61+
62+
type Query {
63+
getUser(id: String!): User!
64+
}
65+
66+
"
67+
`);
68+
});
69+
70+
it("propagates doc comments to enum descriptions", async () => {
71+
const code = `
72+
@schema
73+
namespace TestNamespace {
74+
/** The role of a user */
75+
enum Role {
76+
/** Administrator with full access */
77+
Admin,
78+
/** Regular user */
79+
User,
80+
}
81+
82+
model Person {
83+
role: Role;
84+
}
85+
86+
@query
87+
op getPerson(): Person;
88+
}
89+
`;
90+
91+
const result = await emitSingleSchema(code, {});
92+
expect(result).toMatchInlineSnapshot(`
93+
""""The role of a user"""
94+
enum Role {
95+
"""Administrator with full access"""
96+
Admin
97+
98+
"""Regular user"""
99+
User
100+
}
101+
102+
type Person {
103+
role: Role!
104+
}
105+
106+
type Query {
107+
getPerson: Person!
108+
}
109+
110+
"
111+
`);
112+
});
113+
});

0 commit comments

Comments
 (0)