apollo-codegen-swift
Version:
Swift generator module for Apollo Codegen
411 lines (368 loc) • 12.4 kB
text/typescript
import { stripIndent } from "common-tags";
import { SwiftGenerator, SwiftSource, swift } from "../language";
import { valueFromAST } from "graphql";
describe("Swift code generation: Basic language constructs", () => {
let generator: SwiftGenerator<any>;
beforeEach(() => {
generator = new SwiftGenerator({});
});
it(`should generate a class declaration`, () => {
generator.classDeclaration(
{ className: "Hero", modifiers: ["public", "final"] },
() => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "Int",
});
}
);
expect(generator.output).toBe(stripIndent`
public final class Hero {
public var name: String
public var age: Int
}
`);
});
it(`should generate a class declaration matching modifiers`, () => {
generator.classDeclaration(
{ className: "Hero", modifiers: ["final"] },
() => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "Int",
});
}
);
expect(generator.output).toBe(stripIndent`
final class Hero {
public var name: String
public var age: Int
}
`);
});
it(`should generate a class declaration with proper escaping`, () => {
generator.classDeclaration(
{ className: "Type", modifiers: ["public", "final"] },
() => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "Int",
});
generator.propertyDeclaration({
propertyName: "self",
typeName: "Self",
});
}
);
expect(generator.output).toBe(stripIndent`
public final class \`Type\` {
public var name: String
public var age: Int
public var \`self\`: \`Self\`
}
`);
});
it(`should generate a struct declaration`, () => {
generator.structDeclaration({ structName: "Hero" }, false, () => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "Int",
});
});
expect(generator.output).toBe(stripIndent`
public struct Hero {
public var name: String
public var age: Int
}
`);
});
it(`should generate a namespaced fragment`, () => {
generator.structDeclaration(
{
structName: "Hero",
adoptedProtocols: ["GraphQLFragment"],
namespace: "StarWars",
},
false,
() => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "Int",
});
}
);
expect(generator.output).toBe(stripIndent`
public struct Hero: GraphQLFragment {
public var name: String
public var age: Int
}
`);
});
it(`should generate a namespaced fragment which is not public for individual files`, () => {
generator.structDeclaration(
{
structName: "Hero",
adoptedProtocols: ["GraphQLFragment"],
namespace: "StarWars",
},
true,
() => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "Int",
});
}
);
expect(generator.output).toBe(stripIndent`
struct Hero: GraphQLFragment {
public var name: String
public var age: Int
}
`);
});
it(`should generate an escaped struct declaration`, () => {
generator.structDeclaration({ structName: "Type" }, false, () => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "yearOfBirth",
typeName: "Int",
});
generator.propertyDeclaration({
propertyName: "self",
typeName: "Self",
});
});
expect(generator.output).toBe(stripIndent`
public struct \`Type\` {
public var name: String
public var yearOfBirth: Int
public var \`self\`: \`Self\`
}
`);
});
it(`should generate nested struct declarations`, () => {
generator.structDeclaration({ structName: "Hero" }, false, () => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.propertyDeclaration({
propertyName: "friends",
typeName: "[Friend]",
});
generator.structDeclaration({ structName: "Friend" }, false, () => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
});
});
});
expect(generator.output).toBe(stripIndent`
public struct Hero {
public var name: String
public var friends: [Friend]
public struct Friend {
public var name: String
}
}
`);
});
it(`should generate a protocol declaration`, () => {
generator.protocolDeclaration(
{ protocolName: "HeroDetails", adoptedProtocols: ["HasName"] },
() => {
generator.protocolPropertyDeclaration({
propertyName: "name",
typeName: "String",
});
generator.protocolPropertyDeclaration({
propertyName: "age",
typeName: "Int",
});
generator.protocolPropertyDeclaration({
propertyName: "default",
typeName: "Boolean",
});
}
);
expect(generator.output).toBe(stripIndent`
public protocol HeroDetails: HasName {
var name: String { get }
var age: Int { get }
var \`default\`: Boolean { get }
}
`);
});
it(`should handle multi-line descriptions`, () => {
generator.structDeclaration(
{ structName: "Hero", description: "A hero" },
false,
() => {
generator.propertyDeclaration({
propertyName: "name",
typeName: "String",
description: `A multiline comment \n on the hero's name.`,
});
generator.propertyDeclaration({
propertyName: "age",
typeName: "String",
description: `A multiline comment \n on the hero's age.`,
});
}
);
expect(generator.output).toMatchSnapshot();
});
});
describe("Swift code generation: Escaping", () => {
describe("using SwiftSource", () => {
it(`should escape identifiers`, () => {
expect(SwiftSource.identifier("self").source).toBe("`self`");
expect(SwiftSource.identifier("public").source).toBe("`public`");
expect(SwiftSource.identifier("Array<Type>").source).toBe(
"Array<`Type`>"
);
expect(SwiftSource.identifier("[Self?]?").source).toBe("[`Self`?]?");
});
it(`should not escape other words`, () => {
expect(SwiftSource.identifier("me").source).toBe("me");
expect(SwiftSource.identifier("_Self").source).toBe("_Self");
expect(SwiftSource.identifier("classes").source).toBe("classes");
});
it(`should escape fewer words in member position`, () => {
expect(SwiftSource.identifier(".self").source).toBe(".`self`");
expect(SwiftSource.identifier(".public").source).toBe(".public");
expect(SwiftSource.identifier("Foo.Self.Type.self.class").source).toBe(
"Foo.Self.`Type`.`self`.class"
);
});
it(`should escape fewer words at offset 0 with member escaping`, () => {
expect(SwiftSource.memberName("self").source).toBe("`self`");
expect(SwiftSource.memberName("public").source).toBe("public");
expect(SwiftSource.memberName(" public").source).toBe(" `public`");
expect(SwiftSource.memberName("Foo.Self.Type.self.class").source).toBe(
"Foo.Self.`Type`.`self`.class"
);
});
it(`should escape strings`, () => {
expect(SwiftSource.string("foobar").source).toBe('"foobar"');
expect(SwiftSource.string("foo\n bar ").source).toBe('"foo\\n bar "');
expect(SwiftSource.string("one'two\"three\\four\tfive").source).toBe(
'"one\'two\\"three\\\\four\\tfive"'
);
});
it(`should trim strings when asked`, () => {
expect(SwiftSource.string("foobar", true).source).toBe('"foobar"');
expect(SwiftSource.string("foo\n bar ", true).source).toBe('"foo bar"');
});
it(`should generate multiline strings`, () => {
expect(SwiftSource.multilineString("foobar").source).toBe(
'"""\nfoobar\n"""'
);
expect(SwiftSource.multilineString("foo\n bar ").source).toBe(
'"""\nfoo\n bar \n"""'
);
expect(SwiftSource.multilineString(`"""foo"""`).source).toBe(
'#"""\n"""foo"""\n"""#'
);
expect(SwiftSource.multilineString("foo\\nbar").source).toBe(
'#"""\nfoo\\nbar\n"""#'
);
expect(SwiftSource.multilineString(`"""\\"""#"""`).source).toBe(
'##"""\n"""\\"""#"""\n"""##'
);
expect(SwiftSource.multilineString(`foo\\\\#bar`).source).toBe(
'##"""\nfoo\\\\#bar\n"""##'
);
expect(SwiftSource.multilineString(`foo\\\\#\\##bar`).source).toBe(
'###"""\nfoo\\\\#\\##bar\n"""###'
);
expect(SwiftSource.multilineString("foo\\###nbar").source).toBe(
'####"""\nfoo\\###nbar\n"""####'
);
});
it(`should support concatenation`, () => {
expect(swift`one`.concat().source).toBe("one");
expect(swift`one`.concat(swift`two`).source).toBe("onetwo");
expect(swift`one`.concat(swift`two`, swift`three`).source).toBe(
"onetwothree"
);
});
it(`should support appending`, () => {
let value = swift`one`;
value.append();
expect(value.source).toBe("one");
value.append(swift`foo`);
expect(value.source).toBe("onefoo");
value.append(swift`bar`, swift`baz`, swift`qux`);
expect(value.source).toBe("onefoobarbazqux");
});
});
describe("using SwiftGenerator", () => {
let generator: SwiftGenerator<any>;
beforeEach(() => {
generator = new SwiftGenerator({});
});
it(`should not trim with multiline string if multiline strings are not suppressed and there is no triple quote`, () => {
generator.multilineString("foo\n bar ", false);
expect(generator.output).toBe('"""\nfoo\n bar \n"""');
});
it(`should trim with multilineString if multiline strings are suppressed`, () => {
generator.multilineString("foo\n bar ", true);
expect(generator.output).toBe('"foo bar"');
});
it(`shouldn't trim with multilineString when using """ even when multiline strings are suppressed`, () => {
generator.multilineString('"""\nfoo\n bar \n"""', true);
expect(generator.output).toBe('"\\"\\"\\"\\nfoo\\n bar \\n\\"\\"\\""');
});
});
describe("using template strings", () => {
it(`should escape interpolated strings but not string literals`, () => {
expect(swift`self`.source).toBe("self");
expect(swift`${"self"}`.source).toBe("`self`");
expect(swift`class ${"Foo.Type.self"}: ${"Protocol?"}`.source).toBe(
"class Foo.`Type`.`self`: `Protocol`?"
);
expect(swift`${["Self", "Foo.Self.self"]}`.source).toBe(
"`Self`,Foo.Self.`self`"
);
expect(swift`${true} ${"true"}`.source).toBe("true `true`");
expect(swift`${{ toString: () => "self" }}`.source).toBe("`self`");
});
it(`should not escape already-escaped interpolated strings`, () => {
expect(swift`${swift`${"self"}`}`.source).toBe("`self`");
expect(swift`${"public"} ${new SwiftSource("public")}`.source).toBe(
"`public` public"
);
});
it(`should not escape with the raw tag`, () => {
expect(SwiftSource.raw`${"self"}`.source).toBe("self");
});
});
});