+ assert!(format!("{bad2:?}").contains("line 3: Entry with no header"));
+ }
+
+ #[test]
+ fn test_read_config() {
+ assert_eq!(
+ read_config(&b"!col_threshold 10"[..])
+ .unwrap()
+ .column_threshold,
+ 10
+ );
+ assert_eq!(
+ read_config(&b"!col foo"[..]).unwrap().static_columns,
+ vec![Some("foo".to_owned())]
+ );
+ assert_eq!(
+ read_config(&b"!label foo:bar"[..])
+ .unwrap()
+ .substitute_labels["foo"],
+ "bar"
+ );
+
+ let bad_command = read_config(&b"!no such command"[..]);
+ assert!(bad_command.is_err());
+ assert!(format!("{bad_command:?}").contains("line 1: Unknown command"));
+
+ let bad_num = read_config(&b"!col_threshold foo"[..]);
+ assert!(bad_num.is_err());
+ assert!(format!("{bad_num:?}").contains("line 1: col_threshold must be numeric"));
+
+ let bad_sub = read_config(&b"!label foo"[..]);
+ assert!(bad_sub.is_err());
+ assert!(format!("{bad_sub:?}").contains("line 1: Annotation missing ':'"));
+ }
+
+ #[test]
+ fn test_column_counts() {
+ assert_eq!(
+ column_counts(&read_rows(&b"foo\n bar\n baz\n"[..]).unwrap()),
+ vec![(1, String::from("bar")), (1, String::from("baz"))]
+ );
+ assert_eq!(
+ column_counts(&read_rows(&b"foo\n bar\n baz\nquux\n baz"[..]).unwrap()),
+ vec![(2, String::from("baz")), (1, String::from("bar"))]
+ );
+ assert_eq!(
+ column_counts(&read_rows(&b"foo\n bar\n bar\n baz\n bar\nquux\n baz"[..]).unwrap()),
+ vec![(2, String::from("baz")), (1, String::from("bar"))]
+ );
+ assert_eq!(
+ column_counts(
+ &read_rows(&b"foo\n bar: 1\n bar: 2\n baz\n bar\nquux\n baz"[..]).unwrap()
+ ),
+ vec![(2, String::from("baz")), (1, String::from("bar"))]
+ );
+ }
+
+ #[test]
+ fn test_column_header_labels() {
+ let mut cfg = Config::default();
+
+ assert!(column_header_labels(&cfg, &["foo".to_owned()]).eq([Some(&"foo".to_owned())]));
+
+ cfg.static_columns.push(Some("bar".to_owned()));
+ assert!(column_header_labels(&cfg, &["foo".to_owned()])
+ .eq([Some(&"bar".to_owned()), Some(&"foo".to_owned())]));
+
+ cfg.static_columns.push(None);
+ assert!(column_header_labels(&cfg, &["foo".to_owned()]).eq([
+ Some(&"bar".to_owned()),
+ None,
+ Some(&"foo".to_owned())
+ ]));
+
+ cfg.substitute_labels
+ .insert("foo".to_owned(), "foo (bits)".to_owned());
+ assert!(column_header_labels(&cfg, &["foo".to_owned()]).eq([
+ Some(&"bar".to_owned()),
+ None,
+ Some(&"foo (bits)".to_owned())
+ ]));
+
+ cfg.hidden_columns.insert("foo".to_owned());
+ assert!(column_header_labels(&cfg, &["foo".to_owned()]).eq([Some(&"bar".to_owned()), None]));
+
+ cfg.hidden_columns.insert("bar".to_owned());
+ assert!(column_header_labels(&cfg, &["foo".to_owned()]).eq([None]));
+ }
+
+ #[test]
+ fn test_render_cell() {
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::new(),
+ }
+ ),
+ HTML::from(
+ r#"<td onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')"></td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("bar".to_owned(), vec![None])]),
+ }
+ ),
+ HTML::from(
+ r#"<td onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')"></td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![None])]),
+ }
+ ),
+ HTML::from(
+ r#"<td class="yes" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">𝍷</td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![None, None])]),
+ }
+ ),
+ HTML::from(
+ r#"<td class="yes" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">𝍷𝍷</td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([(
+ "foo".to_owned(),
+ vec![Some("5".to_owned()), Some("10".to_owned())]
+ )]),
+ }
+ ),
+ HTML::from(
+ r#"<td class="yes" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">5 10</td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![Some("5".to_owned()), None])]),
+ }
+ ),
+ HTML::from(
+ r#"<td class="yes" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">5 𝍷</td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![Some("×".to_owned())])]),
+ }
+ ),
+ HTML::from(
+ r#"<td onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">×</td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "heart",
+ &mut Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("heart".to_owned(), vec![Some("<3".to_owned())])]),
+ }
+ ),
+ HTML::from(
+ r#"<td class="yes" onmouseover="h2('nope','heart')" onmouseout="ch2('nope','heart')"><3</td>"#
+ )
+ );
+ assert_eq!(
+ render_cell(
+ &Config::default(),
+ "foo",
+ &mut Row {
+ label: "bob's".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![None])]),
+ }
+ ),
+ HTML::from(
+ r#"<td class="yes" onmouseover="h2('bob's','foo')" onmouseout="ch2('bob's','foo')">𝍷</td>"#
+ )
+ );
+ let mut r = Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([
+ ("foo".to_owned(), vec![None]),
+ ("baz".to_owned(), vec![None]),
+ ]),
+ };
+ assert_eq!(r.entries.len(), 2);
+ render_cell(&Config::default(), "foo", &mut r);
+ assert_eq!(r.entries.len(), 1);
+ render_cell(&Config::default(), "bar", &mut r);
+ assert_eq!(r.entries.len(), 1);
+ render_cell(&Config::default(), "baz", &mut r);
+ assert_eq!(r.entries.len(), 0);
+ }
+
+ #[test]
+ fn test_render_leftovers() {
+ assert_eq!(
+ render_all_leftovers(
+ &Config::default(),
+ &Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![None])]),
+ }
+ ),
+ HTML::from("foo")
+ );
+ assert_eq!(
+ render_all_leftovers(
+ &Config::default(),
+ &Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([
+ ("foo".to_owned(), vec![None]),
+ ("bar".to_owned(), vec![None])
+ ]),
+ }
+ ),
+ HTML::from("bar, foo")
+ );
+ assert_eq!(
+ render_all_leftovers(
+ &Config::default(),
+ &Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([
+ ("foo".to_owned(), vec![None]),
+ ("bar".to_owned(), vec![None, None])
+ ]),
+ }
+ ),
+ HTML::from("bar: 𝍷𝍷, foo")
+ );
+ assert_eq!(
+ render_all_leftovers(
+ &Config {
+ column_threshold: 2,
+ static_columns: vec![],
+ hidden_columns: HashSet::from(["private".to_owned()]),
+ substitute_labels: HashMap::new(),
+ },
+ &Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("private".to_owned(), vec![None]),]),
+ }
+ ),
+ HTML::from("")
+ );
+ }
+
+ #[test]
+ fn test_render_row() {
+ assert_eq!(
+ render_row(
+ &Config::default(),
+ &["foo".to_owned()],
+ &mut Rowlike::Row(Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("bar".to_owned(), vec![None])]),
+ })
+ ),
+ HTML::from(
+ r#"<tr><th id="nope">nope</th><td onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')"></td><td class="leftover" onmouseover="highlight('nope')" onmouseout="clear_highlight('nope')">bar</td></tr>
+"#
+ )
+ );
+ assert_eq!(
+ render_row(
+ &Config {
+ column_threshold: 0,
+ static_columns: vec![Some("foo".to_owned()), Some("bar".to_owned())],
+ hidden_columns: HashSet::new(),
+ substitute_labels: HashMap::new(),
+ },
+ &["baz".to_owned()],
+ &mut Rowlike::Row(Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([
+ ("bar".to_owned(), vec![Some("r".to_owned())]),
+ ("baz".to_owned(), vec![Some("z".to_owned())]),
+ ("foo".to_owned(), vec![Some("f".to_owned())]),
+ ]),
+ })
+ ),
+ HTML::from(
+ r#"<tr><th id="nope">nope</th><td class="yes" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">f</td><td class="yes" onmouseover="h2('nope','bar')" onmouseout="ch2('nope','bar')">r</td><td class="yes" onmouseover="h2('nope','baz')" onmouseout="ch2('nope','baz')">z</td><td class="leftover" onmouseover="highlight('nope')" onmouseout="clear_highlight('nope')"></td></tr>
+"#
+ )
+ );
+ assert_eq!(
+ render_row(
+ &Config {
+ column_threshold: 0,
+ static_columns: vec![Some("foo".to_owned()), None, Some("bar".to_owned())],
+ hidden_columns: HashSet::new(),
+ substitute_labels: HashMap::new(),
+ },
+ &[],
+ &mut Rowlike::Row(Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([
+ ("bar".to_owned(), vec![Some("r".to_owned())]),
+ ("foo".to_owned(), vec![Some("f".to_owned())]),
+ ]),
+ })
+ ),
+ HTML::from(
+ r#"<tr><th id="nope">nope</th><td class="yes" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')">f</td><td class="spacer_col"></td><td class="yes" onmouseover="h2('nope','bar')" onmouseout="ch2('nope','bar')">r</td><td class="leftover" onmouseover="highlight('nope')" onmouseout="clear_highlight('nope')"></td></tr>
+"#
+ )
+ );
+ assert_eq!(
+ render_row(
+ &Config {
+ column_threshold: 0,
+ static_columns: vec![],
+ hidden_columns: HashSet::from(["foo".to_owned()]),
+ substitute_labels: HashMap::new(),
+ },
+ &[],
+ &mut Rowlike::Row(Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![Some("f".to_owned())]),]),
+ })
+ ),
+ HTML::from(
+ r#"<tr><th id="nope">nope</th><td class="leftover" onmouseover="highlight('nope')" onmouseout="clear_highlight('nope')"></td></tr>
+"#
+ )
+ );
+ assert_eq!(
+ render_row(
+ &Config {
+ column_threshold: 0,
+ static_columns: vec![Some("foo".to_owned())],
+ hidden_columns: HashSet::from(["foo".to_owned()]),
+ substitute_labels: HashMap::new(),
+ },
+ &[],
+ &mut Rowlike::Row(Row {
+ label: "nope".to_owned(),
+ entries: HashMap::from([("foo".to_owned(), vec![Some("f".to_owned())]),]),
+ })
+ ),
+ HTML::from(
+ r#"<tr><th id="nope">nope</th><td class="leftover" onmouseover="highlight('nope')" onmouseout="clear_highlight('nope')"></td></tr>
+"#
+ )
+ );