From 251f957a7f353f830ba8f75feedd319d4334f565 Mon Sep 17 00:00:00 2001 From: Magnus Hallin Date: Sat, 12 Nov 2016 10:59:33 +0100 Subject: [PATCH] Add directive tests, fix some bugs --- src/executor_tests/directives.rs | 251 +++++++++++++++++++++++++++++++ src/executor_tests/mod.rs | 1 + src/parser/document.rs | 14 ++ src/types/base.rs | 20 ++- 4 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 src/executor_tests/directives.rs diff --git a/src/executor_tests/directives.rs b/src/executor_tests/directives.rs new file mode 100644 index 00000000..e3acaf08 --- /dev/null +++ b/src/executor_tests/directives.rs @@ -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(query: &str, vars: HashMap, f: F) + where F: Fn(&HashMap) -> () +{ + 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(query: &str, f: F) + where F: Fn(&HashMap) -> () +{ + 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); + }); +} diff --git a/src/executor_tests/mod.rs b/src/executor_tests/mod.rs index c3a93d1f..74b8ac9a 100644 --- a/src/executor_tests/mod.rs +++ b/src/executor_tests/mod.rs @@ -1,3 +1,4 @@ mod introspection; mod variables; mod enums; +mod directives; diff --git a/src/parser/document.rs b/src/parser/document.rs index 4592000e..5473e830 100644 --- a/src/parser/document.rs +++ b/src/parser/document.rs @@ -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)), } } diff --git a/src/types/base.rs b/src/types/base.rs index 11d0cf09..5d0f73f9 100644 --- a/src/types/base.rs +++ b/src/types/base.rs @@ -218,12 +218,12 @@ pub trait GraphQLType: 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) -> 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: 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>, executor: &mut Executor) -> 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: 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>, executor: &mut Executor) -> Value { if let Some(selection_set) = selection_set { let mut result = HashMap::new(); @@ -262,7 +266,7 @@ pub trait GraphQLType: Sized { Value::object(result) } else { - unimplemented!(); + panic!("resolve() must be implemented by non-object output types"); } } }