googletest/
description.rs1use std::{
16 borrow::Cow,
17 fmt::{Display, Formatter, Result},
18};
19
20use crate::internal::description_renderer::{List, INDENTATION_SIZE};
21
22#[derive(Debug, Default)]
89pub struct Description {
90 elements: List,
91 initial_indentation: usize,
92 is_conjunction: bool,
93 is_disjunction: bool,
94}
95
96impl Description {
97 pub fn new() -> Self {
99 Default::default()
100 }
101
102 pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
106 self.elements.push_literal(text.into());
107 self
108 }
109
110 pub fn nested(mut self, inner: Description) -> Self {
115 self.elements.push_nested(inner.elements);
116 self
117 }
118
119 pub fn collect(self, inner: impl IntoIterator<Item = Description>) -> Self {
125 inner.into_iter().fold(self, |outer, inner| outer.nested(inner))
126 }
127
128 pub fn indent(self) -> Self {
144 Self { initial_indentation: INDENTATION_SIZE, ..self }
145 }
146
147 pub fn bullet_list(self) -> Self {
168 Self { elements: self.elements.bullet_list(), ..self }
169 }
170
171 pub fn enumerate(self) -> Self {
192 Self { elements: self.elements.enumerate(), ..self }
193 }
194
195 pub fn len(&self) -> usize {
197 self.elements.len()
198 }
199
200 pub fn is_empty(&self) -> bool {
202 self.elements.is_empty()
203 }
204
205 pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
206 self.elements.push_at_end(inner.elements);
207 self
208 }
209
210 pub(crate) fn conjunction_description(self) -> Self {
211 Self { is_conjunction: true, ..self }
212 }
213
214 pub(crate) fn is_conjunction_description(&self) -> bool {
215 self.is_conjunction
216 }
217
218 pub(crate) fn disjunction_description(self) -> Self {
219 Self { is_disjunction: true, ..self }
220 }
221
222 pub(crate) fn is_disjunction_description(&self) -> bool {
223 self.is_disjunction
224 }
225}
226
227impl Display for Description {
228 fn fmt(&self, f: &mut Formatter) -> Result {
229 self.elements.render(f, self.initial_indentation)
230 }
231}
232
233impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for Description {
234 fn from_iter<T>(iter: T) -> Self
235 where
236 T: IntoIterator<Item = ElementT>,
237 {
238 Self { elements: iter.into_iter().map(ElementT::into).collect(), ..Default::default() }
239 }
240}
241
242impl FromIterator<Description> for Description {
243 fn from_iter<T>(iter: T) -> Self
244 where
245 T: IntoIterator<Item = Description>,
246 {
247 Self { elements: iter.into_iter().map(|s| s.elements).collect(), ..Default::default() }
248 }
249}
250
251impl<T: Into<Cow<'static, str>>> From<T> for Description {
252 fn from(value: T) -> Self {
253 let mut elements = List::default();
254 elements.push_literal(value.into());
255 Self { elements, ..Default::default() }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::Description;
262 use crate::prelude::*;
263 use crate::Result;
264 use indoc::indoc;
265
266 #[test]
267 fn renders_single_fragment() -> Result<()> {
268 let description: Description = "A B C".into();
269 verify_that!(description, displays_as(eq("A B C")))
270 }
271
272 #[test]
273 fn renders_two_fragments() -> Result<()> {
274 let description =
275 ["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>();
276 verify_that!(description, displays_as(eq("A B C\nD E F")))
277 }
278
279 #[test]
280 fn nested_description_is_indented() -> Result<()> {
281 let description = Description::new()
282 .text("Header")
283 .nested(["A B C".to_string()].into_iter().collect::<Description>());
284 verify_that!(description, displays_as(eq("Header\n A B C")))
285 }
286
287 #[test]
288 fn nested_description_indents_two_elements() -> Result<()> {
289 let description = Description::new().text("Header").nested(
290 ["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>(),
291 );
292 verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
293 }
294
295 #[test]
296 fn nested_description_indents_one_element_on_two_lines() -> Result<()> {
297 let description = Description::new().text("Header").nested("A B C\nD E F".into());
298 verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
299 }
300
301 #[test]
302 fn single_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
303 let description = Description::new().text("A B C").bullet_list();
304 verify_that!(description, displays_as(eq("* A B C")))
305 }
306
307 #[test]
308 fn single_nested_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
309 let description = Description::new().nested("A B C".into()).bullet_list();
310 verify_that!(description, displays_as(eq("* A B C")))
311 }
312
313 #[test]
314 fn two_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
315 let description = Description::new().text("A B C").text("D E F").bullet_list();
316 verify_that!(description, displays_as(eq("* A B C\n* D E F")))
317 }
318
319 #[test]
320 fn two_nested_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
321 let description =
322 Description::new().nested("A B C".into()).nested("D E F".into()).bullet_list();
323 verify_that!(description, displays_as(eq("* A B C\n* D E F")))
324 }
325
326 #[test]
327 fn single_fragment_with_more_than_one_line_renders_with_one_bullet() -> Result<()> {
328 let description = Description::new().text("A B C\nD E F").bullet_list();
329 verify_that!(description, displays_as(eq("* A B C\n D E F")))
330 }
331
332 #[test]
333 fn single_fragment_renders_with_enumeration_when_enumerate_enabled() -> Result<()> {
334 let description = Description::new().text("A B C").enumerate();
335 verify_that!(description, displays_as(eq("0. A B C")))
336 }
337
338 #[test]
339 fn two_fragments_render_with_enumeration_when_enumerate_enabled() -> Result<()> {
340 let description = Description::new().text("A B C").text("D E F").enumerate();
341 verify_that!(description, displays_as(eq("0. A B C\n1. D E F")))
342 }
343
344 #[test]
345 fn single_fragment_with_two_lines_renders_with_one_enumeration_label() -> Result<()> {
346 let description = Description::new().text("A B C\nD E F").enumerate();
347 verify_that!(description, displays_as(eq("0. A B C\n D E F")))
348 }
349
350 #[test]
351 fn multi_digit_enumeration_renders_with_correct_offset() -> Result<()> {
352 let description = ["A B C\nD E F"; 11]
353 .into_iter()
354 .map(str::to_string)
355 .collect::<Description>()
356 .enumerate();
357 verify_that!(
358 description,
359 displays_as(eq(indoc!(
360 "
361 0. A B C
362 D E F
363 1. A B C
364 D E F
365 2. A B C
366 D E F
367 3. A B C
368 D E F
369 4. A B C
370 D E F
371 5. A B C
372 D E F
373 6. A B C
374 D E F
375 7. A B C
376 D E F
377 8. A B C
378 D E F
379 9. A B C
380 D E F
381 10. A B C
382 D E F"
383 )))
384 )
385 }
386
387 #[test]
388 fn new_is_empty() -> Result<()> {
389 verify_that!(Description::new(), predicate(Description::is_empty))
390 }
391
392 #[test]
393 fn text_is_not_empty() -> Result<()> {
394 verify_that!(Description::new().text("something"), not(predicate(Description::is_empty)))
395 }
396
397 #[test]
398 fn new_zero_length() -> Result<()> {
399 verify_that!(Description::new().len(), eq(0))
400 }
401
402 #[test]
403 fn text_one_length() -> Result<()> {
404 verify_that!(Description::new().text("something").len(), eq(1))
405 }
406}