From 8307d3cb0526c7af66645cf9d7c79f01e77be632 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 22 Oct 2026 08:25:54 +0000 Subject: [PATCH] Treat empty instance_num in TEXTBEFORE/TEXTAFTER as 1 Found while testing crates/functions/src/text.rs:553 Co-Authored-By: Claude Opus 5.5 --- crates/calc/src/tests.rs | 13 +++++++++++++ crates/functions/src/text.rs | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/calc/src/tests.rs b/crates/calc/src/tests.rs index e5e287d38..622ed6cc9 100644 --- a/crates/calc/src/tests.rs +++ b/crates/calc/src/tests.rs @@ -1768,3 +1768,16 @@ fn a_dynamic_array_of_one_value_still_anchors_a_spill() { t.set("E11", "=ROWS(D1#)"); assert_eq!(t.get("E11"), Value::Error(CellError::Ref)); } + +#[test] +fn text_before_after_empty_instance_defaults_to_one() { + let mut t = T::new(); + t.set("A1", "=TEXTBEFORE(\"Little Red Riding Hood\",\"red\",,1)"); + assert_eq!(t.get("A1"), Value::Text("Little ".into())); + t.set("A2", "=TEXTAFTER(\"a-b\",\"-\",,,,\"nf\")"); + assert_eq!(t.get("A2"), Value::Text("b".into())); + t.set("A3", "=TEXTAFTER(\"a-b\",\"x\",,,,\"nf\")"); + assert_eq!(t.get("A3"), Value::Text("nf".into())); + t.set("A4", "=TEXTBEFORE(\"a-b\",\"-\",0)"); + assert_eq!(t.get("A4"), Value::Error(CellError::Value)); +} diff --git a/crates/functions/src/text.rs b/crates/functions/src/text.rs index 71a6ccb4b..baa86693a 100644 --- a/crates/functions/src/text.rs +++ b/crates/functions/src/text.rs @@ -5,8 +5,8 @@ use gridcraft_core::{CellError, Value, number_to_text}; use crate::criteria::wildcard_find; use crate::util::{ - A, MAX_CELLS, MAX_TEXT, R, S, arg, array_val, as_array, boolean, has, num, num_val, opt_bool, opt_int, opt_num, round_half_away, scalar, text, - text_val, + A, MAX_CELLS, MAX_TEXT, R, S, arg, array_val, as_array, boolean, has, int, num, num_val, opt_bool, opt_int, opt_num, round_half_away, scalar, + text, text_val, }; use crate::{Arg, Ctx, FnSpec, VAR}; @@ -550,7 +550,7 @@ fn find_matches(text: &[char], delims: &[Vec], insensitive: bool, from_end fn text_before_after(a: &[Arg], before: bool) -> R { let s = chars(&text(a, 0)?); let delims = delimiters(&arg(a, 1)?.value)?; - let inst = opt_int(a, 2, 1)?; + let inst = if has(a, 2) { int(a, 2)? } else { 1 }; let mode = opt_int(a, 3, 0)?; let match_end = opt_bool(a, 4, false)?; if !(0..=1).contains(&mode) || inst == 0 || inst.unsigned_abs() as usize > s.len().max(1) {