Run doctests in CI and fix up existing doctests (#37851)

Follows on from
https://github.com/zed-industries/zed/pull/37716#pullrequestreview-3195695110
by @SomeoneToIgnore

After this the doctests will be run in CI to check that the examples are
still accurate.

Note that doctests aren't run by Nextest: you can run them locally with
`cargo test --doc`.

Summary:
* Run tests from CI
* Loosen an exact float comparison to match approximately (otherwise it
fails)
* Fixed one actual bug in the tests for `dilate` where the test code
assumed that `dilate` mutates `self` rather than returning a new object
* Add some `must_use` on some functions that seemed at risk of similar
bugs, following the Rust stdlib style to add it where ignoring the
result is almost certainly a bug.
* Fix some cases where the doc examples seem to have gone out of date
with the code
* Add imports to doctests that need them
* Add some dev-dependencies to make the tests build
* Fix the `key_dispatch` module docstring, which was accidentally
attached to objects within that module
* Skip some doctest examples that seem like they need an async
environment or that just looked hard to get running

AI usage: I asked Claude to do some of the repetitive tests. I checked
the output and fixed up some things that seemed to not be in the right
spirit of the test, or too longwinded.

I think we could reasonably run the tests on only Linux to save CI
CPU-seconds and latency, but I haven't done that yet, partly because of
how it's implemented in the action.

Release Notes:

- N/A
This commit is contained in:
Martin Pool
2025-09-12 23:24:04 +03:00
committed by GitHub
parent 174a0b1517
commit b9cf5886e4
28 changed files with 262 additions and 147 deletions
+2 -3
View File
@@ -8,10 +8,9 @@ use gpui::{AnyElement, Hsla, ImageSource, Img, IntoElement, Styled, img};
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
/// use ui::Avatar;
///
/// Avatar::new("path/to/image.png")
/// .shape(AvatarShape::Circle)
/// .grayscale(true)
/// .border_color(gpui::red());
/// ```
@@ -39,7 +38,7 @@ impl Avatar {
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
/// use ui::Avatar;
///
/// let avatar = Avatar::new("path/to/image.png").grayscale(true);
/// ```
+6 -5
View File
@@ -7,17 +7,18 @@ use gpui::{AnyElement, IntoElement, ParentElement, Styled};
/// # Usage Example
///
/// ```
/// use ui::{Banner};
/// use ui::prelude::*;
/// use ui::{Banner, Button, IconName, IconPosition, IconSize, Label, Severity};
///
/// Banner::new()
/// Banner::new()
/// .severity(Severity::Success)
/// .children(Label::new("This is a success message"))
/// .children([Label::new("This is a success message")])
/// .action_slot(
/// Button::new("learn-more", "Learn More")
/// .icon(IconName::ArrowUpRight)
/// .icon_size(IconSize::Small)
/// .icon_position(IconPosition::End),
/// )
/// .icon_position(IconPosition::End)
/// );
/// ```
#[derive(IntoElement, RegisterComponent)]
pub struct Banner {
+6 -6
View File
@@ -43,7 +43,7 @@ use super::button_icon::ButtonIcon;
///
/// Button::new("button_id", "Click me!")
/// .icon(IconName::Check)
/// .selected(true)
/// .toggle_state(true)
/// .on_click(|event, window, cx| {
/// // Handle click event
/// });
@@ -56,7 +56,7 @@ use super::button_icon::ButtonIcon;
/// use ui::TintColor;
///
/// Button::new("button_id", "Click me!")
/// .selected(true)
/// .toggle_state(true)
/// .selected_style(ButtonStyle::Tinted(TintColor::Accent))
/// .on_click(|event, window, cx| {
/// // Handle click event
@@ -228,7 +228,7 @@ impl Toggleable for Button {
/// use ui::prelude::*;
///
/// Button::new("button_id", "Click me!")
/// .selected(true)
/// .toggle_state(true)
/// .on_click(|event, window, cx| {
/// // Handle click event
/// });
@@ -251,7 +251,7 @@ impl SelectableButton for Button {
/// use ui::TintColor;
///
/// Button::new("button_id", "Click me!")
/// .selected(true)
/// .toggle_state(true)
/// .selected_style(ButtonStyle::Tinted(TintColor::Accent))
/// .on_click(|event, window, cx| {
/// // Handle click event
@@ -317,7 +317,7 @@ impl FixedWidth for Button {
/// use ui::prelude::*;
///
/// Button::new("button_id", "Click me!")
/// .width(px(100.).into())
/// .width(px(100.))
/// .on_click(|event, window, cx| {
/// // Handle click event
/// });
@@ -381,7 +381,7 @@ impl ButtonCommon for Button {
/// use ui::Tooltip;
///
/// Button::new("button_id", "Click me!")
/// .tooltip(Tooltip::text_f("This is a tooltip", cx))
/// .tooltip(Tooltip::text("This is a tooltip"))
/// .on_click(|event, window, cx| {
/// // Handle click event
/// });
+6 -5
View File
@@ -13,14 +13,15 @@ pub enum BorderPosition {
/// # Usage Example
///
/// ```
/// use ui::{Callout};
/// use ui::prelude::*;
/// use ui::{Button, Callout, IconName, Label, Severity};
///
/// Callout::new()
/// let callout = Callout::new()
/// .severity(Severity::Warning)
/// .icon(IconName::Warning)
/// .title(Label::new("Be aware of your subscription!"))
/// .description(Label::new("Your subscription is about to expire. Renew now!"))
/// .actions_slot(Button::new("renew", "Renew Now"))
/// .title("Be aware of your subscription!")
/// .description("Your subscription is about to expire. Renew now!")
/// .actions_slot(Button::new("renew", "Renew Now"));
/// ```
///
#[derive(IntoElement, RegisterComponent)]
+2 -2
View File
@@ -6,9 +6,9 @@ use gpui::{AnyElement, Hsla, IntoElement, ParentElement, Styled};
/// # Usage Example
///
/// ```
/// use ui::{Chip};
/// use ui::Chip;
///
/// Chip::new("This Chip")
/// let chip = Chip::new("This Chip");
/// ```
#[derive(IntoElement, RegisterComponent)]
pub struct Chip {
+5 -3
View File
@@ -19,11 +19,13 @@ use super::Avatar;
/// A default, horizontal facepile.
///
/// ```
/// use gpui::IntoElement;
/// use ui::{Avatar, Facepile, EXAMPLE_FACES};
///
/// Facepile::new(
/// EXAMPLE_FACES.iter().take(3).iter().map(|&url|
/// Avatar::new(url).into_any_element()).collect())
/// let facepile = Facepile::new(
/// EXAMPLE_FACES.iter().take(3).map(|&url|
/// Avatar::new(url).into_any_element()).collect()
/// );
/// ```
#[derive(IntoElement, Documented, RegisterComponent)]
pub struct Facepile {
+65 -14
View File
@@ -10,12 +10,19 @@ use theme::Appearance;
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+S"))
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::new(
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-s").unwrap())], cx),
/// Hsla::black()
/// )
/// .prefix("Save:")
/// .size(Pixels::from(14.0));
/// # }
/// ```
#[derive(Debug, IntoElement, RegisterComponent)]
pub struct KeybindingHint {
@@ -34,10 +41,17 @@ impl KeybindingHint {
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::new(
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-c").unwrap())], cx),
/// Hsla::black()
/// );
/// # }
/// ```
pub fn new(keybinding: KeyBinding, background_color: Hsla) -> Self {
Self {
@@ -56,10 +70,18 @@ impl KeybindingHint {
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::with_prefix("Copy:", KeyBinding::from_str("Ctrl+C"), Hsla::new(0.0, 0.0, 0.0, 1.0));
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::with_prefix(
/// "Copy:",
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-c").unwrap())], cx),
/// Hsla::black()
/// );
/// # }
/// ```
pub fn with_prefix(
prefix: impl Into<SharedString>,
@@ -82,10 +104,18 @@ impl KeybindingHint {
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::with_suffix(KeyBinding::from_str("Ctrl+V"), "Paste", Hsla::new(0.0, 0.0, 0.0, 1.0));
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::with_suffix(
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-v").unwrap())], cx),
/// "Paste",
/// Hsla::black()
/// );
/// # }
/// ```
pub fn with_suffix(
keybinding: KeyBinding,
@@ -107,11 +137,18 @@ impl KeybindingHint {
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+X"))
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::new(
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-x").unwrap())], cx),
/// Hsla::black()
/// )
/// .prefix("Cut:");
/// # }
/// ```
pub fn prefix(mut self, prefix: impl Into<SharedString>) -> Self {
self.prefix = Some(prefix.into());
@@ -124,11 +161,18 @@ impl KeybindingHint {
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+F"))
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::new(
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-f").unwrap())], cx),
/// Hsla::black()
/// )
/// .suffix("Find");
/// # }
/// ```
pub fn suffix(mut self, suffix: impl Into<SharedString>) -> Self {
self.suffix = Some(suffix.into());
@@ -141,11 +185,18 @@ impl KeybindingHint {
///
/// # Examples
///
/// ```
/// ```no_run
/// use gpui::{App, Hsla, KeybindingKeystroke, Keystroke};
/// use ui::prelude::*;
/// use ui::{KeyBinding, KeybindingHint};
///
/// let hint = KeybindingHint::new(KeyBinding::from_str("Ctrl+Z"))
/// # fn example(cx: &App) {
/// let hint = KeybindingHint::new(
/// KeyBinding::new(vec![KeybindingKeystroke::from_keystroke(Keystroke::parse("ctrl-z").unwrap())], cx),
/// Hsla::black()
/// )
/// .size(Pixels::from(16.0));
/// # }
/// ```
pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.size = size.into();
+5 -4
View File
@@ -27,7 +27,7 @@ use gpui::StyleRefinement;
/// ```
/// use ui::prelude::*;
///
/// let my_label = Label::new("Deleted").strikethrough(true);
/// let my_label = Label::new("Deleted").strikethrough();
/// ```
#[derive(IntoElement, RegisterComponent)]
pub struct Label {
@@ -89,9 +89,10 @@ impl LabelCommon for Label {
/// # Examples
///
/// ```
/// use gpui::FontWeight;
/// use ui::prelude::*;
///
/// let my_label = Label::new("Hello, World!").weight(FontWeight::Bold);
/// let my_label = Label::new("Hello, World!").weight(FontWeight::BOLD);
/// ```
fn weight(mut self, weight: gpui::FontWeight) -> Self {
self.base = self.base.weight(weight);
@@ -133,7 +134,7 @@ impl LabelCommon for Label {
/// ```
/// use ui::prelude::*;
///
/// let my_label = Label::new("Hello, World!").strikethrough(true);
/// let my_label = Label::new("Hello, World!").strikethrough();
/// ```
fn strikethrough(mut self) -> Self {
self.base = self.base.strikethrough();
@@ -147,7 +148,7 @@ impl LabelCommon for Label {
/// ```
/// use ui::prelude::*;
///
/// let my_label = Label::new("Hello, World!").italic(true);
/// let my_label = Label::new("Hello, World!").italic();
/// ```
fn italic(mut self) -> Self {
self.base = self.base.italic();
+3 -2
View File
@@ -581,11 +581,12 @@ impl RenderOnce for Switch {
///
/// ```
/// use ui::prelude::*;
/// use ui::{SwitchField, ToggleState};
///
/// SwitchField::new(
/// let switch_field = SwitchField::new(
/// "feature-toggle",
/// "Enable feature",
/// "This feature adds new functionality to the app.",
/// Some("This feature adds new functionality to the app.".into()),
/// ToggleState::Unselected,
/// |state, window, cx| {
/// // Logic here