1 | import { stripIndent } from "common-tags";
|
2 |
|
3 | import { SwiftGenerator, SwiftSource, swift } from "../language";
|
4 | import { valueFromAST } from "graphql";
|
5 |
|
6 | describe("Swift code generation: Basic language constructs", () => {
|
7 | let generator: SwiftGenerator<any>;
|
8 |
|
9 | beforeEach(() => {
|
10 | generator = new SwiftGenerator({});
|
11 | });
|
12 |
|
13 | it(`should generate a class declaration`, () => {
|
14 | generator.classDeclaration(
|
15 | { className: "Hero", modifiers: ["public", "final"] },
|
16 | () => {
|
17 | generator.propertyDeclaration({
|
18 | propertyName: "name",
|
19 | typeName: "String"
|
20 | });
|
21 | generator.propertyDeclaration({
|
22 | propertyName: "age",
|
23 | typeName: "Int"
|
24 | });
|
25 | }
|
26 | );
|
27 |
|
28 | expect(generator.output).toBe(stripIndent`
|
29 | public final class Hero {
|
30 | public var name: String
|
31 | public var age: Int
|
32 | }
|
33 | `);
|
34 | });
|
35 |
|
36 | it(`should generate a class declaration matching modifiers`, () => {
|
37 | generator.classDeclaration(
|
38 | { className: "Hero", modifiers: ["final"] },
|
39 | () => {
|
40 | generator.propertyDeclaration({
|
41 | propertyName: "name",
|
42 | typeName: "String"
|
43 | });
|
44 | generator.propertyDeclaration({
|
45 | propertyName: "age",
|
46 | typeName: "Int"
|
47 | });
|
48 | }
|
49 | );
|
50 |
|
51 | expect(generator.output).toBe(stripIndent`
|
52 | final class Hero {
|
53 | public var name: String
|
54 | public var age: Int
|
55 | }
|
56 | `);
|
57 | });
|
58 |
|
59 | it(`should generate a class declaration with proper escaping`, () => {
|
60 | generator.classDeclaration(
|
61 | { className: "Type", modifiers: ["public", "final"] },
|
62 | () => {
|
63 | generator.propertyDeclaration({
|
64 | propertyName: "name",
|
65 | typeName: "String"
|
66 | });
|
67 | generator.propertyDeclaration({
|
68 | propertyName: "age",
|
69 | typeName: "Int"
|
70 | });
|
71 | generator.propertyDeclaration({
|
72 | propertyName: "self",
|
73 | typeName: "Self"
|
74 | });
|
75 | }
|
76 | );
|
77 |
|
78 | expect(generator.output).toBe(stripIndent`
|
79 | public final class \`Type\` {
|
80 | public var name: String
|
81 | public var age: Int
|
82 | public var \`self\`: \`Self\`
|
83 | }
|
84 | `);
|
85 | });
|
86 |
|
87 | it(`should generate a struct declaration`, () => {
|
88 | generator.structDeclaration({ structName: "Hero" }, false, () => {
|
89 | generator.propertyDeclaration({
|
90 | propertyName: "name",
|
91 | typeName: "String"
|
92 | });
|
93 | generator.propertyDeclaration({
|
94 | propertyName: "age",
|
95 | typeName: "Int"
|
96 | });
|
97 | });
|
98 |
|
99 | expect(generator.output).toBe(stripIndent`
|
100 | public struct Hero {
|
101 | public var name: String
|
102 | public var age: Int
|
103 | }
|
104 | `);
|
105 | });
|
106 |
|
107 | it(`should generate a namespaced fragment`, () => {
|
108 | generator.structDeclaration(
|
109 | {
|
110 | structName: "Hero",
|
111 | adoptedProtocols: ["GraphQLFragment"],
|
112 | namespace: "StarWars"
|
113 | },
|
114 | false,
|
115 | () => {
|
116 | generator.propertyDeclaration({
|
117 | propertyName: "name",
|
118 | typeName: "String"
|
119 | });
|
120 | generator.propertyDeclaration({
|
121 | propertyName: "age",
|
122 | typeName: "Int"
|
123 | });
|
124 | }
|
125 | );
|
126 |
|
127 | expect(generator.output).toBe(stripIndent`
|
128 | public struct Hero: GraphQLFragment {
|
129 | public var name: String
|
130 | public var age: Int
|
131 | }
|
132 | `);
|
133 | });
|
134 |
|
135 | it(`should generate a namespaced fragment which is not public for individual files`, () => {
|
136 | generator.structDeclaration(
|
137 | {
|
138 | structName: "Hero",
|
139 | adoptedProtocols: ["GraphQLFragment"],
|
140 | namespace: "StarWars"
|
141 | },
|
142 | true,
|
143 | () => {
|
144 | generator.propertyDeclaration({
|
145 | propertyName: "name",
|
146 | typeName: "String"
|
147 | });
|
148 | generator.propertyDeclaration({
|
149 | propertyName: "age",
|
150 | typeName: "Int"
|
151 | });
|
152 | }
|
153 | );
|
154 |
|
155 | expect(generator.output).toBe(stripIndent`
|
156 | struct Hero: GraphQLFragment {
|
157 | public var name: String
|
158 | public var age: Int
|
159 | }
|
160 | `);
|
161 | });
|
162 |
|
163 | it(`should generate an escaped struct declaration`, () => {
|
164 | generator.structDeclaration({ structName: "Type" }, false, () => {
|
165 | generator.propertyDeclaration({
|
166 | propertyName: "name",
|
167 | typeName: "String"
|
168 | });
|
169 | generator.propertyDeclaration({
|
170 | propertyName: "yearOfBirth",
|
171 | typeName: "Int"
|
172 | });
|
173 | generator.propertyDeclaration({
|
174 | propertyName: "self",
|
175 | typeName: "Self"
|
176 | });
|
177 | });
|
178 |
|
179 | expect(generator.output).toBe(stripIndent`
|
180 | public struct \`Type\` {
|
181 | public var name: String
|
182 | public var yearOfBirth: Int
|
183 | public var \`self\`: \`Self\`
|
184 | }
|
185 | `);
|
186 | });
|
187 |
|
188 | it(`should generate nested struct declarations`, () => {
|
189 | generator.structDeclaration({ structName: "Hero" }, false, () => {
|
190 | generator.propertyDeclaration({
|
191 | propertyName: "name",
|
192 | typeName: "String"
|
193 | });
|
194 | generator.propertyDeclaration({
|
195 | propertyName: "friends",
|
196 | typeName: "[Friend]"
|
197 | });
|
198 |
|
199 | generator.structDeclaration({ structName: "Friend" }, false, () => {
|
200 | generator.propertyDeclaration({
|
201 | propertyName: "name",
|
202 | typeName: "String"
|
203 | });
|
204 | });
|
205 | });
|
206 |
|
207 | expect(generator.output).toBe(stripIndent`
|
208 | public struct Hero {
|
209 | public var name: String
|
210 | public var friends: [Friend]
|
211 |
|
212 | public struct Friend {
|
213 | public var name: String
|
214 | }
|
215 | }
|
216 | `);
|
217 | });
|
218 |
|
219 | it(`should generate a protocol declaration`, () => {
|
220 | generator.protocolDeclaration(
|
221 | { protocolName: "HeroDetails", adoptedProtocols: ["HasName"] },
|
222 | () => {
|
223 | generator.protocolPropertyDeclaration({
|
224 | propertyName: "name",
|
225 | typeName: "String"
|
226 | });
|
227 | generator.protocolPropertyDeclaration({
|
228 | propertyName: "age",
|
229 | typeName: "Int"
|
230 | });
|
231 | generator.protocolPropertyDeclaration({
|
232 | propertyName: "default",
|
233 | typeName: "Boolean"
|
234 | });
|
235 | }
|
236 | );
|
237 |
|
238 | expect(generator.output).toBe(stripIndent`
|
239 | public protocol HeroDetails: HasName {
|
240 | var name: String { get }
|
241 | var age: Int { get }
|
242 | var \`default\`: Boolean { get }
|
243 | }
|
244 | `);
|
245 | });
|
246 |
|
247 | it(`should handle multi-line descriptions`, () => {
|
248 | generator.structDeclaration(
|
249 | { structName: "Hero", description: "A hero" },
|
250 | false,
|
251 | () => {
|
252 | generator.propertyDeclaration({
|
253 | propertyName: "name",
|
254 | typeName: "String",
|
255 | description: `A multiline comment \n on the hero's name.`
|
256 | });
|
257 | generator.propertyDeclaration({
|
258 | propertyName: "age",
|
259 | typeName: "String",
|
260 | description: `A multiline comment \n on the hero's age.`
|
261 | });
|
262 | }
|
263 | );
|
264 |
|
265 | expect(generator.output).toMatchSnapshot();
|
266 | });
|
267 | });
|
268 |
|
269 | describe("Swift code generation: Escaping", () => {
|
270 | describe("using SwiftSource", () => {
|
271 | it(`should escape identifiers`, () => {
|
272 | expect(SwiftSource.identifier("self").source).toBe("`self`");
|
273 | expect(SwiftSource.identifier("public").source).toBe("`public`");
|
274 | expect(SwiftSource.identifier("Array<Type>").source).toBe(
|
275 | "Array<`Type`>"
|
276 | );
|
277 | expect(SwiftSource.identifier("[Self?]?").source).toBe("[`Self`?]?");
|
278 | });
|
279 |
|
280 | it(`should not escape other words`, () => {
|
281 | expect(SwiftSource.identifier("me").source).toBe("me");
|
282 | expect(SwiftSource.identifier("_Self").source).toBe("_Self");
|
283 | expect(SwiftSource.identifier("classes").source).toBe("classes");
|
284 | });
|
285 |
|
286 | it(`should escape fewer words in member position`, () => {
|
287 | expect(SwiftSource.identifier(".self").source).toBe(".`self`");
|
288 | expect(SwiftSource.identifier(".public").source).toBe(".public");
|
289 | expect(SwiftSource.identifier("Foo.Self.Type.self.class").source).toBe(
|
290 | "Foo.Self.`Type`.`self`.class"
|
291 | );
|
292 | });
|
293 |
|
294 | it(`should escape fewer words at offset 0 with member escaping`, () => {
|
295 | expect(SwiftSource.memberName("self").source).toBe("`self`");
|
296 | expect(SwiftSource.memberName("public").source).toBe("public");
|
297 | expect(SwiftSource.memberName(" public").source).toBe(" `public`");
|
298 | expect(SwiftSource.memberName("Foo.Self.Type.self.class").source).toBe(
|
299 | "Foo.Self.`Type`.`self`.class"
|
300 | );
|
301 | });
|
302 |
|
303 | it(`should escape strings`, () => {
|
304 | expect(SwiftSource.string("foobar").source).toBe('"foobar"');
|
305 | expect(SwiftSource.string("foo\n bar ").source).toBe('"foo\\n bar "');
|
306 | expect(SwiftSource.string("one'two\"three\\four\tfive").source).toBe(
|
307 | '"one\'two\\"three\\\\four\\tfive"'
|
308 | );
|
309 | });
|
310 |
|
311 | it(`should trim strings when asked`, () => {
|
312 | expect(SwiftSource.string("foobar", true).source).toBe('"foobar"');
|
313 | expect(SwiftSource.string("foo\n bar ", true).source).toBe('"foo bar"');
|
314 | });
|
315 |
|
316 | it(`should generate multiline strings`, () => {
|
317 | expect(SwiftSource.multilineString("foobar").source).toBe(
|
318 | '"""\nfoobar\n"""'
|
319 | );
|
320 | expect(SwiftSource.multilineString("foo\n bar ").source).toBe(
|
321 | '"""\nfoo\n bar \n"""'
|
322 | );
|
323 | expect(SwiftSource.multilineString(`"""foo"""`).source).toBe(
|
324 | '#"""\n"""foo"""\n"""#'
|
325 | );
|
326 | expect(SwiftSource.multilineString("foo\\nbar").source).toBe(
|
327 | '#"""\nfoo\\nbar\n"""#'
|
328 | );
|
329 | expect(SwiftSource.multilineString(`"""\\"""#"""`).source).toBe(
|
330 | '##"""\n"""\\"""#"""\n"""##'
|
331 | );
|
332 | expect(SwiftSource.multilineString(`foo\\\\#bar`).source).toBe(
|
333 | '##"""\nfoo\\\\#bar\n"""##'
|
334 | );
|
335 | expect(SwiftSource.multilineString(`foo\\\\#\\##bar`).source).toBe(
|
336 | '###"""\nfoo\\\\#\\##bar\n"""###'
|
337 | );
|
338 | expect(SwiftSource.multilineString("foo\\###nbar").source).toBe(
|
339 | '####"""\nfoo\\###nbar\n"""####'
|
340 | );
|
341 | });
|
342 |
|
343 | it(`should support concatenation`, () => {
|
344 | expect(swift`one`.concat().source).toBe("one");
|
345 | expect(swift`one`.concat(swift`two`).source).toBe("onetwo");
|
346 | expect(swift`one`.concat(swift`two`, swift`three`).source).toBe(
|
347 | "onetwothree"
|
348 | );
|
349 | });
|
350 |
|
351 | it(`should support appending`, () => {
|
352 | let value = swift`one`;
|
353 | value.append();
|
354 | expect(value.source).toBe("one");
|
355 | value.append(swift`foo`);
|
356 | expect(value.source).toBe("onefoo");
|
357 | value.append(swift`bar`, swift`baz`, swift`qux`);
|
358 | expect(value.source).toBe("onefoobarbazqux");
|
359 | });
|
360 | });
|
361 | describe("using SwiftGenerator", () => {
|
362 | let generator: SwiftGenerator<any>;
|
363 |
|
364 | beforeEach(() => {
|
365 | generator = new SwiftGenerator({});
|
366 | });
|
367 |
|
368 | it(`should not trim with multiline string if multiline strings are not suppressed and there is no triple quote`, () => {
|
369 | generator.multilineString("foo\n bar ", false);
|
370 |
|
371 | expect(generator.output).toBe('"""\nfoo\n bar \n"""');
|
372 | });
|
373 |
|
374 | it(`should trim with multilineString if multiline strings are suppressed`, () => {
|
375 | generator.multilineString("foo\n bar ", true);
|
376 |
|
377 | expect(generator.output).toBe('"foo bar"');
|
378 | });
|
379 |
|
380 | it(`shouldn't trim with multilineString when using """ even when multiline strings are suppressed`, () => {
|
381 | generator.multilineString('"""\nfoo\n bar \n"""', true);
|
382 | expect(generator.output).toBe('"\\"\\"\\"\\nfoo\\n bar \\n\\"\\"\\""');
|
383 | });
|
384 | });
|
385 | describe("using template strings", () => {
|
386 | it(`should escape interpolated strings but not string literals`, () => {
|
387 | expect(swift`self`.source).toBe("self");
|
388 | expect(swift`${"self"}`.source).toBe("`self`");
|
389 | expect(swift`class ${"Foo.Type.self"}: ${"Protocol?"}`.source).toBe(
|
390 | "class Foo.`Type`.`self`: `Protocol`?"
|
391 | );
|
392 | expect(swift`${["Self", "Foo.Self.self"]}`.source).toBe(
|
393 | "`Self`,Foo.Self.`self`"
|
394 | );
|
395 | expect(swift`${true} ${"true"}`.source).toBe("true `true`");
|
396 | expect(swift`${{ toString: () => "self" }}`.source).toBe("`self`");
|
397 | });
|
398 |
|
399 | it(`should not escape already-escaped interpolated strings`, () => {
|
400 | expect(swift`${swift`${"self"}`}`.source).toBe("`self`");
|
401 | expect(swift`${"public"} ${new SwiftSource("public")}`.source).toBe(
|
402 | "`public` public"
|
403 | );
|
404 | });
|
405 |
|
406 | it(`should not escape with the raw tag`, () => {
|
407 | expect(SwiftSource.raw`${"self"}`.source).toBe("self");
|
408 | });
|
409 | });
|
410 | });
|
411 |
|
\ | No newline at end of file |