An answer as it was kept before its numbers were.
The few words of each answer, as the feed prints them.
An answer to one question, and whether it was sent now or kept.
18type Reply = (Judged, Sent);
A question that went to Jev, answered or still waiting.
29struct Asked(&'static str, Option<Reply>);
A tool call the LLM made, and the question it became, if it was sent.
41fn outcome(judged: Vec<Judged>, sent: Sent) -> Outcome { 42 Outcome::Answered { 43 judged, 44 sent, 45 body: r#"{"model":"jev-1.13.0"}"#.into(), 46 request_id: Some("req_1".into()), 47 attempts: 1, 48 usage: Usage { input_tokens: 300, output_tokens: 20 }, 49 took: Duration::from_millis(150), 50 } 51} 52 53fn call(id: &'static str, parts: Vec<(String, &'static str)>, outcome: Option<Outcome>) -> JevCall { 54 JevCall { id, parts, request: r#"{"model":"jev-1.13.0"}"#.into(), outcome, kept: None } 55}
A view after the facts were asked for: the tools, and the one request that carries every question that was sent. It is answered only when every question in it is.
60fn view(tools: Vec<(ToolCall, Option<Asked>)>, ending: Option<Ending>) -> View { 61 let mut shown = View::new("what is the best editor".into()); 62 shown.facts = Some(call("facts", vec![("answerable".into(), "noul")], None)); 63 let mut parts = Vec::new(); 64 let mut answers = Vec::new(); 65 for (index, (tool, asked)) in tools.into_iter().enumerate() { 66 let id = format!("q{}", index + 1); 67 if let Some(Asked(kind, answer)) = asked { 68 parts.push((id.clone(), kind)); 69 answers.push(answer); 70 } 71 shown.tools.push(Tool { id, call: tool, refused: None }); 72 } 73 if !parts.is_empty() { 74 let sent = answers.iter().flatten().map(|(_, sent)| *sent).next().unwrap_or(Sent::Now); 75 let judged: Option<Vec<Judged>> = answers.into_iter().map(|answer| answer.map(|(judged, _)| judged)).collect(); 76 let answered = judged.is_some(); 77 shown.judging = Some(call("answers", parts, judged.map(|judged| outcome(judged, sent)))); 78 shown.known.learn(Fact::Judged, rules::Value::Bool(answered)); 79 } 80 shown.ending = ending; 81 shown 82}
84#[test] 85fn the_input_is_escaped() { 86 let mut shown = View::new("<script>alert(1)</script>".into()); 87 shown.ending = Some(Ending::Offline); 88 let html = transcript(&shown).into_string(); 89 assert!(!html.contains("<script>")); 90 assert!(html.contains("<script>")); 91} 92 93#[test] 94fn the_share_link_round_trips_a_question() { 95 assert_eq!(url_encode("is 2+2 = 4?"), "is+2%2B2+%3D+4%3F"); 96} 97 98#[test] 99fn a_pending_call_shows_its_request_and_no_response() { 100 let html = transcript(&view(Vec::new(), None)).into_string(); 101 assert!(html.contains("waiting for Jev")); 102 assert!(html.contains(r#"<span class="k">"model"</span>: <span class="s">"jev-1.13.0"</span>"#)); 103 assert!(!html.contains(">response</span>")); 104 assert!(html.contains("Step 1")); 105} 106 107#[test] 108fn a_choice_is_drawn_most_likely_first_with_the_winner_marked() { 109 let draft = Draft::Choice { 110 instructions: "Which editor is best?".into(), 111 options: vec![ 112 Opt { label: "Vim".into(), description: "modal".into() }, 113 Opt { label: "Emacs".into(), description: "lisp".into() }, 114 ], 115 }; 116 let answer = ChoiceAnswer { 117 choice: "Emacs".into(), 118 confidence: 0.8, 119 probabilities: vec![("Vim".into(), 0.25), ("Emacs".into(), 0.75)], 120 }; 121 let shown = view(vec![tool(Ok(draft), Some(jev("choice", answered(Judged::Choice(answer)))))], Some(Ending::Answered)); 122 let html = transcript(&shown).into_string(); 123 assert!(html.contains("Which editor is best?")); 124 assert!(html.contains(r#"<p class="answer pick">Emacs.</p>"#)); 125 assert!(html.find("left: 75.0%").unwrap() < html.find("left: 25.0%").unwrap()); 126 assert!(html.contains(r#"<div class="bar won"><span class="label">Emacs</span>"#)); 127 assert!(html.contains("Was that so hard?")); 128} 129 130#[test] 131fn a_score_is_drawn_lowest_first_with_the_nearest_level_marked() { 132 let draft = Draft::Score { instructions: "How good?".into(), levels: vec!["bad".into(), "fine".into(), "good".into()] }; 133 let answer = ScoreAnswer { 134 score: 1.6, 135 confidence: 0.6, 136 probabilities: vec![0.1, 0.2, 0.7], 137 legend: vec!["bad".into(), "fine".into(), "good".into()], 138 }; 139 let shown = view(vec![tool(Ok(draft), Some(jev("score", answered(Judged::Score(answer)))))], Some(Ending::Answered)); 140 let html = transcript(&shown).into_string(); 141 assert!(html.contains(r#"<p class="answer pick">good.<span class="of"> mean 1.6 / 2</span></p>"#)); 142 assert!(html.contains(r#"<div class="bar won"><span class="label">2 · good <span class="tag">likeliest</span></span>"#)); 143 assert!(html.find("0 · bad").unwrap() < html.find("2 · good").unwrap()); 144}
Jev's score is the mean over the levels (TypeSafe's docs), so it is the answer, and a spread-out one (confidence 0.08) is asked, not stated: the headline is the level nearest the mean with a question mark, the likeliest level (55%) is tagged, and everything Jev said is on the page.
150#[test] 151fn a_spread_out_score_is_headed_by_its_mean_with_a_question_and_tags_the_likeliest() { 152 let levels = ["None", "A few", "Several", "Many", "Numerous"]; 153 let draft = Draft::Score { instructions: "How many?".into(), levels: levels.iter().map(|l| l.to_string()).collect() }; 154 let answer = ScoreAnswer { 155 score: 1.1, 156 confidence: 0.08, 157 probabilities: vec![0.55, 0.13, 0.09, 0.14, 0.09], 158 legend: levels.iter().map(|l| l.to_string()).collect(), 159 }; 160 let shown = view(vec![tool(Ok(draft), Some(jev("score", answered(Judged::Score(answer)))))], Some(Ending::Answered)); 161 let html = transcript(&shown).into_string(); 162 assert!(html.contains(r#"<p class="answer pick">A few?<span class="of"> mean 1.1 / 4</span></p>"#), "{html}"); 163 assert!(html.contains(r#"<div class="bar won"><span class="label">1 · A few</span>"#), "the headline's level is inked"); 164 assert!(html.contains(r#"<span class="label">0 · None <span class="tag">likeliest</span></span>"#), "{html}"); 165 assert_eq!(html.matches("likeliest").count(), 1); 166 assert!(html.contains("55%") && html.contains("14%") && html.contains("confidence 0.08 · unsure"), "{html}"); 167}
169#[test] 170fn a_sure_score_is_stated_flatly_and_inks_the_level_it_tags() { 171 let draft = Draft::Score { instructions: "How good?".into(), levels: vec!["bad".into(), "fine".into(), "good".into()] }; 172 let answer = ScoreAnswer { 173 score: 1.8, 174 confidence: 0.8, 175 probabilities: vec![0.05, 0.1, 0.85], 176 legend: vec!["bad".into(), "fine".into(), "good".into()], 177 }; 178 let shown = view(vec![tool(Ok(draft), Some(jev("score", answered(Judged::Score(answer)))))], Some(Ending::Answered)); 179 let html = transcript(&shown).into_string(); 180 assert!(html.contains(r#"<p class="answer pick">good.<span class="of"> mean 1.8 / 2</span></p>"#), "{html}"); 181 assert!(html.contains(r#"<div class="bar won"><span class="label">2 · good <span class="tag">likeliest</span></span>"#), "{html}"); 182 assert!(!html.contains("unsure")); 183} 184 185#[test] 186fn a_call_the_llm_got_wrong_is_shown_and_not_sent() { 187 let shown = view(vec![tool(Err("missing field `yes_means`".into()), None)], Some(Ending::NoQuestion)); 188 let html = transcript(&shown).into_string(); 189 assert!(html.contains("Not sent to Jev: missing field `yes_means`")); 190 assert!(html.contains("Jev wasn't asked anything.") || html.contains("Jev wasn't asked anything.")); 191 assert!(!html.contains("Was that so hard?")); 192}
The printed JSON, with the markup taken back out, is the same JSON.
195#[test] 196fn pretty_printing_changes_only_whitespace() { 197 let raw = r#"{"model":"jev-1.13.0","questions":{"gate":{"type":"noul","criteria":{"true":"a \"quoted\" <b>","false":""}}},"state":{"input":"é?","n":[0.97,1,true,null,{},[]]}}"#; 198 let html = json(raw).into_string(); 199 assert!(html.contains("\n <span class=\"k\">"model"</span>")); 200 let mut text = String::new(); 201 let mut in_tag = false; 202 for ch in html.chars() { 203 match ch { 204 '<' => in_tag = true, 205 '>' => in_tag = false, 206 _ if !in_tag => text.push(ch), 207 _ => {} 208 } 209 } 210 let text = text.replace(""", "\"").replace("<", "<").replace(">", ">").replace("&", "&"); 211 let shown: Value = serde_json::from_str(&text).unwrap(); 212 assert_eq!(serde_json::to_string(&shown).unwrap(), raw); 213}
215#[test] 216fn text_that_is_not_json_is_shown_as_it_is() { 217 assert_eq!(json("upstream <down>").into_string(), "<pre>upstream <down></pre>"); 218} 219 220#[test] 221fn the_live_line_says_whether_jev_can_judge_it_and_what_kind_it_is() { 222 let line = |answerable, kind| live(Some(Glance { answerable, kind, unfinished: false })).into_string(); 223 assert!(line(0.97, Some(Kind::Choice)).contains("Jev can judge this · a pick")); 224 assert!(line(0.97, None).contains("Jev can judge this")); 225 let no = line(0.05, Some(Kind::Noul)); 226 assert!(no.contains("can't judge this yet") || no.contains("can't judge this yet")); 227 assert!(!no.contains("a yes or no")); 228 assert!(no.contains("press Ask for an LLM that can"), "{no}"); 229 assert!(!line(0.97, None).contains("press Ask"), "only when Jev cannot judge it"); 230 assert_eq!(live(None).into_string(), r#"<p class="live" id="live" aria-live="polite"></p>"#); // Half a question is not judged: the line says to go on, and nothing of what Jev made of it. 231 let half = live(Some(Glance { answerable: 0.97, kind: Some(Kind::Noul), unfinished: true })).into_string(); 232 assert!(half.contains("Jev reads this as unfinished") && !half.contains("can judge") && !half.contains("97") && !half.contains("press Ask"), "{half}"); 233} 234 235#[test] 236fn what_is_typed_is_met_with_questions_already_asked() { 237 let asked = Entry { input: "Is a <hot dog> a sandwich?".into(), answers: Vec::new(), asked_ms: 0.0, times: 3 }; 238 let html = suggested(&[asked]).into_string(); 239 // A link that asks it again; a stranger's words are escaped. 240 assert!(html.contains(r#"id="suggested""#) && html.contains("href=\"/?q=Is") && html.contains("<hot dog>") && html.contains("×3"), "{html}"); 241 // With none, the list is there and empty, so the last ones are cleared. 242 assert_eq!(suggested(&[]).into_string(), r#"<ul class="feed suggested" id="suggested" aria-label="Already asked"></ul>"#); 243} 244 245 246#[test] 247fn an_icon_draws_one_square_per_inked_cell() { 248 let inked: usize = icons::JEV.iter().map(|row| row.matches('#').count()).sum(); 249 assert_eq!(icon(&icons::JEV, "").into_string().matches("<rect").count(), inked); 250 for rows in [icons::ASK, icons::JEV, icons::LLM, icons::TOOL] { 251 assert!(rows.iter().all(|row| row.len() == 12)); 252 } 253} 254 255#[test] 256fn the_budget_window_shows_what_is_left_and_whose_count_it_is() { 257 let status = budget::Status { neurons: 1068.2, counted: budget::Counted::Account, jev_dollars: 0.0123 }; 258 let html = budget(Some(Shared { status, neurons_per_day: 10_000.0, jev_dollars_per_day: 0.25 })).into_string(); 259 assert!(html.contains("8,932 of 10,000"), "{html}"); 260 assert!(html.contains("$0.2377 of $0.25")); 261 assert!(html.contains("left: 89.3%")); 262 assert!(html.contains("Cloudflare's own count") || html.contains("Cloudflare's own count")); 263 let own = budget::Status { counted: budget::Counted::Own, ..status }; 264 let html = budget(Some(Shared { status: own, neurons_per_day: 10_000.0, jev_dollars_per_day: 0.25 })).into_string(); 265 assert!(html.contains("this site") && html.contains("own count")); 266 assert!(budget(None).into_string().contains("could not be read")); 267} 268 269#[test] 270fn an_overspent_budget_shows_nothing_left_not_a_negative() { 271 let status = budget::Status { neurons: 10_400.0, counted: budget::Counted::Account, jev_dollars: 0.3 }; 272 let html = budget(Some(Shared { status, neurons_per_day: 10_000.0, jev_dollars_per_day: 0.25 })).into_string(); 273 assert!(html.contains("0 of 10,000")); 274 assert!(html.contains("$0.0000 of $0.25")); 275 assert!(html.contains("left: 0.0%")); 276} 277 278#[test] 279fn numbers_are_grouped_in_thousands() { 280 assert_eq!(grouped(0.4), "0"); 281 assert_eq!(grouped(999.0), "999"); 282 assert_eq!(grouped(1000.0), "1,000"); 283 assert_eq!(grouped(1_234_567.0), "1,234,567"); 284} 285 286#[test] 287fn an_unsure_noul_ends_in_a_question_mark() { 288 let draft = || Draft::Noul { instructions: "Is AI a bit stupid?".into(), yes_means: "y".into(), no_means: "n".into() }; 289 for (p_yes, word) in [(0.55, "Yes?"), (0.45, "No?"), (0.7, "Yes."), (0.3, "No."), (0.97, "Yes."), (0.02, "No.")] { 290 let shown = view(vec![tool(Ok(draft()), Some(jev("noul", answered(Judged::Noul(p_yes)))))], Some(Ending::Answered)); 291 let html = transcript(&shown).into_string(); 292 assert!(html.contains(&format!(r#"<p class="answer">{word}</p>"#)), "{p_yes}: {html}"); 293 } 294 // The gate's refusal is a Noul too. 295 let refused = view(Vec::new(), Some(Ending::NotAQuestion { p_yes: 0.4 })); 296 assert!(transcript(&refused).into_string().contains(r#"<p class="answer">No?</p>"#)); 297} 298 299#[test] 300fn a_kept_response_says_it_was_not_sent_and_when_it_was_answered() { 301 let draft = Draft::Noul { instructions: "Is it?".into(), yes_means: "y".into(), no_means: "n".into() }; 302 let kept = answered_and(Judged::Noul(0.9), Sent::Before { at_ms: 1_790_971_860_000.0 }); 303 let mut shown = view(vec![tool(Ok(draft), Some(jev("noul", kept)))], Some(Ending::Answered)); 304 shown.llm = Some(LlmCall { 305 model: "@cf/x", 306 request: "{}".into(), 307 outcome: Some(LlmOutcome { 308 body: "{}".into(), 309 neurons: 16.0, 310 took: Duration::from_millis(3000), 311 dropped: 0, 312 sent: Sent::Before { at_ms: 1_790_971_860_000.0 }, 313 }), 314 kept: Some(Versions { id: "aaaaaaaaaaaa".into(), version: 2, versions: 3 }), 315 }); 316 let html = transcript(&shown).into_string(); 317 // An old answer of three: a step each way, and ↻. 318 assert!(html.contains("answer 2 of 3"), "{html}"); 319 assert!(html.contains("aaaaaaaaaaaa:1'") && html.contains("aaaaaaaaaaaa:3'") && html.contains("aaaaaaaaaaaa:fresh'"), "{html}"); 320 assert_eq!(shown.pinned(), "aaaaaaaaaaaa:2"); 321 assert_eq!(html.matches("not sent · answered 2026-10-02 20:11 UTC").count(), 2, "{html}"); 322 // The answer and the response body are shown as for a call just made. 323 assert!(html.contains(r#"<p class="answer">Yes.</p>"#)); 324 assert!(html.contains(">response</span>")); 325 assert!(!html.contains("3000 ms")); 326} 327 328#[test] 329fn the_feed_links_each_question_and_escapes_it() { 330 let entries = [ 331 Entry { input: "is <b>this</b> ok?".into(), answers: vec![said("No?")], asked_ms: 1_790_971_860_000.0, times: 1 }, 332 Entry { input: "best editor".into(), answers: vec![said("Vim."), said("Yes.")], asked_ms: 0.0, times: 12 }, 333 ]; 334 let home = Home { lately: entries.to_vec(), most: vec![entries[1].clone()], stats: Stats::default() }; 335 let html = feed(Some(&home)).into_string(); 336 assert!(html.contains("most asked")); 337 assert_eq!(html.matches("×12").count(), 2); 338 assert!(!html.contains("×1<")); 339 assert!(html.contains(r#"href="/?q=is+%3Cb%3Ethis%3C%2Fb%3E+ok%3F""#), "{html}"); 340 assert!(html.contains("is <b>this</b> ok?")); 341 assert!(!html.contains("<b>")); 342 assert!(html.contains(r#"<span class="said-s">Vim. Yes.<span class="times"> ×12</span></span>"#), "{html}"); 343 assert!(html.find("is <b>").unwrap() < html.find("best editor").unwrap()); 344} 345 346#[test] 347fn an_empty_or_unread_feed_is_still_a_place_to_put_the_first_line() { 348 for empty in [feed(None), feed(Some(&Home::default()))] { 349 let html = empty.into_string(); 350 assert!(html.contains(r#"<ol class="feed" id="lately"><li id="more" hidden></li></ol>"#), "{html}"); 351 assert!(!html.contains("class=\"entry\""), "the style sheet hides a feed with no line: {html}"); 352 } 353} 354 355fn asked(n: usize) -> Entry { 356 Entry { input: format!("question {n}?"), answers: vec![said("Yes.")], asked_ms: 1_790_000_000_000.0 - n as f64, times: 1 } 357} 358 359#[test] 360fn a_full_page_of_the_feed_asks_for_the_page_after_its_last_line_once() { 361 let lately: Vec<Entry> = (0..::archive::FEED as usize).map(asked).collect(); 362 let html = feed(Some(&Home { lately, ..Home::default() })).into_string(); 363 let last = ::archive::FEED - 1; 364 assert!( 365 html.contains(&format!(r#"<li id="more" data-on-intersect__once="@get('/feed?ms={}&q=question+{last}%3F')">"#, 1_790_000_000_000.0 - f64::from(last))), 366 "{html}" 367 ); 368} 369 370#[test] 371fn a_short_page_is_the_end_of_the_feed_and_asks_nothing() { 372 let (lines, end) = older(&[asked(1), asked(2)]); 373 assert_eq!(lines.into_string().matches("<li class=\"entry\"").count(), 2); 374 assert_eq!(end.into_string(), r#"<li id="more" hidden></li>"#); 375 assert_eq!(older(&[]).1.into_string(), r#"<li id="more" hidden></li>"#); 376} 377 378#[test] 379fn a_question_has_one_id_in_asked_lately_and_none_in_most_asked() { 380 let entry = asked(7); 381 let home = Home { lately: vec![entry.clone()], most: vec![entry.clone()], stats: Stats::default() }; 382 let html = feed(Some(&home)).into_string(); 383 let top = lately_top(&entry).into_string(); 384 let id = top.split("id=\"").nth(1).and_then(|rest| rest.split('"').next()).expect("an id"); 385 assert!(id.starts_with("q-") && id.len() == 18, "{id}"); 386 assert_eq!(html.matches(&format!("id=\"{id}\"")).count(), 1, "{html}"); 387 assert!(top.starts_with(r#"<ol hidden data-prepend="lately">"#), "{top}"); 388 assert_ne!(lately_top(&asked(8)).into_string(), top); 389} 390 391#[test] 392fn the_feed_gets_every_answer_in_a_few_words() { 393 let noul = Draft::Noul { instructions: "Is it?".into(), yes_means: "y".into(), no_means: "n".into() }; 394 let pick = ChoiceAnswer { choice: "Vim".into(), confidence: 0.9, probabilities: vec![("Vim".into(), 0.9), ("Emacs".into(), 0.1)] }; 395 let shown = view( 396 vec![ 397 tool(Ok(noul.clone()), Some(jev("noul", answered(Judged::Noul(0.55))))), 398 tool(Ok(noul.clone()), Some(jev("choice", answered(Judged::Choice(pick))))), 399 tool(Err("not a question".into()), None), 400 ], 401 Some(Ending::Answered), 402 ); 403 assert_eq!(headlines(&shown), ["Yes?", "Vim."]); 404}
Marks the input as one answerable question read as kind, with Jev's
own answers in hand.
408fn read_as(shown: &mut View, kinds: &[Kind]) { 409 use rules::Value; 410 shown.known.learn(Fact::Answerable, Value::Bool(true)); 411 shown.known.learn(Fact::Several, Value::Bool(false)); 412 shown.known.learn(Fact::Scale, Value::Bool(false)); 413 shown.known.learn(Fact::Yes, Value::Given); 414 shown.known.learn(Fact::Degree, Value::Given); 415 shown.known.learn(Fact::Chance, Value::Given); 416 for kind in Kind::ALL { 417 shown.known.learn(Fact::Reads(kind), Value::Bool(kinds.contains(&kind))); 418 } 419}
421fn facts(answers: Vec<(Fact, Judged)>) -> JevCall { 422 let kind = |judged: &Judged| match judged { 423 Judged::Noul(_) => "noul", 424 Judged::Choice(_) => "choice", 425 Judged::Score(_) => "score", 426 }; 427 let parts = answers.iter().map(|(fact, judged)| (ask::question_id(*fact).to_owned(), kind(judged))).collect(); 428 call("facts", parts, Some(outcome(answers.into_iter().map(|(_, judged)| judged).collect(), Sent::Now))) 429} 430 431#[test] 432fn a_direct_answer_is_the_input_answered_by_jev_with_no_llm() { 433 let mut shown = View::new("is water wet?".into()); 434 shown.facts = Some(facts(vec![(Fact::Answerable, Judged::Noul(0.95)), (Fact::Yes, Judged::Noul(0.88))])); 435 read_as(&mut shown, &[Kind::Noul]); 436 shown.ending = Some(Ending::Answered); 437 let html = transcript(&shown).into_string(); 438 assert!(html.contains(r#"<p class="asked">is water wet?</p>"#), "{html}"); 439 assert!(html.contains(r#"<p class="answer">Yes.</p>"#)); 440 assert!(html.contains("p(yes) = 0.88")); 441 assert!(html.contains("Was that so hard?")); 442 assert!(html.contains("Send it to them:")); 443 // The diagram names the LLM; no call to it is shown. 444 assert!(!html.contains("neurons") && !html.contains("the LLM's tool call")); 445 assert_eq!(headlines(&shown), ["Yes."]); 446} 447 448#[test] 449fn a_request_says_how_many_questions_it_carries() { 450 let mut shown = View::new("is water wet?".into()); 451 shown.facts = Some(facts(vec![(Fact::Answerable, Judged::Noul(0.95)), (Fact::Yes, Judged::Noul(0.88))])); 452 let html = transcript(&shown).into_string(); 453 assert!(html.contains("jev · facts · 2 questions"), "{html}"); 454} 455 456#[test] 457fn the_rules_are_drawn_with_what_is_known_marked() { 458 let mut shown = View::new("is water wet?".into()); 459 shown.facts = Some(facts(vec![(Fact::Answerable, Judged::Noul(0.1))])); 460 shown.known.learn(Fact::Answerable, rules::Value::Bool(false)); 461 shown.fired = vec![0]; 462 shown.ending = Some(Ending::NotAQuestion { p_yes: 0.1 }); 463 let html = transcript(&shown).into_string(); 464 assert!(html.contains("rules · rete")); 465 assert!(html.contains(r#"<span class="meta">1 refuse</span>"#), "{html}"); 466 assert!(!shown.answered()); 467 // Jev's own number, not the yes or no it was read as. 468 assert!(html.contains(">0.10</text>")); 469} 470 471#[test] 472fn a_tool_call_says_which_question_it_became() { 473 let noul = Draft::Noul { instructions: "Is it?".into(), yes_means: "y".into(), no_means: "n".into() }; 474 let sent = tool(Ok(noul), Some(jev("noul", answered(Judged::Noul(0.9))))); 475 let shown = view(vec![sent, tool(Err("bad".into()), None)], Some(Ending::Answered)); 476 let html = transcript(&shown).into_string(); 477 assert!(html.contains("sent to Jev as q1")); 478 assert!(html.contains("Not sent to Jev: bad")); 479 assert!(html.contains("jev · answers · 1 question<"), "{html}"); 480} 481 482#[test] 483fn a_link_unfurls_as_the_question_and_what_jev_said() { 484 let said = Entry { input: "is AI a bit stupid?".into(), answers: vec![Answer { headline: "No?".into(), detail: Detail::Noul { p_yes: 0.36 } }], asked_ms: 0.0, times: 1 }; 485 let html = page("is AI a bit stupid?", Some(&said), false, "https://x.test", "https://code.x.test", None, None).into_string(); 486 assert!(html.contains(r#"<meta property="og:title" content="Q: is AI a bit stupid?">"#), "{html}"); 487 assert!(html.contains(r#"<meta property="og:description" content="Jev: No? · Noul · p(yes) = 0.36">"#), "{html}"); 488 assert!(html.contains("<title>Q: is AI a bit stupid?</title>")); 489 // The picture's address carries the question and a mark of the answer. 490 let picture = format!("https://x.test/card.png?q=is+AI+a+bit+stupid%3F&a={}", mark("No? · Noul · p(yes) = 0.36")); 491 assert!(html.contains(&format!(r#"<meta property="og:image" content="{picture}">"#)), "{html}"); 492 assert!(html.contains(r#"<meta property="og:image:width" content="600">"#)); 493 assert_ne!(mark("No?"), mark("Yes?")); 494 assert_ne!(mark("No?"), mark("")); 495} 496 497#[test] 498fn a_link_to_a_question_never_asked_unfurls_without_an_answer() { 499 let html = page("is <b> ok \"here\"?", None, false, "https://x.test", "https://code.x.test", None, None).into_string(); 500 assert!(html.contains(r#"<meta property="og:title" content="Q: is <b> ok "here"?">"#), "{html}"); 501 assert!(html.contains(r#"<meta property="og:description" content="Let me Jev that for you.">"#)); 502} 503 504#[test] 505fn the_home_page_offers_the_clone_from_the_code_host() { 506 use crate::host::{GitRedirect, Host}; 507 // As the Worker builds it, in both modes: the code host only once the 508 // redirect is on; until then the page never names an address that has 509 // not been checked. 510 let on = Host::Home.code_origin("https://lmjtfy.fun", GitRedirect::On); 511 let html = page("", None, false, "https://lmjtfy.fun", &on, None, None).into_string(); 512 assert!(html.contains("git clone --recurse-submodules https://code.lmjtfy.fun/lmjtfy.git"), "{html}"); 513 let off = Host::Home.code_origin("https://lmjtfy.fun", GitRedirect::Off); 514 let html = page("", None, false, "https://lmjtfy.fun", &off, None, None).into_string(); 515 assert!(html.contains("git clone --recurse-submodules https://lmjtfy.fun/lmjtfy.git"), "{html}"); 516 assert!(html.contains(r#"<a href="https://lmjtfy.fun/lmjtfy.git">Get the code:</a>"#), "{html}"); 517 assert!(!html.contains("code.lmjtfy.fun"), "{html}"); 518 let html = page("", None, false, "https://lmjtfy.fun", "https://code.lmjtfy.fun", None, None).into_string(); 519 assert!(html.contains("git clone --recurse-submodules https://code.lmjtfy.fun/lmjtfy.git"), "{html}"); 520 assert!(html.contains(r#"<a href="https://code.lmjtfy.fun/lmjtfy.git">Get the code:</a>"#), "{html}"); 521 assert!(!html.contains("https://lmjtfy.fun/lmjtfy.git"), "{html}"); 522} 523 524#[test] 525fn the_home_page_unfurls_as_what_the_site_is() { 526 let html = page("", None, false, "https://x.test", "https://code.x.test", None, None).into_string(); 527 assert!(html.contains(r#"<meta property="og:title" content="Let Me Jev That For You">"#)); 528 assert!(html.contains("og:description\" content=\"Ask Jev anything.")); 529 assert!(html.contains(r##"<meta name="theme-color" content="#f386a1">"##)); 530 assert!(html.contains(r#"<meta property="og:image" content="https://x.test/card.png">"#)); 531} 532 533#[test] 534fn a_how_much_question_answered_directly_shows_the_fixed_scale() { 535 let answer = ScoreAnswer { 536 score: 2.2, 537 confidence: 0.7, 538 probabilities: vec![0.0, 0.1, 0.6, 0.3, 0.0], 539 legend: ask::DEGREES.iter().map(|level| (*level).to_owned()).collect(), 540 }; 541 let mut shown = View::new("how spicy is a jalapeño".into()); 542 shown.facts = Some(facts(vec![(Fact::Answerable, Judged::Noul(0.95)), (Fact::Degree, Judged::Score(answer))])); 543 read_as(&mut shown, &[Kind::Score]); 544 shown.ending = Some(Ending::Answered); 545 let html = transcript(&shown).into_string(); 546 assert!(html.contains(r#"<p class="answer pick">Moderately.<span class="of"> mean 2.2 / 4</span></p>"#), "{html}"); 547 assert_eq!(headlines(&shown), ["Moderately."]); 548 assert!(!html.contains("neurons")); 549} 550 551#[test] 552fn the_tally_says_what_jev_settled_alone_and_what_was_never_sent() { 553 let stats = Stats { questions: 3, asks: 1200, no_llm: 2, sent: 9, kept: 1, clones: 2, pulls: 0 }; 554 let html = tally(Some(&stats)).into_string(); 555 assert!(html.contains("<b>3 questions</b> answered, asked 1,200 times."), "{html}"); 556 assert!(html.contains("<b>67%</b> of them Jev answered alone")); 557 assert!(html.contains("<b>9 requests</b> sent")); 558 assert_eq!(tally(Some(&Stats::default())).into_string(), r#"<div class="" id="tally"></div>"#); 559 assert_eq!(tally(None).into_string(), r#"<div class="" id="tally"></div>"#); 560} 561 562#[test] 563fn asking_too_fast_says_so_and_offers_no_link() { 564 let mut shown = View::new("is it?".into()); 565 shown.ending = Some(Ending::Slow); 566 let html = transcript(&shown).into_string(); 567 assert!(html.contains("Slow down.")); 568 assert!(!html.contains("Send it to them:")); 569} 570 571#[test] 572fn the_playground_makes_every_fact_and_test_a_link() { 573 let network = Network::lmjtfy(); 574 let html = playground(&network, &Known::default(), &[], "Ask Jev.", |_| "/rules?x=y".to_owned()).into_string(); 575 assert_eq!(html.matches(r#"<a href="/rules?x=y""#).count(), network.alphas.len() + Fact::ALL.len()); 576 assert!(html.contains("Ask Jev.")); 577 assert!(html.contains("<title>The rules · LMJTFY</title>")); 578} 579 580#[test] 581fn a_question_jev_is_split_on_is_answered_both_ways_and_says_how_each_was_read() { 582 let degree = ScoreAnswer { 583 score: 3.1, 584 confidence: 0.7, 585 probabilities: vec![0.0, 0.0, 0.2, 0.6, 0.2], 586 legend: ask::DEGREES.iter().map(|level| (*level).to_owned()).collect(), 587 }; 588 let mut shown = View::new("how likely is rain in November".into()); 589 shown.facts = Some(facts(vec![(Fact::Yes, Judged::Noul(0.8)), (Fact::Degree, Judged::Score(degree))])); 590 read_as(&mut shown, &[Kind::Noul, Kind::Score]); 591 shown.ending = Some(Ending::Answered); 592 let html = transcript(&shown).into_string(); 593 assert!(html.contains(r#"<span class="read-as"> · read as a yes or no</span>"#), "{html}"); 594 assert!(html.contains(r#"<span class="read-as"> · read as a how much</span>"#)); 595 assert_eq!(headlines(&shown), ["Yes.", "Very."]); 596 597 // Read one way, there is nothing to say about how. 598 read_as(&mut shown, &[Kind::Noul]); 599 assert!(!transcript(&shown).into_string().contains("read-as")); 600} 601 602#[test] 603fn the_diagram_shows_jevs_probability_for_each_reading() { 604 let kind = ChoiceAnswer { 605 choice: "yes_or_no".into(), 606 confidence: 0.4, 607 probabilities: vec![("yes_or_no".into(), 0.45), ("pick_one".into(), 0.15), ("how_much".into(), 0.4)], 608 }; 609 let mut shown = View::new("how likely is rain".into()); 610 shown.facts = Some(facts(vec![(Fact::Reads(Kind::Noul), Judged::Choice(kind))])); 611 read_as(&mut shown, &[Kind::Noul, Kind::Score]); 612 let html = transcript(&shown).into_string(); 613 for p in [">0.45</text>", ">0.15</text>", ">0.40</text>"] { 614 assert!(html.contains(p), "{p}: {html}"); 615 } 616 assert_eq!(shown.reading(), Some(Kind::Noul)); 617} 618 619#[test] 620fn a_how_likely_question_is_answered_with_the_probability_and_says_it_is_a_noul() { 621 let mut shown = View::new("what are the chances of rain?".into()); 622 shown.facts = Some(facts(vec![(Fact::Answerable, Judged::Noul(0.9)), (Fact::Chance, Judged::Noul(0.28))])); 623 read_as(&mut shown, &[Kind::Chance]); 624 shown.ending = Some(Ending::Answered); 625 let html = transcript(&shown).into_string(); 626 assert!(html.contains(r#"<p class="answer">28%.</p>"#), "{html}"); 627 let note = "Noul · p = 0.28 · Jev thinks that's unlikely"; 628 assert!(html.contains(note) || html.contains(¬e.replace('\'', "'")), "{html}"); 629 // Not a yes or a no, and nobody rewrote the question. 630 assert!(!html.contains(">No.<") && !html.contains("neurons")); 631 assert_eq!(shown.said(), [Answer { headline: "28%.".into(), detail: Detail::Chance { p: 0.28 } }]); 632} 633 634#[test] 635fn what_is_kept_of_an_answer_has_its_numbers() { 636 let pick = ChoiceAnswer { choice: "Vim".into(), confidence: 0.9, probabilities: vec![("Emacs".into(), 0.2), ("Vim".into(), 0.8)] }; 637 let draft = Draft::Choice { instructions: "Which?".into(), options: Vec::new() }; 638 let shown = view(vec![tool(Ok(draft), Some(jev("choice", answered(Judged::Choice(pick)))))], Some(Ending::Answered)); 639 let kept = shown.said(); 640 // Most likely first, whatever order they were asked in. 641 let options = vec![("Vim".to_owned(), 0.8), ("Emacs".to_owned(), 0.2)]; 642 assert_eq!(kept, [Answer { headline: "Vim.".into(), detail: Detail::Choice { confidence: 0.9, options } }]); 643 assert_eq!(kept[0].line(), "Vim. · Choice · confidence 0.90 · Vim 80%, Emacs 20%"); 644} 645 646 647#[test] 648fn the_comment_box_has_a_real_placeholder_a_label_and_a_cap() { 649 let html = comment_box(false).into_string(); 650 assert!(html.contains(r#"placeholder="Let us know what we can improve""#), "{html}"); 651 assert!(html.contains(r#"<label class="label-s" for="comment-text">Comments?</label>"#), "{html}"); 652 assert!(html.contains(r#"maxlength="1000""#), "{html}"); 653 assert!(html.contains(r#"data-bind:comment"#) && html.contains("@post('/comment')") && html.contains(">Save<"), "{html}"); 654 // Closed it is out of the tab order, and it opens by the page's own signal. 655 assert!(html.contains(r#"data-class:open="$open""#) && html.contains(r#"data-attr:inert="!$open""#), "{html}"); 656 // The textarea starts empty: what is typed lives in the signal, so no patch holds it. 657 assert!(html.contains("></textarea>"), "{html}"); 658} 659 660#[test] 661fn the_comment_box_for_a_declined_question_asks_why_jev_could_have_answered() { 662 let html = comment_box(true).into_string(); 663 assert!(html.contains(r#"placeholder="Why could Jev have answered? Or how can we improve?""#), "{html}"); 664 // Everything else about it is the same box. 665 assert!(html.contains(r#"maxlength="1000""#) && html.contains("></textarea>") && html.contains("@post('/comment')"), "{html}"); 666 assert!(!comment_box(false).into_string().contains("Why could Jev")); 667} 668 669#[test] 670fn the_thumbs_mean_right_to_pass_and_could_have_answered_under_a_declined_question() { 671 let answered = rating(Some(&archive::Rating { up: 2, down: 1, mine: None, declined: false }), true).into_string(); 672 assert!(answered.contains("Was Jev right?") && answered.contains("Jev was wrong"), "{answered}"); 673 let declined = rating(Some(&archive::Rating { up: 2, down: 1, mine: Some(archive::Vote::Down), declined: true }), true).into_string(); 674 assert!(declined.contains("Was Jev right to pass?"), "{declined}"); 675 assert!(declined.contains(r#"title="Jev was right to pass""#) && declined.contains(r#"title="Jev could have answered""#), "{declined}"); 676 assert!(declined.contains("$vote = 'up'") && declined.contains("$vote = 'down'") && declined.contains("@post('/rate')"), "{declined}"); 677} 678 679#[test] 680fn the_rating_and_the_comment_box_are_under_an_answer_and_a_declined_question() { 681 for ending in [Ending::NotAQuestion { p_yes: 0.1 }, Ending::NoQuestion] { 682 let html = transcript(&view(Vec::new(), Some(ending))).into_string(); 683 assert!(html.contains(r#"id="rating""#) && html.contains(r#"id="comment""#), "{html}"); 684 assert!(html.contains("Why could Jev have answered?"), "{html}"); 685 } 686 let answered = transcript(&view(Vec::new(), Some(Ending::Answered))).into_string(); 687 assert!(answered.contains(r#"id="rating""#) && !answered.contains("Why could Jev"), "{answered}"); 688 for ending in [Ending::Failed { error: "x".into() }, Ending::Offline, Ending::Slow, Ending::Empty] { 689 assert!(!transcript(&view(Vec::new(), Some(ending))).into_string().contains(r#"id="rating""#)); 690 } 691} 692 693#[test] 694fn the_comment_box_is_under_an_answer_and_only_there() { 695 let mut answered = View::new("is it?".into()); 696 answered.ending = Some(Ending::Answered); 697 assert!(transcript(&answered).into_string().contains(r#"id="comment""#)); 698 let mut offline = View::new("is it?".into()); 699 offline.ending = Some(Ending::Offline); 700 assert!(!transcript(&offline).into_string().contains(r#"id="comment""#)); 701} 702 703#[test] 704fn why_a_comment_was_not_saved_is_said_in_fixed_words() { 705 use archive::Unsaid; 706 assert!(!comment_error(None).into_string().contains("Write")); 707 for (unsaid, words) in [ 708 (Unsaid::Empty, "Write something first."), 709 (Unsaid::TooLong, "over 1000 characters"), 710 (Unsaid::NoVote, "Vote first"), 711 (Unsaid::Limit, "Slow down"), 712 (Unsaid::Failed, "Your text is still here"), 713 ] { 714 let html = comment_error(Some(unsaid)).into_string(); 715 assert!(html.contains(words) && html.contains(r#"id="comment-error""#), "{html}"); 716 } 717} 718 719#[test] 720fn a_question_jev_cannot_take_suggests_an_llm_and_an_answer_does_not() { 721 let links = |ending| transcript(&view(Vec::new(), Some(ending))).into_string().matches("https://lmmptfy.com/#v3/").count(); 722 assert_eq!(links(Ending::NotAQuestion { p_yes: 0.1 }), 3); 723 assert_eq!(links(Ending::NoQuestion), 3); 724 assert_eq!(links(Ending::Answered), 0); 725 // An outage is not a reason to send people away. 726 assert_eq!(links(Ending::Failed { error: "down".into() }), 0); 727 let html = transcript(&view(Vec::new(), Some(Ending::NotAQuestion { p_yes: 0.1 }))).into_string(); 728 assert!(html.contains("prompt=what%20is%20the%20best%20editor"), "{html}"); 729} 730 731#[test] 732fn an_unanswerable_ending_offers_llms_and_a_card_only_if_one_was_read() { 733 let head = crate::meatproxy::Head { title: "Let Me MeatProxy That for You".into(), image: Some("https://lmmptfy.com/a.png".into()), ..Default::default() }; 734 for ending in [Ending::NotAQuestion { p_yes: 0.1 }, Ending::NoQuestion] { 735 let mut shown = View::new("why?".into()); 736 shown.ending = Some(ending); 737 let bare = transcript(&shown).into_string(); 738 assert!(bare.contains("Please choose an LLM instead:") && bare.contains("class=\"llms\""), "{bare}"); 739 assert!(!bare.contains("Provided by") && !bare.contains("linkcard"), "{bare}"); 740 shown.preview = Some(head.clone()); 741 let carded = transcript(&shown).into_string(); 742 assert!(carded.contains("Provided by:") && carded.contains(r#"src="/meatproxy/card""#) && carded.contains("Let Me MeatProxy That for You"), "{carded}"); 743 } 744 // Other endings never show one, even if a card were set. 745 let mut failed = View::new("why?".into()); 746 failed.ending = Some(Ending::Failed { error: "x".into() }); 747 failed.preview = Some(head); 748 assert!(!transcript(&failed).into_string().contains("linkcard")); 749} 750 751#[test] 752fn a_declined_questions_link_unfurls_without_jev_saying_anything() { 753 let html = page("asdf qwerty", None, true, "https://x.test", "https://code.x.test", None, None).into_string(); 754 assert!(html.contains(r#"<meta property="og:description" content="Please choose an LLM instead: Claude, ChatGPT or Gemini.">"#), "{html}"); 755 assert!(html.contains(r#"<meta property="og:title" content="Q: asdf qwerty">"#), "{html}"); 756 assert!(!html.contains("Jev:") && !html.contains("No."), "{html}"); 757 let declined_picture = format!("https://x.test/card.png?q=asdf+qwerty&a={}", mark("declined")); 758 assert!(html.contains(&declined_picture), "{html}"); 759 // Not declined, or answered since: as before, and another picture address. 760 let asked_before = page("asdf qwerty", None, false, "https://x.test", "https://code.x.test", None, None).into_string(); 761 assert!(asked_before.contains("Let me Jev that for you.") && !asked_before.contains(&declined_picture), "{asked_before}"); 762 let answered = Entry { input: "asdf qwerty".into(), answers: vec![Answer { headline: "Yes.".into(), detail: Detail::Noul { p_yes: 0.9 } }], asked_ms: 0.0, times: 1 }; 763 let html = page("asdf qwerty", Some(&answered), true, "https://x.test", "https://code.x.test", None, None).into_string(); 764 assert!(html.contains("Jev: Yes.") && !html.contains("Please choose"), "{html}"); 765}