## Coverage Report

#### Created: 2026-05-25 08:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/runner/work/MathCAT/MathCAT/src/navigate.rs

    Line
    Count
    Source
    1
    //! Navigation is controlled by a `Navigation_Rules.yaml` file in conjunction with preferences.
    2
    //! See preference documentation for more info on navigation preferences.
    3
    #![allow(clippy::needless_return)]
    4
    5
    use std::cell::{Ref, RefCell, RefMut};
    6
    use sxd_xpath::context::Evaluation;
    7
    use sxd_xpath::Value;
    8
    use sxd_document::dom::Element;
    9
    use sxd_document::Package;
    10
    11
    use std::fmt;
    12
    use crate::canonicalize::{name, get_parent};
    13
    use crate::pretty_print::mml_to_string;
    14
    use crate::speech::{NAVIGATION_RULES, CONCAT_INDICATOR, CONCAT_STRING, SpeechRules, SpeechRulesWithContext};
    15
    use crate::infer_intent::add_fixity_children;
    16
    use crate::interface::copy_mathml;
    17
    #[cfg(not(target_family = "wasm"))]
    18
    use std::time::Instant;
    19
    use crate::errors::*;
    20
    use phf::phf_set;
    21
    use log::{debug};
    22
    23
    pub const ID_OFFSET: &str = "data-id-offset";
    24
    25
    const MAX_PLACE_MARKERS: usize = 10;
    26
    27
    thread_local!{
    28
        /// The current set of navigation rules
    29
        pub static NAVIGATION_STATE: RefCell<NavigationState> =
    30
                RefCell::new( NavigationState::new() );
    31
    }
    32
    33
    pub static NAV_COMMANDS: phf::Set<&str> = phf_set! {
    34
        "MovePrevious", "MoveNext", "MoveStart", "MoveEnd", "MoveLineStart", "MoveLineEnd",
    35
        "MoveCellPrevious", "MoveCellNext", "MoveCellUp", "MoveCellDown", "MoveColumnStart", "MoveColumnEnd",
    36
        "ZoomIn", "ZoomOut", "ZoomOutAll", "ZoomInAll",
    37
        "MoveLastLocation",
    38
        "ReadPrevious", "ReadNext", "ReadCurrent", "ReadCellCurrent", "ReadStart", "ReadEnd", "ReadLineStart", "ReadLineEnd",
    39
        "DescribePrevious", "DescribeNext", "DescribeCurrent",
    40
        "WhereAmI", "WhereAmIAll",
    41
        "ToggleZoomLockUp", "ToggleZoomLockDown", "ToggleSpeakMode",
    42
        "Exit",
    43
        "MoveTo0","MoveTo1","MoveTo2","MoveTo3","MoveTo4","MoveTo5","MoveTo6","MoveTo7","MoveTo8","MoveTo9",
    44
        "Read0","Read1","Read2","Read3","Read4","Read5","Read6","Read7","Read8","Read9",
    45
        "Describe0","Describe1","Describe2","Describe3","Describe4","Describe5","Describe6","Describe7","Describe8","Describe9",
    46
        "SetPlacemarker0","SetPlacemarker1","SetPlacemarker2","SetPlacemarker3","SetPlacemarker4","SetPlacemarker5","SetPlacemarker6","SetPlacemarker7","SetPlacemarker8","SetPlacemarker9",
    47
    };
    48
    49
    #[derive(Clone, PartialEq, Debug)]
    50
    struct NavigationPosition {
    51
        current_node: String,           // id of current node
    52
        current_node_offset: usize,     // for leaves, char offset in leaf (default = 0), otherwise id for artificial intent node
    53
    }
    54
    55
    impl fmt::Display for NavigationPosition {
    56
    0
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    57
    0
            return write!(f, "{}[+{}]", self.current_node, self.current_node_offset);
    58
    0
        }
    59
    }
    60
    61
    const ILLEGAL_NODE_ID: &str = "!not set";     // an illegal 'id' value
    62
    impl Default for NavigationPosition {
    63
    48.6k
        fn default() -> Self {
    64
    48.6k
            NavigationPosition {
    65
    48.6k
                current_node: ILLEGAL_NODE_ID.to_string(),
    66
    48.6k
                current_node_offset: 0
    67
    48.6k
            }
    68
    48.6k
         }
    69
    }
    70
    71
    72
    #[derive(Debug, Clone)]
    73
    pub struct NavigationState {
    74
        // it might be better to use a linked for the stacks, with the first node being the top
    75
        // these two stacks should be kept in sync.
    76
        position_stack: Vec<NavigationPosition>,    // all positions, so we can go back to them
    77
        command_stack: Vec<&'static str>,           // all commands, so we can undo them
    78
        place_markers: [NavigationPosition; MAX_PLACE_MARKERS],
    79
        where_am_i: NavigationPosition,             // current 'where am i' location
    80
    81
        #[cfg(target_family = "wasm")]
    82
        where_am_i_start_time: usize,               // FIX: for web
    83
        #[cfg(not(target_family = "wasm"))]
    84
        where_am_i_start_time: Instant,
    85
        mode: String,                               // one of "Character", "Simple", or "Enhanced"
    86
        speak_overview: bool,                       // true => describe after move; false => (standard) speech rules
    87
    }
    88
    89
    impl fmt::Display for NavigationState {
    90
    0
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    91
    0
            writeln!(f, "NavigationState{{")?;
    92
    0
            write!(f, "  Position Stack: ")?;
    93
    0
            for (i, nav_state) in self.position_stack.iter().enumerate() {
    94
    0
                write!(f, "{}{}", if i==0 {""} else {", "}, nav_state)?;
    95
            }
    96
    0
            writeln!(f)?;
    97
    0
            write!(f, "  Command Stack: ")?;
    98
    0
            for (i, nav_state) in self.command_stack.iter().enumerate() {
    99
    0
                write!(f, "{}{}", if i==0 {""} else {", "}, *nav_state)?;
    100
            }
    101
    0
            writeln!(f)?;
    102
    0
            writeln!(f, "  where_am_i: {}, start_time: {:?}", self.where_am_i, self.where_am_i_start_time)?;
    103
    0
            writeln!(f, "  mode: {}, speak_overview: {}", self.mode, self.speak_overview)?;
    104
    0
            writeln!(f, "}}")?;
    105
    0
            return Ok( () );
    106
    0
        }
    107
    }
    108
    109
    impl NavigationState {
    110
    3.92k
        fn new() -> NavigationState {
    111
    3.92k
            return NavigationState {
    112
    3.92k
                position_stack: Vec::with_capacity(1024),
    113
    3.92k
                command_stack: Vec::with_capacity(1024),
    114
    3.92k
                place_markers: Default::default(),
    115
    3.92k
                where_am_i: NavigationPosition::default(),
    116
    3.92k
                // FIX: figure this out for the web
    117
    3.92k
                #[cfg(target_family = "wasm")]
    118
    3.92k
                where_am_i_start_time: 0,           // FIX: for web
    119
    3.92k
                #[cfg(not(target_family = "wasm"))]
    120
    3.92k
                where_am_i_start_time: Instant::now(),      // need to give it some value, and "default()" isn't an option
    121
    3.92k
                mode: "".to_string(),                       // set latter when we have some context
    122
    3.92k
                speak_overview: false,                      // set latter when we have some context
    123
    3.92k
            };
    124
    3.92k
        }
    125
    126
    4.88k
        pub fn reset(&mut self) {
    127
    4.88k
            self.position_stack.clear();
    128
    4.88k
            self.command_stack.clear();
    129
    4.88k
            self.where_am_i = NavigationPosition::default();
    130
    4.88k
            self.reset_start_time()
    131
    4.88k
        }
    132
    133
    134
        // defining reset_start_time because of the following message if done inline
    135
        // attributes on expressions are experimental
    136
        // see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
    137
        #[cfg(target_family = "wasm")]
    138
        fn reset_start_time(&mut self) {
    139
             self.where_am_i_start_time = 0;
    140
        }
    141
    142
        #[cfg(not(target_family = "wasm"))]
    143
    4.88k
        fn reset_start_time(&mut self) {
    144
    4.88k
             self.where_am_i_start_time = Instant::now();      // need to give it some value, and "default()" isn't an option
    145
    4.88k
        }
    146
    147
    148
    563
        fn push(&mut self, position: NavigationPosition, command: &'static str) {
    149
    563
            self.position_stack.push(position);
    150
    563
            self.command_stack.push(command);
    151
    563
        }
    152
    153
    46
        fn pop(&mut self) -> Option<(NavigationPosition, &'static str)> {
    154
    46
            assert_eq!(self.position_stack.len(), self.command_stack.len());
    155
    46
            if self.position_stack.is_empty() {
    156
    0
                return None;
    157
            } else {
    158
    46
                return Some( (self.position_stack.pop().unwrap(), self.command_stack.pop().unwrap()) );
    159
            }
    160
    46
        }
    161
    162
    2.75k
        fn top(&self) -> Option<(&NavigationPosition, &'static str)> {
    163
    2.75k
            if self.position_stack.is_empty() {
    164
    0
                return None;
    165
    2.75k
            }
    166
    2.75k
            let last = self.position_stack.len()-1;
    167
    2.75k
            return Some( (&self.position_stack[last], self.command_stack[last]) );
    168
    2.75k
        }
    169
    170
    0
        pub fn get_navigation_mathml<'a>(&self, mathml: Element<'a>) -> Result<(Element<'a>, usize)> {
    171
    0
            if self.position_stack.is_empty() {
    172
    0
                return Ok( (mathml, 0) );
    173
            } else {
    174
    0
                let (position, _) = self.top().unwrap();
    175
    0
                return match get_node_by_id(mathml, position) {
    176
    0
                    None => bail!("internal error: id '{}' was not found in mathml:\n{}",
    177
    0
                                    position.current_node, mml_to_string(mathml)),
    178
    0
                    Some(found) => Ok( (found, position.current_node_offset) )
    179
                };
    180
            }
    181
    0
        }
    182
    183
    1.09k
        pub fn get_navigation_mathml_id(&self, mathml: Element) -> (String, usize) {
    184
    1.09k
            if self.position_stack.is_empty() {
    185
    47
                return (mathml.attribute_value("id").unwrap().to_string(), 0);
    186
            } else {
    187
    1.05k
                let (position, _) = self.top().unwrap();
    188
    1.05k
                return (position.current_node.clone(), position.current_node_offset);
    189
            }
    190
    1.09k
        }
    191
    192
    549
        fn init_navigation_context(&self, context: &mut sxd_xpath::Context, command: &'static str,
    193
    549
                                   nav_state_top: Option<(&NavigationPosition, &'static str)>) {
    194
    549
            context.set_variable("NavCommand", command);
    195
    196
    549
            if command == "WhereAmI" && self.where_am_i == NavigationPosition::default()0 {
    197
    0
                context.set_variable("NavNode", self.where_am_i.current_node.as_str());
    198
    0
                context.set_variable("NavNodeOffset", self.where_am_i.current_node_offset as f64);
    199
    549
            } else {
    200
    549
                let position = &self.position_stack[self.position_stack.len()-1];
    201
    549
                context.set_variable("NavNode", position.current_node.as_str());
    202
    549
                context.set_variable("NavNodeOffset", position.current_node_offset as f64);
    203
    549
            }
    204
    205
            // get the index from command (e.g., '3' in 'SetPlacemarker3 or MoveTo3' and set 'PlaceMarker' to it's position)
    206
    549
            if command.ends_with(|ch: char| ch.is_ascii_digit()) {
    207
    6
                let index = convert_last_char_to_number(command);
    208
    6
                let position = &self.place_markers[index];
    209
    6
                context.set_variable("PlaceMarkerIndex", index as f64);
    210
    6
                context.set_variable("PlaceMarker", position.current_node.as_str());
    211
    6
                context.set_variable("PlaceMarkerOffset", position.current_node_offset as f64);
    212
    543
            }
    213
    214
    549
            context.set_variable("Overview", self.speak_overview);
    215
    549
            context.set_variable("ReadZoomLevel", (if self.mode == "Enhanced" {-1200} else {1349}) as f64);
    216
    549
            context.set_variable("MatchCounter", 0 as f64);
    217
    218
    549
            if command == "MoveLastLocation" {
    219
    3
                let previous_command = match nav_state_top {
    220
    0
                    None => "None",
    221
    3
                    Some( (_, previous_command) ) => previous_command,
    222
                };
    223
    3
                context.set_variable("PreviousNavCommand", previous_command);
    224
    546
            }
    225
    226
            // used by nav rules for speech -- needs an initial value so tests don't fail
    227
    549
            context.set_variable("SayCommand", "" );
    228
    549
            context.set_variable("Move2D", "" );
    229
    549
            context.set_variable("SpeakExpression", true );    // default is to speak the expr after navigation
    230
    549
            return;
    231
    232
    6
            fn convert_last_char_to_number(str: &str) -> usize {
    233
    6
                let last_char = str.as_bytes()[str.len()-1];
    234
    6
                assert!( last_char.is_ascii_digit() );
    235
    6
                return (last_char - b'0') as usize;
    236
    6
            }
    237
    549
        }
    238
    }
    239
    240
    // convert the last digit of a Placemarker command to an integer
    241
    2
    fn convert_last_char_to_number(str: &str) -> usize {
    242
    2
        let last_char = str.as_bytes()[str.len()-1];
    243
    2
        assert!( last_char.is_ascii_digit() );
    244
    2
        return (last_char - b'0') as usize;
    245
    2
    }
    246
    247
    /// Get the node associated with a `NavigationPosition`.
    248
    /// This can be called on an intent tree
    249
    9.18k
    fn get_node_by_id<'a>(mathml: Element<'a>, pos: &NavigationPosition) -> Option<Element<'a>> {
    250
    9.18k
        if let Some(mathml_id9.17k) = mathml.attribute_value("id") &&
    251
    9.17k
           mathml_id == pos.current_node.as_str() &&
    252
    1.46k
            (crate::xpath_functions::is_leaf(mathml) ||
    253
    537
            mathml.attribute_value(ID_OFFSET).unwrap_or("0") == pos.current_node_offset.to_string()) {
    254
    1.46k
            return Some(mathml);
    255
    7.71k
        }
    256
    257
    10.0k
        for child in mathml7.71k.children7.71k() {
    258
    10.0k
            if let Some(child7.71k) = child.element() &&
    259
    7.71k
               let Some(found4.41k) = get_node_by_id(child, pos) {
    260
    4.41k
                    return Some(found);
    261
    5.60k
                }
    262
        }
    263
    3.29k
        return None;
    264
    9.18k
    }
    265
    266
    /// Search the mathml for the id and set the navigation node to that id
    267
    /// Resets the navigation stack
    268
    2
    pub fn set_navigation_node_from_id(mathml: Element, id: &str, offset: usize) -> Result<()> {
    269
    2
        let current_node = id.to_string();
    270
    2
        let pos = NavigationPosition { current_node: current_node.clone(), current_node_offset: offset };
    271
    2
        let node = get_node_by_id(mathml, &pos);
    272
    2
        if node.is_some() {
    273
    2
            return NAVIGATION_STATE.with(|nav_state| {
    274
    2
                let mut nav_state = nav_state.borrow_mut();
    275
    2
                nav_state.reset();
    276
    2
                nav_state.push(NavigationPosition{
    277
    2
                    current_node,
    278
    2
                    current_node_offset: offset
    279
    2
                }, "None");
    280
    2
                return Ok( () );
    281
    2
            })
    282
        } else {
    283
    0
            bail!("Id {} not found in MathML {}", id, mml_to_string(mathml));
    284
        }
    285
    2
    }
    286
    287
    /// Get's the Nav Node from the context, with some exceptions such as Toggle commands where it isn't set.
    288
    /// Note: mathml can be any node. It isn't really used but some Element needs to be part of Evaluate().
    289
    571
    pub fn get_nav_node<'c>(context: &sxd_xpath::Context<'c>, var_name: &str, mathml: Element<'c>, start_node: Element<'c>, command: &str, nav_mode: &str) -> Result<String> {
    290
    571
        let start_id = start_node.attribute_value("id").unwrap_or_default();
    291
    571
        if command.starts_with("Toggle") {
    292
    1
            return Ok( start_id.to_string() );
    293
        } else {
    294
    570
            return context_get_variable(context, var_name, mathml)
    295
    570
                    .with_context(|| format!0("When trying to {} starting at id={} in {} mode",
    296
    0
                                                    command, start_node.attribute_value("id").unwrap_or_default(), nav_mode));
    297
        }
    298
    571
    }
    299
    300
    // FIX: think of a better place to put this, and maybe a better interface
    301
    /// Note: mathml can be any node. It isn't really used but some Element needs to be part of Evaluate().
    302
    /// If the context variable has String, Number, or Boolean xpath value, return it as a string. Otherwise it is an error
    303
    4.55k
    pub fn context_get_variable<'c>(context: &sxd_xpath::Context<'c>, var_name: &str, mathml: Element<'c>) -> Result<String> {
    304
        // This is slightly roundabout because Context doesn't expose a way to get the values.
    305
        // Instead, we create an "Evaluation", which is just one level of indirection.
    306
        use sxd_xpath::nodeset::Node;
    307
    4.55k
        let evaluation = Evaluation::new(context, Node::Element(mathml));
    308
    4.55k
        return match evaluation.value_of(var_name.into()) {
    309
    4.55k
            Some(value) => match value {
    310
    1.74k
                Value::String(s) => Ok(s.clone()),
    311
    1.20k
                Value::Number(f) => Ok(f.to_string()),
    312
    1.09k
                Value::Boolean(b) => Ok(format!("{b}")),    // "true" or "false"
    313
    509
                Value::Nodeset(nodes) => {
    314
    509
                    if nodes.size() == 1 &&
    315
    509
                       let Some(attr) = nodes.document_order_first().unwrap().attribute() {
    316
    509
                            return Ok(attr.value().to_string());
    317
    0
                        };
    318
    0
                    let mut error_message = format!("Variable '{var_name}' set somewhere in navigate.yaml is nodeset and not an attribute: ");
    319
    0
                    if nodes.size() == 0 {
    320
    0
                        error_message += &format!("0 nodes (false) -- {} set to non-existent node in\n{}",
    321
    0
                                                  var_name, mml_to_string(mathml));
    322
    0
                    } else {
    323
    0
                        let singular = nodes.size()==1;
    324
    0
                        error_message += &format!("{} node{}. {}:",
    325
    0
                                nodes.size(),
    326
    0
                                if singular {""} else {"s"},
    327
    0
                                if singular {"Node is"} else {"Nodes are"});
    328
    0
                        nodes.document_order()
    329
    0
                            .iter()
    330
    0
                            .enumerate()
    331
    0
                            .for_each(|(i, node)| {
    332
    0
                                match node {
    333
    0
                                    sxd_xpath::nodeset::Node::Element(mathml) =>
    334
    0
                                        error_message += &format!("#{}:\n{}",i, mml_to_string(*mathml)),
    335
    0
                                    _ => error_message += &format!("'{node:?}'"),
    336
                                }
    337
    0
                            })
    338
                    };
    339
    0
                    bail!(error_message);
    340
                },
    341
            },
    342
    0
            None => bail!("Could not find value for navigation variable '{}'", var_name),
    343
        }
    344
    4.55k
    }
    345
    346
    /// Wrapper around context_get_variable to get an integer variable
    347
    1.70k
    fn context_get_int_variable<'c>(context: &sxd_xpath::Context<'c>, var_name: &str, mathml: Element<'c>) -> Result<usize> {
    348
    1.70k
        let value = context_get_variable(context, var_name, mathml)?0;
    349
    1.70k
        return match value.parse::<usize>() {
    350
    1.70k
            Ok(i) => Ok(i),
    351
    0
            Err(e) => bail!("Could not parse navigation variable '{}' with value '{}' as integer: {}", var_name, value, e),
    352
        }
    353
    1.70k
    }
    354
    355
    /// Given a key code along with the modifier keys, the current node is moved accordingly (or value reported in some cases).]
    356
    /// The spoken text for the new current node is returned.
    357
    0
    pub fn do_mathml_navigate_key_press(mathml: Element,
    358
    0
                key: usize, shift_key: bool, control_key: bool, alt_key: bool, meta_key: bool) -> Result<String> {
    359
    0
        let (command, param) = key_press_to_command_and_param(key, shift_key, control_key, alt_key, meta_key)?;
    360
    0
        return do_navigate_command_and_param(mathml, command, param);
    361
    0
    }
    362
    363
    2
    fn do_navigate_command_and_param(mathml: Element, command: NavigationCommand, param: NavigationParam) -> Result<String> {
    364
    2
        return do_navigate_command_string(mathml, navigation_command_string(command, param));
    365
    2
    }
    366
    367
    549
    pub fn do_navigate_command_string(mathml: Element, nav_command: &'static str) -> Result<String> {
    368
        // first check to see if nav file has been changed -- don't bother checking in loop below
    369
    549
        NAVIGATION_RULES.with(|rules| {
    370
    549
            rules.borrow_mut().read_files()
    371
    549
        })?0;
    372
    373
    549
        if mathml.children().is_empty() {
    374
    0
            bail!("MathML has not been set -- can't navigate");
    375
    549
        };
    376
    377
    549
        return NAVIGATION_STATE.with(|nav_state| {
    378
    549
            let mut nav_state = nav_state.borrow_mut();
    379
            // debug!("MathML: {}", mml_to_string(mathml));
    380
    549
            if nav_state.position_stack.is_empty() {
    381
                // initialize to root node
    382
    47
                nav_state.push(NavigationPosition{
    383
    47
                    current_node: mathml.attribute_value("id").unwrap().to_string(),
    384
    47
                    current_node_offset: 0
    385
    47
                }, "None")
    386
    502
            };
    387
    388
    549
            return NAVIGATION_RULES.with(|rules| {
    389
    549
                let rules = rules.borrow();
    390
    549
                let new_package = Package::new();
    391
    549
                let mut rules_with_context = SpeechRulesWithContext::new(&rules, new_package.as_document(), "", 0);
    392
    393
    549
                nav_state.mode = rules.pref_manager.as_ref().borrow().pref_to_string("NavMode");
    394
    549
                nav_state.speak_overview = rules.pref_manager.as_ref().borrow().pref_to_string("Overview") == "true";
    395
    396
    549
                nav_state.init_navigation_context(rules_with_context.get_context(), nav_command, nav_state.top());
    397
    398
                // start navigation off at the right node
    399
    549
                if nav_command == "MoveLastLocation" {
    400
    3
                    nav_state.pop();
    401
    546
                }
    402
    403
                // If no speech happened for some calls, we try the call again (e.g, no speech for invisible times).
    404
                // To prevent to infinite loop, we limit the number of tries
    405
                const LOOP_LIMIT: usize = 3;
    406
    549
                let mut cumulative_speech = String::with_capacity(120);
    407
    569
                for loop_count in 0..LOOP_LIMIT549 {
    408
    569
                    match apply_navigation_rules(mathml, nav_command, &rules, &mut rules_with_context, &mut nav_state, loop_count) {
    409
    569
                        Ok( (speech, done)) => {
    410
    569
                            cumulative_speech = cumulative_speech + if loop_count==0 {""549} else {" "20} + speech.trim();
    411
    569
                            if done {
    412
    549
                                let (tts, rate) = {
    413
    549
                                    let prefs = rules.pref_manager.borrow();
    414
    549
                                    (prefs.pref_to_string("TTS"), prefs.pref_to_string("MathRate"))
    415
    549
                                };
    416
    549
                                if rate != "100" {
    417
    0
                                    match tts.as_str() {
    418
    0
                                        "SSML"
    419
    0
                                            if !cumulative_speech.starts_with("<prosody rate") => {
    420
    0
                                                cumulative_speech = format!("<prosody rate='{}%'>{}</prosody>", &rate, &cumulative_speech);
    421
    0
                                            }
    422
    0
                                        "SAPI5"
    423
    0
                                            if !cumulative_speech.starts_with("<rate speed") => {
    424
    0
                                                cumulative_speech = format!(
    425
    0
                                                    "<rate speed='{:.1}'>{}</rate>",
    426
    0
                                                    10.0 * (0.01 * rate.parse::<f32>().unwrap_or(100.0)).log(3.0),
    427
    0
                                                    cumulative_speech
    428
    0
                                                );
    429
    0
                                            }
    430
    0
                                        _ => (),  // do nothing
    431
                                    }
    432
    549
                                }
    433
    549
                                                    return Ok( rules.pref_manager.borrow().get_tts()
    434
    549
                                                .merge_pauses(crate::speech::remove_optional_indicators(
    435
    549
                                                    &cumulative_speech.replace(CONCAT_STRING, "")
    436
    549
                                                                        .replace(CONCAT_INDICATOR, "")
    437
    549
                                                                )
    438
    549
                                                .trim_start().trim_end_matches([' ', ',', ';'])) );
    439
    20
                            }
    440
                        },
    441
    0
                        Err(e) => {
    442
    0
                            return Err(e);
    443
                        }
    444
                    }
    445
                }
    446
    0
                bail!("Internal error: Navigation exceeded limit of number of times no speech generated
    447
                       when attempting to {} in {} mode start at id={} in this MathML:\n{}.",
    448
    0
                       nav_command, nav_state.mode, nav_state.top().unwrap().0.current_node, mml_to_string(mathml));
    449
    549
            });
    450
    549
        });
    451
    452
    570
        fn get_start_node<'m>(mathml: Element<'m>, nav_state: &RefMut<NavigationState>) -> Result<Element<'m>>  {
    453
    570
            let element = match nav_state.top() {
    454
                None => {
    455
    0
                    let nav_position = NavigationPosition { current_node: mathml.attribute_value("id").unwrap().to_string(), current_node_offset: 0 };
    456
    0
                    get_node_by_id(mathml, &nav_position)
    457
                },
    458
    570
                Some( (position, _) ) => get_node_by_id(mathml, position),
    459
            };
    460
    461
    570
            return match element {
    462
    569
                Some(node) => Ok(node),
    463
                None => {
    464
    1
                    bail!("Internal Error: didn't find id/offset '{:?}' while attempting to start navigation. MathML is\n{}",
    465
    1
                          nav_state.top().map(|t| t.0), mml_to_string(mathml));
    466
                }
    467
            };
    468
    570
        }
    469
    470
    471
    472
    569
        fn apply_navigation_rules<'c, 'm:'c>(mathml: Element<'m>, nav_command: &'static str,
    473
    569
                rules: &Ref<SpeechRules>, rules_with_context: &mut SpeechRulesWithContext<'c, '_, 'm>, nav_state: &mut RefMut<NavigationState>,
    474
    569
                loop_count: usize) -> Result<(String, bool)> {
    475
            {
    476
    569
                let context = rules_with_context.get_context();
    477
    569
                context.set_variable("MatchCounter", loop_count as f64);
    478
    569
                nav_state.mode = context_get_variable(context, "NavMode", mathml)?0;
    479
            }
    480
    481
    569
            let mut add_literal = nav_state.mode == "Character";
    482
    569
            let (intent, nav_intent) = if add_literal {
    483
    206
                (mathml, mathml)
    484
            } else {
    485
    363
                let intent = crate::speech::intent_from_mathml(mathml, rules_with_context.get_document())?0;
    486
    363
                (intent, add_fixity_children(copy_mathml(intent)))
    487
            };
    488
    489
    569
            let mut properties = "";
    490
    569
            if add_literal {
    491
    206
                properties  = mathml.attribute_value("data-intent-property").unwrap_or_default();
    492
    206
                if properties.contains(":literal:") {
    493
    0
                    add_literal = false;
    494
    206
                } else {
    495
    206
                    mathml.set_attribute_value("data-intent-property", (":literal:".to_string() + properties).as_str());
    496
    206
                };
    497
    363
            }
    498
            // we should always find the start node.
    499
            // however, if we were navigating by character, then switched the NavMode, the intent tree might not have that node in it
    500
    569
            let start_node = match get_start_node(nav_intent, nav_state) {
    501
    568
                Ok(node) => node,
    502
                Err(_) => {
    503
                    // find the node in the other tree (probably mathml) and walk up to find a parent that has an id in both
    504
    1
                    debug!("Could not find start_node in nav_intent -- trying other_tree");
    505
    1
                    let other_tree = if nav_state.mode == "Character" {nav_intent0} else {mathml};
    506
    1
                    let mut found_node = get_start_node(other_tree, nav_state)?0;
    507
    2
                    while name(found_node) != "math" {
    508
    2
                        found_node = get_parent(found_node);
    509
                        // debug!("found_node:\n{}", mml_to_string(found_node));
    510
    2
                        let temp_pos = NavigationPosition {
    511
    2
                            current_node: found_node.attribute_value("id").unwrap_or_default().to_string().clone(),
    512
    2
                            current_node_offset: found_node.attribute_value(ID_OFFSET).unwrap_or_default().parse::<usize>().unwrap_or_default(),
    513
    2
                        };
    514
    2
                        if let Some(intent_node1) = get_node_by_id(nav_intent, &temp_pos) {
    515
    1
                            found_node = intent_node;
    516
    1
                            break;
    517
    1
                        }
    518
                    }
    519
    1
                    found_node
    520
                }
    521
            };
    522
    523
            // debug!("intent=\n{}", mml_to_string(intent));
    524
            // debug!("nav intent=\n{}", mml_to_string(nav_intent));
    525
            // debug!("start_node id={}\n{}", nav_state.top().unwrap().0.current_node.as_str(), mml_to_string(start_node));
    526
            // if name(start_node) != "math" {
    527
            //     let mut parent= get_parent(start_node);
    528
            //     if name(parent) != "math" {
    529
            //         parent = get_parent(parent);
    530
            //     }
    531
            //     debug!("parent or grandparent of start_node:\n{}", mml_to_string(parent));
    532
            // }
    533
    569
            let offset = context_get_int_variable(rules_with_context.get_context(), "NavNodeOffset", intent)?0;
    534
    569
            rules_with_context.set_nav_node_offset(offset);
    535
    569
            debug!("starting nav_position: {}, start node ={}", nav_state.top()0.unwrap0().0, name0(start_node0));
    536
    537
    569
            let raw_speech_string = rules_with_context.match_pattern::<String>(start_node)
    538
    569
                        .context("Pattern match/replacement failure during math navigation!")?0;
    539
    569
            let speech = rules.pref_manager.borrow().get_tts()
    540
    569
                        .merge_pauses(crate::speech::remove_optional_indicators(
    541
    569
                            &raw_speech_string.replace(CONCAT_STRING, "")
    542
    569
                                                    .replace(CONCAT_INDICATOR, "")
    543
    569
                                        )
    544
    569
                        .trim());
    545
            // debug!("Nav Speech: {}", speech);
    546
    547
            // FIX: add things that need to do a speech replacement based on some marker for "where am i" and others that loop ([Speak: id])???
    548
            // what else needs to be done/set???
    549
    550
            // transfer some values that might have been set into the prefs
    551
    569
            let offset = context_get_int_variable(rules_with_context.get_context(), "NavNodeOffset", intent)?0;
    552
    569
            rules_with_context.set_nav_node_offset(offset);
    553
    569
            let context = rules_with_context.get_context();
    554
    569
            nav_state.speak_overview = context_get_variable(context, "Overview", intent)?0 == "true";
    555
    569
            nav_state.mode = context_get_variable(context, "NavMode", intent)?0;
    556
    569
            rules.pref_manager.as_ref().borrow_mut().set_user_prefs("NavMode", &nav_state.mode)?0;
    557
    558
    569
            debug!("context value of NavNodeOffset: {:?}", context_get_variable0(context0, "NavNodeOffset"0, intent0)?0);
    559
    569
            let nav_position = NavigationPosition {
    560
    569
                    current_node: get_nav_node(context, "NavNode", intent, start_node, nav_command, &nav_state.mode)?0,
    561
    569
                    current_node_offset: context_get_int_variable(context, "NavNodeOffset", intent)?0,
    562
                };
    563
    564
            // after a command, we either read or describe the new location (part of state)
    565
            // also some commands are DescribeXXX/ReadXXX, so we need to look at the commands also
    566
    569
            let use_read_rules = if nav_command.starts_with("Read") {
    567
    5
                true
    568
    564
            } else if nav_command.starts_with("Describe") {
    569
    3
                false
    570
            } else {
    571
    561
                !nav_state.speak_overview
    572
            };
    573
    574
    569
            debug!("after match nav_position: {}", nav_position);
    575
            // push the new location on the stack
    576
    569
            if nav_position != NavigationPosition::default() && &nav_position != nav_state.top().unwrap().0 {
    577
    483
                nav_state.push(nav_position.clone(), nav_command);
    578
    483
            }86
    579
    580
    569
            if nav_command.starts_with("SetPlacemarker") {
    581
    2
                let new_node_id = get_nav_node(context, "NavNode", intent, start_node, nav_command, &nav_state.mode)?0;
    582
    2
                nav_state.place_markers[convert_last_char_to_number(nav_command)] = NavigationPosition{
    583
    2
                    current_node: new_node_id,
    584
    2
                    current_node_offset: context_get_int_variable(context, "NavNodeOffset", intent)?0,
    585
                }
    586
    567
            }
    587
    588
    569
            let nav_mathml = get_node_by_id(intent, &nav_position);
    589
    569
            if nav_mathml.is_some() && context_get_variable(context, "SpeakExpression", intent)?0 == "true" {
    590
                // Speak/Overview of where we landed (if we are supposed to speak it) -- use intent, not nav_intent
    591
                // Note: NavMode might have changed, so we need to recheck the mode to see if we use LiteralSpeak
    592
    519
                let literal_speak = nav_state.mode == "Character";
    593
    519
                let node_speech_result = speak(mathml, intent, &nav_position, literal_speak, use_read_rules);
    594
    519
                remove_literal_property(mathml, add_literal, properties);
    595
    519
                let node_speech = match node_speech_result {
    596
    519
                    Ok(speech) => speech,
    597
    0
                    Err(e) => {
    598
    0
                        if e.to_string() == crate::speech::NAV_NODE_SPEECH_NOT_FOUND {
    599
    0
                            bail!("Internal error: With {}/{} in {} mode, can't {} from expression with id '{}' inside:\n{}",
    600
    0
                                  rules.pref_manager.as_ref().borrow().pref_to_string("Language"),
    601
    0
                                  rules.pref_manager.as_ref().borrow().pref_to_string("SpeechStyle"),
    602
    0
                                  &nav_state.mode, nav_command, &nav_position.current_node, mml_to_string(if literal_speak {mathml} else {intent}));
    603
    0
                        }
    604
    0
                        return Err(e);
    605
                    }
    606
                };
    607
    608
                // debug!("node_speech: '{}', speech: '{}'\n", node_speech, speech);
    609
    519
                if node_speech.is_empty() {
    610
                    // try again in loop
    611
    20
                    return Ok( (speech, false));
    612
                } else {
    613
    499
                    pop_stack(nav_state, loop_count, nav_command);
    614
                    // debug!("returning: '{}'", speech.clone() + " " + &node_speech);
    615
    499
                    return Ok( (speech + " " + &node_speech, true) );
    616
                }
    617
            } else {
    618
    50
                remove_literal_property(mathml, add_literal, properties);
    619
    50
                pop_stack(nav_state, loop_count, nav_command);
    620
    50
                return Ok( (speech, true) );
    621
            };
    622
    623
    569
            fn remove_literal_property(mathml: Element, add_literal: bool, properties: &str) {
    624
    569
                if add_literal {
    625
    206
                    if properties.is_empty() {
    626
    206
                        mathml.remove_attribute("data-intent-property");
    627
    206
                    } else {
    628
    0
                        mathml.set_attribute_value("data-intent-property", properties);
    629
    0
                    }
    630
    363
                }
    631
    569
            }
    632
    633
    569
        }
    634
    635
    636
    549
        fn pop_stack(nav_state: &mut NavigationState, count: usize, nav_command: &'static str) {
    637
            // save the final state and pop the intermediate states that did nothing
    638
    549
            let push_command_on_stack = (nav_command.starts_with("Move") && nav_command != "MoveLastLocation"355) || nav_command197.starts_with197("Zoom");
    639
            // debug!("pop_stack: nav_command={}, count={}, push? {} stack=\n{}", nav_command, count, push_command_on_stack, nav_state);
    640
    549
            if count == 0 {
    641
    529
                if !push_command_on_stack && nav_command13 == nav_state13.top().unwrap().1 {
    642
    3
                    nav_state.pop();    // remove ReadXXX, SetPlacemarker, etc. commands that don't change the state
    643
    526
                }
    644
    529
                return;
    645
    20
            }
    646
    20
            let (top_position, top_command) = nav_state.pop().unwrap();
    647
    20
            let mut count = count - 1;
    648
            loop {
    649
                // debug!("  ... loop count={}", count);
    650
    20
                nav_state.pop();
    651
    20
                if count == 0 {
    652
    20
                    break;
    653
    0
                };
    654
    0
                count -= 1;
    655
            };
    656
    20
            if push_command_on_stack {
    657
    19
                nav_state.push(top_position, top_command);
    658
    19
            }1
    659
            // debug!("END pop_stack: stack=\n{}", nav_state);
    660
    549
        }
    661
    549
    }
    662
    663
    /// Speak the intent tree at the nav_node_id if that id exists in the intent tree; otherwise use the mathml tree.
    664
    /// If full_read is true, we speak the tree, otherwise we use the overview rules.
    665
    /// If literal_speak is true, we use the literal speak rules (and use the mathml tree).
    666
    519
    fn speak(mathml: Element, intent: Element, nav_position: &NavigationPosition, literal_speak: bool, full_read: bool) -> Result<String> {
    667
    519
        if full_read {
    668
            // In something like x^3, we might be looking for the '3', but it will be "cubed", so we don't find it.
    669
            // Or we might be on a "(" surrounding a matrix and that isn't part of the intent
    670
            // We are probably safer in terms of getting the same speech if we retry intent starting at the nav node,
    671
            //  but the node to speak is almost certainly trivial.
    672
            // By speaking the non-intent tree, we are certain to speak on the next try
    673
    505
            if !literal_speak && get_node_by_id327(intent, nav_position).is_some327() {
    674
                    // debug!("speak: nav_node_id={}, intent=\n{}", nav_node_id, mml_to_string(intent));
    675
    327
                match crate::speech::speak_mathml(intent, &nav_position.current_node, nav_position.current_node_offset) {
    676
    326
                    Ok(speech) => return Ok(speech),
    677
    1
                    Err(e) => {
    678
    1
                        if e.to_string() != crate::speech::NAV_NODE_SPEECH_NOT_FOUND {
    679
    0
                            return Err(e);
    680
    1
                        }
    681
                        // else could be something like '3' in 'x^3' ("cubed")
    682
                    },
    683
                }
    684
    178
            }
    685
            // debug!("speak (literal): nav_node_id={}, mathml=\n{}", nav_node_id, mml_to_string(mathml));
    686
    179
            let speech = crate::speech::speak_mathml(mathml,
    687
    179
                    &nav_position.current_node, nav_position.current_node_offset);
    688
            // debug!("speech from speak: {:?}", speech);
    689
    179
            return speech;
    690
        } else {
    691
    14
            return crate::speech::overview_mathml(mathml, &nav_position.current_node, nav_position.current_node_offset);
    692
        }
    693
    519
    }
    694
    695
    696
    // MathPlayer's interface mentions these, so we keep them.
    697
    // These (KeyboardEvent.keyCode) are consistent across platforms (mostly?) but are deprecated.
    698
    //   KeyboardEvent.code is recommended instead (a string)
    699
    const VK_LEFT: usize = 0x25;
    700
    const VK_RIGHT: usize = 0x27;
    701
    const VK_UP: usize = 0x26;
    702
    const VK_DOWN: usize = 0x28;
    703
    const VK_RETURN: usize = 0x0D;
    704
    const VK_SPACE: usize = 0x20;
    705
    const VK_HOME: usize = 0x24;
    706
    const VK_END: usize = 0x23;
    707
    const VK_BACK: usize = 0x08;
    708
    const VK_ESCAPE: usize = 0x1B;
    709
    710
    // Utilities that returns one of four commands/params based on shift/control key combinations
    711
    712
    enum NavigationCommand {
    713
        Move,
    714
        Zoom,
    715
        MoveLastLocation,
    716
        Read,
    717
        Describe,
    718
        ReadTo,
    719
        Locate,
    720
        ChangeNavMode,
    721
        ToggleSpeakMode,
    722
        SetPlacemarker,
    723
        Exit,
    724
        Last,
    725
    }
    726
    727
    #[derive(PartialEq, PartialOrd, Clone, Copy)]
    728
    enum NavigationParam {
    729
        Placemarker0,
    730
        Placemarker1,
    731
        Placemarker2,
    732
        Placemarker3,
    733
        Placemarker4,
    734
        Placemarker5,
    735
        Placemarker6,
    736
        Placemarker7,
    737
        Placemarker8,
    738
        Placemarker9,
    739
        Previous,
    740
        Current,
    741
        Next,
    742
        Start,
    743
        End,
    744
        LineStart,
    745
        LineEnd,
    746
        CellPrevious,
    747
        CellCurrent,
    748
        CellNext,
    749
        ColStart,
    750
        ColEnd,
    751
        CellUp,
    752
        CellDown,
    753
        Last
    754
    }
    755
    756
    757
    0
    fn choose_command(
    758
    0
      shift_key: bool,
    759
    0
      control_key: bool,
    760
    0
      none: NavigationCommand,
    761
    0
      shift: NavigationCommand,
    762
    0
      control: NavigationCommand,
    763
    0
      shift_control: NavigationCommand
    764
    0
    ) -> NavigationCommand {
    765
    0
         if shift_key && control_key {
    766
    0
        return shift_control;
    767
    0
        } else if control_key {
    768
    0
            return control;
    769
    0
        } else if shift_key {
    770
    0
        return shift;
    771
      } else {
    772
    0
        return none;
    773
        }
    774
    0
    }
    775
    776
    0
    fn choose_param(
    777
    0
      shift_key: bool,
    778
    0
      control_key: bool,
    779
    0
      none: NavigationParam,
    780
    0
      shift: NavigationParam,
    781
    0
      control: NavigationParam,
    782
    0
      shift_control: NavigationParam
    783
    0
    ) -> NavigationParam {
    784
    0
        if shift_key && control_key {
    785
    0
        return shift_control;
    786
    0
        } else if control_key {
    787
    0
            return control;
    788
    0
        } else if shift_key {
    789
    0
        return shift;
    790
      } else {
    791
    0
        return none;
    792
        }
    793
    0
    }
    794
    795
    0
    fn key_press_to_command_and_param(
    796
    0
        key: usize,
    797
    0
      shift_key: bool,
    798
    0
      control_key: bool,
    799
    0
      alt_key: bool,
    800
    0
      meta_key: bool,
    801
    0
    ) -> Result<(NavigationCommand, NavigationParam)> {
    802
      // key press mapping should probably be stored externally (registry) with an app that allows changes
    803
      // for now, we build in the defaults
    804
    805
        // this is a hack to map alt+ctl+arrow to ctl+arrow to change table mappings (github.com/NSoiffer/MathCAT/issues/105)
    806
        // if this change sticks, choose_command() needs to be changed and this hack should go away
    807
    0
        let mut alt_key = alt_key;
    808
    0
        if alt_key && control_key && [VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN].contains(&key) {
    809
    0
            alt_key = false;
    810
    0
        }
    811
    0
      if alt_key || meta_key {
    812
    0
            bail!("Invalid argument to key_press_to_command_and_param");
    813
    0
        }
    814
    815
        let command;
    816
        let param;
    817
    0
      match key {
    818
    0
            VK_LEFT => {
    819
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Move,   NavigationCommand::Read, NavigationCommand::Move,     NavigationCommand::Describe);
    820
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Previous, NavigationParam::Previous, NavigationParam::CellPrevious, NavigationParam::Previous);
    821
    0
                },
    822
    0
            VK_RIGHT => {
    823
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Move, NavigationCommand::Read, NavigationCommand::Move,    NavigationCommand::Describe);
    824
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Next, NavigationParam::Next, NavigationParam::CellNext, NavigationParam::Next);
    825
    0
                },
    826
    0
            VK_UP => {
    827
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Zoom,      NavigationCommand::ChangeNavMode, NavigationCommand::Move,   NavigationCommand::Zoom);
    828
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Previous,  NavigationParam::Previous,      NavigationParam::CellUp, NavigationParam::Start);
    829
    0
                },
    830
    0
            VK_DOWN => {
    831
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Zoom, NavigationCommand::ChangeNavMode, NavigationCommand::Move,     NavigationCommand::Zoom);
    832
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Next, NavigationParam::Next,          NavigationParam::CellDown, NavigationParam::End);
    833
    0
                },
    834
    0
            VK_RETURN => {
    835
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Locate,  NavigationCommand::Last, NavigationCommand::Locate, NavigationCommand::Last);
    836
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Previous,NavigationParam::Last, NavigationParam::Last,    NavigationParam::Last);
    837
    0
                },
    838
    0
            VK_SPACE => {
    839
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Read,   NavigationCommand::ToggleSpeakMode,    NavigationCommand::Read,        NavigationCommand::Describe);
    840
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Current, NavigationParam::Last,                NavigationParam::CellCurrent, NavigationParam::Current);
    841
    0
                },
    842
    843
    0
            VK_HOME => {
    844
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Move, NavigationCommand::Move,    NavigationCommand::Move,      NavigationCommand::ReadTo);
    845
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::Start,NavigationParam::ColStart, NavigationParam::LineStart, NavigationParam::Start);
    846
    0
                },
    847
    0
            VK_END => {
    848
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Move, NavigationCommand::Move,   NavigationCommand::Move,    NavigationCommand::ReadTo);
    849
    0
                param =   choose_param(  shift_key, control_key, NavigationParam::End,  NavigationParam::ColEnd, NavigationParam::LineEnd, NavigationParam::End);
    850
    0
                },
    851
    0
            VK_BACK => {
    852
    0
                command = NavigationCommand::MoveLastLocation;
    853
    0
                param = NavigationParam::Last;
    854
    0
                },
    855
    0
            VK_ESCAPE => {
    856
    0
                command = NavigationCommand::Exit;
    857
    0
                param = NavigationParam::Last;
    858
    0
                },
    859
    0
            0x30..=0x39 => {  // '0' ... '9'
    860
    0
                command = choose_command(shift_key, control_key, NavigationCommand::Move, NavigationCommand::Read, NavigationCommand::SetPlacemarker, NavigationCommand::Describe);
    861
                static PLACE_MARKER: &[NavigationParam] = &[
    862
                    NavigationParam::Placemarker0,
    863
                    NavigationParam::Placemarker1,
    864
                    NavigationParam::Placemarker2,
    865
                    NavigationParam::Placemarker3,
    866
                    NavigationParam::Placemarker4,
    867
                    NavigationParam::Placemarker5,
    868
                    NavigationParam::Placemarker6,
    869
                    NavigationParam::Placemarker7,
    870
                    NavigationParam::Placemarker8,
    871
                    NavigationParam::Placemarker9,
    872
                ];
    873
    0
                param = PLACE_MARKER[key-0x30];
    874
            },
    875
    0
            _ => bail!("Unknown key press/command"),
    876
        };
    877
    878
    0
      return Ok( (command, param) );
    879
    0
    }
    880
    881
    // translate the key presses into commands
    882
    883
    884
    2
    fn navigation_command_string(command: NavigationCommand, param: NavigationParam) -> &'static str {
    885
    2
      match command {
    886
          NavigationCommand::Move => {
    887
    1
                return match param {
    888
    0
                    NavigationParam::Previous => "MovePrevious",
    889
    0
                    NavigationParam::Next => "MoveNext",
    890
    1
                    NavigationParam::Start => "MoveStart",
    891
    0
                    NavigationParam::End => "MoveEnd",
    892
    0
                    NavigationParam::LineStart => "MoveLineStart",
    893
    0
                    NavigationParam::LineEnd => "MoveLineEnd",
    894
    0
                    NavigationParam::CellPrevious => "MoveCellPrevious",
    895
    0
                    NavigationParam::CellNext => "MoveCellNext",
    896
    0
                    NavigationParam::CellUp => "MoveCellUp",
    897
    0
                    NavigationParam::CellDown => "MoveCellDown",
    898
    0
                    NavigationParam::ColStart => "MoveColumnStart",
    899
    0
                    NavigationParam::ColEnd => "MoveColumnEnd",
    900
                    _ => {
    901
    0
                        if param < NavigationParam::Placemarker0 || param > NavigationParam::Placemarker9 {
    902
    0
                            panic!("Internal Error: Found illegal value for param of NavigationCommand::Move");
    903
    0
                        }
    904
                        static MOVE_TO: &[&str] = &["MoveTo0","MoveTo1","MoveTo2","MoveTo3","MoveTo4","MoveTo5","MoveTo6","MoveTo7","MoveTo8","MoveTo9"];
    905
    0
                        return MOVE_TO[(param as usize) - (NavigationParam::Placemarker0 as usize)];
    906
                    }
    907
                }
    908
            },
    909
            NavigationCommand::Zoom => {
    910
    1
                return match param {
    911
    0
                    NavigationParam::Next => "ZoomIn",
    912
    1
                    NavigationParam::Previous => "ZoomOut",
    913
    0
                    NavigationParam::Start => "ZoomOutAll",
    914
    0
                    NavigationParam::End => "ZoomInAll",
    915
    0
                    _  => panic!("Illegal param for NavigationCommand::Zoom"),
    916
                }
    917
            },
    918
            NavigationCommand::MoveLastLocation => {
    919
    0
                return "MoveLastLocation";
    920
            },
    921
            NavigationCommand::Read => {
    922
    0
                return match param {
    923
    0
                    NavigationParam::Previous => "ReadPrevious",
    924
    0
                    NavigationParam::Next => "ReadNext",
    925
    0
                    NavigationParam::Current => "ReadCurrent",
    926
    0
                    NavigationParam::CellCurrent => "ReadCellCurrent",
    927
    0
                    NavigationParam::Start => "ReadStart",
    928
    0
                    NavigationParam::End => "ReadEnd",
    929
    0
                    NavigationParam::LineStart => "ReadLineStart",
    930
    0
                    NavigationParam::LineEnd => "ReadLineEnd",
    931
                    _ => {
    932
    0
                        if param < NavigationParam::Placemarker0 || param > NavigationParam::Placemarker9 {
    933
    0
                            panic!("Internal Error: Found illegal value for param of NavigationCommand::Move");
    934
    0
                        }
    935
                        static READ_PLACE_MARKERS: &[&str] = &["Read0","Read1","Read2","Read3","Read4","Read5","Read6","Read7","Read8","Read9"];
    936
    0
                        return READ_PLACE_MARKERS[(param as usize) - (NavigationParam::Placemarker0 as usize)];
    937
                    },
    938
                }
    939
            },
    940
            NavigationCommand::Describe => {
    941
    0
                return match param {
    942
    0
                    NavigationParam::Previous => "DescribePrevious",
    943
    0
                    NavigationParam::Next => "DescribeNext",
    944
    0
                    NavigationParam::Current => "DescribeCurrent",
    945
                    _ => {
    946
    0
                        if param < NavigationParam::Placemarker0 || param > NavigationParam::Placemarker9 {
    947
    0
                            panic!("Internal Error: Found illegal value for param of NavigationCommand::Describe");
    948
    0
                        }
    949
                        static DESCRIBE_PLACE_MARKERS: &[&str] = &["Describe0","Describe1","Describe2","Describe3","Describe4","Describe5","Describe6","Describe7","Describe8","Describe9"];
    950
    0
                        return DESCRIBE_PLACE_MARKERS[(param as usize) - (NavigationParam::Placemarker0 as usize)];
    951
                    }
    952
                }
    953
            },
    954
            NavigationCommand::ReadTo => {
    955
    0
                todo!("ReadTo navigation command")
    956
            },
    957
            NavigationCommand::Locate => {
    958
    0
                if param ==NavigationParam::Previous {
    959
    0
                    return "WhereAmI";
    960
    0
                } else if param ==NavigationParam::Last {
    961
    0
                    return "WhereAmIAll";
    962
    0
                }
    963
            },
    964
            NavigationCommand::ChangeNavMode => {
    965
    0
                if param ==NavigationParam::Previous {
    966
    0
                    return "ToggleZoomLockUp";
    967
    0
                } else if param ==NavigationParam::Next {
    968
    0
                    return "ToggleZoomLockDown";
    969
    0
                }
    970
            },
    971
            NavigationCommand::ToggleSpeakMode => {
    972
    0
                return "ToggleSpeakMode";
    973
            },
    974
            NavigationCommand::SetPlacemarker => {
    975
    0
                if param < NavigationParam::Placemarker0 || param > NavigationParam::Placemarker9 {
    976
    0
                    panic!("Internal Error: Found illegal value for param of NavigationCommand::SetPlacemarker");
    977
    0
                }
    978
                static SET_PLACE_MARKER: &[&str] = &["SetPlacemarker0","SetPlacemarker1","SetPlacemarker2","SetPlacemarker3","SetPlacemarker4","SetPlacemarker5","SetPlacemarker6","SetPlacemarker7","SetPlacemarker8","SetPlacemarker9"];
    979
    0
                return SET_PLACE_MARKER[(param as usize) - (NavigationParam::Placemarker0 as usize)];
    980
            },
    981
            NavigationCommand::Exit => {
    982
    0
                return "Exit";
    983
            },
    984
            NavigationCommand::Last => {
    985
    0
                return "Error";
    986
            }
    987
        };
    988
    0
        return "Error";
    989
    2
    }
    990
    991
    #[cfg(test)]
    992
    mod tests {
    993
        use super::*;
    994
        #[allow(unused_imports)]
    995
        use crate::init_logger;
    996
        use crate::interface::*;
    997
    998
        #[cfg(test)]
    999
        /// Assert if result_id != '' and it doesn't match the id of the result of the move
    1000
        /// Returns the speech from the command
    1001
    547
        fn test_command(command: &'static str, mathml: Element, result_id: &str) -> String {
    1002
            // debug!("\nCommand: {}", command);
    1003
    547
            NAVIGATION_STATE.with(|nav_stack| {
    1004
    547
                let (start_id, _) = nav_stack.borrow().get_navigation_mathml_id(mathml);
    1005
    547
                match do_navigate_command_string(mathml, command) {
    1006
    0
                    Err(e) => {
    1007
    0
                        panic!("\nStarting at '{}', '{} failed.\n{}",
    1008
    0
                                            start_id, command, &crate::interface::errors_to_string(&e))
    1009
                    },
    1010
    547
                    Ok(nav_speech) => {
    1011
    547
                        let nav_speech = nav_speech.trim_end_matches(&[' ', ',', ';']);
    1012
                        // debug!("Full speech: {}", nav_speech);
    1013
    547
                        if !result_id.is_empty() {
    1014
    547
                            let (id, _) = nav_stack.borrow().get_navigation_mathml_id(mathml);
    1015
    547
                            assert_eq!(result_id, id, "\nStarting at '{}', '{} failed.", start_id, command);
    1016
    0
                        }
    1017
    547
                        return nav_speech.to_string();
    1018
                    }
    1019
                };
    1020
    547
            })
    1021
    547
        }
    1022
    1023
    56
        fn init_default_prefs(mathml: &str, nav_mode_default: &str) {
    1024
    56
            set_rules_dir(super::super::abs_rules_dir_path()).unwrap();
    1025
    56
            set_preference("NavMode", nav_mode_default).unwrap();
    1026
    56
            set_preference("NavVerbosity", "Verbose").unwrap();
    1027
    56
            set_preference("AutoZoomOut", "True").unwrap();
    1028
    56
            set_preference("Language", "en").unwrap();
    1029
    56
            set_preference("SpeechStyle", "SimpleSpeak").unwrap();
    1030
    56
            set_preference("Verbosity", "Medium").unwrap();
    1031
    56
            set_preference("Overview", "False").unwrap();
    1032
    56
            set_mathml(mathml).unwrap();
    1033
    56
        }
    1034
    1035
        #[test]
    1036
    1
        fn zoom_in() -> Result<()> {
    1037
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    1038
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    1039
    1
                    <mi id='denom'>d</mi>
    1040
    1
                </mfrac></math>";
    1041
    1
            init_default_prefs(mathml_str, "Enhanced");
    1042
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1043
    1
                let package_instance = package_instance.borrow();
    1044
    1
                let mathml = get_element(&package_instance);
    1045
    1
                test_command("ZoomIn", mathml, "msup");
    1046
    1
                test_command("ZoomIn", mathml, "base");
    1047
    1
                test_command("ZoomIn", mathml, "base");
    1048
    1
                return Ok( () );
    1049
    1
            });
    1050
    1
        }
    1051
    1052
        #[test]
    1053
    1
        fn test_init_navigate_move_right() -> Result<()> {
    1054
            // this is how navigation typically starts up
    1055
    1
            let mathml_str = " <math display='block' id='id-0'>
    1056
    1
                <mrow id='id-1'>
    1057
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    1058
    1
                    <mo id='id-3'>=</mo>
    1059
    1
                    <mrow id='id-4'>
    1060
    1
                        <mi id='id-5'>a</mi>
    1061
    1
                        <mo id='id-6'>-</mo>
    1062
    1
                        <mn id='id-7'>2</mn>
    1063
    1
                    </mrow>
    1064
    1
                </mrow>
    1065
    1
            </math>";
    1066
    1
            init_default_prefs(mathml_str, "Enhanced");
    1067
    1
            debug!("--- Enhanced ---");
    1068
    1
            MATHML_INSTANCE.with(|package_instance| {
    1069
    1
                let package_instance = package_instance.borrow();
    1070
    1
                let mathml = get_element(&package_instance);
    1071
    1
                test_command("ZoomIn", mathml, "msup");
    1072
    1
                test_command("MoveNext", mathml, "id-3");
    1073
    1
            });
    1074
    1075
    1
            init_default_prefs(mathml_str, "Simple");
    1076
    1
            debug!("--- Simple ---");
    1077
    1
            MATHML_INSTANCE.with(|package_instance: &RefCell<Package>| {
    1078
    1
                let package_instance = package_instance.borrow();
    1079
    1
                let mathml = get_element(&package_instance);
    1080
    1
                test_command("ZoomIn", mathml, "msup");
    1081
    1
                test_command("MoveNext", mathml, "id-3");
    1082
    1
            });
    1083
    1084
    1
            init_default_prefs(mathml_str, "Character");
    1085
    1
            debug!("--- Character ---");
    1086
    1
            MATHML_INSTANCE.with(|package_instance| {
    1087
    1
                let package_instance = package_instance.borrow();
    1088
    1
                let mathml = get_element(&package_instance);
    1089
    1
                test_command("ZoomIn", mathml, "base");
    1090
    1
                test_command("MoveNext", mathml, "exp");
    1091
    1
            });
    1092
    1
            return Ok( () );
    1093
    1
        }
    1094
    1095
        #[test]
    1096
    1
        fn zoom_in_parens() -> Result<()> {
    1097
            // (a+b)(c+d) + 1
    1098
    1
            let mathml_str = " <math display='block' id='id-0'>
    1099
    1
                <mrow id='id-1'>
    1100
    1
                    <mrow id='id-2'>
    1101
    1
                        <mrow id='id-3'>
    1102
    1
                        <mo stretchy='false' id='id-4'>(</mo>
    1103
    1
                        <mrow id='id-5'>
    1104
    1
                            <mi id='id-6'>a</mi>
    1105
    1
                            <mo id='id-7'>+</mo>
    1106
    1
                            <mi id='id-8'>b</mi>
    1107
    1
                        </mrow>
    1108
    1
                        <mo stretchy='false' id='id-9'>)</mo>
    1109
    1
                        </mrow>
    1110
    1
                        <mo id='id-10'>&#x2062;</mo>
    1111
    1
                        <mrow id='id-11'>
    1112
    1
                        <mo stretchy='false' id='id-12'>(</mo>
    1113
    1
                        <mrow id='id-13'>
    1114
    1
                            <mi id='id-14'>c</mi>
    1115
    1
                            <mo id='id-15'>+</mo>
    1116
    1
                            <mi id='id-16'>d</mi>
    1117
    1
                        </mrow>
    1118
    1
                        <mo stretchy='false' id='id-17'>)</mo>
    1119
    1
                        </mrow>
    1120
    1
                    </mrow>
    1121
    1
                    <mo id='id-18'>+</mo>
    1122
    1
                    <mn id='id-19'>1</mn>
    1123
    1
                </mrow>
    1124
    1
            </math>";
    1125
    1
            init_default_prefs(mathml_str, "Enhanced");
    1126
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1127
    1
                let package_instance = package_instance.borrow();
    1128
    1
                let mathml = get_element(&package_instance);
    1129
    1
                set_preference("NavMode", "Enhanced")?0;
    1130
    1
                debug!("\n------EnhancedMode----------");
    1131
    1
                test_command("ZoomIn", mathml, "id-2");
    1132
    1
                test_command("ZoomIn", mathml, "id-5");
    1133
    1
                test_command("ZoomIn", mathml, "id-6");
    1134
    1135
                // repeat, but this time with "Simple
    1136
    1
                set_preference("NavMode", "Simple")?0;
    1137
    1
                debug!("\n------SimpleMode----------");
    1138
    1
                test_command("ZoomOutAll", mathml, "id-1");
    1139
    1
                test_command("ZoomIn", mathml, "id-4");
    1140
    1
                test_command("ZoomIn", mathml, "id-4");
    1141
    1
                return Ok( () );
    1142
    1
            });
    1143
    1
        }
    1144
    1145
        #[test]
    1146
    1
        fn zoom_in_all() -> Result<()> {
    1147
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    1148
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    1149
    1
                    <mi id='denom'>d</mi>
    1150
    1
                </mfrac></math>";
    1151
    1
            init_default_prefs(mathml_str, "Enhanced");
    1152
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1153
    1
                let package_instance = package_instance.borrow();
    1154
    1
                let mathml = get_element(&package_instance);
    1155
    1
                test_command("ZoomInAll", mathml, "base");
    1156
    1
                return Ok( () );
    1157
    1
            });
    1158
    1
        }
    1159
    1160
    1161
        #[test]
    1162
    1
        fn zoom_out() -> Result<()> {
    1163
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    1164
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    1165
    1
                    <mi id='denom'>d</mi>
    1166
    1
                </mfrac></math>";
    1167
    1
                init_default_prefs(mathml_str, "Enhanced");
    1168
    1
                return MATHML_INSTANCE.with(|package_instance| {
    1169
    1
                let package_instance = package_instance.borrow();
    1170
    1
                let mathml = get_element(&package_instance);
    1171
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1172
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1173
    1
                        current_node: "base".to_string(),
    1174
    1
                        current_node_offset: 0
    1175
    1
                    }, "None")
    1176
    1
                });
    1177
    1
                test_command("ZoomOut", mathml, "msup");
    1178
    1179
    1
                let _nav_speech = do_navigate_command_and_param(mathml, NavigationCommand::Zoom, NavigationParam::Previous)?0;
    1180
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1181
    1
                    let (id, _) = nav_stack.borrow().get_navigation_mathml_id(mathml);
    1182
    1
                    assert_eq!(id, "mfrac");
    1183
    1
                });
    1184
    1
                return Ok( () );
    1185
    1
            });
    1186
    1
        }
    1187
    1188
        #[test]
    1189
    1
        fn zoom_out_all() -> Result<()> {
    1190
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    1191
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    1192
    1
                    <mi id='denom'>d</mi>
    1193
    1
                </mfrac></math>";
    1194
    1
                init_default_prefs(mathml_str, "Enhanced");
    1195
    1
                return MATHML_INSTANCE.with(|package_instance| {
    1196
    1
                let package_instance = package_instance.borrow();
    1197
    1
                let mathml = get_element(&package_instance);
    1198
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1199
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1200
    1
                        current_node: "base".to_string(),
    1201
    1
                        current_node_offset: 0
    1202
    1
                    }, "None")
    1203
    1
                });
    1204
    1205
    1
                test_command("ZoomOutAll", mathml, "mfrac");
    1206
    1
                return Ok( () );
    1207
    1
            });
    1208
    1
        }
    1209
    1210
        #[test]
    1211
    1
        fn move_start_end() -> Result<()> {
    1212
    1
            let mathml_str = " <math display='block' id='id-0'>
    1213
    1
            <mrow id='id-1'>
    1214
    1
              <mi id='id-2'>x</mi>
    1215
    1
              <mo id='id-3'>=</mo>
    1216
    1
              <mrow id='id-4'>
    1217
    1
                <mi id='id-5'>a</mi>
    1218
    1
                <mo id='id-6'>-</mo>
    1219
    1
                <mn id='id-7'>2</mn>
    1220
    1
              </mrow>
    1221
    1
            </mrow>
    1222
    1
           </math>";
    1223
    1
           init_default_prefs(mathml_str, "Enhanced");
    1224
    1
           return MATHML_INSTANCE.with(|package_instance| {
    1225
    1
                let package_instance = package_instance.borrow();
    1226
    1
                let mathml = get_element(&package_instance);
    1227
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1228
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1229
    1
                        current_node: "id-4".to_string(),
    1230
    1
                        current_node_offset: 0
    1231
    1
                    }, "None")
    1232
    1
                });
    1233
    1234
    1
               set_preference("NavMode", "Character")?0;
    1235
    1
                test_command("MoveStart", mathml, "id-2");
    1236
    1
                test_command("MoveEnd", mathml, "id-7");
    1237
    1
               set_preference("NavMode", "Simple")?0;
    1238
    1
                test_command("MoveStart", mathml, "id-2");
    1239
    1
                test_command("MoveEnd", mathml, "id-7");
    1240
    1
               set_preference("NavMode", "Enhanced")?0;
    1241
    1
                test_command("MoveStart", mathml, "id-2");
    1242
    1
                test_command("MovePrevious", mathml, "id-2");
    1243
    1
                test_command("MoveEnd", mathml, "id-4");
    1244
    1
                test_command("MoveNext", mathml, "id-4");
    1245
    1
                return Ok( () );
    1246
    1
            });
    1247
    1
        }
    1248
    1249
        #[test]
    1250
    1
        fn move_line_start_end() -> Result<()> {
    1251
    1
            let mathml_str = " <math display='block' id='id-0'>
    1252
    1
            <mfrac displaystyle='true' id='id-1'>
    1253
    1
              <mi id='id-2'>x</mi>
    1254
    1
              <mrow id='id-3'>
    1255
    1
                <msup id='id-4'>
    1256
    1
                  <mi id='id-5'>y</mi>
    1257
    1
                  <mn id='id-6'>2</mn>
    1258
    1
                </msup>
    1259
    1
                <mo id='id-7'>+</mo>
    1260
    1
                <mn id='id-8'>1</mn>
    1261
    1
              </mrow>
    1262
    1
            </mfrac>
    1263
    1
           </math>";
    1264
    1
           init_default_prefs(mathml_str, "Enhanced");
    1265
    1
           return MATHML_INSTANCE.with(|package_instance| {
    1266
    1
                let package_instance = package_instance.borrow();
    1267
    1
                let mathml = get_element(&package_instance);
    1268
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1269
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1270
    1
                        current_node: "id-7".to_string(),
    1271
    1
                        current_node_offset: 0
    1272
    1
                    }, "None")
    1273
    1
                });
    1274
    1275
    1
               set_preference("NavMode", "Character")?0;
    1276
    1
                test_command("MoveLineStart", mathml, "id-5");
    1277
    1
                test_command("MoveLineEnd", mathml, "id-8");
    1278
    1
               set_preference("NavMode", "Simple")?0;
    1279
    1
                test_command("MoveLineStart", mathml, "id-4");
    1280
    1
                test_command("MoveLineEnd", mathml, "id-8");
    1281
    1
               set_preference("NavMode", "Enhanced")?0;
    1282
    1
                test_command("MoveLineStart", mathml, "id-4");
    1283
    1
                test_command("MoveLineEnd", mathml, "id-8");
    1284
    1
                test_command("MoveEnd", mathml, "id-3");
    1285
    1
                return Ok( () );
    1286
    1
            });
    1287
    1
        }
    1288
    1289
        #[test]
    1290
    1
        fn text_extremes_and_move_last_location() -> Result<()> {
    1291
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    1292
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    1293
    1
                    <mi id='denom'>d</mi>
    1294
    1
                </mfrac></math>";
    1295
    1
                init_default_prefs(mathml_str, "Enhanced");
    1296
    1
                return MATHML_INSTANCE.with(|package_instance| {
    1297
    1
                let package_instance = package_instance.borrow();
    1298
    1
                let mathml = get_element(&package_instance);
    1299
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1300
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1301
    1
                        current_node: "base".to_string(),
    1302
    1
                        current_node_offset: 0
    1303
    1
                    }, "None")
    1304
    1
                });
    1305
    1306
    1
                test_command("ZoomOutAll", mathml, "mfrac");
    1307
    1
                test_command("ZoomOut", mathml, "mfrac");
    1308
    1
                test_command("MoveLastLocation", mathml, "base");       // second zoom out should do nothing
    1309
    1310
    1
                test_command("ZoomOut", mathml, "msup");
    1311
    1
                test_command("ZoomInAll", mathml, "base");
    1312
    1
                test_command("ZoomIn", mathml, "base");
    1313
    1
                test_command("MoveLastLocation", mathml, "msup");       // second zoom in should do nothing
    1314
    1315
    1
                return Ok( () );
    1316
    1
            });
    1317
    1
        }
    1318
    1319
        #[test]
    1320
    1
        fn move_to_start() -> Result<()> {
    1321
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    1322
    1
                    <mrow id='num'><msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup><mo id='factorial'>!</mo></mrow>
    1323
    1
                    <mi id='denom'>d</mi>
    1324
    1
                </mfrac></math>";
    1325
    1
                init_default_prefs(mathml_str, "Enhanced");
    1326
    1
                return MATHML_INSTANCE.with(|package_instance| {
    1327
    1
                let package_instance = package_instance.borrow();
    1328
    1
                let mathml = get_element(&package_instance);
    1329
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1330
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1331
    1
                        current_node: "denom".to_string(),
    1332
    1
                        current_node_offset: 0
    1333
    1
                    }, "None")
    1334
    1
                });
    1335
    1
                test_command("MoveLineStart", mathml, "denom");
    1336
    1337
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1338
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1339
    1
                        current_node: "factorial".to_string(),
    1340
    1
                        current_node_offset: 0
    1341
    1
                    }, "None")
    1342
    1
                });
    1343
    1
                test_command("MoveLineStart", mathml, "msup");
    1344
    1345
    1
                let _nav_speech = do_navigate_command_and_param(mathml, NavigationCommand::Move, NavigationParam::Start)?0;
    1346
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1347
    1
                    let (id, _) = nav_stack.borrow().get_navigation_mathml_id(mathml);
    1348
    1
                    assert_eq!(id, "num");
    1349
    1
                });
    1350
    1
                return Ok( () );
    1351
    1
            });
    1352
    1
        }
    1353
    1354
        #[test]
    1355
    1
        fn move_right_sup() -> Result<()> {
    1356
    1
            let mathml_str = "<math display='block' id='id-0'>
    1357
    1
            <mrow id='id-1'>
    1358
    1
              <msup id='id-2'>
    1359
    1
                <mn id='id-3'>2</mn>
    1360
    1
                <mi id='id-4'>q</mi>
    1361
    1
              </msup>
    1362
    1
              <mo id='id-5'>-</mo>
    1363
    1
              <mi id='id-6'>x</mi>
    1364
    1
            </mrow>
    1365
    1
            </math>";
    1366
    1
            init_default_prefs(mathml_str, "Enhanced");
    1367
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1368
    1
                let package_instance = package_instance.borrow();
    1369
    1
                let mathml = get_element(&package_instance);
    1370
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1371
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1372
    1
                        current_node: "id-2".to_string(),
    1373
    1
                        current_node_offset: 0
    1374
    1
                    }, "None")
    1375
    1
                });
    1376
    1
                set_preference("NavMode", "Enhanced")?0;
    1377
    1
                test_command("MoveNext", mathml, "id-5");
    1378
    1379
                // reset start and test Simple
    1380
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1381
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1382
    1
                        current_node: "id-2".to_string(),
    1383
    1
                        current_node_offset: 0
    1384
    1
                    }, "None")
    1385
    1
                });
    1386
    1
                set_preference("NavMode", "Simple")?0;
    1387
    1
                test_command("MoveNext", mathml, "id-5");
    1388
    1389
                // reset start and test Character
    1390
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1391
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1392
    1
                        current_node: "id-3".to_string(),
    1393
    1
                        current_node_offset: 0
    1394
    1
                    }, "None")
    1395
    1
                });
    1396
    1
                set_preference("NavMode", "Character")?0;
    1397
    1
                test_command("MoveNext", mathml, "id-4");
    1398
    1
                test_command("MoveNext", mathml, "id-5");
    1399
    1
                return Ok( () );
    1400
    1
            });
    1401
    1
        }
    1402
    1403
    1404
        #[test]
    1405
    1
        fn move_msubsup_char() -> Result<()> {
    1406
    1
            let mathml_str = "<math display='block' id='id-0'>
    1407
    1
            <mrow id='id-1'>
    1408
    1
              <mn id='id-2'>1</mn>
    1409
    1
              <mo id='id-3'>+</mo>
    1410
    1
              <msubsup id='id-4'>
    1411
    1
                <mi id='id-5'>x</mi>
    1412
    1
                <mn id='id-6'>2</mn>
    1413
    1
                <mn id='id-7'>3</mn>
    1414
    1
              </msubsup>
    1415
    1
              <mo id='id-8'>+</mo>
    1416
    1
              <mn id='id-9'>4</mn>
    1417
    1
            </mrow>
    1418
    1
           </math>";
    1419
    1
            init_default_prefs(mathml_str, "Character");
    1420
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1421
    1
                let package_instance = package_instance.borrow();
    1422
    1
                let mathml = get_element(&package_instance);
    1423
    1
                assert_eq!("zoomed in all of the way; 1", test_command("ZoomInAll", mathml, "id-2"));
    1424
    1
                assert_eq!("move right; plus", test_command("MoveNext", mathml, "id-3"));
    1425
    1
                assert_eq!("move right; in base; x", test_command("MoveNext", mathml, "id-5"));
    1426
    1
                assert_eq!("move right; in subscript; 2", test_command("MoveNext", mathml, "id-6"));
    1427
    1
                assert_eq!("move right; in superscript; 3", test_command("MoveNext", mathml, "id-7"));
    1428
    1
                assert_eq!("move right; out of superscript; plus", test_command("MoveNext", mathml, "id-8"));
    1429
    1
                assert_eq!("move left; in superscript; 3", test_command("MovePrevious", mathml, "id-7"));
    1430
    1
                assert_eq!("move left; in subscript; 2", test_command("MovePrevious", mathml, "id-6"));
    1431
    1
                assert_eq!("move left; in base; x", test_command("MovePrevious", mathml, "id-5"));
    1432
    1
                assert_eq!("move left; out of base; plus", test_command("MovePrevious", mathml, "id-3"));
    1433
    1434
    1
                return Ok( () );
    1435
    1
            });
    1436
    1
        }
    1437
    1438
        #[test]
    1439
    1
        fn zoom_logbase() -> Result<()> {
    1440
    1
            let mathml_str = "<math display='block' id='id-0'>
    1441
    1
                <mrow displaystyle='true' id='id-1'>
    1442
    1
                    <msub id='id-2'>
    1443
    1
                        <mi id='id-3'>log</mi>
    1444
    1
                        <mn id='id-4'>2</mn>
    1445
    1
                    </msub>
    1446
    1
                    <mo data-changed='added' id='id-5'>&#x2061;</mo>
    1447
    1
                    <mi id='id-6'>x</mi>a
    1448
    1
                </mrow>
    1449
    1
                </math>";
    1450
    1
            init_default_prefs(mathml_str, "Enhanced");
    1451
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1452
    1
                let package_instance = package_instance.borrow();
    1453
    1
                let mathml = get_element(&package_instance);
    1454
    1
                assert_eq!("zoom in; the log base 2", test_command("ZoomIn", mathml, "id-2"));
    1455
    1
                assert_eq!("zoom in; in base; 2", test_command("ZoomIn", mathml, "id-4"));
    1456
    1
                assert_eq!("zoomed in all of the way; 2", test_command("ZoomIn", mathml, "id-4"));
    1457
    1
                debug!("Now zooming out");
    1458
    1
                assert_eq!("zoom out; out of base; the log base 2", test_command("ZoomOut", mathml, "id-2"));
    1459
    1
                assert_eq!("zoom out; the log base 2, of x", test_command("ZoomOut", mathml, "id-1"));
    1460
    1
                assert_eq!("zoomed out all of the way; the log base 2, of x", test_command("ZoomOut", mathml, "id-1"));
    1461
    1
                return Ok( () );
    1462
    1
            });
    1463
    1
        }
    1464
    1465
        #[test]
    1466
    1
        fn zoom_logbase_power() -> Result<()> {
    1467
    1
            let mathml_str = "<math display='block' id='id-0'>
    1468
    1
                <mrow displaystyle='true' id='id-1'>
    1469
    1
                    <msubsup id='id-2'>
    1470
    1
                        <mi id='id-3'>log</mi>
    1471
    1
                        <mn id='id-4'>2</mn>
    1472
    1
                        <mn id='id-5'>3</mn>
    1473
    1
                    </msubsup>
    1474
    1
                    <mo data-changed='added' id='id-6'>&#x2061;</mo>
    1475
    1
                    <mi id='id-7'>x</mi>
    1476
    1
                </mrow>
    1477
    1
                </math>";
    1478
    1
            init_default_prefs(mathml_str, "Enhanced");
    1479
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1480
    1
                let package_instance = package_instance.borrow();
    1481
    1
                let mathml = get_element(&package_instance);
    1482
    1
                assert_eq!("zoom in; the log base 2, cubed", test_command("ZoomIn", mathml, "id-2"));
    1483
    1
                assert_eq!("zoom in; in base; the log base 2", test_command("ZoomIn", mathml, "id-2-log-base"));
    1484
    1
                assert_eq!("zoom in; in base; 2", test_command("ZoomIn", mathml, "id-4"));
    1485
    1
                assert_eq!("zoomed in all of the way; 2", test_command("ZoomIn", mathml, "id-4"));
    1486
    1
                debug!("Now zooming out");
    1487
    1
                assert_eq!("zoom out; out of base; the log base 2", test_command("ZoomOut", mathml, "id-2-log-base"));
    1488
    1
                assert_eq!("zoom out; out of base; the log base 2, cubed", test_command("ZoomOut", mathml, "id-2"));
    1489
    1
                assert_eq!("zoom out; the log base 2, cubed of x", test_command("ZoomOut", mathml, "id-1"));
    1490
    1
                assert_eq!("zoomed out all of the way; the log base 2, cubed of x", test_command("ZoomOut", mathml, "id-1"));
    1491
    1
                return Ok( () );
    1492
    1
            });
    1493
    1
        }
    1494
    1495
        #[test]
    1496
    1
        fn zoom_msubsup() -> Result<()> {
    1497
            // msubsup is trickier because it creates an intent within an intent, so offsets need to be handled properly
    1498
    1
            let mathml_str = "<math id='math'><msubsup id='msubsup'><mi id='base'>𝑥</mi><mn id='sub'>1</mn><mn id='sup'>2</mn></msubsup></math>";
    1499
    1
            init_default_prefs(mathml_str, "Enhanced");
    1500
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1501
    1
                let package_instance = package_instance.borrow();
    1502
    1
                let mathml = get_element(&package_instance);
    1503
    1
                set_preference("NavMode", "Enhanced").unwrap();
    1504
    1
                debug!("Enhanced mode");
    1505
    1
                do_commands(mathml)?0;
    1506
    1
                set_preference("NavMode", "Simple").unwrap();
    1507
    1
                debug!("Simple mode");
    1508
    1
                do_commands(mathml)?0;
    1509
    1
                set_preference("NavMode", "Character").unwrap();
    1510
    1
                debug!("Character mode");
    1511
    1
                assert_eq!("zoom in; in base; x", test_command("ZoomIn", mathml, "base"));
    1512
    1
                assert_eq!("zoom out; out of base; x sub 1 super 2 end super", test_command("ZoomOut", mathml, "msubsup"));
    1513
    1
                return Ok( () );
    1514
    1515
            /// Enhanced and Simple mode should behave the same
    1516
    2
            fn do_commands(mathml: Element) -> Result<()> {
    1517
    2
                assert_eq!("zoom in; in base; x sub 1", test_command("ZoomIn", mathml, "msubsup-indexed-by"));
    1518
    2
                assert_eq!("zoom in; in base; x", test_command("ZoomIn", mathml, "base"));
    1519
    2
                assert_eq!("zoomed in all of the way; x", test_command("ZoomIn", mathml, "base"));
    1520
    2
                debug!("Now zooming out");
    1521
    2
                assert_eq!("zoom out; out of base; x sub 1", test_command("ZoomOut", mathml, "msubsup-indexed-by"));
    1522
    2
                assert_eq!("zoom out; out of base; x sub 1, squared", test_command("ZoomOut", mathml, "msubsup"));
    1523
    2
                assert_eq!("zoomed out all of the way; x sub 1, squared", test_command("ZoomOut", mathml, "msubsup"));
    1524
    2
                return Ok( () );
    1525
    2
            }
    1526
    1
            });
    1527
    1
        }
    1528
    1529
        #[test]
    1530
    1
        fn move_mmultiscripts_char() -> Result<()> {
    1531
    1
            let mathml_str = "<math display='block' id='id-0'>
    1532
    1
                <mmultiscripts data-mjx-texclass='ORD' data-chem-formula='5' id='id-1'>
    1533
    1
                    <mrow data-chem-formula='3' id='id-2'>
    1534
    1
                        <mo stretchy='false' id='id-3'>[</mo>
    1535
    1
                        <mmultiscripts data-chem-formula='3' id='id-4'>
    1536
    1
                            <mi data-chem-element='3' id='id-5'>Co</mi>
    1537
    1
                            <mn id='id-6'>6</mn>
    1538
    1
                            <none id='id-7'></none>
    1539
    1
                        </mmultiscripts>
    1540
    1
                        <mo stretchy='false' id='id-8'>]</mo>
    1541
    1
                    </mrow>
    1542
    1
                    <none id='id-9'></none>
    1543
    1
                    <mrow id='id-10'>
    1544
    1
                        <mn id='id-11'>3</mn>
    1545
    1
                        <mo id='id-12'>+</mo>
    1546
    1
                    </mrow>
    1547
    1
                </mmultiscripts>
    1548
    1
                </math>";
    1549
    1
                init_default_prefs(mathml_str, "Character");
    1550
    1
                return MATHML_INSTANCE.with(|package_instance| {
    1551
    1
                let package_instance = package_instance.borrow();
    1552
    1
                let mathml = get_element(&package_instance);
    1553
    1
                assert_eq!("zoomed in all of the way; in base; open bracket", test_command("ZoomInAll", mathml, "id-3"));
    1554
    1
                assert_eq!("move right; in base; cap c o", test_command("MoveNext", mathml, "id-5"));
    1555
    1
                assert_eq!("move right; in subscript; 6", test_command("MoveNext", mathml, "id-6"));
    1556
    1
                assert_eq!("move right; out of subscript; close bracket", test_command("MoveNext", mathml, "id-8"));
    1557
    1
                assert_eq!("move right; in superscript; 3", test_command("MoveNext", mathml, "id-11"));
    1558
    1
                assert_eq!("move right; plus", test_command("MoveNext", mathml, "id-12"));
    1559
    1
                assert_eq!("cannot move right, end of math", test_command("MoveNext", mathml, "id-12"));
    1560
    1
                assert_eq!("move left; 3", test_command("MovePrevious", mathml, "id-11"));
    1561
    1
                assert_eq!("move left; in base; close bracket", test_command("MovePrevious", mathml, "id-8"));
    1562
    1
                assert_eq!("move left; in subscript; 6", test_command("MovePrevious", mathml, "id-6"));
    1563
    1
                assert_eq!("move left; in base; cap c o", test_command("MovePrevious", mathml, "id-5"));
    1564
    1
                assert_eq!("move left; out of base; open bracket", test_command("MovePrevious", mathml, "id-3"));
    1565
    1566
    1
                return Ok( () );
    1567
    1
            });
    1568
    1
        }
    1569
    1570
        #[test]
    1571
    1
        fn move_right_char() -> Result<()> {
    1572
    1
            let mathml_str = "<math id='id-0'>
    1573
    1
            <mrow displaystyle='true' id='id-1'>
    1574
    1
              <mi id='id-2'>x</mi>
    1575
    1
              <mo id='id-3'>=</mo>
    1576
    1
              <mrow id='id-4'>
    1577
    1
                <mfrac id='id-5'>
    1578
    1
                  <mn id='id-6'>1</mn>
    1579
    1
                  <mrow id='id-7'>
    1580
    1
                    <mi id='id-8'>a</mi>
    1581
    1
                    <mo id='id-9'>+</mo>
    1582
    1
                    <mn id='id-10'>2</mn>
    1583
    1
                  </mrow>
    1584
    1
                </mfrac>
    1585
    1
                <mo id='id-11'>+</mo>
    1586
    1
                <mrow id='id-12'>
    1587
    1
                  <mn id='id-13'>3</mn>
    1588
    1
                  <mo id='id-14'>&#x2062;</mo>
    1589
    1
                  <mi id='id-15'>b</mi>
    1590
    1
                </mrow>
    1591
    1
              </mrow>
    1592
    1
            </mrow>
    1593
    1
            </math>";
    1594
    1
            init_default_prefs(mathml_str, "Character");
    1595
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1596
    1
                let package_instance = package_instance.borrow();
    1597
    1
                let mathml = get_element(&package_instance);
    1598
    1
                test_command("ZoomInAll", mathml, "id-2");
    1599
    1
                test_command("MoveNext", mathml, "id-3");
    1600
    1
                test_command("MoveNext", mathml, "id-6");
    1601
    1
                test_command("MoveNext", mathml, "id-8");
    1602
    1
                test_command("MoveNext", mathml, "id-9");
    1603
    1
                test_command("MoveNext", mathml, "id-10");
    1604
    1
                test_command("MoveNext", mathml, "id-11");
    1605
    1
                test_command("MoveNext", mathml, "id-13");
    1606
    1
                test_command("MoveNext", mathml, "id-15");
    1607
    1
                test_command("MoveNext", mathml, "id-15");
    1608
    1609
    1
                return Ok( () );
    1610
    1
            });
    1611
    1
        }
    1612
    1613
        #[test]
    1614
    1
        fn char_mode_paren_test() -> Result<()> {
    1615
    1
            let mathml_str = "<math display='block' id='id-0'>
    1616
    1
                <mrow displaystyle='true' id='id-1'>
    1617
    1
                    <mrow id='id-2'>
    1618
    1
                        <mo id='id-3'>(</mo>
    1619
    1
                        <mi id='id-4'>a</mi>
    1620
    1
                        <mo id='id-5'>)</mo>
    1621
    1
                    </mrow>
    1622
    1
                    <mo id='id-6'>&#x2062;</mo>
    1623
    1
                    <mrow id='id-7'>
    1624
    1
                        <mo id='id-8'>(</mo>
    1625
    1
                        <mi id='id-9'>b</mi>
    1626
    1
                        <mo id='id-10'>)</mo>
    1627
    1
                    </mrow>
    1628
    1
                </mrow>
    1629
    1
            </math>";
    1630
    1
            init_default_prefs(mathml_str, "Character");
    1631
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1632
    1
                let package_instance = package_instance.borrow();
    1633
    1
                let mathml = get_element(&package_instance);
    1634
    1
                debug!("Character mode");
    1635
    1
                do_commands(mathml)?0;
    1636
    1
                set_preference("NavMode", "Simple").unwrap();
    1637
    1
                debug!("Simple mode");
    1638
    1
                test_command("ZoomIn", mathml, "id-3");  // zooms to the first parenthesis
    1639
    1
                do_commands(mathml)?0;
    1640
    1
                set_preference("NavMode", "Enhanced").unwrap();
    1641
    1
                debug!("Enhanced mode");
    1642
    1
                test_command("ZoomIn", mathml, "id-4");
    1643
    1
                test_command("MoveNext", mathml, "id-6");
    1644
    1
                test_command("MoveNext", mathml, "id-9");
    1645
    1
                test_command("MovePrevious", mathml, "id-6");
    1646
    1
                test_command("MovePrevious", mathml, "id-4");
    1647
    1648
    1
                return Ok( () );
    1649
    1
            });
    1650
    1651
            /// Simple and Character mode should behave the same
    1652
    2
            fn do_commands(mathml: Element) -> Result<()> {
    1653
    2
                test_command("ZoomIn", mathml, "id-3");
    1654
    2
                test_command("MoveNext", mathml, "id-4");
    1655
    2
                test_command("MoveNext", mathml, "id-5");
    1656
    2
                test_command("MoveNext", mathml, "id-8");
    1657
    2
                test_command("MoveNext", mathml, "id-9");
    1658
    2
                test_command("MoveNext", mathml, "id-10");
    1659
    2
                test_command("MovePrevious", mathml, "id-9");
    1660
    2
                test_command("MovePrevious", mathml, "id-8");
    1661
    2
                test_command("MovePrevious", mathml, "id-5");
    1662
    2
                test_command("ZoomOutAll", mathml, "id-1");
    1663
    2
                return Ok( () );
    1664
    2
            }
    1665
    1
        }
    1666
    1667
        #[test]
    1668
    1
        fn char_mode_trig_test() -> Result<()> {
    1669
    1
            let mathml_str = "<math id='id-0'>
    1670
    1
                <mrow id='id-1'>
    1671
    1
                <mi id='id-2'>sin</mi>
    1672
    1
                <mo id='id-3'>&#x2061;</mo>
    1673
    1
                <mrow id='id-4'>
    1674
    1
                    <mo id='id-5'>(</mo>
    1675
    1
                    <mi id='id-6'>x</mi>
    1676
    1
                    <mo id='id-7'>)</mo>
    1677
    1
                </mrow>
    1678
    1
                </mrow>
    1679
    1
            </math>";
    1680
    1
            init_default_prefs(mathml_str, "Simple");
    1681
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1682
    1
                let package_instance = package_instance.borrow();
    1683
    1
                let mathml = get_element(&package_instance);
    1684
    1
                do_commands(mathml)?0;
    1685
    1
                set_preference("NavMode", "Simple").unwrap();
    1686
    1
                do_commands(mathml)?0;
    1687
    1
                set_preference("NavMode", "Enhanced").unwrap();
    1688
    1
                test_command("ZoomIn", mathml, "id-2");
    1689
    1
                test_command("MoveNext", mathml, "id-6");
    1690
    1
                test_command("MovePrevious", mathml, "id-2");
    1691
    1692
    1
                return Ok( () );
    1693
    1
            });
    1694
    1695
    1696
            /// Simple and Character mode should behave the same
    1697
    2
            fn do_commands(mathml: Element) -> Result<()> {
    1698
    2
                test_command("ZoomIn", mathml, "id-2");
    1699
    2
                test_command("MoveNext", mathml, "id-5");
    1700
    2
                test_command("MoveNext", mathml, "id-6");
    1701
    2
                test_command("MoveNext", mathml, "id-7");
    1702
    2
                test_command("MovePrevious", mathml, "id-6");
    1703
    2
                test_command("MovePrevious", mathml, "id-5");
    1704
    2
                test_command("MovePrevious", mathml, "id-2");
    1705
    2
                test_command("ZoomOutAll", mathml, "id-1");
    1706
    2
                return Ok( () );
    1707
    2
            }
    1708
    1
        }
    1709
    1710
        #[test]
    1711
    1
        fn move_char_speech() -> Result<()> {
    1712
    1
            let mathml_str = "<math display='block' id='id-0'>
    1713
    1
                    <mrow id='id-1'>
    1714
    1
                    <mfrac id='id-2'>
    1715
    1
                        <mi id='id-3'>x</mi>
    1716
    1
                        <mi id='id-4'>y</mi>
    1717
    1
                    </mfrac>
    1718
    1
                    <mo id='id-5'>&#x2062;</mo>
    1719
    1
                    <mi id='id-6'>z</mi>
    1720
    1
                    </mrow>
    1721
    1
                </math>";
    1722
    1
                init_default_prefs(mathml_str, "Character");
    1723
    1
                return MATHML_INSTANCE.with(|package_instance| {
    1724
    1
                let package_instance = package_instance.borrow();
    1725
    1
                let mathml = get_element(&package_instance);
    1726
    1
                test_command("ZoomInAll", mathml, "id-3");
    1727
    1
                assert_eq!("move right; in denominator; y", test_command("MoveNext", mathml, "id-4"));
    1728
    1
                assert_eq!("move right; out of denominator; z", test_command("MoveNext", mathml, "id-6"));
    1729
    1
                assert_eq!("move left; in denominator; y", test_command("MovePrevious", mathml, "id-4"));
    1730
    1
                assert_eq!("move left; in numerator; x", test_command("MovePrevious", mathml, "id-3"));
    1731
    1732
    1
                return Ok( () );
    1733
    1
            });
    1734
    1
        }
    1735
    1736
        #[test]
    1737
    1
        fn move_inside_leaves() -> Result<()> {
    1738
    1
            let mathml_str = "<math display='block' id='id-0'>
    1739
    1
                    <mrow id='id-1'>
    1740
    1
                        <mfrac id='id-2'>
    1741
    1
                            <mi id='id-3'>top</mi>
    1742
    1
                            <mi id='id-4'>αβγ</mi>
    1743
    1
                        </mfrac>
    1744
    1
                    </mrow>
    1745
    1
                </math>";
    1746
    1
            init_default_prefs(mathml_str, "Character");
    1747
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1748
    1
            let package_instance = package_instance.borrow();
    1749
    1
            let mathml = get_element(&package_instance);
    1750
    1
            test_command("ZoomInAll", mathml, "id-3");
    1751
    1
            assert_eq!("zoomed in to first character; t", test_command("ZoomIn", mathml, "id-3"));
    1752
    1
            assert_eq!("move right; o", test_command("MoveNext", mathml, "id-3"));
    1753
    1
            assert_eq!("move right; p", test_command("MoveNext", mathml, "id-3"));
    1754
    1
            assert_eq!("move right; in denominator; αβγ", test_command("MoveNext", mathml, "id-4"));
    1755
    1
            assert_eq!("zoomed in to first character; alpha", test_command("ZoomIn", mathml, "id-4"));
    1756
    1
            assert_eq!("move right; beta", test_command("MoveNext", mathml, "id-4"));
    1757
    1
            assert_eq!("move right; gamma", test_command("MoveNext", mathml, "id-4"));
    1758
    1
            assert_eq!("cannot move right, end of math", test_command("MoveNext", mathml, "id-4"));
    1759
    1
            assert_eq!("move left; beta", test_command("MovePrevious", mathml, "id-4"));
    1760
    1
            assert_eq!("zoom out; αβγ", test_command("ZoomOut", mathml, "id-4"));
    1761
    1762
    1
            return Ok( () );
    1763
    1
            });
    1764
    1
        }
    1765
    1766
        #[test]
    1767
    1
        fn move_enhanced_times() -> Result<()> {
    1768
    1
            let mathml_str = "<math display='block' id='id-0'>
    1769
    1
            <mrow displaystyle='true' id='id-1'>
    1770
    1
              <mn id='id-2'>2</mn>
    1771
    1
              <mo id='id-3'>&#x2062;</mo>
    1772
    1
              <mrow id='id-4'>
    1773
    1
                <mo id='id-5'>(</mo>
    1774
    1
                <mrow id='id-6'>
    1775
    1
                  <mn id='id-7'>1</mn>
    1776
    1
                  <mo id='id-8'>-</mo>
    1777
    1
                  <mi id='id-9'>x</mi>
    1778
    1
                </mrow>
    1779
    1
                <mo id='id-10'>)</mo>
    1780
    1
              </mrow>
    1781
    1
            </mrow>
    1782
    1
           </math>";
    1783
    1
            init_default_prefs(mathml_str, "Enhanced");
    1784
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1785
    1
                let package_instance = package_instance.borrow();
    1786
    1
                let mathml = get_element(&package_instance);
    1787
    1
                test_command("ZoomIn", mathml, "id-2");
    1788
    1
                assert_eq!("move right; times", test_command("MoveNext", mathml, "id-3"));
    1789
    1
                assert_eq!("move right; 1 minus x", test_command("MoveNext", mathml, "id-6"));
    1790
    1
                assert_eq!("move left; times", test_command("MovePrevious", mathml, "id-3"));
    1791
    1
                assert_eq!("move left; 2", test_command("MovePrevious", mathml, "id-2"));
    1792
    1793
    1
                return Ok( () );
    1794
    1
            });
    1795
    1
        }
    1796
    1797
        #[test]
    1798
    1
        fn move_simple_no_times() -> Result<()> {
    1799
    1
            let mathml_str = "<math display='block' id='id-0'>
    1800
    1
            <mrow displaystyle='true' id='id-1'>
    1801
    1
              <mn id='id-2'>2</mn>
    1802
    1
              <mo id='id-3'>&#x2062;</mo>
    1803
    1
              <mrow id='id-4'>
    1804
    1
                <mo id='id-5'>(</mo>
    1805
    1
                <mrow id='id-6'>
    1806
    1
                  <mn id='id-7'>1</mn>
    1807
    1
                  <mo id='id-8'>-</mo>
    1808
    1
                  <mi id='id-9'>x</mi>
    1809
    1
                </mrow>
    1810
    1
                <mo id='id-10'>)</mo>
    1811
    1
              </mrow>
    1812
    1
            </mrow>
    1813
    1
           </math>";
    1814
    1
            init_default_prefs(mathml_str, "Simple");
    1815
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    1816
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1817
    1
                let package_instance = package_instance.borrow();
    1818
    1
                let mathml = get_element(&package_instance);
    1819
    1
                test_command("ZoomIn", mathml, "id-2");
    1820
    1
                assert_eq!("move right; open paren", test_command("MoveNext", mathml, "id-5"));
    1821
    1
                assert_eq!("move right; 1", test_command("MoveNext", mathml, "id-7"));
    1822
    1
                assert_eq!("move left; open paren", test_command("MovePrevious", mathml, "id-5"));
    1823
    1
                assert_eq!("move left; 2", test_command("MovePrevious", mathml, "id-2"));
    1824
    1825
    1
                return Ok( () );
    1826
    1
            });
    1827
    1
        }
    1828
    1829
    1830
        #[test]
    1831
    1
        fn move_cell() -> Result<()> {
    1832
    1
            let mathml_str = "<math id='nav-0'>
    1833
    1
            <mtable id='nav-1'>
    1834
    1
              <mtr id='nav-2'>
    1835
    1
                <mtd id='nav-3'> <mn id='nav-4'>1</mn></mtd>
    1836
    1
                <mtd id='nav-5'> <mn id='nav-6'>2</mn></mtd>
    1837
    1
                <mtd id='nav-7'><mn id='nav-8'>3</mn> </mtd>
    1838
    1
              </mtr>
    1839
    1
              <mtr id='nav-9'>
    1840
    1
                <mtd id='nav-10'>
    1841
    1
                  <mrow id='nav-11'>
    1842
    1
                    <mi id='nav-12'>x</mi>
    1843
    1
                    <mo id='nav-13'>-</mo>
    1844
    1
                    <mi id='nav-14'>y</mi>
    1845
    1
                  </mrow>
    1846
    1
                </mtd>
    1847
    1
                <mtd id='nav-15'>
    1848
    1
                  <mfrac id='nav-16'>
    1849
    1
                    <mn id='nav-17'>1</mn>
    1850
    1
                    <mn id='nav-18'>2</mn>
    1851
    1
                  </mfrac>
    1852
    1
                </mtd>
    1853
    1
                <mtd id='nav-19'>
    1854
    1
                  <mi id='nav-20'>z</mi>
    1855
    1
                </mtd>
    1856
    1
              </mtr>
    1857
    1
              <mtr id='nav-21'>
    1858
    1
                <mtd id='nav-22'><mn id='nav-23'>7</mn> </mtd>
    1859
    1
                <mtd id='nav-24'><mn id='nav-25'>8</mn> </mtd>
    1860
    1
                <mtd id='nav-26'> <mn id='nav-27'>9</mn></mtd>
    1861
    1
              </mtr>
    1862
    1
              <mtr id='nav-28'>
    1863
    1
                <mtd id='nav-29'>
    1864
    1
                  <mrow id='nav-30'>
    1865
    1
                    <mi id='nav-31'>sin</mi>
    1866
    1
                    <mo id='nav-32'>&#x2061;</mo>
    1867
    1
                    <mi id='nav-33'>x</mi>
    1868
    1
                  </mrow>
    1869
    1
                </mtd>
    1870
    1
                <mtd id='nav-34'>
    1871
    1
                  <msup id='nav-35'>
    1872
    1
                    <mi id='nav-36'>e</mi>
    1873
    1
                    <mi id='nav-37'>x</mi>
    1874
    1
                  </msup>
    1875
    1
                </mtd>
    1876
    1
                <mtd id='nav-38'>
    1877
    1
                  <mrow id='nav-39'>
    1878
    1
                    <mn id='nav-40'>2</mn>
    1879
    1
                    <mo id='nav-41'>-</mo>
    1880
    1
                    <mi id='nav-42'>y</mi>
    1881
    1
                  </mrow>
    1882
    1
                </mtd>
    1883
    1
              </mtr>
    1884
    1
            </mtable>
    1885
    1
           </math>";
    1886
    1
            init_default_prefs(mathml_str, "Enhanced");
    1887
    1
            return MATHML_INSTANCE.with(|package_instance| {
    1888
    1
                let package_instance = package_instance.borrow();
    1889
    1
                let mathml = get_element(&package_instance);
    1890
    1
                test_command("ZoomInAll", mathml, "nav-4");
    1891
    1
                test_command("MoveCellNext", mathml, "nav-6");
    1892
    1
                test_command("MoveCellNext", mathml, "nav-8");
    1893
    1
                test_command("MoveCellNext", mathml, "nav-8");
    1894
    1
                test_command("MoveCellDown", mathml, "nav-20");
    1895
    1
                test_command("MoveCellDown", mathml, "nav-27");
    1896
    1
                let speech = test_command("MoveCellDown", mathml, "nav-39");
    1897
    1
                assert_eq!(speech, "move down, row 4, column 3; 2 minus y");
    1898
    1
                let speech = test_command("MoveCellDown", mathml, "nav-39");
    1899
    1
                assert_eq!(speech, "no next row");
    1900
    1
                test_command("MoveCellPrevious", mathml, "nav-35");
    1901
    1
                test_command("ZoomIn", mathml, "nav-36");
    1902
    1
                test_command("MoveCellUp", mathml, "nav-25");
    1903
    1
                test_command("MoveCellUp", mathml, "nav-16");
    1904
    1
                test_command("MoveCellUp", mathml, "nav-6");
    1905
    1
                test_command("MoveCellUp", mathml, "nav-6");
    1906
    1907
    1
                return Ok( () );
    1908
    1
            });
    1909
    1
        }
    1910
    1911
        #[test]
    1912
    1
        fn move_cell_char_mode() -> Result<()> {
    1913
    1
            let mathml_str = "<math id='nav-0'>
    1914
    1
            <mtable id='nav-1'>
    1915
    1
              <mtr id='nav-2'>
    1916
    1
                <mtd id='nav-3'> <mn id='nav-4'>1</mn></mtd>
    1917
    1
                <mtd id='nav-5'> <mn id='nav-6'>2</mn></mtd>
    1918
    1
                <mtd id='nav-7'><mn id='nav-8'>3</mn> </mtd>
    1919
    1
              </mtr>
    1920
    1
              <mtr id='nav-9'>
    1921
    1
                <mtd id='nav-10'>
    1922
    1
                  <mrow id='nav-11'>
    1923
    1
                    <mi id='nav-12'>x</mi>
    1924
    1
                    <mo id='nav-13'>-</mo>
    1925
    1
                    <mi id='nav-14'>y</mi>
    1926
    1
                  </mrow>
    1927
    1
                </mtd>
    1928
    1
                <mtd id='nav-15'>
    1929
    1
                  <mfrac id='nav-16'>
    1930
    1
                    <mn id='nav-17'>1</mn>
    1931
    1
                    <mn id='nav-18'>2</mn>
    1932
    1
                  </mfrac>
    1933
    1
                </mtd>
    1934
    1
                <mtd id='nav-19'>
    1935
    1
                  <mi id='nav-20'>z</mi>
    1936
    1
                </mtd>
    1937
    1
              </mtr>
    1938
    1
              <mtr id='nav-21'>
    1939
    1
                <mtd id='nav-22'><mn id='nav-23'>7</mn> </mtd>
    1940
    1
                <mtd id='nav-24'><mn id='nav-25'>8</mn> </mtd>
    1941
    1
                <mtd id='nav-26'> <mn id='nav-27'>9</mn></mtd>
    1942
    1
              </mtr>
    1943
    1
              <mtr id='nav-28'>
    1944
    1
                <mtd id='nav-29'>
    1945
    1
                  <mrow id='nav-30'>
    1946
    1
                    <mi id='nav-31'>sin</mi>
    1947
    1
                    <mo id='nav-32'>&#x2061;</mo>
    1948
    1
                    <mi id='nav-33'>x</mi>
    1949
    1
                  </mrow>
    1950
    1
                </mtd>
    1951
    1
                <mtd id='nav-34'>
    1952
    1
                  <msup id='nav-35'>
    1953
    1
                    <mi id='nav-36'>e</mi>
    1954
    1
                    <mi id='nav-37'>x</mi>
    1955
    1
                  </msup>
    1956
    1
                </mtd>
    1957
    1
                <mtd id='nav-38'>
    1958
    1
                  <mrow id='nav-39'>
    1959
    1
                    <mn id='nav-40'>2</mn>
    1960
    1
                    <mo id='nav-41'>-</mo>
    1961
    1
                    <mi id='nav-42'>y</mi>
    1962
    1
                  </mrow>
    1963
    1
                </mtd>
    1964
    1
              </mtr>
    1965
    1
            </mtable>
    1966
    1
           </math>";
    1967
    1
           init_default_prefs(mathml_str, "Character");
    1968
    1
           return MATHML_INSTANCE.with(|package_instance| {
    1969
    1
                let package_instance = package_instance.borrow();
    1970
    1
                let mathml = get_element(&package_instance);
    1971
    1
                NAVIGATION_STATE.with(|nav_stack| {
    1972
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    1973
    1
                        current_node: "nav-8".to_string(),
    1974
    1
                        current_node_offset: 0
    1975
    1
                    }, "None")
    1976
    1
                });
    1977
    1
                test_command("MoveNext", mathml, "nav-12");
    1978
    1
                test_command("MoveNext", mathml, "nav-13");
    1979
    1
                test_command("MoveNext", mathml, "nav-14");
    1980
    1
                test_command("MoveNext", mathml, "nav-17");
    1981
    1
                test_command("MovePrevious", mathml, "nav-14");
    1982
    1
                test_command("MoveCellNext", mathml, "nav-17");
    1983
    1
                test_command("MoveCellPrevious", mathml, "nav-14");
    1984
    1
                test_command("MovePrevious", mathml, "nav-13");
    1985
    1
                test_command("MovePrevious", mathml, "nav-12");
    1986
    1
                test_command("MoveCellPrevious", mathml, "nav-12");
    1987
    1
                test_command("MovePrevious", mathml, "nav-8");
    1988
    1
                test_command("MoveCellDown", mathml, "nav-20");
    1989
    1
                test_command("MoveCellDown", mathml, "nav-27");
    1990
    1
                test_command("MoveCellDown", mathml, "nav-40");
    1991
    1
                test_command("MoveCellDown", mathml, "nav-40");
    1992
    1
                test_command("MoveCellPrevious", mathml, "nav-37");
    1993
    1
                test_command("MoveCellUp", mathml, "nav-25");
    1994
    1995
    1
                return Ok( () );
    1996
    1
            });
    1997
    1
        }
    1998
    1999
        #[test]
    2000
    1
        fn placemarker() -> Result<()> {
    2001
    1
            let mathml_str = "<math display='block' id='math'>
    2002
    1
            <mrow displaystyle='true' id='mrow'>
    2003
    1
              <mi id='a'>a</mi>
    2004
    1
              <mo id='plus-1'>+</mo>
    2005
    1
              <mi id='b'>b</mi>
    2006
    1
              <mo id='plus-2'>+</mo>
    2007
    1
              <mi id='c'>c</mi>
    2008
    1
            </mrow>
    2009
    1
            </math>";
    2010
    1
            init_default_prefs(mathml_str, "Character");
    2011
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2012
    1
                let package_instance = package_instance.borrow();
    2013
    1
                let mathml = get_element(&package_instance);
    2014
    1
                test_command("MoveStart", mathml, "a");
    2015
    1
                test_command("SetPlacemarker0", mathml, "a");
    2016
    1
                test_command("MoveEnd", mathml, "c");
    2017
    1
                test_command("Read0", mathml, "c");
    2018
    1
                test_command("Describe0", mathml, "c");
    2019
    1
                test_command("SetPlacemarker1", mathml, "c");
    2020
    1
                test_command("MoveTo0", mathml, "a");
    2021
    1
                test_command("MoveTo1", mathml, "c");
    2022
    1
                test_command("MoveLastLocation", mathml, "a");
    2023
    2024
    1
                return Ok( () );
    2025
    1
            });
    2026
    1
        }
    2027
    2028
        #[test]
    2029
    1
        fn where_am_i_all() -> Result<()> {
    2030
    1
            let mathml_str = "<math id='math'><mfrac id='mfrac'>
    2031
    1
                    <msup id='msup'><mi id='base'>b</mi><mn id='exp'>2</mn></msup>
    2032
    1
                    <mi id='denom'>d</mi>
    2033
    1
                </mfrac></math>";
    2034
    1
            init_default_prefs(mathml_str, "Enhanced");
    2035
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    2036
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2037
    1
                let package_instance = package_instance.borrow();
    2038
    1
                let mathml = get_element(&package_instance);
    2039
    1
                NAVIGATION_STATE.with(|nav_stack| {
    2040
    1
                    nav_stack.borrow_mut().push(NavigationPosition{
    2041
    1
                        current_node: "exp".to_string(),
    2042
    1
                        current_node_offset: 0
    2043
    1
                    }, "None")
    2044
    1
                });
    2045
                // WhereAmIAll doesn't change the stack
    2046
    1
                let speech =test_command("WhereAmIAll", mathml, "exp");
    2047
                // should be 2 "inside" strings corresponding to steps to the root
    2048
    1
                assert_eq!(speech, "2; inside; b squared; inside; the fraction with numerator; b squared; and denominator d");
    2049
    1
                return Ok( () );
    2050
    1
            });
    2051
    1
        }
    2052
    2053
        #[test]
    2054
    1
        fn auto_zoom_out_mrow() -> Result<()> {
    2055
    1
            let mathml_str = "<math id='math'>
    2056
    1
            <mrow id='id-1'>
    2057
    1
              <mrow id='id-2'>
    2058
    1
                <mrow id='2ax'>
    2059
    1
                  <mn id='2'>2</mn>
    2060
    1
                  <mo id='id-5'>&#x2062;</mo>
    2061
    1
                  <mi id='a'>a</mi>
    2062
    1
                  <mo id='id-7'>&#x2062;</mo>
    2063
    1
                  <mi id='x'>x</mi>
    2064
    1
                </mrow>
    2065
    1
                <mo id='plus'>+</mo>
    2066
    1
                <mi id='b'>b</mi>
    2067
    1
              </mrow>
    2068
    1
              <mo id='equal'>=</mo>
    2069
    1
              <mn id='10'>10</mn>
    2070
    1
            </mrow>
    2071
    1
           </math>";
    2072
    1
            init_default_prefs(mathml_str, "Enhanced");
    2073
    1
            set_preference("AutoZoomOut", "False")?0;
    2074
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2075
    1
                let package_instance = package_instance.borrow();
    2076
    1
                let mathml = get_element(&package_instance);
    2077
    1
                test_command("ZoomInAll", mathml, "2");
    2078
    1
                test_command("MoveNext", mathml, "a");
    2079
    1
                test_command("MoveNext", mathml, "x");
    2080
    1
                test_command("MoveNext", mathml, "plus");
    2081
    1
                test_command("MovePrevious", mathml, "2ax");
    2082
    1
                return Ok( () );
    2083
    1
            });
    2084
    1
        }
    2085
    2086
        #[test]
    2087
    1
        fn auto_zoom_out_fraction() -> Result<()> {
    2088
    1
            let mathml_str = "<math id='math'>
    2089
    1
                <mrow id='mrow'>
    2090
    1
                    <mfrac id='frac'>
    2091
    1
                        <mrow id='num'><mi id='a'>a</mi><mo id='plus'>+</mo><mn id='1'>1</mn></mrow>
    2092
    1
                        <mrow id='denom'><mn id='2'>2</mn><mo id='invisible-times'>&#x2062;</mo><mi id='b'>b</mi></mrow>
    2093
    1
                    </mfrac>
    2094
    1
                    <mo id='minus'>-</mo>
    2095
    1
                    <mn id='3'>3</mn>
    2096
    1
                </mrow>
    2097
    1
            </math>";
    2098
    1
            init_default_prefs(mathml_str, "Enhanced");
    2099
    1
            set_preference("AutoZoomOut", "False")?0;
    2100
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2101
    1
                let package_instance = package_instance.borrow();
    2102
    1
                let mathml = get_element(&package_instance);
    2103
    1
                test_command("ZoomIn", mathml, "frac");
    2104
    1
                test_command("ZoomIn", mathml, "num");
    2105
    1
                test_command("MoveNext", mathml, "denom");
    2106
    1
                test_command("MoveNext", mathml, "denom");
    2107
    1
                test_command("MovePrevious", mathml, "num");
    2108
    1
                test_command("MovePrevious", mathml, "num");
    2109
    1
                test_command("ZoomOut", mathml, "frac");
    2110
    1
                test_command("MoveNext", mathml, "minus");
    2111
    1
                return Ok( () );
    2112
    1
            });
    2113
    1
        }
    2114
    2115
        #[test]
    2116
    1
        fn zoom_root() -> Result<()> {
    2117
    1
            let mathml_str = r#"<math display='block' id='id-0'>
    2118
    1
            <mrow id='id-1'>
    2119
    1
                <mo id='id-9'>±</mo>
    2120
    1
                <msqrt id='id-10'>
    2121
    1
                    <mrow id='id-11'>
    2122
    1
                        <msup id='id-12'> <mi id='id-13'>b</mi> <mn id='id-14'>2</mn> </msup>
    2123
    1
                        <mo id='id-15'>-</mo>
    2124
    1
                        <mn id='id-17'>4</mn>
    2125
    1
                    </mrow>
    2126
    1
                </msqrt>
    2127
    1
            </mrow>
    2128
    1
            </math>"#;
    2129
    2130
    1
            test_mode(mathml_str, "Enhanced")?0;
    2131
    1
            test_mode(mathml_str, "Simple")?0;
    2132
    1
            test_mode(mathml_str, "Character")?0;
    2133
    1
            return Ok( () );
    2134
    2135
    3
            fn test_mode(mathml_str: &str, mode: &str) -> Result<()> {
    2136
    3
                init_default_prefs(mathml_str, mode);
    2137
    3
                set_preference("AutoZoomOut", "False")?0;
    2138
    3
                return MATHML_INSTANCE.with(|package_instance| {
    2139
    3
                    debug!("--- Testing mode {mode} ---");
    2140
    3
                    let package_instance = package_instance.borrow();
    2141
    3
                    let mathml = get_element(&package_instance);
    2142
    3
                    test_command("ZoomIn", mathml, "id-9");
    2143
    3
                    debug!("\nStart zoom in");
    2144
    3
                    match mode {
    2145
    3
                        "Enhanced" => {
    2146
    1
                            test_command("MoveNext", mathml, "id-10");
    2147
    1
                            let speech = test_command("ZoomIn", mathml, "id-11");
    2148
    1
                            assert_eq!(speech, "zoom in; in root; b squared minus 4");  // only one arg, so don't say "in root"
    2149
    1
                            let speech = test_command("ZoomIn", mathml, "id-12");
    2150
    1
                            assert_eq!(speech, "zoom in; b squared");  // only one arg, so don't say "in root"
    2151
    1
                            let speech = test_command("ZoomIn", mathml, "id-13");
    2152
    1
                            assert_eq!(speech, "zoom in; in base; b");
    2153
                        },
    2154
    2
                        "Simple" => {
    2155
    1
                            test_command("MoveNext", mathml, "id-10");
    2156
    1
                            let speech = test_command("ZoomIn", mathml, "id-12");
    2157
    1
                            assert_eq!(speech, "zoom in; in root; b squared");
    2158
    1
                            let speech = test_command("ZoomIn", mathml, "id-13");
    2159
    1
                            assert_eq!(speech, "zoom in; in base; b");
    2160
                        },
    2161
                        _ => { // "Character"
    2162
    1
                            let speech = test_command("MoveNext", mathml, "id-13");
    2163
    1
                            assert_eq!(speech, "move right; in root; in base; b");
    2164
                        }
    2165
                    }
    2166
    3
                    let squared_speech = if mode == "Character" {"b super 2 end super"1} else {"b squared"2};
    2167
    3
                    let sqrt_speech = if mode == "Character" {"root"1} else {"square root"2};
    2168
    3
                    let speech = test_command("ZoomOut", mathml, "id-12");
    2169
    3
                    assert_eq!(speech, format!("zoom out; out of base; {squared_speech}"));
    2170
    3
                    let speech = test_command("ZoomOut", mathml, "id-11");
    2171
    3
                    assert_eq!(speech, format!("zoom out; {squared_speech} minus 4"));
    2172
    3
                    let speech = test_command("ZoomOut", mathml, "id-10");
    2173
    3
                    assert_eq!(speech, format!("zoom out; out of root; the {sqrt_speech} of {squared_speech} minus 4, end root",));
    2174
    3
                    return Ok( () );
    2175
    3
                });
    2176
    3
            }
    2177
    1
        }
    2178
    2179
        #[test]
    2180
    1
        fn matrix_speech() -> Result<()> {
    2181
    1
            let mathml_str = r#"<math id='math'>
    2182
    1
                <mrow id='mrow'>
    2183
    1
                <mo id='open'>[</mo>
    2184
    1
                <mtable columnspacing='1em' rowspacing='4pt' id='table'>
    2185
    1
                    <mtr id='row-1'>
    2186
    1
                        <mtd id='1-1'><mn id='id-6'>9</mn></mtd>
    2187
    1
                        <mtd id='1-2'><mrow id='id-8'><mo id='id-9'>-</mo><mn id='id-10'>13</mn></mrow></mtd>
    2188
    1
                    </mtr>
    2189
    1
                    <mtr id='row-2'>
    2190
    1
                        <mtd id='2-1'><mn id='id-13'>5</mn></mtd>
    2191
    1
                        <mtd id='2-2'><mo id='id-16'>-</mo><mn id='id-17'>6</mn></mtd>
    2192
    1
                    </mtr>
    2193
    1
                </mtable>
    2194
    1
                <mo id='close'>]</mo>
    2195
    1
                </mrow>
    2196
    1
            </math>"#;
    2197
    1
            init_default_prefs(mathml_str, "Enhanced");
    2198
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2199
    1
                let package_instance = package_instance.borrow();
    2200
    1
                let mathml = get_element(&package_instance);
    2201
    1
                test_command("ZoomIn", mathml, "row-1");
    2202
    1
                let speech = test_command("MoveNext", mathml, "row-2");
    2203
    1
                assert_eq!(speech, "move right; row 2; 5, negative 6");
    2204
    1
                let speech = test_command("ZoomIn", mathml, "id-13");
    2205
    1
                assert_eq!(speech, "zoom in; column 1; 5");
    2206
    1
                let speech = test_command("ZoomOut", mathml, "row-2");
    2207
    1
                assert_eq!(speech, "zoom out; row 2; 5, negative 6");
    2208
    1
                let speech = test_command("ZoomOut", mathml, "table");
    2209
    1
                assert_eq!(speech, "zoom out; the 2 by 2 matrix; row 1; 9, negative 13; row 2; 5, negative 6");
    2210
    1
            return Ok( () );
    2211
    1
            });
    2212
    1
        }
    2213
    2214
        #[test]
    2215
    1
        fn chem_speech() -> Result<()> {
    2216
            // this comes from bug 218
    2217
    1
            let mathml_str = "<math display='block' id='id-0'>
    2218
    1
                <mrow data-chem-formula='5' id='id-1'>
    2219
    1
                    <msub data-chem-formula='1' id='id-2'>
    2220
    1
                        <mi data-chem-element='1' id='id-3'>H</mi>
    2221
    1
                        <mn id='id-4'>2</mn>
    2222
    1
                    </msub>
    2223
    1
                    <mo data-chem-formula-op='0' id='id-5'>&#x2063;</mo>
    2224
    1
                    <mi data-chem-element='1' id='id-6'>S</mi>
    2225
    1
                    <mo data-chem-formula-op='0' id='id-7'>&#x2063;</mo>
    2226
    1
                    <msub data-chem-formula='1' id='id-8'>
    2227
    1
                        <mi data-chem-element='1' id='id-9'>O</mi>
    2228
    1
                        <mn id='id-10'>4</mn>
    2229
    1
                    </msub>
    2230
    1
                </mrow>
    2231
    1
            </math>";
    2232
    1
            init_default_prefs(mathml_str, "Enhanced");
    2233
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2234
    1
                let package_instance = package_instance.borrow();
    2235
    1
                let mathml = get_element(&package_instance);
    2236
    1
                test_command("ZoomIn", mathml, "id-2");
    2237
    1
                let speech = test_command("MoveNext", mathml, "id-6");
    2238
                // tables need to check their parent for proper speech
    2239
    1
                assert_eq!(speech, "move right; cap s");
    2240
    1
                return Ok( () );
    2241
    1
            });
    2242
    1
        }
    2243
    2244
        #[test]
    2245
    1
        fn determinant_speech() -> Result<()> {
    2246
    1
            let mathml_str = "<math id='math'>
    2247
    1
                <mrow id='mrow'>
    2248
    1
                <mo id='open'>|</mo>
    2249
    1
                <mtable columnspacing='1em' rowspacing='4pt' id='table'>
    2250
    1
                    <mtr id='row-1'>
    2251
    1
                        <mtd id='1-1'><mn id='id-6'>9</mn></mtd>
    2252
    1
                        <mtd id='1-2'><mrow id='id-8'><mo id='id-9'>-</mo><mn id='id-10'>13</mn></mrow></mtd>
    2253
    1
                    </mtr>
    2254
    1
                    <mtr id='row-2'>
    2255
    1
                        <mtd id='2-1'><mn id='id-13'>5</mn></mtd>
    2256
    1
                        <mtd id='2-2'><mrow id='row2-negative'><mo id='id-16'>-</mo><mn id='id-17'>6</mn></mrow></mtd>
    2257
    1
                    </mtr>
    2258
    1
                </mtable>
    2259
    1
                <mo id='close'>|</mo>
    2260
    1
                </mrow>
    2261
    1
            </math>";
    2262
    1
            init_default_prefs(mathml_str, "Enhanced");
    2263
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    2264
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2265
    1
                let package_instance = package_instance.borrow();
    2266
    1
                let mathml = get_element(&package_instance);
    2267
    1
                let speech = test_command("ZoomIn", mathml, "row-1");
    2268
    1
                assert_eq!(speech, "zoom in; row 1; 9, negative 13");
    2269
    1
                let speech = test_command("MoveNext", mathml, "row-2");
    2270
    1
                assert_eq!(speech, "move right; row 2; 5, negative 6");
    2271
    1
                let speech = test_command("MoveNext", mathml, "row-2");
    2272
    1
                assert_eq!(speech, "cannot move right, end of math");
    2273
    1
                let speech = test_command("ZoomIn", mathml, "id-13");
    2274
    1
                assert_eq!(speech, "zoom in; column 1; 5");
    2275
    1
                let speech = test_command("MoveNext", mathml, "row2-negative");
    2276
    1
                assert_eq!(speech, "move right; column 2, negative 6");
    2277
    1
                let speech = test_command("ZoomOutAll", mathml, "table");
    2278
    1
                assert_eq!(speech, "zoomed out all of the way; the 2 by 2 determinant; row 1; 9, negative 13; row 2; 5, negative 6");
    2279
    1
                return Ok( () );
    2280
    1
            });
    2281
    1
        }
    2282
    2283
        #[test]
    2284
    1
        fn cases_speech() -> Result<()> {
    2285
    1
            let mathml_str = "<math id='id-0'>
    2286
    1
            <mrow id='id-1'>
    2287
    1
              <mo id='open'>{</mo>
    2288
    1
              <mtable columnalign='left left' columnspacing='1em' displaystyle='false' rowspacing='.2em' id='table'>
    2289
    1
                <mtr id='row-1'>
    2290
    1
                  <mtd id='id-5'><mrow id='id-6'><mrow id='id-7'><mo id='id-8'>-</mo><mi id='id-9'>x</mi></mrow><mo id='id-10'>,</mo></mrow></mtd>
    2291
    1
                  <mtd id='id-11'><mrow id='id-12'><mrow id='id-13'><mtext id='id-14'>if</mtext><mo id='id-15'>&#x2062;</mo><mi id='id-16'>x</mi></mrow><mo id='id-17'>&lt;</mo><mn id='id-18'>0</mn></mrow></mtd>
    2292
    1
                </mtr>
    2293
    1
                <mtr id='row-2'>
    2294
    1
                  <mtd id='id-20'><mrow id='id-21'><mrow id='id-22'><mo id='id-23'>+</mo><mi id='id-24'>x</mi></mrow><mo id='id-25'>,</mo></mrow></mtd>
    2295
    1
                  <mtd id='id-26'><mrow id='id-27'><mrow id='id-28'><mtext id='id-29'>if</mtext><mo id='id-30'>&#x2062;</mo><mi id='id-31'>x</mi></mrow><mo id='id-32'>≥</mo><mn id='id-33'>0</mn></mrow></mtd>
    2296
    1
                </mtr>
    2297
    1
              </mtable>
    2298
    1
            </mrow>
    2299
    1
           </math>";
    2300
    1
            init_default_prefs(mathml_str, "Enhanced");
    2301
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    2302
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2303
    1
                let package_instance = package_instance.borrow();
    2304
    1
                let mathml = get_element(&package_instance);
    2305
    1
                test_command("ZoomIn", mathml, "row-1");
    2306
    1
                let speech = test_command("MovePrevious", mathml, "row-1");
    2307
    1
                assert_eq!(speech, "move left; start of math");
    2308
    1
                let speech = test_command("MoveNext", mathml, "row-2");
    2309
    1
                assert_eq!(speech, "move right; case 2; positive x comma; if x, is greater than or equal to 0");
    2310
    1
                let speech = test_command("ZoomOut", mathml, "table");
    2311
    1
                assert_eq!(speech, "zoom out; 2 cases; case 1; negative x comma; if x is less than 0; case 2; positive x comma; if x, is greater than or equal to 0");
    2312
    1
                let speech = test_command("ZoomIn", mathml, "row-1");
    2313
    1
                assert_eq!(speech, "zoom in; case 1; negative x comma; if x is less than 0");
    2314
    1
                set_preference("NavMode", "Character").unwrap();
    2315
    1
                let speech = test_command("MovePrevious", mathml, "open");
    2316
    1
                assert_eq!(speech, "move left; open brace");
    2317
    1
                return Ok( () );
    2318
    1
            });
    2319
    1
        }
    2320
    2321
        #[test]
    2322
    1
        fn base_superscript() -> Result<()> {
    2323
            // bug #217 -- zoom into base of parenthesized script
    2324
    1
            let mathml_str = "<math display='block' id='id-0'>
    2325
    1
                <msup id='id-1'>
    2326
    1
                    <mrow id='id-2'>
    2327
    1
                        <mo stretchy='false' id='id-3'>(</mo>
    2328
    1
                        <mrow id='id-4'>
    2329
    1
                            <mn id='id-5'>2</mn>
    2330
    1
                            <mo id='id-6'>&#x2062;</mo>
    2331
    1
                            <mi id='id-7'>x</mi>
    2332
    1
                        </mrow>
    2333
    1
                        <mo stretchy='false' id='id-8'>)</mo>
    2334
    1
                    </mrow>
    2335
    1
                    <mn id='id-9'>2</mn>
    2336
    1
                </msup>
    2337
    1
            </math>";
    2338
    1
            init_default_prefs(mathml_str, "Enhanced");
    2339
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    2340
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2341
    1
                let package_instance = package_instance.borrow();
    2342
    1
                let mathml = get_element(&package_instance);
    2343
    1
                let speech = test_command("ZoomIn", mathml, "id-4");
    2344
    1
                assert_eq!(speech, "zoom in; in base; 2 x");
    2345
    1
                let speech = test_command("MoveNext", mathml, "id-9");
    2346
    1
                assert_eq!(speech, "move right; in exponent; 2");
    2347
    1
                return Ok( () );
    2348
    1
            });
    2349
    1
        }
    2350
    2351
        #[test]
    2352
    1
        fn binomial_intent() -> Result<()> {
    2353
    1
            let mathml_str = "<math display='block' id='id-0'>
    2354
    1
                        <mrow intent='binomial($n,$k)' id='id-1'>
    2355
    1
                            <mo id='id-2'>(</mo>
    2356
    1
                            <mfrac linethickness='0pt' id='id-3'>
    2357
    1
                                <mi arg='n' id='id-4'>n</mi>
    2358
    1
                                <mi arg='k' id='id-5'>k</mi>
    2359
    1
                            </mfrac>
    2360
    1
                        <mo id='id-6'>)</mo>
    2361
    1
                        </mrow>
    2362
    1
                    </math>";
    2363
    1
            init_default_prefs(mathml_str, "Character");
    2364
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    2365
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2366
    1
                let package_instance = package_instance.borrow();
    2367
    1
                let mathml = get_element(&package_instance);
    2368
    1
                debug!("Character mode");
    2369
    1
                let speech = test_command("MoveStart", mathml, "id-2");
    2370
    1
                assert_eq!(speech, "move to start of math; open paren");
    2371
    1
                let speech = test_command("MoveNext", mathml, "id-4");
    2372
                // I'm not keen on the use of numerator/denominator here, but character mode turns off intent
    2373
    1
                assert_eq!(speech, "move right; in numerator; n");
    2374
    1
                let speech = test_command("MoveNext", mathml, "id-5");
    2375
    1
                assert_eq!(speech, "move right; in denominator; k");
    2376
    1
                debug!("before zoom out");
    2377
    1
                let speech = test_command("ZoomOut", mathml, "id-3");
    2378
    1
                assert_eq!(speech, "zoom out; out of denominator; n over k");
    2379
                // let speech = test_command("ZoomOut", mathml, "id-1");
    2380
                // assert_eq!(speech, "zoom out; open paren n over k, close paren");
    2381
    2382
    1
                set_preference("NavMode", "Simple").unwrap();
    2383
    1
                debug!("Simple mode");
    2384
    1
                let speech = test_command("ZoomIn", mathml, "id-4");
    2385
    1
                assert_eq!(speech, "zoom in; in part 1; n");
    2386
    1
                let speech = test_command("MoveNext", mathml, "id-5");
    2387
    1
                assert_eq!(speech, "move right; in part 2; k");
    2388
    1
                let speech = test_command("MoveNext", mathml, "id-5");
    2389
    1
                assert_eq!(speech, "cannot move right, end of math");
    2390
    1
                let speech = test_command("ZoomOut", mathml, "id-1-literal-0");
    2391
    1
                assert_eq!(speech, "zoom out; out of part 2; n choose k");
    2392
    2393
    1
                set_preference("NavMode", "Enhanced").unwrap();
    2394
    1
                debug!("Enhanced mode");
    2395
    1
                let speech = test_command("ZoomIn", mathml, "id-4");
    2396
    1
                assert_eq!(speech, "zoom in; in part 1; n");
    2397
    1
                let speech = test_command("MoveNext", mathml, "id-5");
    2398
    1
                assert_eq!(speech, "move right; in part 2; k");
    2399
    1
                let speech = test_command("MoveNext", mathml, "id-5");
    2400
    1
                assert_eq!(speech, "cannot move right, end of math");
    2401
    1
                let speech = test_command("ZoomOut", mathml, "id-1-literal-0");
    2402
    1
                assert_eq!(speech, "zoom out; out of part 2; n choose k");
    2403
    2404
    1
                return Ok( () );
    2405
    1
            });
    2406
    1
        }
    2407
    2408
        #[test]
    2409
    1
        fn matrix_literal_intent() -> Result<()> {
    2410
    1
            let mathml_str = r#"<math display='block' id='id-0'>
    2411
    1
                <mrow intent='$m' id='id-1'>
    2412
    1
                    <mo id='id-2'>(</mo>
    2413
    1
                    <mtable arg='m' intent='_diagonal:prefix(1,2,3)' id='id-3'>
    2414
    1
                    <mtr id='id-4'>
    2415
    1
                        <mtd id='id-5'><mn id='id-6'>1</mn></mtd>
    2416
    1
                        <mtd id='id-7'><mn id='id-8'>0</mn></mtd>
    2417
    1
                        <mtd id='id-9'><mn id='id-10'>0</mn></mtd>
    2418
    1
                    </mtr>
    2419
    1
                    <mtr id='id-11'>
    2420
    1
                        <mtd id='id-12'><mn id='id-13'>0</mn></mtd>
    2421
    1
                        <mtd id='id-14'><mn id='id-15'>2</mn></mtd>
    2422
    1
                        <mtd id='id-16'><mn id='id-17'>0</mn></mtd>
    2423
    1
                    </mtr>
    2424
    1
                    <mtr id='id-18'>
    2425
    1
                        <mtd id='id-19'><mn id='id-20'>0</mn></mtd>
    2426
    1
                        <mtd id='id-21'><mn id='id-22'>0</mn></mtd>
    2427
    1
                        <mtd id='id-23'><mn id='id-24'>3</mn></mtd>
    2428
    1
                    </mtr>
    2429
    1
                    </mtable>
    2430
    1
                    <mo id='id-25'>)</mo>
    2431
    1
                </mrow>
    2432
    1
            </math>"#;
    2433
    1
            init_default_prefs(mathml_str, "Simple");
    2434
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2435
    1
                let package_instance = package_instance.borrow();
    2436
    1
                let mathml = get_element(&package_instance);
    2437
    1
                let speech = test_command("ZoomIn", mathml, "id-3-literal-1");
    2438
    1
                assert_eq!(speech, "zoom in; 1");
    2439
    1
                let speech = test_command("MoveNext", mathml, "id-3-literal-2");
    2440
    1
                assert_eq!(speech, "move right; 2");
    2441
    1
                let speech = test_command("MoveNext", mathml, "id-3-literal-3");
    2442
    1
                assert_eq!(speech, "move right; 3");
    2443
    1
                let speech = test_command("MoveNext", mathml, "id-3-literal-3");
    2444
    1
                assert_eq!(speech, "cannot move right, end of math");
    2445
    1
                let speech = test_command("ZoomOut", mathml, "id-3-literal-0");
    2446
    1
                assert_eq!(speech, "zoom out; diagonal 1 2 3");
    2447
    2448
    1
                return Ok( () );
    2449
    1
            });
    2450
    1
        }
    2451
    2452
        #[test]
    2453
    1
        fn absolute_value() -> Result<()> {
    2454
    1
            let mathml_str = "<math id='math'>
    2455
    1
                    <mrow id='expr'>
    2456
    1
                        <mn id='2'>2</mn>
    2457
    1
                        <mrow id='abs'>
    2458
    1
                            <mo id='start'>|</mo>
    2459
    1
                            <mi id='x'>x</mi>
    2460
    1
                            <mo id='end'>|</mo>
    2461
    1
                        </mrow>
    2462
    1
                    </mrow>
    2463
    1
                </math>";
    2464
    1
            init_default_prefs(mathml_str, "Enhanced");
    2465
    1
            set_preference("SpeechStyle", "ClearSpeak").unwrap();
    2466
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2467
    1
                let package_instance = package_instance.borrow();
    2468
    1
                let mathml = get_element(&package_instance);
    2469
    1
                let speech = test_command("ZoomIn", mathml, "2");
    2470
    1
                assert_eq!(speech, "zoom in; 2");
    2471
    1
                let speech = test_command("MoveNext", mathml, "abs");
    2472
    1
                assert_eq!(speech, "move right; the absolute value of x");
    2473
    1
                let speech = test_command("ZoomIn", mathml, "x");
    2474
    1
                assert_eq!(speech, "zoom in; in absolute value; x");
    2475
    1
                let speech = test_command("MoveNext", mathml, "x");
    2476
    1
                assert_eq!(speech, "cannot move right, end of math");
    2477
    1
                set_preference("NavMode", "Character").unwrap();
    2478
    1
                let speech = test_command("MoveNext", mathml, "end");
    2479
    1
                assert_eq!(speech, "move right; vertical line");
    2480
    1
                let speech = test_command("MoveLineStart", mathml, "2");
    2481
    1
                assert_eq!(speech, "move to start of line; 2");
    2482
    1
                let speech = test_command("MoveNext", mathml, "start");
    2483
    1
                assert_eq!(speech, "move right; vertical line");
    2484
    1
                return Ok( () );
    2485
    1
            });
    2486
    1
        }
    2487
    2488
        #[test]
    2489
    1
        fn read_and_describe_fraction() -> Result<()> {
    2490
    1
            let mathml_str = "<math id='math'>
    2491
    1
                <mrow id='mrow'>
    2492
    1
                    <mfrac id='frac'>
    2493
    1
                        <mrow id='numerator'><mi>b</mi><mo>+</mo><mn>1</mn></mrow>
    2494
    1
                    <mn id='denom'>3</mn>
    2495
    1
                    </mfrac>
    2496
    1
                    <mo id='minus'>-</mo>
    2497
    1
                    <mn id='3'>3</mn>
    2498
    1
                </mrow>
    2499
    1
            </math>";
    2500
    1
            init_default_prefs(mathml_str, "Enhanced");
    2501
    1
            set_preference("SpeechStyle", "SimpleSpeak").unwrap();
    2502
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2503
    1
                let package_instance = package_instance.borrow();
    2504
    1
                let mathml = get_element(&package_instance);
    2505
    1
                test_command("ZoomIn", mathml, "frac");
    2506
    1
                let speech = test_command("ReadCurrent", mathml, "frac");
    2507
    1
                assert_eq!(speech, "read current; fraction, b plus 1, over 3, end fraction");
    2508
    1
                let speech = test_command("DescribeCurrent", mathml, "frac");
    2509
    1
                assert_eq!(speech, "describe current; fraction");
    2510
    1
                return Ok( () );
    2511
    1
            });
    2512
    1
        }
    2513
    2514
    2515
        #[test]
    2516
    1
        fn read_and_describe_mrow() -> Result<()> {
    2517
    1
            let mathml_str = "<math id='math'>
    2518
    1
                <mrow id='mrow'>
    2519
    1
                    <mn>1</mn><mo>+</mo>
    2520
    1
                    <mn>2</mn><mo>+</mo>
    2521
    1
                    <mn>3</mn><mo>+</mo>
    2522
    1
                    <mn>4</mn><mo>+</mo>
    2523
    1
                    <mn>5</mn><mo>+</mo>
    2524
    1
                    <mn>6</mn><mo>+</mo>
    2525
    1
                    <mn>7</mn>
    2526
    1
                </mrow>
    2527
    1
            </math>";
    2528
    1
            init_default_prefs(mathml_str, "Enhanced");
    2529
    1
            set_preference("SpeechStyle", "SimpleSpeak").unwrap();
    2530
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2531
    1
                let package_instance = package_instance.borrow();
    2532
    1
                let mathml = get_element(&package_instance);
    2533
    1
                let speech = test_command("ZoomOutAll", mathml, "mrow");
    2534
    1
                assert_eq!(speech, "zoomed out all of the way; 1 plus 2 plus 3 plus 4 plus 5 plus 6 plus 7");
    2535
    1
                let speech = test_command("ReadCurrent", mathml, "mrow");
    2536
    1
                assert_eq!(speech, "read current; 1 plus 2 plus 3 plus 4 plus 5 plus 6 plus 7");
    2537
    1
                let speech = test_command("DescribeCurrent", mathml, "mrow");
    2538
    1
                assert_eq!(speech, "describe current; 1 plus 2 plus 3 and so on");
    2539
    1
                return Ok( () );
    2540
    1
            });
    2541
    1
        }
    2542
    2543
    2544
        #[test]
    2545
    1
        fn read_next_invisible_char() -> Result<()> {
    2546
    1
            let mathml_str = "<math id='id-0'>
    2547
    1
                <mrow id='id-1'>
    2548
    1
                    <mi id='id-2'>x</mi>
    2549
    1
                    <mo id='id-3'>&#x2062;</mo>
    2550
    1
                    <mi id='id-4'>y</mi>
    2551
    1
                </mrow>
    2552
    1
                </math>";
    2553
    1
            init_default_prefs(mathml_str, "Simple");
    2554
    1
            set_preference("SpeechStyle", "SimpleSpeak").unwrap();
    2555
    1
            return MATHML_INSTANCE.with(|package_instance| {
    2556
    1
                let package_instance = package_instance.borrow();
    2557
    1
                let mathml = get_element(&package_instance);
    2558
    1
                let speech = test_command("ZoomIn", mathml, "id-2");
    2559
    1
                assert_eq!(speech, "zoom in; x");
    2560
    1
                let speech = test_command("ToggleZoomLockUp", mathml, "id-2");
    2561
    1
                assert_eq!(speech, "enhanced mode; x");
    2562
    1
                let speech = test_command("ReadNext", mathml, "id-2");
    2563
    1
                assert_eq!(speech, "read right; y");
    2564
    1
                return Ok( () );
    2565
    1
            });
    2566
    1
        }
    2567
    2568
    2569
        #[test]
    2570
    1
        fn basic_language_test() -> Result<()> {
    2571
            // this is basically a sanity check that all the language's navigation.yaml files are at least syntactically correct
    2572
            // FIX: should look through the Languages dir and figure this is out
    2573
    1
            let mathml_str = "<math id='math'>
    2574
    1
                    <mrow id='contents'>
    2575
    1
                        <mrow id='lhs'>
    2576
    1
                            <mrow id='term'>
    2577
    1
                                <mn id='2'>2</mn>
    2578
    1
                                <mo id='invisible-times'>&#x2062;</mo>
    2579
    1
                                <msup id='msup'>
    2580
    1
                                    <mi id='x'>x</mi>
    2581
    1
                                    <mn id='3'>3</mn>
    2582
    1
                                </msup>
    2583
    1
                            </mrow>
    2584
    1
                            <mo id='plus'>+</mo>
    2585
    1
                            <mn id='1'>1</mn>
    2586
    1
                        </mrow>
    2587
    1
                    <mo id='id-11'>=</mo>
    2588
    1
                    <mi id='id-12'>y</mi>
    2589
    1
                    </mrow>
    2590
    1
                </math>";
    2591
    2592
    1
            set_rules_dir(super::super::abs_rules_dir_path()).unwrap();
    2593
    11
            for lang in get_supported_languages1().unwrap_or_default1() {
    2594
    11
                test_language(&lang, mathml_str);
    2595
    11
            }
    2596
    1
            return Ok( () );
    2597
    2598
    11
            fn test_language(lang: &str, mathml_str: &str) {
    2599
    11
                init_default_prefs(mathml_str, "Enhanced");
    2600
    11
                set_preference("Language", lang).unwrap();
    2601
    2602
    11
                set_preference("NavMode", "Enhanced").unwrap();
    2603
    11
                MATHML_INSTANCE.with(|package_instance| {
    2604
    11
                    let package_instance = package_instance.borrow();
    2605
    11
                    let mathml = get_element(&package_instance);
    2606
    11
                    test_command("ZoomInAll", mathml, "2");
    2607
    11
                    test_command("MoveNext", mathml, "msup");
    2608
    11
                    test_command("MoveNext", mathml, "plus");
    2609
    11
                    test_command("MovePrevious", mathml, "term");
    2610
    11
                    test_command("MovePrevious", mathml, "term");
    2611
    11
                    test_command("ZoomOutAll", mathml, "contents");
    2612
    11
                });
    2613
    2614
    11
                set_preference("NavMode", "Simple").unwrap();
    2615
    11
                MATHML_INSTANCE.with(|package_instance: &RefCell<Package>| {
    2616
    11
                    let package_instance = package_instance.borrow();
    2617
    11
                    let mathml = get_element(&package_instance);
    2618
    11
                    test_command("ZoomInAll", mathml, "2");
    2619
    11
                    test_command("MoveNext", mathml, "msup");
    2620
    11
                    test_command("MoveNext", mathml, "plus");
    2621
    11
                    test_command("MovePrevious", mathml, "msup");
    2622
    11
                    test_command("MovePrevious", mathml, "2");
    2623
    11
                    test_command("MovePrevious", mathml, "2");
    2624
    11
                    test_command("ZoomOutAll", mathml, "contents");
    2625
    11
                });
    2626
    2627
    11
                set_preference("NavMode", "Character").unwrap();
    2628
    11
                MATHML_INSTANCE.with(|package_instance| {
    2629
    11
                    let package_instance = package_instance.borrow();
    2630
    11
                    let mathml = get_element(&package_instance);
    2631
    11
                    test_command("ZoomIn", mathml, "2");
    2632
    11
                    test_command("MoveNext", mathml, "x");
    2633
    11
                    test_command("MoveNext", mathml, "3");
    2634
    11
                    test_command("MoveNext", mathml, "plus");
    2635
    11
                    test_command("MovePrevious", mathml, "3");
    2636
    11
                    test_command("MovePrevious", mathml, "x");
    2637
    11
                    test_command("MovePrevious", mathml, "2");
    2638
    11
                    test_command("MovePrevious", mathml, "2");
    2639
    11
                });
    2640
    2641
                // simple sanity check that "overview.yaml" doesn't have a syntax error
    2642
    11
                set_preference("Overview", "True").unwrap();
    2643
    11
                set_preference("NavMode", "Character").unwrap();
    2644
    11
                MATHML_INSTANCE.with(|package_instance| {
    2645
    11
                    let package_instance = package_instance.borrow();
    2646
    11
                    let mathml = get_element(&package_instance);
    2647
    11
                    test_command("ZoomIn", mathml, "2");
    2648
    11
                });
    2649
    11
            }
    2650
    1
        }
    2651
    }