use super::*;
use std::collections::{HashMap, HashSet};
fn accessors_for(path: &str) -> Vec<(&'static str, String)> {
const LANGUAGES: &[&str] = &[
"rust",
"python",
"typescript",
"node",
"wasm",
"go",
"java",
"kotlin",
"kotlin_android",
"csharp",
"ruby",
"php",
"elixir",
"r",
"c",
"swift",
"dart",
"zig",
"gleam",
];
let mut fields = HashMap::new();
fields.insert("theme".to_string(), path.to_string());
let resolver = FieldResolver::new(
&fields,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
LANGUAGES
.iter()
.map(|language| (*language, resolver.accessor("theme", language, "result")))
.collect()
}
fn expected_singly_quoted_theme() -> Vec<(&'static str, &'static str)> {
vec![
("rust", r#"result.labels.get("theme").map(|s| s.as_str())"#),
("python", r#"result.labels.get("theme")"#),
("typescript", r#"result.labels["theme"]"#),
("node", r#"result.labels["theme"]"#),
("wasm", r#"result.labels.get("theme")"#),
("go", r#"result.Labels["theme"]"#),
("java", r#"result.labels().get("theme")"#),
("kotlin", r#"result.labels().get("theme")"#),
("kotlin_android", r#"result.labels.get("theme")"#),
("csharp", r#"result.Labels["theme"]"#),
("ruby", r#"result.labels["theme"]"#),
("php", r#"result->labels["theme"]"#),
("elixir", r#"result.labels["theme"]"#),
("r", r#"result$labels[["theme"]]"#),
("c", r#"result_labels(result)["theme"]"#),
("swift", r#"result.labels()["theme"]"#),
("dart", r#"result.labels["theme"]"#),
("zig", r#"result.labels.get("theme")"#),
("gleam", r#"result.labels.get("theme")"#),
]
}
fn assert_matches_expected(path: &str) {
let actual = accessors_for(path);
let expected = expected_singly_quoted_theme();
assert_eq!(
actual.len(),
expected.len(),
"language list and expectation table disagree"
);
for ((language, rendered), (expected_language, wanted)) in actual.iter().zip(expected.iter()) {
assert_eq!(language, expected_language, "language tables out of order");
assert_eq!(rendered, wanted, "path `{path}` rendered the wrong {language} accessor");
}
}
#[test]
fn double_quoted_map_key_is_quoted_exactly_once_in_every_language() {
assert_matches_expected(r#"labels["theme"]"#);
}
#[test]
fn single_quoted_map_key_is_quoted_exactly_once_in_every_language() {
assert_matches_expected("labels['theme']");
}
#[test]
fn bare_map_key_renders_identically_to_a_quoted_one() {
assert_matches_expected("labels[theme]");
}
#[test]
fn no_language_emits_a_doubled_or_escaped_quote_for_a_plain_key() {
for (language, rendered) in accessors_for(r#"labels["theme"]"#) {
assert!(
!rendered.contains("\"\""),
"{language} double-quoted the key: {rendered}"
);
assert!(
!rendered.contains("\\\""),
"{language} escaped a quote into the key: {rendered}"
);
assert!(
rendered.contains("\"theme\""),
"{language} lost the key `theme`: {rendered}"
);
}
}
#[test]
fn a_quoted_digit_key_indexes_like_a_bare_digit_key() {
let quoted = accessors_for(r#"labels["0"]"#);
let bare = accessors_for("labels[0]");
assert_eq!(quoted, bare);
let typescript = "ed
.iter()
.find(|(language, _)| *language == "typescript")
.expect("typescript accessor")
.1;
assert_eq!(typescript, "result.labels[0]");
}
const ESCAPING_LANGUAGES: &[&str] = &[
"rust",
"python",
"typescript",
"node",
"wasm",
"go",
"java",
"kotlin",
"kotlin_android",
"csharp",
"ruby",
"php",
"elixir",
"r",
"c",
"swift",
"dart",
"zig",
"gleam",
];
fn accessor_for(path: &str, language: &str) -> String {
accessors_for(path)
.into_iter()
.find(|(candidate, _)| candidate == &language)
.unwrap_or_else(|| panic!("no accessor for {language}"))
.1
}
#[test]
fn a_key_containing_a_quote_stays_inside_its_literal() {
let expected = [
r#"result.labels.get("a\"b").map(|s| s.as_str())"#, r#"result.labels.get("a\"b")"#, r#"result.labels["a\"b"]"#, r#"result.labels["a\"b"]"#, r#"result.labels.get("a\"b")"#, r#"result.Labels["a\"b"]"#, r#"result.labels().get("a\"b")"#, r#"result.labels().get("a\"b")"#, r#"result.labels.get("a\"b")"#, r#"result.Labels["a\"b"]"#, r#"result.labels["a\"b"]"#, r#"result->labels["a\"b"]"#, r#"result.labels["a\"b"]"#, r#"result$labels[["a\"b"]]"#, r#"result_labels(result)["a\"b"]"#, r#"result.labels()["a\"b"]"#, r#"result.labels["a\"b"]"#, r#"result.labels.get("a\"b")"#, r#"result.labels.get("a\"b")"#, ];
assert_eq!(expected.len(), ESCAPING_LANGUAGES.len());
for (language, wanted) in ESCAPING_LANGUAGES.iter().zip(expected.iter()) {
assert_eq!(&accessor_for(r#"labels["a"b"]"#, language), wanted, "{language}");
}
}
#[test]
fn a_key_containing_a_backslash_stays_inside_its_literal() {
let expected = [
r#"result.labels.get("a\\b").map(|s| s.as_str())"#, r#"result.labels.get("a\\b")"#, r#"result.labels["a\\b"]"#, r#"result.labels["a\\b"]"#, r#"result.labels.get("a\\b")"#, r#"result.Labels["a\\b"]"#, r#"result.labels().get("a\\b")"#, r#"result.labels().get("a\\b")"#, r#"result.labels.get("a\\b")"#, r#"result.Labels["a\\b"]"#, r#"result.labels["a\\b"]"#, r#"result->labels["a\\b"]"#, r#"result.labels["a\\b"]"#, r#"result$labels[["a\\b"]]"#, r#"result_labels(result)["a\\b"]"#, r#"result.labels()["a\\b"]"#, r#"result.labels["a\\b"]"#, r#"result.labels.get("a\\b")"#, r#"result.labels.get("a\\b")"#, ];
assert_eq!(expected.len(), ESCAPING_LANGUAGES.len());
for (language, wanted) in ESCAPING_LANGUAGES.iter().zip(expected.iter()) {
assert_eq!(&accessor_for(r#"labels["a\b"]"#, language), wanted, "{language}");
}
}
#[test]
fn quoted_map_key_survives_a_longer_path() {
let mut fields = HashMap::new();
fields.insert(
"title".to_string(),
r#"metadata.open_graph_tags["og_title"].value"#.to_string(),
);
let resolver = FieldResolver::new(
&fields,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
assert_eq!(
resolver.accessor("title", "typescript", "result"),
r#"result.metadata.openGraphTags["og_title"].value"#
);
assert_eq!(
resolver.accessor("title", "go", "result"),
r#"result.Metadata.OpenGraphTags["og_title"].Value"#
);
}