diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a43d416..bc4a7e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,9 +42,3 @@ jobs: crate: cargo-all-features version: latest - run: cargo test-all-features - - - name: Test Parsing - uses: actions-rs/cargo@v1 - with: - command: test - args: --package parsing diff --git a/Cargo.toml b/Cargo.toml index c4db3e2..5c9c98f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/Titaniumtown/YTBN-Graphing-Software" description = "Crossplatform (and web-compatible) graphing calculator" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [features] threading = ["async-lock", "rayon"] diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs index 3eb7859..bb32dd0 100644 --- a/benchmarks/src/lib.rs +++ b/benchmarks/src/lib.rs @@ -2,7 +2,7 @@ #![test_runner(criterion::runner)] #[allow(unused_imports)] -use parsing::suggestions::split_function_chars; +use parsing::split_function_chars; #[allow(unused_imports)] use std::time::Duration; diff --git a/parsing/src/autocomplete_helper.rs b/parsing/src/autocomplete_helper.rs index 5a9b3ae..83bfe7e 100644 --- a/parsing/src/autocomplete_helper.rs +++ b/parsing/src/autocomplete_helper.rs @@ -11,7 +11,7 @@ fn compare_len_reverse_alpha(a: &String, b: &String) -> Ordering { /// Generates hashmap (well really a vector of tuple of strings that are then turned into a hashmap by phf) #[allow(dead_code)] -fn compile_hashmap(data: Vec) -> Vec<(String, String)> { +pub fn compile_hashmap(data: Vec) -> Vec<(String, String)> { let mut seen = HashSet::new(); let tuple_list_1: Vec<(String, String)> = data @@ -70,33 +70,3 @@ fn all_possible_splits( }) .collect::>() } - -#[cfg(test)] -mod tests { - use super::*; - - /// Tests to make sure hashmap generation works as expected - #[test] - fn hashmap_gen_test() { - let data = vec!["time", "text", "test"]; - let expect = vec![ - ("t", r#"Hint::Many(&["ime(", "ext(", "est("])"#), - ("ti", r#"Hint::Single("me(")"#), - ("tim", r#"Hint::Single("e(")"#), - ("time", r#"Hint::Single("(")"#), - ("te", r#"Hint::Many(&["xt(", "st("])"#), - ("tex", r#"Hint::Single("t(")"#), - ("text", r#"Hint::Single("(")"#), - ("tes", r#"Hint::Single("t(")"#), - ("test", r#"Hint::Single("(")"#), - ]; - - assert_eq!( - compile_hashmap(data.iter().map(|e| e.to_string()).collect()), - expect - .iter() - .map(|(a, b)| (a.to_string(), b.to_string())) - .collect::>() - ); - } -} diff --git a/parsing/src/lib.rs b/parsing/src/lib.rs index 7b05fc3..0c5e32b 100644 --- a/parsing/src/lib.rs +++ b/parsing/src/lib.rs @@ -4,5 +4,14 @@ #![feature(const_mut_refs)] mod autocomplete_helper; -pub mod parsing; -pub mod suggestions; +mod parsing; +mod suggestions; + +pub use crate::{ + autocomplete_helper::compile_hashmap, + parsing::{process_func_str, BackingFunction}, + suggestions::{ + generate_hint, get_last_term, split_function, split_function_chars, Hint, HINT_EMPTY, + SUPPORTED_FUNCTIONS, + }, +}; diff --git a/parsing/src/parsing.rs b/parsing/src/parsing.rs index 15da5c4..ac01cf2 100644 --- a/parsing/src/parsing.rs +++ b/parsing/src/parsing.rs @@ -193,104 +193,3 @@ pub fn process_func_str(function_in: &str) -> String { crate::suggestions::split_function(&function_in).join("*") } - -#[cfg(test)] -mod tests { - use super::*; - use crate::suggestions::SUPPORTED_FUNCTIONS; - use std::collections::HashMap; - - /// Returns if function with string `func_str` is valid after processing through [`process_func_str`] - fn func_is_valid(func_str: &str) -> bool { - BackingFunction::new(&process_func_str(func_str)).is_ok() - } - - /// Used for testing: passes function to [`process_func_str`] before running [`test_func`]. if `expect_valid` == `true`, it expects no errors to be created. - fn test_func_helper(func_str: &str, expect_valid: bool) { - let is_valid = func_is_valid(func_str); - let string = format!( - "function: {} (expected: {}, got: {})", - func_str, expect_valid, is_valid - ); - - if is_valid == expect_valid { - println!("{}", string); - } else { - panic!("{}", string); - } - } - - /// Tests to make sure functions that are expected to succeed, succeed. - #[test] - fn test_expected() { - let values = HashMap::from([ - ("", true), - ("x^2", true), - ("2x", true), - ("E^x", true), - ("log10(x)", true), - ("xxxxx", true), - ("sin(x)", true), - ("xsin(x)", true), - ("sin(x)cos(x)", true), - ("x/0", true), - ("(x+1)(x-3)", true), - ("cos(xsin(x)x)", true), - ("(2x+1)x", true), - ("(2x+1)pi", true), - ("pi(2x+1)", true), - ("pipipipipipix", true), - ("e^sin(x)", true), - ("E^sin(x)", true), - ("e^x", true), - ("x**2", true), - ("a", false), - ("log222(x)", false), - ("abcdef", false), - ("log10(x", false), - ("x^a", false), - ("sin(cos(x)))", false), - ("0/0", false), - ]); - - for (key, value) in values { - test_func_helper(key, value); - } - } - - /// Helps with tests of [`process_func_str`] - #[cfg(test)] - fn test_process_helper(input: &str, expected: &str) { - assert_eq!(&process_func_str(input), expected); - } - - /// Tests to make sure my cursed function works as intended - #[test] - fn func_process_test() { - let values = HashMap::from([ - ("2x", "2*x"), - (")(", ")*("), - ("(2", "(2"), - ("log10(x)", "log10(x)"), - ("log2(x)", "log2(x)"), - ("pipipipipipi", "π*π*π*π*π*π"), - ("10pi", "10*π"), - ("pi10", "π*10"), - ("10pi10", "10*π*10"), - ("emax(x)", "e*max(x)"), - ("pisin(x)", "π*sin(x)"), - ("e^sin(x)", "e^sin(x)"), - ("x**2", "x^2"), - ("(x+1)(x-3)", "(x+1)*(x-3)"), - ]); - - for (key, value) in values { - test_process_helper(key, value); - } - - for func in SUPPORTED_FUNCTIONS.iter() { - let func_new = format!("{}(x)", func); - test_process_helper(&func_new, &func_new); - } - } -} diff --git a/parsing/src/suggestions.rs b/parsing/src/suggestions.rs index 801ff99..ad9da8c 100644 --- a/parsing/src/suggestions.rs +++ b/parsing/src/suggestions.rs @@ -202,7 +202,7 @@ pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> { } } -fn get_last_term(chars: &[char]) -> String { +pub fn get_last_term(chars: &[char]) -> String { assert!(!chars.is_empty()); unsafe { @@ -253,143 +253,3 @@ impl<'a> Hint<'a> { } include!(concat!(env!("OUT_DIR"), "/codegen.rs")); - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use super::*; - - /// Tests to make sure hints are properly outputed based on input - #[test] - fn hints() { - let values = HashMap::from([ - ("", Hint::Single("x^2")), - ("si", Hint::Many(&["n(", "nh(", "gnum("])), - ("log", Hint::Many(&["2(", "10("])), - ("cos", Hint::Many(&["(", "h("])), - ("sin(", Hint::Single(")")), - ("sqrt", Hint::Single("(")), - ("ln(x)", Hint::None), - ("ln(x)cos", Hint::Many(&["(", "h("])), - ("ln(x)*cos", Hint::Many(&["(", "h("])), - ("sin(cos", Hint::Many(&["(", "h("])), - ]); - - for (key, value) in values { - println!("{} + {:?}", key, value); - assert_eq!(super::generate_hint(key), &value); - } - } - - #[test] - fn hint_to_string() { - let values = HashMap::from([ - ("x^2", Hint::Single("x^2")), - ( - r#"["n(", "nh(", "gnum("]"#, - Hint::Many(&["n(", "nh(", "gnum("]), - ), - (r#"["n("]"#, Hint::Many(&["n("])), - ("None", Hint::None), - ]); - - for (key, value) in values { - assert_eq!(value.to_string(), key); - } - } - - #[test] - fn invalid_function() { - SUPPORTED_FUNCTIONS - .iter() - .map(|func1| { - SUPPORTED_FUNCTIONS - .iter() - .map(|func2| func1.to_string() + func2) - .collect::>() - }) - .flatten() - .filter(|func| !SUPPORTED_FUNCTIONS.contains(&func.as_str())) - .for_each(|key| { - let split = super::split_function(&key); - - if split.len() != 1 { - panic!("failed: {} (len: {}, split: {:?})", key, split.len(), split); - } - - let generated_hint = super::generate_hint(&key); - if generated_hint.is_none() { - println!("success: {}", key); - } else { - panic!("failed: {} (Hint: '{}')", key, generated_hint.to_string()); - } - }); - } - - #[test] - fn split_function() { - let values = HashMap::from([ - ("cos(x)", vec!["cos(x)"]), - ("cos(", vec!["cos("]), - ("cos(x)sin(x)", vec!["cos(x)", "sin(x)"]), - ("aaaaaaaaaaa", vec!["aaaaaaaaaaa"]), - ("emax(x)", vec!["e", "max(x)"]), - ("x", vec!["x"]), - ("xxx", vec!["x", "x", "x"]), - ("sin(cos(x)x)", vec!["sin(cos(x)", "x)"]), - ("sin(x)*cos(x)", vec!["sin(x)", "cos(x)"]), - ("x*x", vec!["x", "x"]), - ("10*10", vec!["10", "10"]), - ]); - - for (key, value) in values { - assert_eq!(super::split_function(key), value); - } - } - - #[test] - fn hint_tests() { - { - let hint = Hint::None; - assert!(hint.is_none()); - assert!(!hint.is_some()); - assert!(!hint.is_single()); - } - - { - let hint = Hint::Single(&""); - assert!(!hint.is_none()); - assert!(hint.is_some()); - assert!(hint.is_single()); - } - - { - let hint = Hint::Many(&[""]); - assert!(!hint.is_none()); - assert!(hint.is_some()); - assert!(!hint.is_single()); - } - } - - #[test] - fn get_last_term() { - let values = HashMap::from([ - ("cos(x)", "x)"), - ("cos(", "cos("), - ("aaaaaaaaaaa", "aaaaaaaaaaa"), - ("x", "x"), - ("xxx", "x"), - ("x*x", "x"), - ("10*10", "10"), - ("sin(cos", "cos"), - ]); - - for (key, value) in values { - assert_eq!( - super::get_last_term(key.chars().collect::>().as_slice()), - value - ); - } - } -} diff --git a/push.sh b/push.sh index 3fdccf3..6441f0a 100755 --- a/push.sh +++ b/push.sh @@ -2,7 +2,6 @@ set -e #kill script if error occurs cargo test-all-features -cargo test --package parsing bash build.sh release diff --git a/src/function_entry.rs b/src/function_entry.rs index e78b217..cbcef0c 100644 --- a/src/function_entry.rs +++ b/src/function_entry.rs @@ -9,7 +9,7 @@ use egui::{ Checkbox, Context, }; use epaint::Color32; -use parsing::parsing::{process_func_str, BackingFunction}; +use parsing::{process_func_str, BackingFunction}; use std::{ fmt::{self, Debug}, intrinsics::assume, @@ -500,7 +500,7 @@ impl FunctionEntry { pub fn invalidate_nth(&mut self) { self.nth_derivative_data = None } /// Runs asserts to make sure everything is the expected value - #[cfg(test)] + #[allow(dead_code)] pub fn tests( &mut self, settings: AppSettings, back_target: Vec<(f64, f64)>, derivative_target: Vec<(f64, f64)>, area_target: f64, min_x: f64, max_x: f64, @@ -553,77 +553,3 @@ impl FunctionEntry { } } } - -#[cfg(test)] -mod tests { - use super::*; - - fn app_settings_constructor( - sum: Riemann, integral_min_x: f64, integral_max_x: f64, pixel_width: usize, - integral_num: usize, - ) -> AppSettings { - crate::math_app::AppSettings { - riemann_sum: sum, - integral_min_x, - integral_max_x, - integral_changed: true, - integral_num, - do_extrema: false, - do_roots: false, - plot_width: pixel_width, - } - } - - static BACK_TARGET: [(f64, f64); 11] = [ - (-1.0, 1.0), - (-0.8, 0.6400000000000001), - (-0.6, 0.36), - (-0.4, 0.16000000000000003), - (-0.19999999999999996, 0.03999999999999998), - (0.0, 0.0), - (0.19999999999999996, 0.03999999999999998), - (0.3999999999999999, 0.15999999999999992), - (0.6000000000000001, 0.3600000000000001), - (0.8, 0.6400000000000001), - (1.0, 1.0), - ]; - - static DERIVATIVE_TARGET: [(f64, f64); 11] = [ - (-1.0, -2.0), - (-0.8, -1.6), - (-0.6, -1.2), - (-0.4, -0.8), - (-0.19999999999999996, -0.3999999999999999), - (0.0, 0.0), - (0.19999999999999996, 0.3999999999999999), - (0.3999999999999999, 0.7999999999999998), - (0.6000000000000001, 1.2000000000000002), - (0.8, 1.6), - (1.0, 2.0), - ]; - - fn do_test(sum: Riemann, area_target: f64) { - let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10); - - let mut function = FunctionEntry::EMPTY; - function.update_string("x^2"); - function.integral = true; - function.derivative = true; - - function.tests( - settings, - BACK_TARGET.to_vec(), - DERIVATIVE_TARGET.to_vec(), - area_target, - -1.0, - 1.0, - ); - } - - #[test] - fn function_entry_test() { - do_test(Riemann::Left, 0.9600000000000001); - do_test(Riemann::Middle, 0.92); - do_test(Riemann::Right, 0.8800000000000001); - } -} diff --git a/src/function_manager.rs b/src/function_manager.rs index 48f32e1..50f8f6f 100644 --- a/src/function_manager.rs +++ b/src/function_manager.rs @@ -3,7 +3,7 @@ use crate::function_entry::FunctionEntry; use crate::widgets::{widgets_ontop, Movement}; use egui::{Button, Id, Key, Modifiers, TextEdit, WidgetText}; use emath::vec2; -use parsing::suggestions::Hint; +use parsing::Hint; use std::ops::BitXorAssign; use uuid::Uuid; @@ -204,7 +204,9 @@ impl FunctionManager { #[inline] pub fn len(&self) -> usize { self.functions.len() } + #[inline] pub fn get_entries_mut(&mut self) -> &mut Vec<(Id, FunctionEntry)> { &mut self.functions } + #[inline] pub fn get_entries(&self) -> &Vec<(Id, FunctionEntry)> { &self.functions } } diff --git a/src/lib.rs b/src/lib.rs index 39d12e8..612ae41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,13 @@ mod math_app; mod misc; mod widgets; +pub use crate::{ + function_entry::{FunctionEntry, Riemann}, + math_app::AppSettings, + misc::{decimal_round, option_vec_printer, resolution_helper, SteppedVector}, + widgets::{AutoComplete, Movement}, +}; + cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use wasm_bindgen::prelude::*; diff --git a/src/misc.rs b/src/misc.rs index c203944..b9e56e5 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -283,97 +283,3 @@ pub fn resolution_helper(max_i: usize, min_x: &f64, resolution: &f64) -> Vec Vec { (0..max_i).map(|x| (x as f64 * step) + min_x).collect() } - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::HashMap; - - /// Tests [`SteppedVector`] to ensure everything works properly (helped me - /// find a bunch of issues) - #[test] - fn stepped_vector_test() { - let min: i32 = -1000; - let max: i32 = 1000; - let data: Vec = (min..=max).map(|x| x as f64).collect(); - let len_data = data.len(); - let stepped_vector: SteppedVector = data.into(); - - assert_eq!(stepped_vector.get_min(), min as f64); - assert_eq!(stepped_vector.get_max(), max as f64); - - assert_eq!(stepped_vector.get_index(&(min as f64)), Some(0)); - assert_eq!(stepped_vector.get_index(&(max as f64)), Some(len_data - 1)); - - for i in min..=max { - assert_eq!( - stepped_vector.get_index(&(i as f64)), - Some((i + min.abs()) as usize) - ); - } - - assert_eq!(stepped_vector.get_index(&((min - 1) as f64)), None); - assert_eq!(stepped_vector.get_index(&((max + 1) as f64)), None); - } - - /// Ensures [`decimal_round`] returns correct values - #[test] - fn decimal_round_test() { - assert_eq!(decimal_round(0.00001, 1), 0.0); - assert_eq!(decimal_round(0.00001, 2), 0.0); - assert_eq!(decimal_round(0.00001, 3), 0.0); - assert_eq!(decimal_round(0.00001, 4), 0.0); - assert_eq!(decimal_round(0.00001, 5), 0.00001); - - assert_eq!(decimal_round(0.12345, 1), 0.1); - assert_eq!(decimal_round(0.12345, 2), 0.12); - assert_eq!(decimal_round(0.12345, 3), 0.123); - assert_eq!(decimal_round(0.12345, 4), 0.1235); // rounds up - assert_eq!(decimal_round(0.12345, 5), 0.12345); - - assert_eq!(decimal_round(1.9, 0), 2.0); - assert_eq!(decimal_round(1.9, 1), 1.9); - } - - /// Tests [`resolution_helper`] to make sure it returns expected output - #[test] - fn resolution_helper_test() { - assert_eq!( - resolution_helper(10, &1.0, &1.0), - vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - ); - - assert_eq!( - resolution_helper(5, &-2.0, &1.0), - vec![-2.0, -1.0, 0.0, 1.0, 2.0] - ); - - assert_eq!(resolution_helper(3, &-2.0, &1.0), vec![-2.0, -1.0, 0.0]); - } - - /// Tests [`option_vec_printer`] - #[test] - fn option_vec_printer_test() { - let values_strings: HashMap>, &str> = HashMap::from([ - (vec![None], "[None]"), - (vec![Some("text"), None], "[text, None]"), - (vec![None, None], "[None, None]"), - (vec![Some("text1"), Some("text2")], "[text1, text2]"), - ]); - - for (key, value) in values_strings { - assert_eq!(option_vec_printer(&key), value); - } - - let values_nums = HashMap::from([ - (vec![Some(10)], "[10]"), - (vec![Some(10), None], "[10, None]"), - (vec![None, Some(10)], "[None, 10]"), - (vec![Some(10), Some(100)], "[10, 100]"), - ]); - - for (key, value) in values_nums { - assert_eq!(option_vec_printer(&key), value); - } - } -} diff --git a/src/widgets.rs b/src/widgets.rs index ce91d85..c7b4e8c 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,6 +1,6 @@ use std::intrinsics::assume; -use parsing::suggestions::{self, generate_hint, Hint}; +use parsing::{generate_hint, Hint, HINT_EMPTY}; #[derive(PartialEq, Debug)] pub enum Movement { @@ -10,6 +10,10 @@ pub enum Movement { None, } +impl Movement { + pub const fn is_none(&self) -> bool { matches!(&self, Self::None) } +} + impl const Default for Movement { fn default() -> Self { Self::None } } @@ -28,7 +32,7 @@ impl<'a> const Default for AutoComplete<'a> { impl<'a> AutoComplete<'a> { const EMPTY: AutoComplete<'a> = Self { i: 0, - hint: &suggestions::HINT_EMPTY, + hint: &HINT_EMPTY, string: String::new(), }; @@ -52,7 +56,7 @@ impl<'a> AutoComplete<'a> { } pub fn register_movement(&mut self, movement: &Movement) { - if movement == &Movement::None { + if movement.is_none() { return; } @@ -109,141 +113,3 @@ pub fn widgets_ontop( area.show(ui.ctx(), |ui| add_contents(ui)).inner } - -#[cfg(test)] -mod autocomplete_tests { - use super::*; - - enum Action<'a> { - AssertIndex(usize), - AssertString(&'a str), - AssertHint(&'a str), - SetString(&'a str), - Move(Movement), - } - - fn ac_tester(actions: &[Action]) { - let mut ac = AutoComplete::default(); - for action in actions.iter() { - match action { - Action::AssertIndex(target_i) => { - if &ac.i != target_i { - panic!( - "AssertIndex failed: Current: '{}' Expected: '{}'", - ac.i, target_i - ) - } - } - Action::AssertString(target_string) => { - if &ac.string != target_string { - panic!( - "AssertString failed: Current: '{}' Expected: '{}'", - ac.string, target_string - ) - } - } - Action::AssertHint(target_hint) => match ac.hint { - Hint::None => { - if !target_hint.is_empty() { - panic!( - "AssertHint failed on `Hint::None`: Expected: {}", - target_hint - ); - } - } - Hint::Many(hints) => { - let hint = hints[ac.i]; - if &hint != target_hint { - panic!( - "AssertHint failed on `Hint::Many`: Current: '{}' (index: {}) Expected: '{}'", - hint, ac.i, target_hint - ) - } - } - Hint::Single(hint) => { - if hint != target_hint { - panic!( - "AssertHint failed on `Hint::Single`: Current: '{}' Expected: '{}'", - hint, target_hint - ) - } - } - }, - Action::SetString(target_string) => { - ac.update_string(target_string); - } - Action::Move(target_movement) => { - ac.register_movement(target_movement); - } - } - } - } - - #[test] - fn single() { - ac_tester(&[ - Action::SetString(""), - Action::AssertHint("x^2"), - Action::Move(Movement::Up), - Action::AssertIndex(0), - Action::AssertString(""), - Action::AssertHint("x^2"), - Action::Move(Movement::Down), - Action::AssertIndex(0), - Action::AssertString(""), - Action::AssertHint("x^2"), - Action::Move(Movement::Complete), - Action::AssertString("x^2"), - Action::AssertHint(""), - Action::AssertIndex(0), - ]); - } - - #[test] - fn multi() { - ac_tester(&[ - Action::SetString("s"), - Action::AssertHint("in("), - Action::Move(Movement::Up), - Action::AssertIndex(3), - Action::AssertString("s"), - Action::AssertHint("ignum("), - Action::Move(Movement::Down), - Action::AssertIndex(0), - Action::AssertString("s"), - Action::AssertHint("in("), - Action::Move(Movement::Down), - Action::AssertIndex(1), - Action::AssertString("s"), - Action::AssertHint("qrt("), - Action::Move(Movement::Up), - Action::AssertIndex(0), - Action::AssertString("s"), - Action::AssertHint("in("), - Action::Move(Movement::Complete), - Action::AssertString("sin("), - Action::AssertHint(")"), - Action::AssertIndex(0), - ]); - } - - #[test] - fn parens() { - ac_tester(&[ - Action::SetString("sin(x"), - Action::AssertHint(")"), - Action::Move(Movement::Up), - Action::AssertIndex(0), - Action::AssertString("sin(x"), - Action::AssertHint(")"), - Action::Move(Movement::Down), - Action::AssertIndex(0), - Action::AssertString("sin(x"), - Action::AssertHint(")"), - Action::Move(Movement::Complete), - Action::AssertString("sin(x)"), - Action::AssertHint(""), - Action::AssertIndex(0), - ]); - } -} diff --git a/tests/autocomplete.rs b/tests/autocomplete.rs new file mode 100644 index 0000000..45ad833 --- /dev/null +++ b/tests/autocomplete.rs @@ -0,0 +1,135 @@ +use parsing::Hint; +use ytbn_graphing_software::{AutoComplete, Movement}; + +enum Action<'a> { + AssertIndex(usize), + AssertString(&'a str), + AssertHint(&'a str), + SetString(&'a str), + Move(Movement), +} + +fn ac_tester(actions: &[Action]) { + let mut ac = AutoComplete::default(); + for action in actions.iter() { + match action { + Action::AssertIndex(target_i) => { + if &ac.i != target_i { + panic!( + "AssertIndex failed: Current: '{}' Expected: '{}'", + ac.i, target_i + ) + } + } + Action::AssertString(target_string) => { + if &ac.string != target_string { + panic!( + "AssertString failed: Current: '{}' Expected: '{}'", + ac.string, target_string + ) + } + } + Action::AssertHint(target_hint) => match ac.hint { + Hint::None => { + if !target_hint.is_empty() { + panic!( + "AssertHint failed on `Hint::None`: Expected: {}", + target_hint + ); + } + } + Hint::Many(hints) => { + let hint = hints[ac.i]; + if &hint != target_hint { + panic!( + "AssertHint failed on `Hint::Many`: Current: '{}' (index: {}) Expected: '{}'", + hint, ac.i, target_hint + ) + } + } + Hint::Single(hint) => { + if hint != target_hint { + panic!( + "AssertHint failed on `Hint::Single`: Current: '{}' Expected: '{}'", + hint, target_hint + ) + } + } + }, + Action::SetString(target_string) => { + ac.update_string(target_string); + } + Action::Move(target_movement) => { + ac.register_movement(target_movement); + } + } + } +} + +#[test] +fn single() { + ac_tester(&[ + Action::SetString(""), + Action::AssertHint("x^2"), + Action::Move(Movement::Up), + Action::AssertIndex(0), + Action::AssertString(""), + Action::AssertHint("x^2"), + Action::Move(Movement::Down), + Action::AssertIndex(0), + Action::AssertString(""), + Action::AssertHint("x^2"), + Action::Move(Movement::Complete), + Action::AssertString("x^2"), + Action::AssertHint(""), + Action::AssertIndex(0), + ]); +} + +#[test] +fn multi() { + ac_tester(&[ + Action::SetString("s"), + Action::AssertHint("in("), + Action::Move(Movement::Up), + Action::AssertIndex(3), + Action::AssertString("s"), + Action::AssertHint("ignum("), + Action::Move(Movement::Down), + Action::AssertIndex(0), + Action::AssertString("s"), + Action::AssertHint("in("), + Action::Move(Movement::Down), + Action::AssertIndex(1), + Action::AssertString("s"), + Action::AssertHint("qrt("), + Action::Move(Movement::Up), + Action::AssertIndex(0), + Action::AssertString("s"), + Action::AssertHint("in("), + Action::Move(Movement::Complete), + Action::AssertString("sin("), + Action::AssertHint(")"), + Action::AssertIndex(0), + ]); +} + +#[test] +fn parens() { + ac_tester(&[ + Action::SetString("sin(x"), + Action::AssertHint(")"), + Action::Move(Movement::Up), + Action::AssertIndex(0), + Action::AssertString("sin(x"), + Action::AssertHint(")"), + Action::Move(Movement::Down), + Action::AssertIndex(0), + Action::AssertString("sin(x"), + Action::AssertHint(")"), + Action::Move(Movement::Complete), + Action::AssertString("sin(x)"), + Action::AssertHint(""), + Action::AssertIndex(0), + ]); +} diff --git a/tests/function.rs b/tests/function.rs new file mode 100644 index 0000000..dc6d593 --- /dev/null +++ b/tests/function.rs @@ -0,0 +1,69 @@ +use ytbn_graphing_software::{AppSettings, FunctionEntry, Riemann}; + +fn app_settings_constructor( + sum: Riemann, integral_min_x: f64, integral_max_x: f64, pixel_width: usize, integral_num: usize, +) -> AppSettings { + AppSettings { + riemann_sum: sum, + integral_min_x, + integral_max_x, + integral_changed: true, + integral_num, + do_extrema: false, + do_roots: false, + plot_width: pixel_width, + } +} + +static BACK_TARGET: [(f64, f64); 11] = [ + (-1.0, 1.0), + (-0.8, 0.6400000000000001), + (-0.6, 0.36), + (-0.4, 0.16000000000000003), + (-0.19999999999999996, 0.03999999999999998), + (0.0, 0.0), + (0.19999999999999996, 0.03999999999999998), + (0.3999999999999999, 0.15999999999999992), + (0.6000000000000001, 0.3600000000000001), + (0.8, 0.6400000000000001), + (1.0, 1.0), +]; + +static DERIVATIVE_TARGET: [(f64, f64); 11] = [ + (-1.0, -2.0), + (-0.8, -1.6), + (-0.6, -1.2), + (-0.4, -0.8), + (-0.19999999999999996, -0.3999999999999999), + (0.0, 0.0), + (0.19999999999999996, 0.3999999999999999), + (0.3999999999999999, 0.7999999999999998), + (0.6000000000000001, 1.2000000000000002), + (0.8, 1.6), + (1.0, 2.0), +]; + +fn do_test(sum: Riemann, area_target: f64) { + let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10); + + let mut function = FunctionEntry::EMPTY; + function.update_string("x^2"); + function.integral = true; + function.derivative = true; + + function.tests( + settings, + BACK_TARGET.to_vec(), + DERIVATIVE_TARGET.to_vec(), + area_target, + -1.0, + 1.0, + ); +} + +#[test] +fn function_entries() { + do_test(Riemann::Left, 0.9600000000000001); + do_test(Riemann::Middle, 0.92); + do_test(Riemann::Right, 0.8800000000000001); +} diff --git a/tests/misc.rs b/tests/misc.rs new file mode 100644 index 0000000..a7406ce --- /dev/null +++ b/tests/misc.rs @@ -0,0 +1,95 @@ +/// Tests [`SteppedVector`] to ensure everything works properly (helped me find a bunch of issues) +#[test] +fn stepped_vector() { + use ytbn_graphing_software::SteppedVector; + + let min: i32 = -1000; + let max: i32 = 1000; + let data: Vec = (min..=max).map(|x| x as f64).collect(); + let len_data = data.len(); + let stepped_vector: SteppedVector = data.into(); + + assert_eq!(stepped_vector.get_min(), min as f64); + assert_eq!(stepped_vector.get_max(), max as f64); + + assert_eq!(stepped_vector.get_index(&(min as f64)), Some(0)); + assert_eq!(stepped_vector.get_index(&(max as f64)), Some(len_data - 1)); + + for i in min..=max { + assert_eq!( + stepped_vector.get_index(&(i as f64)), + Some((i + min.abs()) as usize) + ); + } + + assert_eq!(stepped_vector.get_index(&((min - 1) as f64)), None); + assert_eq!(stepped_vector.get_index(&((max + 1) as f64)), None); +} + +/// Ensures [`decimal_round`] returns correct values +#[test] +fn decimal_round() { + use ytbn_graphing_software::decimal_round; + + assert_eq!(decimal_round(0.00001, 1), 0.0); + assert_eq!(decimal_round(0.00001, 2), 0.0); + assert_eq!(decimal_round(0.00001, 3), 0.0); + assert_eq!(decimal_round(0.00001, 4), 0.0); + assert_eq!(decimal_round(0.00001, 5), 0.00001); + + assert_eq!(decimal_round(0.12345, 1), 0.1); + assert_eq!(decimal_round(0.12345, 2), 0.12); + assert_eq!(decimal_round(0.12345, 3), 0.123); + assert_eq!(decimal_round(0.12345, 4), 0.1235); // rounds up + assert_eq!(decimal_round(0.12345, 5), 0.12345); + + assert_eq!(decimal_round(1.9, 0), 2.0); + assert_eq!(decimal_round(1.9, 1), 1.9); +} + +/// Tests [`resolution_helper`] to make sure it returns expected output +#[test] +fn resolution_helper() { + use ytbn_graphing_software::resolution_helper; + + assert_eq!( + resolution_helper(10, &1.0, &1.0), + vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + ); + + assert_eq!( + resolution_helper(5, &-2.0, &1.0), + vec![-2.0, -1.0, 0.0, 1.0, 2.0] + ); + + assert_eq!(resolution_helper(3, &-2.0, &1.0), vec![-2.0, -1.0, 0.0]); +} + +/// Tests [`option_vec_printer`] +#[test] +fn option_vec_printer() { + use std::collections::HashMap; + use ytbn_graphing_software::option_vec_printer; + + let values_strings: HashMap>, &str> = HashMap::from([ + (vec![None], "[None]"), + (vec![Some("text"), None], "[text, None]"), + (vec![None, None], "[None, None]"), + (vec![Some("text1"), Some("text2")], "[text1, text2]"), + ]); + + for (key, value) in values_strings { + assert_eq!(option_vec_printer(&key), value); + } + + let values_nums = HashMap::from([ + (vec![Some(10)], "[10]"), + (vec![Some(10), None], "[10, None]"), + (vec![None, Some(10)], "[None, 10]"), + (vec![Some(10), Some(100)], "[10, 100]"), + ]); + + for (key, value) in values_nums { + assert_eq!(option_vec_printer(&key), value); + } +} diff --git a/tests/parsing.rs b/tests/parsing.rs new file mode 100644 index 0000000..cb436b0 --- /dev/null +++ b/tests/parsing.rs @@ -0,0 +1,252 @@ +use parsing::{Hint, SUPPORTED_FUNCTIONS}; +use std::collections::HashMap; + +#[test] +fn hashmap_gen_test() { + let data = vec!["time", "text", "test"]; + let expect = vec![ + ("t", r#"Hint::Many(&["ime(", "ext(", "est("])"#), + ("ti", r#"Hint::Single("me(")"#), + ("tim", r#"Hint::Single("e(")"#), + ("time", r#"Hint::Single("(")"#), + ("te", r#"Hint::Many(&["xt(", "st("])"#), + ("tex", r#"Hint::Single("t(")"#), + ("text", r#"Hint::Single("(")"#), + ("tes", r#"Hint::Single("t(")"#), + ("test", r#"Hint::Single("(")"#), + ]; + + assert_eq!( + parsing::compile_hashmap(data.iter().map(|e| e.to_string()).collect()), + expect + .iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect::>() + ); +} + +/// Returns if function with string `func_str` is valid after processing through [`process_func_str`] +fn func_is_valid(func_str: &str) -> bool { + parsing::BackingFunction::new(&parsing::process_func_str(func_str)).is_ok() +} + +/// Used for testing: passes function to [`process_func_str`] before running [`test_func`]. if `expect_valid` == `true`, it expects no errors to be created. +fn test_func_helper(func_str: &str, expect_valid: bool) { + let is_valid = func_is_valid(func_str); + let string = format!( + "function: {} (expected: {}, got: {})", + func_str, expect_valid, is_valid + ); + + if is_valid == expect_valid { + println!("{}", string); + } else { + panic!("{}", string); + } +} + +/// Tests to make sure functions that are expected to succeed, succeed. +#[test] +fn test_expected() { + let values = HashMap::from([ + ("", true), + ("x^2", true), + ("2x", true), + ("E^x", true), + ("log10(x)", true), + ("xxxxx", true), + ("sin(x)", true), + ("xsin(x)", true), + ("sin(x)cos(x)", true), + ("x/0", true), + ("(x+1)(x-3)", true), + ("cos(xsin(x)x)", true), + ("(2x+1)x", true), + ("(2x+1)pi", true), + ("pi(2x+1)", true), + ("pipipipipipix", true), + ("e^sin(x)", true), + ("E^sin(x)", true), + ("e^x", true), + ("x**2", true), + ("a", false), + ("log222(x)", false), + ("abcdef", false), + ("log10(x", false), + ("x^a", false), + ("sin(cos(x)))", false), + ("0/0", false), + ]); + + for (key, value) in values { + test_func_helper(key, value); + } +} + +/// Helps with tests of [`process_func_str`] +fn test_process_helper(input: &str, expected: &str) { + assert_eq!(&parsing::process_func_str(input), expected); +} + +/// Tests to make sure my cursed function works as intended +#[test] +fn func_process_test() { + let values = HashMap::from([ + ("2x", "2*x"), + (")(", ")*("), + ("(2", "(2"), + ("log10(x)", "log10(x)"), + ("log2(x)", "log2(x)"), + ("pipipipipipi", "π*π*π*π*π*π"), + ("10pi", "10*π"), + ("pi10", "π*10"), + ("10pi10", "10*π*10"), + ("emax(x)", "e*max(x)"), + ("pisin(x)", "π*sin(x)"), + ("e^sin(x)", "e^sin(x)"), + ("x**2", "x^2"), + ("(x+1)(x-3)", "(x+1)*(x-3)"), + ]); + + for (key, value) in values { + test_process_helper(key, value); + } + + for func in SUPPORTED_FUNCTIONS.iter() { + let func_new = format!("{}(x)", func); + test_process_helper(&func_new, &func_new); + } +} + +/// Tests to make sure hints are properly outputed based on input +#[test] +fn hints() { + let values = HashMap::from([ + ("", Hint::Single("x^2")), + ("si", Hint::Many(&["n(", "nh(", "gnum("])), + ("log", Hint::Many(&["2(", "10("])), + ("cos", Hint::Many(&["(", "h("])), + ("sin(", Hint::Single(")")), + ("sqrt", Hint::Single("(")), + ("ln(x)", Hint::None), + ("ln(x)cos", Hint::Many(&["(", "h("])), + ("ln(x)*cos", Hint::Many(&["(", "h("])), + ("sin(cos", Hint::Many(&["(", "h("])), + ]); + + for (key, value) in values { + println!("{} + {:?}", key, value); + assert_eq!(parsing::generate_hint(key), &value); + } +} + +#[test] +fn hint_to_string() { + let values = HashMap::from([ + ("x^2", Hint::Single("x^2")), + ( + r#"["n(", "nh(", "gnum("]"#, + Hint::Many(&["n(", "nh(", "gnum("]), + ), + (r#"["n("]"#, Hint::Many(&["n("])), + ("None", Hint::None), + ]); + + for (key, value) in values { + assert_eq!(value.to_string(), key); + } +} + +#[test] +fn invalid_function() { + SUPPORTED_FUNCTIONS + .iter() + .map(|func1| { + SUPPORTED_FUNCTIONS + .iter() + .map(|func2| func1.to_string() + func2) + .collect::>() + }) + .flatten() + .filter(|func| !SUPPORTED_FUNCTIONS.contains(&func.as_str())) + .for_each(|key| { + let split = parsing::split_function(&key); + + if split.len() != 1 { + panic!("failed: {} (len: {}, split: {:?})", key, split.len(), split); + } + + let generated_hint = parsing::generate_hint(&key); + if generated_hint.is_none() { + println!("success: {}", key); + } else { + panic!("failed: {} (Hint: '{}')", key, generated_hint.to_string()); + } + }); +} + +#[test] +fn split_function() { + let values = HashMap::from([ + ("cos(x)", vec!["cos(x)"]), + ("cos(", vec!["cos("]), + ("cos(x)sin(x)", vec!["cos(x)", "sin(x)"]), + ("aaaaaaaaaaa", vec!["aaaaaaaaaaa"]), + ("emax(x)", vec!["e", "max(x)"]), + ("x", vec!["x"]), + ("xxx", vec!["x", "x", "x"]), + ("sin(cos(x)x)", vec!["sin(cos(x)", "x)"]), + ("sin(x)*cos(x)", vec!["sin(x)", "cos(x)"]), + ("x*x", vec!["x", "x"]), + ("10*10", vec!["10", "10"]), + ]); + + for (key, value) in values { + assert_eq!(parsing::split_function(key), value); + } +} + +#[test] +fn hint_tests() { + { + let hint = Hint::None; + assert!(hint.is_none()); + assert!(!hint.is_some()); + assert!(!hint.is_single()); + } + + { + let hint = Hint::Single(&""); + assert!(!hint.is_none()); + assert!(hint.is_some()); + assert!(hint.is_single()); + } + + { + let hint = Hint::Many(&[""]); + assert!(!hint.is_none()); + assert!(hint.is_some()); + assert!(!hint.is_single()); + } +} + +#[test] +fn get_last_term() { + let values = HashMap::from([ + ("cos(x)", "x)"), + ("cos(", "cos("), + ("aaaaaaaaaaa", "aaaaaaaaaaa"), + ("x", "x"), + ("xxx", "x"), + ("x*x", "x"), + ("10*10", "10"), + ("sin(cos", "cos"), + ]); + + for (key, value) in values { + assert_eq!( + parsing::get_last_term(key.chars().collect::>().as_slice()), + value + ); + } +}