use crate::token::GraphQLTokenKind;
use crate::GraphQLStringParsingError;
#[test]
fn is_punctuator_returns_true_for_punctuators() {
let punctuators: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::Ampersand,
GraphQLTokenKind::At,
GraphQLTokenKind::Bang,
GraphQLTokenKind::Colon,
GraphQLTokenKind::CurlyBraceClose,
GraphQLTokenKind::CurlyBraceOpen,
GraphQLTokenKind::Dollar,
GraphQLTokenKind::Ellipsis,
GraphQLTokenKind::Equals,
GraphQLTokenKind::ParenClose,
GraphQLTokenKind::ParenOpen,
GraphQLTokenKind::Pipe,
GraphQLTokenKind::SquareBracketClose,
GraphQLTokenKind::SquareBracketOpen,
];
for punctuator in punctuators {
assert!(
punctuator.is_punctuator(),
"{punctuator:?} should be identified as a punctuator"
);
}
}
#[test]
fn is_punctuator_returns_false_for_non_punctuators() {
let non_punctuators: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::int_value_owned("123".to_string()),
GraphQLTokenKind::float_value_owned("1.5".to_string()),
GraphQLTokenKind::string_value_owned("\"hello\"".to_string()),
GraphQLTokenKind::True,
GraphQLTokenKind::False,
GraphQLTokenKind::Null,
GraphQLTokenKind::Eof,
GraphQLTokenKind::error("test error", Default::default()),
];
for token in non_punctuators {
assert!(
!token.is_punctuator(),
"{token:?} should NOT be identified as a punctuator"
);
}
}
#[test]
fn as_punctuator_str_returns_correct_strings() {
let expected: Vec<(GraphQLTokenKind<'static>, &'static str)> = vec![
(GraphQLTokenKind::Ampersand, "&"),
(GraphQLTokenKind::At, "@"),
(GraphQLTokenKind::Bang, "!"),
(GraphQLTokenKind::Colon, ":"),
(GraphQLTokenKind::CurlyBraceClose, "}"),
(GraphQLTokenKind::CurlyBraceOpen, "{"),
(GraphQLTokenKind::Dollar, "$"),
(GraphQLTokenKind::Ellipsis, "..."),
(GraphQLTokenKind::Equals, "="),
(GraphQLTokenKind::ParenClose, ")"),
(GraphQLTokenKind::ParenOpen, "("),
(GraphQLTokenKind::Pipe, "|"),
(GraphQLTokenKind::SquareBracketClose, "]"),
(GraphQLTokenKind::SquareBracketOpen, "["),
];
for (token, expected_str) in expected {
assert_eq!(
token.as_punctuator_str(),
Some(expected_str),
"{token:?} should return {expected_str:?}"
);
}
}
#[test]
fn as_punctuator_str_returns_none_for_non_punctuators() {
let non_punctuators: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::int_value_owned("123".to_string()),
GraphQLTokenKind::True,
GraphQLTokenKind::Eof,
];
for token in non_punctuators {
assert_eq!(
token.as_punctuator_str(),
None,
"{token:?} should return None for as_punctuator_str()"
);
}
}
#[test]
fn is_value_returns_true_for_value_tokens() {
let value_tokens: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::int_value_owned("42".to_string()),
GraphQLTokenKind::float_value_owned("3.14".to_string()),
GraphQLTokenKind::string_value_owned("\"hello\"".to_string()),
GraphQLTokenKind::True,
GraphQLTokenKind::False,
GraphQLTokenKind::Null,
];
for token in value_tokens {
assert!(
token.is_value(),
"{token:?} should be identified as a value"
);
}
}
#[test]
fn is_value_returns_false_for_non_value_tokens() {
let non_value_tokens: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::Ampersand,
GraphQLTokenKind::CurlyBraceOpen,
GraphQLTokenKind::Eof,
GraphQLTokenKind::error("test", Default::default()),
];
for token in non_value_tokens {
assert!(
!token.is_value(),
"{token:?} should NOT be identified as a value"
);
}
}
#[test]
fn is_error_returns_true_only_for_error_kind() {
let error_token = GraphQLTokenKind::error("test error", Default::default());
assert!(error_token.is_error());
let non_error_tokens: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::int_value_owned("123".to_string()),
GraphQLTokenKind::True,
GraphQLTokenKind::Eof,
GraphQLTokenKind::Bang,
];
for token in non_error_tokens {
assert!(
!token.is_error(),
"{token:?} should NOT be identified as an error"
);
}
}
#[test]
fn parse_int_value_valid_integers() {
let test_cases = [
("0", 0i64),
("1", 1),
("42", 42),
("-1", -1),
("-42", -42),
("123456789", 123456789),
("-123456789", -123456789),
("9223372036854775807", i64::MAX), ("-9223372036854775808", i64::MIN), ];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::int_value_owned(raw.to_string());
let result = token.parse_int_value();
assert!(
result.is_some(),
"parse_int_value() should return Some for IntValue token"
);
assert_eq!(
result.unwrap().unwrap(),
expected,
"Parsing {raw:?} should yield {expected}"
);
}
}
#[test]
fn parse_int_value_returns_none_for_non_int() {
let non_int_tokens: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::float_value_owned("1.5".to_string()),
GraphQLTokenKind::string_value_owned("\"123\"".to_string()),
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::True,
GraphQLTokenKind::Eof,
];
for token in non_int_tokens {
assert!(
token.parse_int_value().is_none(),
"parse_int_value() should return None for {token:?}"
);
}
}
#[test]
fn parse_int_value_overflow_returns_error() {
let token = GraphQLTokenKind::int_value_owned(
"99999999999999999999999999".to_string()
);
let result = token.parse_int_value();
assert!(result.is_some());
assert!(
result.unwrap().is_err(),
"Overflow value should produce ParseIntError"
);
}
#[test]
fn parse_float_value_valid_floats() {
let test_cases = [
("0.0", 0.0f64),
("1.0", 1.0),
("3.25", 3.25),
("-3.25", -3.25),
("1e10", 1e10),
("1E10", 1e10),
("1.5e10", 1.5e10),
("1.5E-10", 1.5e-10),
("-1.5e+10", -1.5e10),
];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::float_value_owned(raw.to_string());
let result = token.parse_float_value();
assert!(
result.is_some(),
"parse_float_value() should return Some for FloatValue token"
);
let parsed = result.unwrap().unwrap();
assert!(
(parsed - expected).abs() < 1e-10,
"Parsing {raw:?} should yield {expected}, got {parsed}"
);
}
}
#[test]
fn parse_float_value_returns_none_for_non_float() {
let non_float_tokens: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::int_value_owned("123".to_string()),
GraphQLTokenKind::string_value_owned("\"1.5\"".to_string()),
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::True,
];
for token in non_float_tokens {
assert!(
token.parse_float_value().is_none(),
"parse_float_value() should return None for {token:?}"
);
}
}
#[test]
fn parse_string_value_basic() {
let test_cases = [
(r#""""#, ""), (r#""hello""#, "hello"), (r#""hello world""#, "hello world"), (r#""123""#, "123"), ];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value();
assert!(
result.is_some(),
"parse_string_value() should return Some for StringValue token"
);
assert_eq!(
result.unwrap().unwrap(),
expected,
"Parsing {raw:?} should yield {expected:?}"
);
}
}
#[test]
fn parse_string_value_returns_none_for_non_string() {
let non_string_tokens: Vec<GraphQLTokenKind<'static>> = vec![
GraphQLTokenKind::int_value_owned("123".to_string()),
GraphQLTokenKind::float_value_owned("1.5".to_string()),
GraphQLTokenKind::name_owned("foo".to_string()),
GraphQLTokenKind::True,
];
for token in non_string_tokens {
assert!(
token.parse_string_value().is_none(),
"parse_string_value() should return None for {token:?}"
);
}
}
#[test]
fn parse_string_standard_escapes() {
let test_cases = [
(r#""hello\nworld""#, "hello\nworld"), (r#""hello\rworld""#, "hello\rworld"), (r#""hello\tworld""#, "hello\tworld"), (r#""hello\\world""#, "hello\\world"), (r#""hello\"world""#, "hello\"world"), (r#""hello\/world""#, "hello/world"), (r#""hello\bworld""#, "hello\u{0008}world"), (r#""hello\fworld""#, "hello\u{000C}world"), ];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert_eq!(
result.unwrap(),
expected,
"Escape sequence in {raw:?} should produce {expected:?}"
);
}
}
#[test]
fn parse_string_invalid_escape_sequence() {
let invalid_escapes = [
r#""hello\xworld""#, r#""hello\qworld""#, r#""hello\!world""#, r#""hello\aworld""#, ];
for raw in invalid_escapes {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidEscapeSequence(_))),
"Invalid escape in {raw:?} should produce InvalidEscapeSequence error"
);
}
}
#[test]
fn parse_string_trailing_backslash() {
let token = GraphQLTokenKind::string_value_owned(r#""hello\""#.to_string());
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidEscapeSequence(_))),
"Trailing backslash should produce InvalidEscapeSequence error"
);
}
#[test]
fn parse_unicode_escape_fixed_4_digit() {
let test_cases = [
(r#""\u0041""#, "A"), (r#""\u0048\u0069""#, "Hi"), (r#""\u00E9""#, "\u{00E9}"), (r#""\u4E2D""#, "\u{4E2D}"), ];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert_eq!(
result.unwrap(),
expected,
"Unicode escape in {raw:?} should produce {expected:?}"
);
}
}
#[test]
fn parse_unicode_escape_variable_length() {
let test_cases = [
(r#""\u{41}""#, "A"), (r#""\u{0041}""#, "A"), (r#""\u{1F600}""#, "\u{1F600}"), (r#""\u{10FFFF}""#, "\u{10FFFF}"), ];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert_eq!(
result.unwrap(),
expected,
"Unicode escape in {raw:?} should produce {expected:?}"
);
}
}
#[test]
fn parse_unicode_escape_empty_braces() {
let token = GraphQLTokenKind::string_value_owned(r#""\u{}""#.to_string());
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidUnicodeEscape(_))),
"Empty unicode braces should produce InvalidUnicodeEscape error"
);
}
#[test]
fn parse_unicode_escape_invalid_hex() {
let invalid_cases = [
r#""\u{XYZ}""#, r#""\u{GHIJ}""#, r#""\uGHIJ""#, ];
for raw in invalid_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidUnicodeEscape(_))),
"Invalid hex in {raw:?} should produce InvalidUnicodeEscape error"
);
}
}
#[test]
fn parse_unicode_escape_out_of_range() {
let token = GraphQLTokenKind::string_value_owned(
r#""\u{110000}""#.to_string()
);
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidUnicodeEscape(_))),
"Code point above U+10FFFF should produce InvalidUnicodeEscape error"
);
}
#[test]
fn parse_unicode_escape_incomplete_fixed() {
let incomplete_cases = [
r#""\u""#, r#""\u0""#, r#""\u00""#, r#""\u004""#, ];
for raw in incomplete_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidUnicodeEscape(_))),
"Incomplete Unicode escape in {raw:?} should produce error"
);
}
}
#[test]
fn parse_unicode_escape_unclosed_braces() {
let token = GraphQLTokenKind::string_value_owned(
r#""\u{1234""#.to_string()
);
let result = token.parse_string_value().unwrap();
assert!(
matches!(result, Err(GraphQLStringParsingError::InvalidUnicodeEscape(_))),
"Unclosed unicode braces should produce InvalidUnicodeEscape error"
);
}
#[test]
fn parse_block_string_basic() {
let test_cases = [
(r#""""hello""""#, "hello"),
(r#""""hello world""""#, "hello world"),
(r#""""line1
line2""""#, "line1\nline2"),
];
for (raw, expected) in test_cases {
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap();
assert_eq!(
result.unwrap(),
expected,
"Block string {raw:?} should produce {expected:?}"
);
}
}
#[test]
fn parse_block_string_indentation_stripping() {
let raw = r#""""
line1
line2
""""#;
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap().unwrap();
assert_eq!(result, "line1\nline2");
}
#[test]
fn parse_block_string_indentation_edge_case() {
let raw = r#""""
short
longer
""""#;
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap().unwrap();
assert_eq!(result, "short\n longer");
}
#[test]
fn parse_block_string_escaped_triple_quotes() {
let raw = r#""""contains \""" quotes""""#;
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap().unwrap();
assert_eq!(result, r#"contains """ quotes"#);
}
#[test]
fn parse_block_string_trims_blank_lines() {
let raw = r#""""
content
""""#;
let token = GraphQLTokenKind::string_value_owned(raw.to_string());
let result = token.parse_string_value().unwrap().unwrap();
assert_eq!(result, "content");
}
#[test]
fn constructor_borrowed_variants() {
let name = GraphQLTokenKind::name_borrowed("foo");
assert!(matches!(name, GraphQLTokenKind::Name(_)));
let int_val = GraphQLTokenKind::int_value_borrowed("123");
assert!(matches!(int_val, GraphQLTokenKind::IntValue(_)));
let float_val = GraphQLTokenKind::float_value_borrowed("1.5");
assert!(matches!(float_val, GraphQLTokenKind::FloatValue(_)));
let string_val = GraphQLTokenKind::string_value_borrowed("\"hello\"");
assert!(matches!(string_val, GraphQLTokenKind::StringValue(_)));
}
#[test]
fn constructor_owned_variants() {
let name = GraphQLTokenKind::name_owned("foo".to_string());
assert!(matches!(name, GraphQLTokenKind::Name(_)));
let int_val = GraphQLTokenKind::int_value_owned("123".to_string());
assert!(matches!(int_val, GraphQLTokenKind::IntValue(_)));
let float_val = GraphQLTokenKind::float_value_owned("1.5".to_string());
assert!(matches!(float_val, GraphQLTokenKind::FloatValue(_)));
let string_val = GraphQLTokenKind::string_value_owned("\"hello\"".to_string());
assert!(matches!(string_val, GraphQLTokenKind::StringValue(_)));
}
#[test]
fn constructor_error() {
let notes = Default::default();
let error = GraphQLTokenKind::error("test message", notes);
assert!(error.is_error());
if let GraphQLTokenKind::Error(err) = error {
assert_eq!(err.message, "test message");
} else {
panic!("Expected Error variant");
}
}