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 variables;
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),
})))
},
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)),
}
}

View file

@ -218,12 +218,12 @@ pub trait GraphQLType<CtxT>: Sized {
///
/// The executor can be used to drive selections into sub-objects.
///
/// The default implementation panics through `unimplemented!()`.
/// The default implementation panics.
#[allow(unused_variables)]
fn resolve_field(&self, field_name: &str, arguments: &Arguments, executor: &mut Executor<CtxT>)
-> ExecutionResult
{
unimplemented!()
panic!("resolve_field must be implemented by object types");
}
/// 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
/// type matches, pass the instance along to `executor.resolve`.
///
/// The default implementation panics through `unimplemented()`.
/// The default implementation panics.
#[allow(unused_variables)]
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.
///
/// The default implementation panics through `unimplemented()`.
/// The default implementation panics.
#[allow(unused_variables)]
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.
@ -254,7 +258,7 @@ pub trait GraphQLType<CtxT>: Sized {
///
/// The default implementation uses `resolve_field` to resolve all fields,
/// 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 {
if let Some(selection_set) = selection_set {
let mut result = HashMap::new();
@ -262,7 +266,7 @@ pub trait GraphQLType<CtxT>: Sized {
Value::object(result)
}
else {
unimplemented!();
panic!("resolve() must be implemented by non-object output types");
}
}
}