use crate::ast;
use crate::ast::AstNode;
use crate::tests::utils::parse_executable;
use crate::tests::utils::parse_mixed;
use crate::tests::utils::parse_schema;
fn assert_roundtrip_executable(source: &str) {
let result = parse_executable(source);
assert!(
!result.has_errors(),
"Parse failed:\n{}",
result.formatted_errors(),
);
let (doc, _) = result.into_valid().unwrap();
let reconstructed = doc.to_source(Some(source));
assert_eq!(reconstructed, source, "Round-trip mismatch");
}
fn assert_roundtrip_schema(source: &str) {
let result = parse_schema(source);
assert!(
!result.has_errors(),
"Parse failed:\n{}",
result.formatted_errors(),
);
let (doc, _) = result.into_valid().unwrap();
let reconstructed = doc.to_source(Some(source));
assert_eq!(reconstructed, source, "Round-trip mismatch");
}
fn assert_roundtrip_mixed(source: &str) {
let result = parse_mixed(source);
assert!(
!result.has_errors(),
"Parse failed:\n{}",
result.formatted_errors(),
);
let (doc, _) = result.into_valid().unwrap();
let reconstructed = doc.to_source(Some(source));
assert_eq!(reconstructed, source, "Round-trip mismatch");
}
#[test]
fn roundtrip_shorthand_query() {
let source = "{ field }";
assert_roundtrip_executable(source);
let (doc, _) = parse_executable(source).into_valid().unwrap();
if let ast::Definition::OperationDefinition(op) = &doc.definitions[0] {
if let ast::Selection::Field(field) =
&op.selection_set.selections[0]
{
assert_eq!(
field.to_source(Some(source)),
"field",
"Sub-node field slice mismatch",
);
} else {
panic!("Expected Field selection");
}
} else {
panic!("Expected OperationDefinition");
}
}
#[test]
fn roundtrip_named_query() {
let source = "query GetUser { name age }";
assert_roundtrip_executable(source);
let (doc, _) = parse_executable(source).into_valid().unwrap();
if let ast::Definition::OperationDefinition(op) = &doc.definitions[0] {
assert_eq!(
op.to_source(Some(source)),
source,
"Sub-node operation slice mismatch",
);
} else {
panic!("Expected OperationDefinition");
}
}
#[test]
fn roundtrip_query_with_variables() {
let source =
"query Q($id: ID!, $limit: Int = 10) { user(id: $id) { name } }";
assert_roundtrip_executable(source);
}
#[test]
fn roundtrip_mutation() {
let source =
"mutation CreateUser($name: String!) { createUser(name: $name) { id } }";
assert_roundtrip_executable(source);
}
#[test]
fn roundtrip_subscription() {
let source = "subscription OnMessage { messageAdded { text } }";
assert_roundtrip_executable(source);
}
#[test]
fn roundtrip_fragment_definition() {
let source =
"fragment UserFields on User { name email }\n\
query { user { ...UserFields } }";
assert_roundtrip_executable(source);
let (doc, _) = parse_executable(source).into_valid().unwrap();
assert_eq!(doc.definitions.len(), 2);
if let ast::Definition::FragmentDefinition(frag) = &doc.definitions[0] {
assert_eq!(
frag.to_source(Some(source)),
"fragment UserFields on User { name email }",
"Sub-node fragment slice mismatch",
);
} else {
panic!("Expected FragmentDefinition");
}
if let ast::Definition::OperationDefinition(op) = &doc.definitions[1] {
assert_eq!(
op.to_source(Some(source)),
"query { user { ...UserFields } }",
"Sub-node query slice mismatch",
);
} else {
panic!("Expected OperationDefinition");
}
}
#[test]
fn roundtrip_inline_fragment() {
let source =
"{ node { ... on User { name } ... on Bot { handle } } }";
assert_roundtrip_executable(source);
}
#[test]
fn roundtrip_all_value_types() {
let source = concat!(
"{ field(",
"int: 42, ",
"float: 3.14, ",
"str: \"hello\", ",
"yes: true, ",
"no: false, ",
"nil: null, ",
"enumVal: ACTIVE, ",
"list: [1, 2], ",
"obj: {key: \"val\"}",
") }",
);
assert_roundtrip_executable(source);
}
#[test]
fn roundtrip_directives() {
let source =
"query @skip(if: true) { field @include(if: false) }";
assert_roundtrip_executable(source);
}
#[test]
fn roundtrip_object_type() {
let source = concat!(
"type User {\n",
" name: String\n",
" age(minAge: Int = 0): Int\n",
"}",
);
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_interface_type() {
let source = "interface Node implements Entity { id: ID! }";
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_enum_type() {
let source = "enum Status { ACTIVE INACTIVE PENDING }";
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_union_type() {
let source = "union SearchResult = User | Post | Comment";
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_scalar_type() {
let source = "scalar DateTime";
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_input_object_type() {
let source = concat!(
"input CreateUserInput {\n",
" name: String!\n",
" email: String\n",
"}",
);
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_directive_definition() {
let source =
"directive @auth(role: String!) on FIELD_DEFINITION | OBJECT";
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_schema_definition() {
let source = "schema { query: Query mutation: Mutation }";
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_type_extensions() {
let source = concat!(
"extend type User { age: Int }\n",
"\n",
"extend enum Status { INACTIVE }\n",
"\n",
"extend union SearchResult = Photo\n",
"\n",
"extend interface Node { createdAt: DateTime }\n",
"\n",
"extend scalar Date @deprecated\n",
"\n",
"extend input CreateUserInput { nickname: String }",
);
assert_roundtrip_schema(source);
}
#[test]
fn roundtrip_multiline_with_comments() {
let source = concat!(
"# This is a comment\n",
"type User {\n",
" # The name field\n",
" name: String\n",
" age: Int\n",
"}",
);
assert_roundtrip_mixed(source);
}
#[test]
fn roundtrip_extra_whitespace() {
let source = " query { field } ";
assert_roundtrip_mixed(source);
}
#[test]
fn roundtrip_commas() {
let source = "{ a, b, c }";
assert_roundtrip_mixed(source);
}
#[test]
fn roundtrip_string_descriptions() {
let source = concat!(
"\"A user in the system\"\n",
"type User {\n",
" \"The user's name\"\n",
" name: String\n",
" \"\"\"\n",
" The user's email address.\n",
" Must be unique.\n",
" \"\"\"\n",
" email: String!\n",
"}",
);
assert_roundtrip_schema(source);
}