Add directive tests, fix some bugs

This commit is contained in:
Magnus Hallin 2016-11-12 10:59:33 +01:00
parent 478c7b7819
commit 251f957a7f
4 changed files with 278 additions and 8 deletions

View file

@ -0,0 +1,251 @@
use std::collections::HashMap;
use value::Value;
use ast::InputValue;
use schema::model::RootNode;
struct TestType;
graphql_object!(TestType: () |&self| {
field a() -> &str {
"a"
}
field b() -> &str {
"b"
}
});
fn run_variable_query<F>(query: &str, vars: HashMap<String, InputValue>, f: F)
where F: Fn(&HashMap<String, Value>) -> ()
{
let schema = RootNode::new(TestType, ());
let (result, errs) = ::execute(query, None, &schema, &vars, &())
.expect("Execution failed");
assert_eq!(errs, []);
println!("Result: {:?}", result);
let obj = result.as_object_value().expect("Result is not an object");
f(obj);
}
fn run_query<F>(query: &str, f: F)
where F: Fn(&HashMap<String, Value>) -> ()
{
run_variable_query(query, HashMap::new(), f);
}
#[test]
fn scalar_include_true() {
run_query(
"{ a, b @include(if: true) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn scalar_include_false() {
run_query(
"{ a, b @include(if: false) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn scalar_skip_false() {
run_query(
"{ a, b @skip(if: false) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn scalar_skip_true() {
run_query(
"{ a, b @skip(if: true) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn fragment_spread_include_true() {
run_query(
"{ a, ...Frag @include(if: true) } fragment Frag on TestType { b }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn fragment_spread_include_false() {
run_query(
"{ a, ...Frag @include(if: false) } fragment Frag on TestType { b }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn fragment_spread_skip_false() {
run_query(
"{ a, ...Frag @skip(if: false) } fragment Frag on TestType { b }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn fragment_spread_skip_true() {
run_query(
"{ a, ...Frag @skip(if: true) } fragment Frag on TestType { b }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn inline_fragment_include_true() {
run_query(
"{ a, ... on TestType @include(if: true) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn inline_fragment_include_false() {
run_query(
"{ a, ... on TestType @include(if: false) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn inline_fragment_skip_false() {
run_query(
"{ a, ... on TestType @skip(if: false) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn inline_fragment_skip_true() {
run_query(
"{ a, ... on TestType @skip(if: true) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn anonymous_inline_fragment_include_true() {
run_query(
"{ a, ... @include(if: true) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn anonymous_inline_fragment_include_false() {
run_query(
"{ a, ... @include(if: false) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn anonymous_inline_fragment_skip_false() {
run_query(
"{ a, ... @skip(if: false) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn anonymous_inline_fragment_skip_true() {
run_query(
"{ a, ... @skip(if: true) { b } }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn scalar_include_true_skip_true() {
run_query(
"{ a, b @include(if: true) @skip(if: true) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn scalar_include_true_skip_false() {
run_query(
"{ a, b @include(if: true) @skip(if: false) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), Some(&Value::string("b")));
});
}
#[test]
fn scalar_include_false_skip_true() {
run_query(
"{ a, b @include(if: false) @skip(if: true) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}
#[test]
fn scalar_include_false_skip_false() {
run_query(
"{ a, b @include(if: false) @skip(if: false) }",
|result| {
assert_eq!(result.get("a"), Some(&Value::string("a")));
assert_eq!(result.get("b"), None);
});
}

View file

@ -1,3 +1,4 @@
mod introspection; mod introspection;
mod variables; mod variables;
mod enums; mod enums;
mod directives;

View file

@ -171,6 +171,20 @@ fn parse_fragment<'a>(parser: &mut Parser<'a>) -> UnlocatedParseResult<'a, Selec
directives: directives.map(|s| s.item), directives: directives.map(|s| s.item),
}))) })))
}, },
Token::At => {
let directives = try!(parse_directives(parser));
let selection_set = try!(parse_selection_set(parser));
Ok(Selection::InlineFragment(
Spanning::start_end(
&start_pos.clone(),
&selection_set.end,
InlineFragment {
type_condition: None,
directives: directives.map(|s| s.item),
selection_set: selection_set.item,
})))
},
_ => Err(parser.next().map(ParseError::UnexpectedToken)), _ => Err(parser.next().map(ParseError::UnexpectedToken)),
} }
} }

View file

@ -218,12 +218,12 @@ pub trait GraphQLType<CtxT>: Sized {
/// ///
/// The executor can be used to drive selections into sub-objects. /// The executor can be used to drive selections into sub-objects.
/// ///
/// The default implementation panics through `unimplemented!()`. /// The default implementation panics.
#[allow(unused_variables)] #[allow(unused_variables)]
fn resolve_field(&self, field_name: &str, arguments: &Arguments, executor: &mut Executor<CtxT>) fn resolve_field(&self, field_name: &str, arguments: &Arguments, executor: &mut Executor<CtxT>)
-> ExecutionResult -> ExecutionResult
{ {
unimplemented!() panic!("resolve_field must be implemented by object types");
} }
/// Resolve this interface or union into a concrete type /// Resolve this interface or union into a concrete type
@ -231,18 +231,22 @@ pub trait GraphQLType<CtxT>: Sized {
/// Try to resolve the current type into the type name provided. If the /// Try to resolve the current type into the type name provided. If the
/// type matches, pass the instance along to `executor.resolve`. /// type matches, pass the instance along to `executor.resolve`.
/// ///
/// The default implementation panics through `unimplemented()`. /// The default implementation panics.
#[allow(unused_variables)] #[allow(unused_variables)]
fn resolve_into_type(&self, type_name: &str, selection_set: Option<Vec<Selection>>, executor: &mut Executor<CtxT>) -> ExecutionResult { fn resolve_into_type(&self, type_name: &str, selection_set: Option<Vec<Selection>>, executor: &mut Executor<CtxT>) -> ExecutionResult {
unimplemented!(); if Self::name().unwrap() == type_name {
Ok(self.resolve(selection_set, executor))
} else {
panic!("resolve_into_type must be implemented by unions and interfaces");
}
} }
/// Return the concrete type name for this instance/union. /// Return the concrete type name for this instance/union.
/// ///
/// The default implementation panics through `unimplemented()`. /// The default implementation panics.
#[allow(unused_variables)] #[allow(unused_variables)]
fn concrete_type_name(&self, context: &CtxT) -> String { fn concrete_type_name(&self, context: &CtxT) -> String {
unimplemented!(); panic!("concrete_type_name must be implemented by unions and interfaces");
} }
/// Resolve the provided selection set against the current object. /// Resolve the provided selection set against the current object.
@ -254,7 +258,7 @@ pub trait GraphQLType<CtxT>: Sized {
/// ///
/// The default implementation uses `resolve_field` to resolve all fields, /// The default implementation uses `resolve_field` to resolve all fields,
/// including those through fragment expansion, for object types. For /// including those through fragment expansion, for object types. For
/// non-object types, this method panics through `unimplemented!()`. /// non-object types, this method panics.
fn resolve(&self, selection_set: Option<Vec<Selection>>, executor: &mut Executor<CtxT>) -> Value { fn resolve(&self, selection_set: Option<Vec<Selection>>, executor: &mut Executor<CtxT>) -> Value {
if let Some(selection_set) = selection_set { if let Some(selection_set) = selection_set {
let mut result = HashMap::new(); let mut result = HashMap::new();
@ -262,7 +266,7 @@ pub trait GraphQLType<CtxT>: Sized {
Value::object(result) Value::object(result)
} }
else { else {
unimplemented!(); panic!("resolve() must be implemented by non-object output types");
} }
} }
} }