Unit test `compute_constants()` (#184)
1//! Helper trait to calculate dimensions during layout resolution
2
3use crate::prelude::{Dimension, Rect, Size};
4
5/// Trait to encapsulate behaviour where we need to resolve from a
6/// potentially context-dependent size or dimension into
7/// a context-independent size or dimension.
8///
9/// Will return a default value if it unable to resolve.
10pub(crate) trait ResolveOrDefault<TContext, TOutput> {
11 /// Resolve a dimension that might be dependent on a context, with a default fallback value
12 fn resolve_or_default(self, context: TContext) -> TOutput;
13}
14
15/// Trait to encapsulate behaviour where we need to resolve from a
16/// potentially context-dependent size or dimension into
17/// a context-independent size or dimension.
18///
19/// Will return a `None` if it unable to resolve.
20pub(crate) trait MaybeResolve<T> {
21 /// Resolve a dimension that might be dependent on a context, with `None` as fallback value
22 fn maybe_resolve(self, context: T) -> T;
23}
24
25impl MaybeResolve<Option<f32>> for Dimension {
26 /// Converts the given [`Dimension`] into a concrete value of points
27 ///
28 /// Can return `None`
29 fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
30 match self {
31 Dimension::Points(points) => Some(points),
32 // parent_dim * percent
33 Dimension::Percent(percent) => context.map(|dim| dim * percent),
34 _ => None,
35 }
36 }
37}
38
39impl MaybeResolve<Size<Option<f32>>> for Size<Dimension> {
40 /// Converts any `parent`-relative values for size into an absolute size
41 fn maybe_resolve(self, context: Size<Option<f32>>) -> Size<Option<f32>> {
42 Size { width: self.width.maybe_resolve(context.width), height: self.height.maybe_resolve(context.height) }
43 }
44}
45
46impl ResolveOrDefault<Option<f32>, f32> for Dimension {
47 /// Will return a default value of result is evaluated to `None`
48 fn resolve_or_default(self, context: Option<f32>) -> f32 {
49 self.maybe_resolve(context).unwrap_or(0.0)
50 }
51}
52
53impl ResolveOrDefault<Size<Option<f32>>, Rect<f32>> for Rect<Dimension> {
54 fn resolve_or_default(self, context: Size<Option<f32>>) -> Rect<f32> {
55 Rect {
56 start: self.start.resolve_or_default(context.width),
57 end: self.end.resolve_or_default(context.width),
58 top: self.top.resolve_or_default(context.height),
59 bottom: self.bottom.resolve_or_default(context.height),
60 }
61 }
62}
63
64impl ResolveOrDefault<Option<f32>, Rect<f32>> for Rect<Dimension> {
65 fn resolve_or_default(self, context: Option<f32>) -> Rect<f32> {
66 Rect {
67 start: self.start.resolve_or_default(context),
68 end: self.end.resolve_or_default(context),
69 top: self.top.resolve_or_default(context),
70 bottom: self.bottom.resolve_or_default(context),
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 mod maybe_resolve_dimension {
78
79 use crate::resolve::MaybeResolve;
80 use crate::style::Dimension;
81 use rstest::rstest;
82
83 /// `Dimension::Undefined` should always return `None`
84 ///
85 /// The parent / context should not affect the outcome.
86 #[rstest]
87 #[case(Dimension::Undefined, None, None)]
88 #[case(Dimension::Undefined, Some(5.0), None)]
89 #[case(Dimension::Undefined, Some(-5.0), None)]
90 #[case(Dimension::Undefined, Some(0.), None)]
91 fn resolve_undefined(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
92 assert_eq!(input.maybe_resolve(context), expected);
93 }
94
95 /// `Dimension::Auto` should always return `None`
96 ///
97 /// The parent / context should not affect the outcome.
98 #[rstest]
99 #[case(Dimension::Auto, None, None)]
100 #[case(Dimension::Auto, Some(5.0), None)]
101 #[case(Dimension::Auto, Some(-5.0), None)]
102 #[case(Dimension::Auto, Some(0.), None)]
103 fn resolve_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
104 assert_eq!(input.maybe_resolve(context), expected);
105 }
106
107 /// `Dimension::Points` should always return `Some(f32)`
108 /// where the f32 value is the inner value of the points.
109 ///
110 /// The parent / context should not affect the outcome.
111 #[rstest]
112 #[case(Dimension::Points(1.0), None, Some(1.0))]
113 #[case(Dimension::Points(1.0), Some(5.0), Some(1.0))]
114 #[case(Dimension::Points(1.0), Some(-5.0), Some(1.0))]
115 #[case(Dimension::Points(1.0), Some(0.), Some(1.0))]
116 fn resolve_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
117 assert_eq!(input.maybe_resolve(context), expected);
118 }
119
120 /// `Dimension::Percent` should return `None` if context is `None`.
121 /// Otherwise it should return `Some(f32)`
122 /// where the f32 value is the inner value of the percent * context value.
123 ///
124 /// The parent / context __should__ affect the outcome.
125 #[rstest]
126 #[case(Dimension::Percent(1.0), None, None)]
127 #[case(Dimension::Percent(1.0), Some(5.0), Some(5.0))]
128 #[case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0))]
129 #[case(Dimension::Percent(1.0), Some(50.0), Some(50.0))]
130 fn resolve_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: Option<f32>) {
131 assert_eq!(input.maybe_resolve(context), expected);
132 }
133 }
134
135 mod maybe_resolve_size_dimension {
136 use crate::{prelude::Size, resolve::MaybeResolve, style::Dimension};
137 use rstest::rstest;
138
139 /// Size<Dimension::Undefined> should always return Size<None>
140 ///
141 /// The parent / context should not affect the outcome.
142 #[rstest]
143 #[case(Size::UNDEFINED, Size::NONE, Size::NONE)]
144 #[case(Size::UNDEFINED, Size::new(5.0, 5.0), Size::NONE)]
145 #[case(Size::UNDEFINED, Size::new(-5.0, -5.0), Size::NONE)]
146 #[case(Size::UNDEFINED, Size::new(0.0, 0.0), Size::NONE)]
147 fn maybe_resolve_undefined(
148 #[case] input: Size<Dimension>,
149 #[case] context: Size<Option<f32>>,
150 #[case] expected: Size<Option<f32>>,
151 ) {
152 assert_eq!(input.maybe_resolve(context), expected);
153 }
154
155 /// Size<Dimension::Auto> should always return Size<None>
156 ///
157 /// The parent / context should not affect the outcome.
158 #[rstest]
159 #[case(Size::AUTO, Size::NONE, Size::NONE)]
160 #[case(Size::AUTO, Size::new(5.0, 5.0), Size::NONE)]
161 #[case(Size::AUTO, Size::new(-5.0, -5.0), Size::NONE)]
162 #[case(Size::AUTO, Size::new(0.0, 0.0), Size::NONE)]
163 fn maybe_resolve_auto(
164 #[case] input: Size<Dimension>,
165 #[case] context: Size<Option<f32>>,
166 #[case] expected: Size<Option<f32>>,
167 ) {
168 assert_eq!(input.maybe_resolve(context), expected);
169 }
170
171 /// Size<Dimension::Points> should always return a Size<Some(f32)>
172 /// where the f32 values are the inner value of the points.
173 ///
174 /// The parent / context should not affect the outcome.
175 #[rstest]
176 #[case(Size::from_points(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0))]
177 #[case(Size::from_points(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0))]
178 #[case(Size::from_points(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0))]
179 #[case(Size::from_points(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0))]
180 fn maybe_resolve_points(
181 #[case] input: Size<Dimension>,
182 #[case] context: Size<Option<f32>>,
183 #[case] expected: Size<Option<f32>>,
184 ) {
185 assert_eq!(input.maybe_resolve(context), expected);
186 }
187
188 /// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
189 /// Otherwise it should return `Size<Some(f32)>`
190 /// where the f32 value is the inner value of the percent * context value.
191 ///
192 /// The context __should__ affect the outcome.
193 #[rstest]
194 #[case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE)]
195 #[case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0))]
196 #[case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0))]
197 #[case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0))]
198 fn maybe_resolve_percent(
199 #[case] input: Size<Dimension>,
200 #[case] context: Size<Option<f32>>,
201 #[case] expected: Size<Option<f32>>,
202 ) {
203 assert_eq!(input.maybe_resolve(context), expected);
204 }
205 }
206
207 mod resolve_or_default_dimension_to_option_f32 {
208 use crate::resolve::ResolveOrDefault;
209 use crate::style::Dimension;
210 use rstest::rstest;
211
212 #[rstest]
213 #[case(Dimension::Undefined, None, 0.0)]
214 #[case(Dimension::Undefined, Some(5.0), 0.0)]
215 #[case(Dimension::Undefined, Some(-5.0), 0.0)]
216 #[case(Dimension::Undefined, Some(0.0), 0.0)]
217 fn resolve_or_default_undefined(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
218 assert_eq!(input.resolve_or_default(context), expected);
219 }
220 #[rstest]
221 #[case(Dimension::Auto, None, 0.0)]
222 #[case(Dimension::Auto, Some(5.0), 0.0)]
223 #[case(Dimension::Auto, Some(-5.0), 0.0)]
224 #[case(Dimension::Auto, Some(0.0), 0.0)]
225 fn resolve_or_default_auto(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
226 assert_eq!(input.resolve_or_default(context), expected);
227 }
228 #[rstest]
229 #[case(Dimension::Points(5.0), None, 5.0)]
230 #[case(Dimension::Points(5.0), Some(5.0), 5.0)]
231 #[case(Dimension::Points(5.0), Some(-5.0), 5.0)]
232 #[case(Dimension::Points(5.0), Some(0.0), 5.0)]
233 fn resolve_or_default_points(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
234 assert_eq!(input.resolve_or_default(context), expected);
235 }
236 #[rstest]
237 #[case(Dimension::Percent(5.0), None, 0.0)]
238 #[case(Dimension::Percent(5.0), Some(5.0), 25.0)]
239 #[case(Dimension::Percent(5.0), Some(-5.0), -25.0)]
240 #[case(Dimension::Percent(5.0), Some(0.0), 0.0)]
241 fn resolve_or_default_percent(#[case] input: Dimension, #[case] context: Option<f32>, #[case] expected: f32) {
242 assert_eq!(input.resolve_or_default(context), expected);
243 }
244 }
245
246 mod resolve_or_default_rect_dimension_to_rect {
247 use crate::geometry::{Rect, Size};
248 use crate::resolve::ResolveOrDefault;
249 use crate::style::Dimension;
250 use rstest::rstest;
251
252 #[rstest]
253 #[case(Rect::UNDEFINED, Size::NONE, Rect::ZERO)]
254 #[case(Rect::UNDEFINED, Size::new(5.0, 5.0), Rect::ZERO)]
255 #[case(Rect::UNDEFINED, Size::new(-5.0, -5.0), Rect::ZERO)]
256 #[case(Rect::UNDEFINED, Size::new(0.0, 0.0), Rect::ZERO)]
257 fn resolve_or_default_undefined(
258 #[case] input: Rect<Dimension>,
259 #[case] context: Size<Option<f32>>,
260 #[case] expected: Rect<f32>,
261 ) {
262 assert_eq!(input.resolve_or_default(context), expected);
263 }
264
265 #[rstest]
266 #[case(Rect::AUTO, Size::NONE, Rect::ZERO)]
267 #[case(Rect::AUTO, Size::new(5.0, 5.0), Rect::ZERO)]
268 #[case(Rect::AUTO, Size::new(-5.0, -5.0), Rect::ZERO)]
269 #[case(Rect::AUTO, Size::new(0.0, 0.0), Rect::ZERO)]
270 fn resolve_or_default_auto(
271 #[case] input: Rect<Dimension>,
272 #[case] context: Size<Option<f32>>,
273 #[case] expected: Rect<f32>,
274 ) {
275 assert_eq!(input.resolve_or_default(context), expected);
276 }
277
278 #[rstest]
279 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::new(5.0, 5.0, 5.0, 5.0))]
280 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
281 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::new(-5.0, -5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
282 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
283 fn resolve_or_default_points(
284 #[case] input: Rect<Dimension>,
285 #[case] context: Size<Option<f32>>,
286 #[case] expected: Rect<f32>,
287 ) {
288 assert_eq!(input.resolve_or_default(context), expected);
289 }
290
291 #[rstest]
292 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::ZERO)]
293 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(25.0, 25.0, 25.0, 25.0))]
294 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(-5.0, -5.0), Rect::new(-25.0, -25.0, -25.0, -25.0))]
295 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::ZERO)]
296 fn resolve_or_default_percent(
297 #[case] input: Rect<Dimension>,
298 #[case] context: Size<Option<f32>>,
299 #[case] expected: Rect<f32>,
300 ) {
301 assert_eq!(input.resolve_or_default(context), expected);
302 }
303 }
304
305 mod resolve_or_default_rect_dimension_to_rect_f32_via_option {
306 use crate::geometry::Rect;
307 use crate::resolve::ResolveOrDefault;
308 use crate::style::Dimension;
309 use rstest::rstest;
310
311 #[rstest]
312 #[case(Rect::UNDEFINED, None, Rect::ZERO)]
313 #[case(Rect::UNDEFINED, Some(5.0), Rect::ZERO)]
314 #[case(Rect::UNDEFINED, Some(-5.0), Rect::ZERO)]
315 #[case(Rect::UNDEFINED, Some(0.0), Rect::ZERO)]
316 fn resolve_or_default_undefined(
317 #[case] input: Rect<Dimension>,
318 #[case] context: Option<f32>,
319 #[case] expected: Rect<f32>,
320 ) {
321 assert_eq!(input.resolve_or_default(context), expected);
322 }
323
324 #[rstest]
325 #[case(Rect::AUTO, None, Rect::ZERO)]
326 #[case(Rect::AUTO, Some(5.0), Rect::ZERO)]
327 #[case(Rect::AUTO, Some(-5.0), Rect::ZERO)]
328 #[case(Rect::AUTO, Some(0.0), Rect::ZERO)]
329 fn resolve_or_default_auto(
330 #[case] input: Rect<Dimension>,
331 #[case] context: Option<f32>,
332 #[case] expected: Rect<f32>,
333 ) {
334 assert_eq!(input.resolve_or_default(context), expected);
335 }
336
337 #[rstest]
338 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0))]
339 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
340 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
341 #[case(Rect::from_points(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::new(5.0, 5.0, 5.0, 5.0))]
342 fn resolve_or_default_points(
343 #[case] input: Rect<Dimension>,
344 #[case] context: Option<f32>,
345 #[case] expected: Rect<f32>,
346 ) {
347 assert_eq!(input.resolve_or_default(context), expected);
348 }
349
350 #[rstest]
351 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::ZERO)]
352 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(25.0, 25.0, 25.0, 25.0))]
353 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(-25.0, -25.0, -25.0, -25.0))]
354 #[case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::ZERO)]
355 fn resolve_or_default_percent(
356 #[case] input: Rect<Dimension>,
357 #[case] context: Option<f32>,
358 #[case] expected: Rect<f32>,
359 ) {
360 assert_eq!(input.resolve_or_default(context), expected);
361 }
362 }
363}