//! parser 模块的集成测试。 //! Integration tests for the parser module. use focus_json::{parse, JsonError, JsonValue}; fn ok(input: &str) -> JsonValue { parse(input).unwrap_or_else(|e| panic!("failed to parse {:?}: {}", input, e)) } fn err(input: &str) -> JsonError { parse(input).expect_err(&format!("expected parse error for {:?}", input)) } // ---- literals --------------------------------------------------------- #[test] fn parses_literals() { assert_eq!(ok("true"), JsonValue::Bool(true)); assert_eq!(ok("false"), JsonValue::Bool(false)); assert_eq!(ok("null"), JsonValue::Null); } #[test] fn rejects_truncated_keywords() { assert!(err("tru").message.contains("expected 'true'")); assert!(err("nul").message.contains("expected 'null'")); } // ---- numbers ---------------------------------------------------------- #[test] fn parses_numbers() { assert_eq!(ok("0"), JsonValue::Num(0.0)); assert_eq!(ok("-0"), JsonValue::Num(0.0)); assert_eq!(ok("42"), JsonValue::Num(42.0)); assert_eq!(ok("-42"), JsonValue::Num(-42.0)); assert_eq!(ok("12.345"), JsonValue::Num(12.345)); assert_eq!(ok("1e10"), JsonValue::Num(1e10)); assert_eq!(ok("1.5e-3"), JsonValue::Num(1.5e-3)); assert_eq!(ok("2.5E+4"), JsonValue::Num(2.5e4)); } #[test] fn rejects_leading_zeros() { assert!(err("01").message.contains("leading zero")); assert!(err("00").message.contains("leading zero")); } #[test] fn rejects_trailing_dot() { assert!(err("3.").message.contains("decimal")); } #[test] fn rejects_empty_exponent() { assert!(err("1e").message.contains("exponent")); } // ---- strings ---------------------------------------------------------- #[test] fn parses_simple_strings() { assert_eq!(ok(r#""hello""#), JsonValue::Str("hello".into())); assert_eq!(ok(r#""""#), JsonValue::Str("".into())); } #[test] fn parses_string_escapes() { assert_eq!(ok(r#""a\"b""#), JsonValue::Str("a\"b".into())); assert_eq!(ok(r#""\n\t\r""#), JsonValue::Str("\n\t\r".into())); assert_eq!(ok(r#""\\""#), JsonValue::Str("\\".into())); assert_eq!(ok(r#""\/""#), JsonValue::Str("/".into())); assert_eq!(ok(r#""\b\f""#), JsonValue::Str("\u{0008}\u{000C}".into())); } #[test] fn parses_unicode_escapes() { assert_eq!(ok(r#""\u0041""#), JsonValue::Str("A".into())); assert_eq!(ok(r#""\u4e2d""#), JsonValue::Str("中".into())); // U+1F600 😀 的代理对。 // Surrogate pair for U+1F600 😀 assert_eq!(ok(r#""\uD83D\uDE00""#), JsonValue::Str("😀".into())); } #[test] fn rejects_lone_surrogates() { assert!(err(r#""\uD800""#).message.contains("low surrogate")); assert!(err(r#""\uDC00""#).message.contains("low surrogate")); } #[test] fn rejects_unterminated_string() { assert!(err(r#""abc"#).message.contains("unterminated")); } #[test] fn rejects_unescaped_control_chars() { assert!(err("\"a\nb\"").message.contains("control character")); } #[test] fn handles_utf8_multibyte() { assert_eq!(ok("\"中文\""), JsonValue::Str("中文".into())); assert_eq!(ok("\"😀\""), JsonValue::Str("😀".into())); } // ---- objects / arrays ------------------------------------------------- #[test] fn parses_empty_containers() { assert_eq!(ok("{}"), JsonValue::obj()); assert_eq!(ok("[]"), JsonValue::arr()); } #[test] fn parses_nested() { let v = ok(r#"{"a":[1,2,{"b":true}],"c":null}"#); assert_eq!(v.get_arr("a").unwrap()[2].get_bool("b"), Some(true)); assert!(v.get("c").unwrap().is_null()); } #[test] fn handles_whitespace() { let v = ok(" { \"x\" : 1 } "); assert_eq!(v.get_num("x"), Some(1.0)); } // ---- structural errors ------------------------------------------------ #[test] fn rejects_trailing_comma() { // 尾随逗号会被拒绝,因为下一个字符(']' 或 '}')不是合法的值/成员起始。 // A trailing comma is rejected because the next char (']' or '}') is // not a valid value/member start. assert!(parse("[1,2,]").is_err()); assert!(parse("{\"a\":1,}").is_err()); } #[test] fn rejects_trailing_characters() { let e = err("1 2"); assert!(e.message.contains("trailing")); } #[test] fn rejects_empty_input() { assert!(err("").message.contains("end of input")); assert!(err(" ").message.contains("end of input")); } #[test] fn rejects_unclosed_containers() { assert!(err("[1,2").message.contains("end of input")); assert!(err("{\"a\":1").message.contains("end of input")); }