use crate::error::{Error, Result};
use crate::lexer::{Lexer, Token};
use crate::types::{PrefEntry, PrefSource, PrefType, PrefValue};
pub fn parse_prefs_js(content: &str) -> Result<Vec<PrefEntry>> {
let mut parser = Parser::new(content);
parser.parse()
}
pub fn parse_prefs_js_file(path: &std::path::Path) -> Result<Vec<PrefEntry>> {
let content = std::fs::read_to_string(path)?;
parse_prefs_js(&content)
}
struct Parser<'a> {
lexer: Lexer<'a>,
current: Option<Token>,
current_line: usize,
current_column: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
let mut lexer = Lexer::new(input);
let current = match lexer.next_token() {
Ok(token) => Some(token),
Err(Error::Lexer { line, column, .. }) => {
return Parser {
lexer,
current: None,
current_line: line,
current_column: column,
}
}
Err(_) => {
return Parser {
lexer,
current: None,
current_line: 1,
current_column: 1,
}
}
};
Parser {
lexer,
current,
current_line: 1,
current_column: 1,
}
}
fn parse(&mut self) -> Result<Vec<PrefEntry>> {
let mut preferences = Vec::new();
loop {
match &self.current {
None => {
return Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: "Lexer error".to_string(),
});
}
Some(Token::Eof) => break,
Some(_) => {
match self.parse_statement_with_type() {
Ok((key, value, pref_type, locked)) => {
let explanation =
crate::explanations::get_preference_explanation_static(&key);
preferences.push(PrefEntry {
key,
value,
pref_type,
explanation,
source: Some(PrefSource::User),
source_file: Some("prefs.js".to_string()),
locked,
});
}
Err(_e) => {
self.skip_to_next_statement();
}
}
}
}
}
Ok(preferences)
}
fn skip_to_next_statement(&mut self) {
loop {
match &self.current {
None => {
return;
}
Some(Token::Eof) => return,
Some(Token::Identifier(ident)) => {
if matches!(
ident.as_str(),
"user_pref" | "pref" | "lock_pref" | "sticky_pref"
) {
return;
} else {
self.advance();
}
}
Some(_) => {
self.advance();
}
}
}
}
fn parse_statement_with_type(&mut self) -> Result<(String, PrefValue, PrefType, Option<bool>)> {
let pref_type = self.parse_pref_type_identifier()?;
self.expect_token(Token::LeftParen)?;
let key = self.expect_string()?;
self.expect_token(Token::Comma)?;
let value = self.parse_value()?;
let locked = self.parse_optional_locked_flag()?;
self.expect_token(Token::RightParen)?;
self.expect_token(Token::Semicolon)?;
Ok((key, value, pref_type, locked))
}
fn parse_pref_type_identifier(&mut self) -> Result<PrefType> {
match &self.current {
Some(Token::Identifier(ident)) => {
let pref_type = match ident.as_str() {
"user_pref" => PrefType::User,
"pref" => PrefType::Default,
"lock_pref" => PrefType::Locked,
"sticky_pref" => PrefType::Sticky,
_ => {
return Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!(
"Unknown pref function '{}'. Expected user_pref, pref, lock_pref, or sticky_pref",
ident
),
});
}
};
self.advance();
Ok(pref_type)
}
_ => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!(
"Expected pref function name (user_pref, pref, lock_pref, sticky_pref), got {:?}",
self.current
),
}),
}
}
fn parse_optional_locked_flag(&mut self) -> Result<Option<bool>> {
match &self.current {
Some(Token::Comma) => {
self.advance();
match &self.current {
Some(Token::Boolean(b)) => {
let flag = *b;
self.advance();
Ok(Some(flag))
}
Some(Token::Identifier(ident)) => {
if ident == "sticky" {
self.advance();
Ok(Some(true))
} else {
Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!(
"Invalid locked flag '{}'. Expected true, false, or sticky",
ident
),
})
}
}
Some(token) => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!(
"Invalid locked flag. Expected true, false, or sticky, got {:?}",
token
),
}),
None => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: "Unexpected end of input while parsing locked flag".to_string(),
}),
}
}
_ => {
Ok(None)
}
}
}
fn parse_value(&mut self) -> Result<PrefValue> {
match &self.current {
Some(Token::String(_)) => {
let token = std::mem::take(&mut self.current);
self.advance();
match token {
Some(Token::String(s)) => Ok(PrefValue::String(s)),
_ => unreachable!(),
}
}
Some(Token::Number(n)) => {
let num_value = *n;
self.current.take();
self.advance();
Ok(PrefValue::from_f64(num_value))
}
Some(Token::Boolean(b)) => {
let result = PrefValue::Bool(*b);
self.current.take();
self.advance();
Ok(result)
}
Some(Token::Null) => {
self.current.take();
self.advance();
Ok(PrefValue::Null)
}
Some(token) => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!("Expected value, got {:?}", token),
}),
None => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: "Unexpected end of input".to_string(),
}),
}
}
fn expect_token(&mut self, expected: Token) -> Result<()> {
match &self.current {
Some(token) if *token == expected => {
self.current.take();
self.advance();
Ok(())
}
Some(token) => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!("Expected {:?}, got {:?}", expected, token),
}),
None => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: "Unexpected end of input".to_string(),
}),
}
}
fn expect_string(&mut self) -> Result<String> {
match &self.current {
Some(Token::String(_)) => {
let token = std::mem::take(&mut self.current);
self.advance();
match token {
Some(Token::String(s)) => Ok(s),
_ => unreachable!(),
}
}
Some(token) => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: format!("Expected string, got {:?}", token),
}),
None => Err(Error::Parser {
line: self.current_line,
column: self.current_column,
message: "Unexpected end of input".to_string(),
}),
}
}
fn advance(&mut self) {
self.current = match self.lexer.next_token() {
Ok(token) => Some(token),
Err(Error::Lexer { line, column, .. }) => {
self.current_line = line;
self.current_column = column;
None
}
Err(_) => {
self.current = None;
None
}
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PrefValueExt;
#[test]
fn test_parse_prefs_js_string() {
let input = r#"user_pref("browser.startup.homepage", "https://example.com");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "browser.startup.homepage")
.unwrap();
assert_eq!(
entry.value,
PrefValue::String("https://example.com".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_prefs_js_boolean() {
let input = r#"user_pref("javascript.enabled", true);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "javascript.enabled")
.unwrap();
assert_eq!(entry.value, PrefValue::Bool(true));
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_prefs_js_integer() {
let input = r#"user_pref("network.cookie.cookieBehavior", 0);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "network.cookie.cookieBehavior")
.unwrap();
match &entry.value {
PrefValue::Integer(n) => {
assert_eq!(*n, 0.0 as i64);
}
_ => panic!("Expected number"),
}
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_prefs_js_multiple() {
let input = r#"
user_pref("browser.startup.homepage", "https://example.com");
user_pref("javascript.enabled", true);
user_pref("network.cookie.cookieBehavior", 0);
"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_parse_prefs_js_with_comments() {
let input = r#"
// This is a comment
user_pref("browser.startup.homepage", "https://example.com");
// Another comment
user_pref("javascript.enabled", true);
"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_parse_value_string_with_escaped_quotes() {
let input = r#"user_pref("test", "value with \"quotes\"");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test").unwrap();
assert_eq!(
entry.value,
PrefValue::String("value with \"quotes\"".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_value_float() {
let input = r#"user_pref("test", 3.14);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test").unwrap();
assert!(entry.value.is_number());
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_value_null() {
let input = r#"user_pref("test", null);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test").unwrap();
assert_eq!(entry.value, PrefValue::Null);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_default_pref() {
let input = r#"pref("browser.startup.homepage", "https://example.com");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "browser.startup.homepage")
.unwrap();
assert_eq!(
entry.value,
PrefValue::String("https://example.com".to_string())
);
assert_eq!(entry.pref_type, PrefType::Default);
}
#[test]
fn test_parse_locked_pref() {
let input = r#"lock_pref("javascript.enabled", false);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "javascript.enabled")
.unwrap();
assert_eq!(entry.value, PrefValue::Bool(false));
assert_eq!(entry.pref_type, PrefType::Locked);
}
#[test]
fn test_parse_sticky_pref() {
let input = r#"sticky_pref("network.proxy.type", 1);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "network.proxy.type")
.unwrap();
match &entry.value {
PrefValue::Integer(n) => {
assert_eq!(*n, 1.0 as i64);
}
_ => panic!("Expected number"),
}
assert_eq!(entry.pref_type, PrefType::Sticky);
}
#[test]
fn test_parse_complex_url_with_commas() {
let input = r#"user_pref("complex.url", "http://example.com?foo=bar,baz");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "complex.url").unwrap();
assert_eq!(
entry.value,
PrefValue::String("http://example.com?foo=bar,baz".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_uuid() {
let input = r#"user_pref("test.id", "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.id").unwrap();
assert_eq!(
entry.value,
PrefValue::String("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_json_object_string() {
let input = r#"user_pref("test", "{\"key\":\"value\"}");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("{\"key\":\"value\"}".to_string())
);
}
#[test]
fn test_parse_json_array_string() {
let input = r#"user_pref("test", "[1,2,3]");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("[1,2,3]".to_string())
);
}
#[test]
fn test_parse_backslash_escapes() {
let input = r#"user_pref("test", "C:\\path\\to\\file");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("C:\\path\\to\\file".to_string())
);
}
#[test]
fn test_parse_unicode_escape() {
let input = r#"user_pref("test", "\u0041");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("A".to_string())
);
}
#[test]
fn test_parse_hex_escape() {
let input = r#"user_pref("test", "\x41");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("A".to_string())
);
}
#[test]
fn test_parse_newline_tab_escapes() {
let input = r#"user_pref("test", "line1\nline2\ttab");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("line1\nline2\ttab".to_string())
);
}
#[test]
fn test_parse_negative_integer() {
let input = r#"user_pref("test", -42);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
match &result.iter().find(|e| e.key == "test").unwrap().value {
PrefValue::Integer(n) => {
assert_eq!(*n, -42.0 as i64);
}
_ => panic!("Expected number"),
}
}
#[test]
fn test_parse_negative_float() {
let input = r#"user_pref("test", -3.14);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.find(|e| e.key == "test")
.unwrap()
.value
.is_number());
}
#[test]
fn test_parse_scientific_notation_positive() {
let input = r#"user_pref("test", 1.5e10);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.find(|e| e.key == "test")
.unwrap()
.value
.is_number());
}
#[test]
fn test_parse_scientific_notation_negative() {
let input = r#"user_pref("test", 3e-8);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.find(|e| e.key == "test")
.unwrap()
.value
.is_number());
}
#[test]
fn test_parse_scientific_notation_decimal() {
let input = r#"user_pref("test", -2.5e+3);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.find(|e| e.key == "test")
.unwrap()
.value
.is_number());
}
#[test]
fn test_malformed_missing_semicolon() {
let input = r#"user_pref("test", "value")"#;
let result = parse_prefs_js(input);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_malformed_unclosed_string() {
let input = r#"user_pref("test", "value);"#;
let result = parse_prefs_js(input);
assert!(result.is_err());
}
#[test]
fn test_malformed_invalid_escape() {
let input = r#"user_pref("test", "\xGG");"#;
let result = parse_prefs_js(input);
assert!(result.is_err());
}
#[test]
fn test_malformed_unknown_pref_function() {
let input = r#"unknown_func("test", "value");"#;
let result = parse_prefs_js(input);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_multiline_statement() {
let input = r#"user_pref(
"test",
"value"
);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("value".to_string())
);
}
#[test]
fn test_multiline_with_comments() {
let input = r#"
user_pref(
"test", // inline comment
"value"
);
"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_block_comment_in_statement() {
let input = r#"user_pref(/* comment */ "test", "value");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_multiple_line_comments() {
let input = r#"
// Comment 1
user_pref("test1", "value1");
// Comment 2
user_pref("test2", "value2");
"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_firefox_sidebar_state() {
let input =
r#"user_pref("sidebar.backupState", "{\"command\":\"\",\"panelOpen\":false}");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "sidebar.backupState")
.unwrap();
assert_eq!(
entry.value,
PrefValue::String("{\"command\":\"\",\"panelOpen\":false}".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_firefox_page_actions() {
let input =
r#"user_pref("browser.pageActions.persistedActions", "{\"ids\":[\"bookmark\"]}");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "browser.pageActions.persistedActions")
.unwrap();
assert_eq!(
entry.value,
PrefValue::String("{\"ids\":[\"bookmark\"]}".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_firefox_telemetry_id() {
let input = r#"user_pref("toolkit.telemetry.cachedClientID", "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "toolkit.telemetry.cachedClientID")
.unwrap();
assert_eq!(
entry.value,
PrefValue::String("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0".to_string())
);
assert_eq!(entry.pref_type, PrefType::User);
}
#[test]
fn test_parse_backspace_escape() {
let input = r#"user_pref("test", "value\bwith\bbackspace");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("value\x08with\x08backspace".to_string())
);
}
#[test]
fn test_parse_form_feed_escape() {
let input = r#"user_pref("test", "value\fform\ffeed");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("value\x0cform\x0cfeed".to_string())
);
}
#[test]
fn test_parse_null_escape() {
let input = r#"user_pref("test", "null\0character");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("null\x00character".to_string())
);
}
#[test]
fn test_parse_all_new_escapes() {
let input = r#"user_pref("test", "\b\f\0");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(
result.iter().find(|e| e.key == "test").unwrap().value,
PrefValue::String("\x08\x0c\x00".to_string())
);
}
#[test]
fn test_parse_octal_escape_error() {
let input = r#"user_pref("test", "\00");"#;
let result = parse_prefs_js(input);
assert!(result.is_err());
}
#[test]
fn test_parse_user_pref_with_type() {
let input = r#"user_pref("browser.startup.homepage", "https://example.com");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "browser.startup.homepage")
.unwrap();
assert_eq!(entry.key, "browser.startup.homepage");
assert_eq!(entry.pref_type, crate::types::PrefType::User);
assert_eq!(
entry.value,
PrefValue::String("https://example.com".to_string())
);
}
#[test]
fn test_parse_default_pref_with_type() {
let input = r#"pref("javascript.enabled", true);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "javascript.enabled")
.unwrap();
assert_eq!(entry.key, "javascript.enabled");
assert_eq!(entry.pref_type, crate::types::PrefType::Default);
assert_eq!(entry.value, PrefValue::Bool(true));
}
#[test]
fn test_parse_locked_pref_with_type() {
let input = r#"lock_pref("network.proxy.type", 1);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result
.iter()
.find(|e| e.key == "network.proxy.type")
.unwrap();
assert_eq!(entry.key, "network.proxy.type");
assert_eq!(entry.pref_type, crate::types::PrefType::Locked);
match &entry.value {
PrefValue::Integer(n) => {
assert_eq!(*n, 1.0 as i64);
}
_ => panic!("Expected number"),
}
}
#[test]
fn test_parse_sticky_pref_with_type() {
let input = r#"sticky_pref("test.pref", "sticky value");"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(entry.key, "test.pref");
assert_eq!(entry.pref_type, crate::types::PrefType::Sticky);
assert_eq!(entry.value, PrefValue::String("sticky value".to_string()));
}
#[test]
fn test_parse_mixed_pref_types() {
let input = r#"
user_pref("user.pref", "value1");
pref("default.pref", "value2");
lock_pref("locked.pref", "value3");
sticky_pref("sticky.pref", "value4");
"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 4);
let user_entry = result.iter().find(|e| e.key == "user.pref").unwrap();
assert_eq!(user_entry.key, "user.pref");
assert_eq!(user_entry.pref_type, crate::types::PrefType::User);
let default_entry = result.iter().find(|e| e.key == "default.pref").unwrap();
assert_eq!(default_entry.key, "default.pref");
assert_eq!(default_entry.pref_type, crate::types::PrefType::Default);
let locked_entry = result.iter().find(|e| e.key == "locked.pref").unwrap();
assert_eq!(locked_entry.key, "locked.pref");
assert_eq!(locked_entry.pref_type, crate::types::PrefType::Locked);
let sticky_entry = result.iter().find(|e| e.key == "sticky.pref").unwrap();
assert_eq!(sticky_entry.key, "sticky.pref");
assert_eq!(sticky_entry.pref_type, crate::types::PrefType::Sticky);
}
#[test]
fn test_parse_unknown_pref_function_error() {
let input = r#"unknown_pref("test", "value");"#;
let result = parse_prefs_js(input);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_parse_with_explanations() {
let input = r#"
user_pref("javascript.enabled", true);
user_pref("unknown.preference", "test");
"#;
let result = parse_prefs_js(input).unwrap();
let js_entry = result
.iter()
.find(|e| e.key == "javascript.enabled")
.unwrap();
assert_eq!(js_entry.key, "javascript.enabled");
assert_eq!(js_entry.pref_type, PrefType::User);
assert!(js_entry.explanation.is_some());
assert!(js_entry.explanation.unwrap().contains("JavaScript"));
let unknown_entry = result
.iter()
.find(|e| e.key == "unknown.preference")
.unwrap();
assert_eq!(unknown_entry.key, "unknown.preference");
assert!(unknown_entry.explanation.is_none());
}
#[test]
fn test_parse_three_arg_locked_true() {
let input = r#"pref("test.pref", 42, true);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(entry.value, crate::types::PrefValue::Integer(42));
assert_eq!(entry.pref_type, crate::types::PrefType::Default);
assert_eq!(entry.locked, Some(true));
}
#[test]
fn test_parse_three_arg_locked_false() {
let input = r#"pref("test.pref", "value", false);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(
entry.value,
crate::types::PrefValue::String("value".to_string())
);
assert_eq!(entry.pref_type, crate::types::PrefType::Default);
assert_eq!(entry.locked, Some(false));
}
#[test]
fn test_parse_three_arg_sticky() {
let input = r#"pref("test.pref", true, sticky);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(entry.value, crate::types::PrefValue::Bool(true));
assert_eq!(entry.pref_type, crate::types::PrefType::Default);
assert_eq!(entry.locked, Some(true)); }
#[test]
fn test_parse_two_arg_still_works() {
let input = r#"pref("test.pref", 42);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(entry.value, crate::types::PrefValue::Integer(42));
assert_eq!(entry.pref_type, crate::types::PrefType::Default);
assert_eq!(entry.locked, None); }
#[test]
fn test_parse_invalid_locked_flag() {
let input = r#"pref("test.pref", 42, invalid_flag);"#;
let result = parse_prefs_js(input);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_parse_user_pref_with_locked_flag() {
let input = r#"user_pref("test.pref", "value", true);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(entry.pref_type, crate::types::PrefType::User);
assert_eq!(entry.locked, Some(true));
}
#[test]
fn test_parse_mixed_two_and_three_arg() {
let input = r#"
pref("two.arg", 1);
pref("three.arg", 2, true);
user_pref("user.pref", "test", false);
lock_pref("locked.pref", true, sticky);
"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 4);
let two_arg = result.iter().find(|e| e.key == "two.arg").unwrap();
assert_eq!(two_arg.locked, None);
let three_arg = result.iter().find(|e| e.key == "three.arg").unwrap();
assert_eq!(three_arg.locked, Some(true));
let user_pref = result.iter().find(|e| e.key == "user.pref").unwrap();
assert_eq!(user_pref.locked, Some(false));
let locked_pref = result.iter().find(|e| e.key == "locked.pref").unwrap();
assert_eq!(locked_pref.locked, Some(true)); }
#[test]
fn test_parse_three_arg_multiline() {
let input = r#"pref(
"test.pref",
42,
true
);"#;
let result = parse_prefs_js(input).unwrap();
assert_eq!(result.len(), 1);
let entry = result.iter().find(|e| e.key == "test.pref").unwrap();
assert_eq!(entry.locked, Some(true));
}
}