viewpoint_core/page/locator/builders/
check.rs

1//! Check builder for locator actions.
2
3use tracing::{debug, instrument};
4
5use super::click::ClickBuilder;
6use super::super::Locator;
7use crate::error::LocatorError;
8use crate::wait::NavigationWaiter;
9
10/// Builder for check/uncheck operations with configurable options.
11///
12/// Created via [`Locator::check`] or [`Locator::uncheck`].
13#[derive(Debug)]
14pub struct CheckBuilder<'l, 'a> {
15    locator: &'l Locator<'a>,
16    /// Whether to check (true) or uncheck (false).
17    check: bool,
18    /// Whether to bypass actionability checks.
19    force: bool,
20    /// Whether to skip waiting for navigation after the action.
21    no_wait_after: bool,
22}
23
24impl<'l, 'a> CheckBuilder<'l, 'a> {
25    pub(crate) fn new_check(locator: &'l Locator<'a>) -> Self {
26        Self {
27            locator,
28            check: true,
29            force: false,
30            no_wait_after: false,
31        }
32    }
33
34    pub(crate) fn new_uncheck(locator: &'l Locator<'a>) -> Self {
35        Self {
36            locator,
37            check: false,
38            force: false,
39            no_wait_after: false,
40        }
41    }
42
43    /// Whether to bypass actionability checks.
44    #[must_use]
45    pub fn force(mut self, force: bool) -> Self {
46        self.force = force;
47        self
48    }
49
50    /// Whether to skip waiting for navigation after the check/uncheck.
51    #[must_use]
52    pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
53        self.no_wait_after = no_wait_after;
54        self
55    }
56
57    /// Execute the check/uncheck operation.
58    #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector, check = self.check))]
59    pub async fn send(self) -> Result<(), LocatorError> {
60        // Set up navigation waiter before the action if needed
61        let navigation_waiter = if self.no_wait_after {
62            None
63        } else {
64            Some(NavigationWaiter::new(
65                self.locator.page.connection().subscribe_events(),
66                self.locator.page.session_id().to_string(),
67                self.locator.page.frame_id().to_string(),
68            ))
69        };
70
71        // Perform the check/uncheck action
72        self.perform_check().await?;
73
74        // Wait for navigation if triggered
75        if let Some(waiter) = navigation_waiter {
76            if let Err(e) = waiter.wait_for_navigation_if_triggered().await {
77                debug!(error = ?e, "Navigation wait failed after check");
78                return Err(LocatorError::WaitError(e));
79            }
80        }
81
82        Ok(())
83    }
84
85    /// Perform the actual check/uncheck without navigation waiting.
86    async fn perform_check(&self) -> Result<(), LocatorError> {
87        let is_checked = self.locator.is_checked().await?;
88
89        if self.check {
90            // Want to check
91            if is_checked {
92                debug!("Element already checked");
93            } else {
94                debug!("Checking element");
95                // Use the click builder without auto-wait (we handle it ourselves)
96                ClickBuilder::new(self.locator)
97                    .force(self.force)
98                    .no_wait_after(true)
99                    .send()
100                    .await?;
101            }
102        } else {
103            // Want to uncheck
104            if is_checked {
105                debug!("Unchecking element");
106                ClickBuilder::new(self.locator)
107                    .force(self.force)
108                    .no_wait_after(true)
109                    .send()
110                    .await?;
111            } else {
112                debug!("Element already unchecked");
113            }
114        }
115
116        Ok(())
117    }
118}
119
120impl<'l> std::future::IntoFuture for CheckBuilder<'l, '_> {
121    type Output = Result<(), LocatorError>;
122    type IntoFuture =
123        std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'l>>;
124
125    fn into_future(self) -> Self::IntoFuture {
126        Box::pin(self.send())
127    }
128}