Files
gridcraft/crates/engine/tests/it/recently_used.rs
T
prxnvt 474472f999 Engine: Recently Used follows the formulas entered; Insert Function category list
formulas.recentlyUsed scanned the session journal for cell.set inputs.
It missed formulas entered by range.* commands, lost them when the
journal dropped its oldest entries (a large file open or a long script
is enough), and started empty in every session. The Function Library's
Recently Used menu and the Insert Function dialog list what it returns.

- Prefs::recent_functions (a new engine module, recent.rs) holds the
  ten functions most recently entered in a formula, newest first.
  Session::execute updates it for every cell.* and range.* command it
  journals, from any source (the interface, the control channel, MCP or
  a script). It reads at most 2,000 of the formulas a command enters,
  starting from the last one; formulas longer than Excel's limit are
  skipped. formulas.recentlyUsed
  returns it. It never reads the journal back, so the journal's caps do
  not affect it.
- The list is kept with the other engine preferences (prefs.json, the
  browser's storage on the web), so it survives a restart as Excel's
  does. It is read leniently: a malformed value loses only this list. A
  fresh install starts from common functions.

The Insert Function dialog is titled "Insert Function", as in Excel (it
said "Formula Builder"), and gains a working category list: Most
Recently Used, All, then each category in Excel's order. A category may
be given by label or by engine id, so a Function Library menu or the
control channel (ui.dialog.set) opens it on one, and a new search
switches to All. The dialog keeps its translated labels and its search
in the interface language. Each row shows the function's name and its
category in two columns; the category column starts after the widest
name shown, so a long name in any font or formula language stays clear
of it. A function counts as recently used once its formula is entered,
not when OK is pressed. The unused "Formula Builder" translations are
removed.

Tests: engine unit tests for reading functions from formulas (strings,
unknown names, over-long and malformed formulas), the formulas a
command enters (newest first, within the budget), the ten-name list
and the lenient read of prefs.json; tests/it/recently_used.rs for
cell.set and range.setValues from the session, a failed command, a
cleared journal and a save and reload of the preferences. UI tests for
the menu's order and the dialog's title, categories, engine-id
category, search and OK, and for the category column with a wide font.
2026-10-31 04:00:56 -04:00

59 lines
2.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Formulas › Recently Used: the functions most recently entered in a formula, from any command
//! that enters one, kept in the preferences rather than read back from the journal.
use gridcraft_engine::Session;
use serde_json::{Value as Json, json};
fn recent(s: &mut Session) -> Vec<String> {
let list = s.execute("formulas.recentlyUsed", json!({})).unwrap();
list.as_array().unwrap().iter().filter_map(Json::as_str).map(String::from).collect()
}
#[test]
fn formulas_entered_by_any_command_lead_the_list() {
let mut s = Session::new();
s.new_workbook();
s.execute("cell.set", json!({"cell": "A1", "input": "=NPV(0.1,1,2)"})).unwrap();
s.execute("cell.set", json!({"cell": "A2", "input": "=IRR({-10,6,6})+ROUND(1,0)"})).unwrap();
s.execute("range.setValues", json!({"range": "B1", "values": [["=FV(0.1,2,-1)", 4, "text"]]})).unwrap();
s.execute("cell.set", json!({"cell": "A3", "input": "PMT is just text"})).unwrap();
assert_eq!(&recent(&mut s)[..4], ["FV", "IRR", "ROUND", "NPV"]);
// A command that fails enters nothing.
assert!(s.execute("cell.set", json!({"cell": "not a cell", "input": "=DB(1,2,3,4)"})).is_err());
assert_ne!(recent(&mut s)[0], "DB");
}
#[test]
fn formulas_typed_with_a_plus_or_minus_count() {
let mut s = Session::new();
s.new_workbook();
s.execute("cell.set", json!({"cell": "A1", "input": "+ROUND(1.5,0)"})).unwrap();
s.execute("cell.set", json!({"cell": "A2", "input": "-SLN(100,10,5)"})).unwrap();
s.execute("cell.set", json!({"cell": "A3", "input": "-5"})).unwrap();
assert_eq!(&recent(&mut s)[..2], ["SLN", "ROUND"]);
}
#[test]
fn the_list_does_not_depend_on_the_journal() {
// The journal drops its oldest entries past its caps (and is public); the list stays.
let mut s = Session::new();
s.new_workbook();
s.execute("cell.set", json!({"cell": "A1", "input": "=NPV(0.1,1,2)"})).unwrap();
s.journal.clear();
assert_eq!(recent(&mut s)[0], "NPV");
s.execute("cell.set", json!({"cell": "A2", "input": "=SLN(100,10,5)"})).unwrap();
assert_eq!(&recent(&mut s)[..2], ["SLN", "NPV"]);
}
#[test]
fn the_list_is_kept_with_the_preferences() {
let mut s = Session::new();
s.new_workbook();
s.execute("cell.set", json!({"cell": "A1", "input": "=SYD(100,10,5,1)"})).unwrap();
let saved = serde_json::to_value(&s.prefs).unwrap();
let mut next = Session::new();
next.new_workbook();
next.prefs = serde_json::from_value(saved).unwrap();
assert_eq!(recent(&mut next)[0], "SYD");
}