1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use super::super::*;
use crate::types::{ErrorKind, Value};
#[test]
fn max_no_args_returns_na() {
assert_eq!(max_fn(&[]), Value::Error(ErrorKind::NA));
}
#[test]
fn max_text_in_args_returns_value_error() {
assert_eq!(
max_fn(&[Value::Text("a".to_string()), Value::Bool(true), Value::Empty]),
Value::Error(ErrorKind::Value)
);
}
#[test]
fn max_empty_array_is_ref_error() {
assert_eq!(
max_fn(&[Value::Array(vec![])]),
Value::Error(ErrorKind::Ref)
);
}
#[test]
fn max_non_empty_array_without_numbers_returns_zero() {
// `=MAX({"a","b"})` and `=MAX({TRUE,FALSE})` are both 0: neither text nor
// booleans contribute a number in array context, but both are enough to
// lift the array out of the #REF! rule. These two variants are the whole
// of the carve-out — nothing else sets `had_content`.
assert_eq!(
max_fn(&[Value::Array(vec![
Value::Text("a".to_string()),
Value::Text("b".to_string()),
])]),
Value::Number(0.0)
);
assert_eq!(
max_fn(&[Value::Array(vec![Value::Bool(true), Value::Bool(false)])]),
Value::Number(0.0)
);
}
#[test]
fn max_array_of_only_blanks_is_zero() {
// `=MAX(A1:A3)` over empty cells is 0 in Google Sheets — the same answer
// MIN, MAXA and MINA give it. MAX used to be alone at #REF! here.
//
// That answer is captured across seven range shapes, each with a populated
// control, but **none of those rows are in this repo yet**: they land in a
// separate fixtures-only PR (see `stat_helpers::is_blank_only_array` for
// the shapes and the branch). Read from this repo alone, this test pins
// the behaviour, not the Sheets answer.
assert_eq!(
max_fn(&[Value::Array(vec![Value::Empty, Value::Empty, Value::Empty])]),
Value::Number(0.0)
);
// Same through the nested-row shape a vertical range materializes as.
assert_eq!(
max_fn(&[Value::Array(vec![
Value::Array(vec![Value::Empty]),
Value::Array(vec![Value::Empty]),
])]),
Value::Number(0.0)
);
}
#[test]
fn max_blank_beside_something_else_does_not_become_the_blank_only_zero() {
// The blank-only rule is decided over the arguments as a whole, not by a
// per-element flag, so a blank cannot on its own pull an array that holds
// something else into the blank-only 0.
//
// This was pinned with a date element and `#REF!`, on the note that "if
// that ever moves it has to move deliberately, not as fallout from this
// rule". It moved deliberately: dates now participate, so this array
// answers the date. The invariant the pin exists for is unchanged and
// still discriminating — the answer is the date, *not* the blank-only 0.
//
// There is no longer any variant that reaches MAX's numberless `#REF!`
// through a populated array: text and booleans set `array_had_content`,
// dates contribute a number, a sparkline sets its own flag, and a zoned
// instant is intercepted by `zoned_extreme` before the loop runs. The
// empty-array assertion below is what still carries the `#REF!` side.
assert_eq!(
max_fn(&[Value::Array(vec![Value::Empty, Value::Date(43831.0)])]),
Value::Date(43831.0)
);
// An empty array argument stays #REF! even alongside an all-blank one.
assert_eq!(
max_fn(&[Value::Array(vec![Value::Empty]), Value::Array(vec![])]),
Value::Error(ErrorKind::Ref)
);
}
#[test]
fn max_array_of_only_dates_returns_the_latest_date() {
// Was #REF! until dates were captured: a date-only array now answers the
// largest serial, date-typed. Replaces the pin that recorded the old
// #REF! as unprobed.
assert_eq!(
max_fn(&[Value::Array(vec![
Value::Date(43831.0),
Value::Date(44197.0)
])]),
Value::Date(44197.0)
);
// Same for a date arriving through a nested-row range materialization.
assert_eq!(
max_fn(&[Value::Array(vec![Value::Array(vec![Value::Date(43831.0)])])]),
Value::Date(43831.0)
);
}
#[test]
fn max_dates_compare_as_bare_serials_and_type_the_answer() {
// A plain number and a date are compared on the serial with no special
// casing, and the answer is date-typed because a date took part — even
// when the plain number is the one that won (that is the MIN direction;
// pinned in min's tests, mirrored here for the losing-date direction).
assert_eq!(
max_fn(&[Value::Array(vec![Value::Date(43831.0), Value::Number(5.0)])]),
Value::Date(43831.0)
);
// Direct (non-array) arguments take the same rule.
assert_eq!(
max_fn(&[Value::Date(43831.0), Value::Date(44197.0)]),
Value::Date(44197.0)
);
assert_eq!(
max_fn(&[Value::Date(43831.0), Value::Number(5.0)]),
Value::Date(43831.0)
);
// No date in scope: the answer stays a plain number.
assert_eq!(
max_fn(&[Value::Number(5.0), Value::Number(1.0)]),
Value::Number(5.0)
);
}
#[test]
fn max_date_beside_a_blank_no_longer_falls_into_the_ref_rule() {
// A date leaves a number behind, so the "populated but numberless" #REF!
// rule can no longer be reached with a date in scope.
assert_eq!(
max_fn(&[Value::Array(vec![Value::Date(43831.0), Value::Empty])]),
Value::Date(43831.0)
);
// An all-blank array is a separate, captured case and answers 0, not the
// date rule and not #REF! — see `max_array_of_only_blanks_is_zero`. Kept
// here as the contrast: a date in scope types the answer, an array with
// nothing in it but blanks does not.
assert_eq!(
max_fn(&[Value::Array(vec![Value::Empty, Value::Empty])]),
Value::Number(0.0)
);
}
#[test]
fn max_one_non_blank_lifts_the_array_out_of_the_ref_rule() {
assert_eq!(
max_fn(&[Value::Array(vec![
Value::Empty,
Value::Text("z".to_string()),
])]),
Value::Number(0.0)
);
assert_eq!(
max_fn(&[Value::Array(vec![Value::Empty, Value::Number(4.0)])]),
Value::Number(4.0)
);
}
#[test]
fn max_negative_numbers() {
assert_eq!(
max_fn(&[Value::Number(-3.0), Value::Number(-1.0), Value::Number(-5.0)]),
Value::Number(-1.0)
);
}