embedded_graphics/mock_display/
mod.rs

1//! Mock display for use in tests.
2//!
3//! [`MockDisplay`] can be used to replace a real display in tests. The internal
4//! framebuffer wraps the color values in `Option` to be able to test which
5//! pixels were modified by drawing operations.
6//!
7//! The [`from_pattern`] method provides a convenient way of creating expected
8//! test results. The same patterns are used by the implementation of `Debug`
9//! and will be shown in failing tests.
10//!
11//! The display is internally capped at 64x64px.
12//!
13//! # Assertions
14//!
15//! [`MockDisplay`] provides the [`assert_eq`] and [`assert_pattern`] methods to check if the
16//! display is in the correct state after some drawing operations were executed. It's recommended
17//! to use these methods instead of the standard `assert_eq!` macro, because they provide an
18//! optional improved debug output for failing tests. If the `EG_FANCY_PANIC` environment variable
19//! is set to `1` at compile time a graphic representation of the display content and a diff of the
20//! display and the expected output will be shown:
21//!
22//! ```bash
23//! EG_FANCY_PANIC=1 cargo test
24//! ```
25//!
26//! Enabling the advanced test output requires a terminal that supports 24 BPP colors and a font
27//! that includes the upper half block character `'\u{2580}'`.
28//!
29//! The color code used to show the difference between the display and the expected output is shown
30//! in the documentation of the [`diff`] method.
31//!
32//! # Additional out of bounds and overdraw checks
33//!
34//! [`MockDisplay`] implements additional checks during drawing operations that will cause a panic if
35//! any pixel is drawn outside the framebuffer or if a pixel is drawn more than once. These
36//! stricter checks were added to help with testing and shouldn't be implemented by normal
37//! [`DrawTarget`]s.
38//!
39//! If a test relies on out of bounds drawing or overdrawing the additional checks can explicitly
40//! be disabled  by using [`set_allow_out_of_bounds_drawing`] and [`set_allow_overdraw`].
41//!
42//! # Characters used in `BinaryColor` patterns
43//!
44//! The following mappings are available for [`BinaryColor`]:
45//!
46//! | Character | Color                    | Description                             |
47//! |-----------|--------------------------|-----------------------------------------|
48//! | `' '`     | `None`                   | No drawing operation changed the pixel  |
49//! | `'.'`     | `Some(BinaryColor::Off)` | Pixel was changed to `BinaryColor::Off` |
50//! | `'#'`     | `Some(BinaryColor::On)`  | Pixel was changed to `BinaryColor::On`  |
51//!
52//! # Characters used in [`Gray2`] patterns
53//!
54//! The following mappings are available for [`Gray2`]:
55//!
56//! | Character | Color                    | Description                             |
57//! |-----------|--------------------------|-----------------------------------------|
58//! | `' '`     | `None`                   | No drawing operation changed the pixel  |
59//! | `'0'`     | `Some(Gray2::new(0x0))`  | Pixel was changed to `Gray2::new(0x0)`  |
60//! | `'1'`     | `Some(Gray2::new(0x1))`  | Pixel was changed to `Gray2::new(0x1)`  |
61//! | `'2'`     | `Some(Gray2::new(0x2))`  | Pixel was changed to `Gray2::new(0x2)`  |
62//! | `'3'`     | `Some(Gray2::new(0x3))`  | Pixel was changed to `Gray2::new(0x3)`  |
63//!
64//! # Characters used in [`Gray4`] patterns
65//!
66//! The following mappings are available for [`Gray4`]:
67//!
68//! | Character | Color                    | Description                             |
69//! |-----------|--------------------------|-----------------------------------------|
70//! | `' '`     | `None`                   | No drawing operation changed the pixel  |
71//! | `'0'`     | `Some(Gray4::new(0x0))`  | Pixel was changed to `Gray4::new(0x0)`  |
72//! | `'1'`     | `Some(Gray4::new(0x1))`  | Pixel was changed to `Gray4::new(0x1)`  |
73//! | ⋮         | ⋮                        | ⋮                                      |
74//! | `'E'`     | `Some(Gray4::new(0xE))`  | Pixel was changed to `Gray4::new(0xE)`  |
75//! | `'F'`     | `Some(Gray4::new(0xF))`  | Pixel was changed to `Gray4::new(0xF)`  |
76//!
77//! # Characters used in [`Gray8`] patterns
78//!
79//! The following mappings are available for [`Gray8`]:
80//!
81//! | Character | Color                    | Description                             |
82//! |-----------|--------------------------|-----------------------------------------|
83//! | `' '`     | `None`                   | No drawing operation changed the pixel  |
84//! | `'0'`     | `Some(Gray8::new(0x00))` | Pixel was changed to `Gray8::new(0x00)` |
85//! | `'1'`     | `Some(Gray8::new(0x11))` | Pixel was changed to `Gray8::new(0x11)` |
86//! | ⋮         | ⋮                        | ⋮                                      |
87//! | `'E'`     | `Some(Gray8::new(0xEE))` | Pixel was changed to `Gray8::new(0xEE)` |
88//! | `'F'`     | `Some(Gray8::new(0xFF))` | Pixel was changed to `Gray8::new(0xFF)` |
89//!
90//! Note: `Gray8` uses a different mapping than `Gray2` and `Gray4`, by duplicating the pattern
91//! value into the high and low nibble. This allows using a single digit to test luma values ranging
92//! from 0 to 255.
93//!
94//! # Characters used in RGB color patterns
95//!
96//! The following mappings are available for all RGB color types in the [`pixelcolor`] module,
97//! like [`Rgb565`] or [`Rgb888`]:
98//!
99//! | Character | Color                    | Description                             |
100//! |-----------|--------------------------|-----------------------------------------|
101//! | `' '`     | `None`                   | No drawing operation changed the pixel  |
102//! | `'K'`     | `Some(C::BLACK)`         | Pixel was changed to `C::BLACK`         |
103//! | `'R'`     | `Some(C::RED)`           | Pixel was changed to `C::RED`           |
104//! | `'G'`     | `Some(C::GREEN)`         | Pixel was changed to `C::GREEN`         |
105//! | `'B'`     | `Some(C::BLUE)`          | Pixel was changed to `C::BLUE`          |
106//! | `'Y'`     | `Some(C::YELLOW)`        | Pixel was changed to `C::YELLOW`        |
107//! | `'M'`     | `Some(C::MAGENTA)`       | Pixel was changed to `C::MAGENTA`       |
108//! | `'C'`     | `Some(C::CYAN)`          | Pixel was changed to `C::CYAN`          |
109//! | `'W'`     | `Some(C::WHITE)`         | Pixel was changed to `C::WHITE`         |
110//!
111//! Note: The table used `C` as a placeholder for the actual color type, like `Rgb565::BLACK`.
112//!
113//! # Examples
114//!
115//! ## Assert that a modified display matches the expected value
116//!
117//! This example sets three pixels on the display and checks that they're turned on.
118//!
119//! ```rust
120//! use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor, prelude::*};
121//!
122//! let mut display = MockDisplay::new();
123//!
124//! Pixel(Point::new(0, 0), BinaryColor::On).draw(&mut display);
125//! Pixel(Point::new(2, 1), BinaryColor::On).draw(&mut display);
126//! Pixel(Point::new(1, 2), BinaryColor::On).draw(&mut display);
127//!
128//! display.assert_pattern(&[
129//!     "#  ", //
130//!     "  #", //
131//!     " # ", //
132//! ]);
133//! ```
134//!
135//! ## Load and validate a 16BPP image
136//!
137//! This example loads the following test image (scaled 10x to make it visible) and tests the
138//! returned pixels against an expected pattern.
139//!
140//! ![Test image, scaled 1000%](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAUCAIAAABwJOjsAAAAMUlEQVRIx2NkwAv+45Vl/E++XiaGAQKjFo9aPGrx0LeYhVAJMxrUoxaPWjxq8aCzGAAVwwQnmlfSgwAAAABJRU5ErkJggg==)
141//!
142//! ```rust
143//! use embedded_graphics::{
144//!     image::{Image, ImageRaw, ImageRawBE},
145//!     mock_display::MockDisplay,
146//!     pixelcolor::{Rgb565, RgbColor},
147//!     prelude::*,
148//! };
149//!
150//! let data = [
151//!     0x00, 0x00, 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, //
152//!     0x00, 0x1F, 0x07, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, //
153//! ];
154//!
155//! let raw: ImageRawBE<Rgb565> = ImageRaw::new(&data, 4);
156//!
157//! let image = Image::new(&raw, Point::zero());
158//!
159//! let mut display: MockDisplay<Rgb565> = MockDisplay::new();
160//!
161//! image.draw(&mut display);
162//!
163//! display.assert_pattern(&[
164//!     "KRGY", //
165//!     "BCMW", //
166//! ]);
167//! ```
168//!
169//! [`pixelcolor`]: super::pixelcolor#structs
170//! [`BinaryColor`]: super::pixelcolor::BinaryColor
171//! [`Gray2`]: super::pixelcolor::Gray2
172//! [`Gray4`]: super::pixelcolor::Gray4
173//! [`Gray8`]: super::pixelcolor::Gray8
174//! [`Rgb565`]: super::pixelcolor::Rgb565
175//! [`Rgb888`]: super::pixelcolor::Rgb888
176//! [`DrawTarget`]: super::draw_target::DrawTarget
177//! [`assert_eq`]: MockDisplay::assert_eq()
178//! [`assert_pattern`]: MockDisplay::assert_pattern()
179//! [`diff`]: MockDisplay::diff()
180//! [`from_pattern`]: MockDisplay::from_pattern()
181//! [`set_allow_overdraw`]: MockDisplay::set_allow_overdraw()
182//! [`set_allow_out_of_bounds_drawing`]: MockDisplay::set_allow_out_of_bounds_drawing()
183
184mod color_mapping;
185mod fancy_panic;
186
187use crate::{
188    draw_target::DrawTarget,
189    geometry::{Dimensions, OriginDimensions, Point, Size},
190    pixelcolor::{PixelColor, Rgb888, RgbColor},
191    primitives::{PointsIter, Rectangle},
192    Pixel,
193};
194pub use color_mapping::ColorMapping;
195use core::{
196    fmt::{self, Write},
197    iter,
198};
199use fancy_panic::FancyPanic;
200
201const SIZE: usize = 64;
202const DISPLAY_AREA: Rectangle = Rectangle::new(Point::zero(), Size::new_equal(SIZE as u32));
203
204/// Mock display struct
205///
206/// See the [module documentation](self) for usage and examples.
207#[derive(Clone)]
208pub struct MockDisplay<C>
209where
210    C: PixelColor,
211{
212    pixels: [Option<C>; SIZE * SIZE],
213    allow_overdraw: bool,
214    allow_out_of_bounds_drawing: bool,
215}
216
217impl<C> MockDisplay<C>
218where
219    C: PixelColor,
220{
221    /// Creates a new empty mock display.
222    pub fn new() -> Self {
223        Self::default()
224    }
225
226    /// Create a mock display from an iterator of [`Point`]s.
227    ///
228    /// This method can be used to create a mock display from the iterator produced by the
229    /// [`PointsIter::points`] method.
230    ///
231    /// # Panics
232    ///
233    /// This method will panic if the iterator returns a point that is outside the display bounding
234    /// box.
235    ///
236    /// # Examples
237    ///
238    /// ```rust
239    /// use embedded_graphics::{prelude::*, pixelcolor::BinaryColor, primitives::Circle, mock_display::MockDisplay};
240    ///
241    /// let circle = Circle::new(Point::new(0, 0), 4);
242    ///
243    /// let mut display = MockDisplay::from_points(circle.points(), BinaryColor::On);
244    ///
245    /// display.assert_pattern(&[
246    ///     " ## ",
247    ///     "####",
248    ///     "####",
249    ///     " ## ",
250    /// ]);
251    /// ```
252    ///
253    /// [`Point`]: super::geometry::Point
254    /// [`PointsIter::points`]: super::primitives::PointsIter::points
255    /// [`map`]: MockDisplay::map()
256    /// [`BinaryColor`]: super::pixelcolor::BinaryColor
257    pub fn from_points<I>(points: I, color: C) -> Self
258    where
259        I: IntoIterator<Item = Point>,
260    {
261        let mut display = Self::new();
262        display.set_pixels(points, Some(color));
263
264        display
265    }
266
267    /// Sets if out of bounds drawing is allowed.
268    ///
269    /// If this is set to `true` the bounds checks during drawing are disabled.
270    pub fn set_allow_out_of_bounds_drawing(&mut self, value: bool) {
271        self.allow_out_of_bounds_drawing = value;
272    }
273
274    /// Sets if overdrawing is allowed.
275    ///
276    /// If this is set to `true` the overdrawing is allowed.
277    pub fn set_allow_overdraw(&mut self, value: bool) {
278        self.allow_overdraw = value;
279    }
280
281    /// Returns the color of a pixel.
282    pub const fn get_pixel(&self, p: Point) -> Option<C> {
283        let Point { x, y } = p;
284
285        self.pixels[x as usize + y as usize * SIZE]
286    }
287
288    /// Changes the value of a pixel without bounds checking.
289    ///
290    /// # Panics
291    ///
292    /// This method will panic if `point` is outside the display bounding box.
293    pub fn set_pixel(&mut self, point: Point, color: Option<C>) {
294        assert!(
295            point.x >= 0 && point.y >= 0 && point.x < SIZE as i32 && point.y < SIZE as i32,
296            "point must be inside display bounding box: {:?}",
297            point
298        );
299
300        let i = point.x + point.y * SIZE as i32;
301        self.pixels[i as usize] = color;
302    }
303
304    /// Changes the value of a pixel without bounds checking.
305    ///
306    /// # Panics
307    ///
308    /// This method will panic if `point` is outside the display bounding box.
309    fn set_pixel_unchecked(&mut self, point: Point, color: Option<C>) {
310        let i = point.x + point.y * SIZE as i32;
311        self.pixels[i as usize] = color;
312    }
313
314    /// Sets the points in an iterator to the given color.
315    ///
316    /// # Panics
317    ///
318    /// This method will panic if the iterator returns points outside the display bounding box.
319    pub fn set_pixels(&mut self, points: impl IntoIterator<Item = Point>, color: Option<C>) {
320        for point in points {
321            self.set_pixel(point, color);
322        }
323    }
324
325    /// Returns the area that was affected by drawing operations.
326    pub fn affected_area(&self) -> Rectangle {
327        let (tl, br) = self
328            .bounding_box()
329            .points()
330            .zip(self.pixels.iter())
331            .filter_map(|(point, color)| color.map(|_| point))
332            .fold(
333                (None, None),
334                |(tl, br): (Option<Point>, Option<Point>), point| {
335                    (
336                        tl.map(|tl| tl.component_min(point)).or(Some(point)),
337                        br.map(|br| br.component_max(point)).or(Some(point)),
338                    )
339                },
340            );
341
342        if let (Some(tl), Some(br)) = (tl, br) {
343            Rectangle::with_corners(tl, br)
344        } else {
345            Rectangle::zero()
346        }
347    }
348
349    /// Returns the `affected_area` with the top left corner extended to `(0, 0)`.
350    fn affected_area_origin(&self) -> Rectangle {
351        self.affected_area()
352            .bottom_right()
353            .map(|bottom_right| Rectangle::with_corners(Point::zero(), bottom_right))
354            .unwrap_or_default()
355    }
356
357    /// Changes the color of a pixel.
358    ///
359    /// # Panics
360    ///
361    /// If out of bounds draw checking is enabled (default), this method will panic if the point
362    /// lies outside the display area. This behavior can be disabled by calling
363    /// [`set_allow_out_of_bounds_drawing(true)`].
364    ///
365    /// Similarly, overdraw is checked by default and will panic if a point is drawn to the same
366    /// coordinate twice. This behavior can be disabled by calling [`set_allow_overdraw(true)`].
367    ///
368    /// [`set_allow_out_of_bounds_drawing(true)`]: MockDisplay::set_allow_out_of_bounds_drawing()
369    /// [`set_allow_overdraw(true)`]: MockDisplay::set_allow_overdraw()
370    pub fn draw_pixel(&mut self, point: Point, color: C) {
371        if !DISPLAY_AREA.contains(point) {
372            if !self.allow_out_of_bounds_drawing {
373                panic!(
374                    "tried to draw pixel outside the display area (x: {}, y: {})",
375                    point.x, point.y
376                );
377            } else {
378                return;
379            }
380        }
381
382        if !self.allow_overdraw && self.get_pixel(point).is_some() {
383            panic!("tried to draw pixel twice (x: {}, y: {})", point.x, point.y);
384        }
385
386        self.set_pixel_unchecked(point, Some(color));
387    }
388
389    /// Returns a copy of with the content mirrored by swapping x and y.
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor};
395    ///
396    /// let display: MockDisplay<BinaryColor> = MockDisplay::from_pattern(&[
397    ///     "#### ####",
398    ///     "#    #   ",
399    ///     "###  # ##",
400    ///     "#    #  #",
401    ///     "#### ####",
402    /// ]);
403    ///
404    /// let mirrored = display.swap_xy();
405    /// mirrored.assert_pattern(&[
406    ///     "#####",
407    ///     "# # #",
408    ///     "# # #",
409    ///     "#   #",
410    ///     "     ",
411    ///     "#####",
412    ///     "#   #",
413    ///     "# # #",
414    ///     "# ###",
415    /// ]);
416    /// ```
417    pub fn swap_xy(&self) -> MockDisplay<C> {
418        let mut mirrored = MockDisplay::new();
419
420        for point in self.bounding_box().points() {
421            mirrored.set_pixel_unchecked(point, self.get_pixel(Point::new(point.y, point.x)));
422        }
423
424        mirrored
425    }
426
427    /// Maps a `MockDisplay<C>` to a `MockDisplay<CT>` by applying a function
428    /// to each pixel.
429    ///
430    /// # Examples
431    ///
432    /// Invert a `MockDisplay` by applying [`BinaryColor::invert`] to the color of each pixel.
433    ///
434    /// [`BinaryColor::invert`]: super::pixelcolor::BinaryColor::invert()
435    ///
436    /// ```
437    /// use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor};
438    ///
439    /// let display: MockDisplay<BinaryColor> = MockDisplay::from_pattern(&[
440    ///     "####",
441    ///     "#  .",
442    ///     "....",
443    /// ]);
444    ///
445    /// let inverted = display.map(|c| c.invert());
446    /// inverted.assert_pattern(&[
447    ///     "....",
448    ///     ".  #",
449    ///     "####",
450    /// ]);
451    /// ```
452    pub fn map<CT, F>(&self, f: F) -> MockDisplay<CT>
453    where
454        CT: PixelColor,
455        F: Fn(C) -> CT + Copy,
456    {
457        let mut target = MockDisplay::new();
458
459        for point in self.bounding_box().points() {
460            target.set_pixel_unchecked(point, self.get_pixel(point).map(f))
461        }
462
463        target
464    }
465
466    /// Compares the display to another display.
467    ///
468    /// The following color code is used to show the difference between the displays:
469    ///
470    /// | Color               | Description                                                   |
471    /// |---------------------|---------------------------------------------------------------|
472    /// | None                | The color of the pixel is equal in both displays.             |
473    /// | Some(Rgb888::GREEN) | The pixel was only set in `self`                              |
474    /// | Some(Rgb888::RED)   | The pixel was only set in `other`                             |
475    /// | Some(Rgb888::BLUE)  | The pixel was set to a different colors in `self` and `other` |
476    pub fn diff(&self, other: &MockDisplay<C>) -> MockDisplay<Rgb888> {
477        let mut display = MockDisplay::new();
478
479        for point in display.bounding_box().points() {
480            let self_color = self.get_pixel(point);
481            let other_color = other.get_pixel(point);
482
483            let diff_color = match (self_color, other_color) {
484                (Some(_), None) => Some(Rgb888::GREEN),
485                (None, Some(_)) => Some(Rgb888::RED),
486                (Some(s), Some(o)) if s != o => Some(Rgb888::BLUE),
487                _ => None,
488            };
489
490            display.set_pixel_unchecked(point, diff_color);
491        }
492
493        display
494    }
495}
496
497impl<C: PixelColor> PartialEq for MockDisplay<C> {
498    fn eq(&self, other: &Self) -> bool {
499        self.pixels.iter().eq(other.pixels.iter())
500    }
501}
502
503impl<C> MockDisplay<C>
504where
505    C: PixelColor + ColorMapping,
506{
507    /// Creates a new mock display from a character pattern.
508    ///
509    /// The color pattern is specified by a slice of string slices. Each string
510    /// slice represents a row of pixels and every character a single pixel.
511    ///
512    /// A space character in the pattern represents a pixel which wasn't
513    /// modified by any drawing routine and is left in the default state.
514    /// All other characters are converted by implementations of the
515    /// [`ColorMapping`] trait.
516    ///
517    pub fn from_pattern(pattern: &[&str]) -> MockDisplay<C> {
518        // Check pattern dimensions.
519        let pattern_width = pattern.first().map_or(0, |row| row.len());
520        let pattern_height = pattern.len();
521        assert!(
522            pattern_width <= SIZE,
523            "Test pattern must not be wider than {} columns",
524            SIZE
525        );
526        assert!(
527            pattern_height <= SIZE,
528            "Test pattern must not be taller than {} rows",
529            SIZE
530        );
531        for (row_idx, row) in pattern.iter().enumerate() {
532            assert_eq!(
533                row.len(),
534                pattern_width,
535                "Row #{} is {} characters wide (must be {} characters to match previous rows)",
536                row_idx + 1,
537                row.len(),
538                pattern_width
539            );
540        }
541
542        // Convert pattern to colors and pad pattern with None.
543        let pattern_colors = pattern
544            .iter()
545            .flat_map(|row| {
546                row.chars()
547                    .map(|c| match c {
548                        ' ' => None,
549                        _ => Some(C::char_to_color(c)),
550                    })
551                    .chain(iter::repeat(None))
552                    .take(SIZE)
553            })
554            .chain(iter::repeat(None))
555            .take(SIZE * SIZE);
556
557        // Copy pattern to display.
558        let mut display = MockDisplay::new();
559        for (i, color) in pattern_colors.enumerate() {
560            display.pixels[i] = color;
561        }
562
563        display
564    }
565
566    /// Checks if the displays are equal.
567    ///
568    /// An advanced output for failing tests can be enabled by setting the environment variable
569    /// `EG_FANCY_PANIC=1`. See the [module-level documentation] for more details.
570    ///
571    /// # Panics
572    ///
573    /// Panics if the displays aren't equal.
574    ///
575    /// [module-level documentation]: self#assertions
576    #[track_caller]
577    pub fn assert_eq(&self, other: &MockDisplay<C>) {
578        if !self.eq(other) {
579            if option_env!("EG_FANCY_PANIC") == Some("1") {
580                let fancy_panic = FancyPanic::new(self, other, 30);
581                panic!("\n{}", fancy_panic);
582            } else {
583                panic!("\ndisplay\n{:?}\nexpected\n{:?}", self, other);
584            }
585        }
586    }
587
588    /// Checks if the displays are equal.
589    ///
590    /// An advanced output for failing tests can be enabled by setting the environment variable
591    /// `EG_FANCY_PANIC=1`. See the [module-level documentation] for more details.
592    ///
593    /// The output of the `msg` function will be prepended to the output if the assertion fails.
594    ///
595    /// # Panics
596    ///
597    /// Panics if the displays aren't equal.
598    ///
599    /// [module-level documentation]: self#assertions
600    #[track_caller]
601    pub fn assert_eq_with_message<F>(&self, other: &MockDisplay<C>, msg: F)
602    where
603        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
604    {
605        if !self.eq(other) {
606            if option_env!("EG_FANCY_PANIC") == Some("1") {
607                let fancy_panic = FancyPanic::new(self, other, 30);
608                panic!("\n{}\n\n{}", MessageWrapper(msg), fancy_panic);
609            } else {
610                panic!(
611                    "\n{}\n\ndisplay:\n{:?}\nexpected:\n{:?}",
612                    MessageWrapper(msg),
613                    self,
614                    other
615                );
616            }
617        }
618    }
619
620    /// Checks if the display is equal to the given pattern.
621    ///
622    /// An advanced output for failing tests can be enabled, see the [module-level documentation]
623    /// for more details.
624    ///
625    /// # Panics
626    ///
627    /// Panics if the display content isn't equal to the pattern.
628    ///
629    /// [module-level documentation]: self#assertions
630    #[track_caller]
631    pub fn assert_pattern(&self, pattern: &[&str]) {
632        let other = MockDisplay::<C>::from_pattern(pattern);
633
634        self.assert_eq(&other);
635    }
636
637    /// Checks if the display is equal to the given pattern.
638    ///
639    /// An advanced output for failing tests can be enabled, see the [module-level documentation]
640    /// for more details.
641    ///
642    /// The output of the `msg` function will be prepended to the output if the assertion fails.
643    ///
644    /// # Panics
645    ///
646    /// Panics if the display content isn't equal to the pattern.
647    ///
648    /// [module-level documentation]: self#assertions
649    #[track_caller]
650    pub fn assert_pattern_with_message<F>(&self, pattern: &[&str], msg: F)
651    where
652        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
653    {
654        let other = MockDisplay::<C>::from_pattern(pattern);
655
656        self.assert_eq_with_message(&other, msg);
657    }
658}
659
660impl<C> Default for MockDisplay<C>
661where
662    C: PixelColor,
663{
664    fn default() -> Self {
665        Self {
666            pixels: [None; SIZE * SIZE],
667            allow_overdraw: false,
668            allow_out_of_bounds_drawing: false,
669        }
670    }
671}
672
673impl<C> fmt::Debug for MockDisplay<C>
674where
675    C: PixelColor + ColorMapping,
676{
677    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
678        let empty_rows = self
679            .pixels
680            .rchunks(SIZE)
681            .take_while(|row| row.iter().all(Option::is_none))
682            .count();
683
684        writeln!(f, "MockDisplay[")?;
685        for row in self.pixels.chunks(SIZE).take(SIZE - empty_rows) {
686            for color in row {
687                f.write_char(color.map_or(' ', C::color_to_char))?;
688            }
689            writeln!(f)?;
690        }
691        if empty_rows > 0 {
692            writeln!(f, "({} empty rows skipped)", empty_rows)?;
693        }
694        writeln!(f, "]")?;
695
696        Ok(())
697    }
698}
699
700impl<C> DrawTarget for MockDisplay<C>
701where
702    C: PixelColor,
703{
704    type Color = C;
705    type Error = core::convert::Infallible;
706
707    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
708    where
709        I: IntoIterator<Item = Pixel<Self::Color>>,
710    {
711        for pixel in pixels.into_iter() {
712            let Pixel(point, color) = pixel;
713
714            self.draw_pixel(point, color);
715        }
716
717        Ok(())
718    }
719}
720
721impl<C> OriginDimensions for MockDisplay<C>
722where
723    C: PixelColor,
724{
725    fn size(&self) -> Size {
726        DISPLAY_AREA.size
727    }
728}
729
730/// Wrapper to implement `Display` for formatting function.
731struct MessageWrapper<F>(F);
732
733impl<F> fmt::Display for MessageWrapper<F>
734where
735    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
736{
737    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738        self.0(f)
739    }
740}
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745    use crate::{
746        pixelcolor::{BinaryColor, Rgb565},
747        Drawable,
748    };
749
750    #[test]
751    #[should_panic(expected = "tried to draw pixel outside the display area (x: 65, y: 0)")]
752    fn panic_on_out_of_bounds_drawing() {
753        let mut display = MockDisplay::new();
754
755        Pixel(Point::new(65, 0), BinaryColor::On)
756            .draw(&mut display)
757            .unwrap();
758    }
759
760    #[test]
761    fn allow_out_of_bounds_drawing() {
762        let mut display = MockDisplay::new();
763        display.set_allow_out_of_bounds_drawing(true);
764
765        Pixel(Point::new(65, 0), BinaryColor::On)
766            .draw(&mut display)
767            .unwrap();
768    }
769
770    #[test]
771    #[should_panic(expected = "tried to draw pixel twice (x: 1, y: 2)")]
772    fn panic_on_overdraw() {
773        let mut display = MockDisplay::new();
774
775        let p = Pixel(Point::new(1, 2), BinaryColor::On);
776        p.draw(&mut display).unwrap();
777        p.draw(&mut display).unwrap();
778    }
779
780    #[test]
781    fn allow_overdraw() {
782        let mut display = MockDisplay::new();
783        display.set_allow_overdraw(true);
784
785        let p = Pixel(Point::new(1, 2), BinaryColor::On);
786        p.draw(&mut display).unwrap();
787        p.draw(&mut display).unwrap();
788    }
789
790    #[test]
791    fn zero_sized_affected_area() {
792        let disp: MockDisplay<BinaryColor> = MockDisplay::new();
793        assert!(disp.affected_area().is_zero_sized(),);
794    }
795
796    #[test]
797    fn diff() {
798        let display1 = MockDisplay::<Rgb565>::from_pattern(&[" R RR"]);
799        let display2 = MockDisplay::<Rgb565>::from_pattern(&[" RR B"]);
800        let expected = MockDisplay::<Rgb888>::from_pattern(&["  RGB"]);
801
802        display1.diff(&display2).assert_eq(&expected);
803    }
804}