|
1 | 1 | import time |
2 | 2 | import sys |
| 3 | +import pytest |
3 | 4 | from dash import Dash, Input, Output, html, dcc |
4 | 5 | from selenium.webdriver.common.keys import Keys |
5 | 6 |
|
@@ -148,6 +149,93 @@ def update_output(val): |
148 | 149 | assert dash_dcc.get_logs() == [] |
149 | 150 |
|
150 | 151 |
|
| 152 | +@pytest.mark.parametrize("step", [0.1, 0.01, 0.001, 0.0001]) |
| 153 | +def test_inni006_stepper_floating_point_precision(dash_dcc, step): |
| 154 | + """Test that stepper increments/decrements with decimal steps don't accumulate floating point errors.""" |
| 155 | + |
| 156 | + app = Dash(__name__) |
| 157 | + app.layout = html.Div( |
| 158 | + [ |
| 159 | + dcc.Input(id="decimal-input", value=0, type="number", step=step), |
| 160 | + html.Div(id="output"), |
| 161 | + ] |
| 162 | + ) |
| 163 | + |
| 164 | + @app.callback(Output("output", "children"), [Input("decimal-input", "value")]) |
| 165 | + def update_output(val): |
| 166 | + return val |
| 167 | + |
| 168 | + dash_dcc.start_server(app) |
| 169 | + increment_btn = dash_dcc.find_element(".dash-stepper-increment") |
| 170 | + decrement_btn = dash_dcc.find_element(".dash-stepper-decrement") |
| 171 | + |
| 172 | + # Determine decimal places for formatting |
| 173 | + decimal_places = len(str(step).split(".")[1]) if "." in str(step) else 0 |
| 174 | + num_clicks = 9 |
| 175 | + |
| 176 | + # Test increment: without precision fix, accumulates floating point errors (e.g., 0.30000000000000004) |
| 177 | + for i in range(1, num_clicks + 1): |
| 178 | + increment_btn.click() |
| 179 | + expected = format(step * i, f".{decimal_places}f") |
| 180 | + dash_dcc.wait_for_text_to_equal("#output", expected) |
| 181 | + |
| 182 | + # Test decrement: should go back down through the same values |
| 183 | + for i in range(num_clicks - 1, 0, -1): |
| 184 | + decrement_btn.click() |
| 185 | + expected = format(step * i, f".{decimal_places}f") |
| 186 | + dash_dcc.wait_for_text_to_equal("#output", expected) |
| 187 | + |
| 188 | + # One more decrement to get back to 0 |
| 189 | + decrement_btn.click() |
| 190 | + dash_dcc.wait_for_text_to_equal("#output", "0") |
| 191 | + |
| 192 | + assert dash_dcc.get_logs() == [] |
| 193 | + |
| 194 | + |
| 195 | +@pytest.mark.parametrize("step", [0.00001, 0.000001]) |
| 196 | +def test_inni007_stepper_very_small_steps(dash_dcc, step): |
| 197 | + """Test that stepper works correctly with very small decimal steps.""" |
| 198 | + |
| 199 | + app = Dash(__name__) |
| 200 | + app.layout = html.Div( |
| 201 | + [ |
| 202 | + dcc.Input(id="decimal-input", value=0, type="number", step=step), |
| 203 | + html.Div(id="output"), |
| 204 | + ] |
| 205 | + ) |
| 206 | + |
| 207 | + @app.callback(Output("output", "children"), [Input("decimal-input", "value")]) |
| 208 | + def update_output(val): |
| 209 | + return val |
| 210 | + |
| 211 | + dash_dcc.start_server(app) |
| 212 | + increment_btn = dash_dcc.find_element(".dash-stepper-increment") |
| 213 | + decrement_btn = dash_dcc.find_element(".dash-stepper-decrement") |
| 214 | + |
| 215 | + # For very small steps, format with enough precision then strip trailing zeros |
| 216 | + step_str = f"{step:.10f}".rstrip("0").rstrip(".") |
| 217 | + decimal_places = len(step_str.split(".")[1]) if "." in step_str else 0 |
| 218 | + num_clicks = 5 |
| 219 | + |
| 220 | + # Test increment |
| 221 | + for i in range(1, num_clicks + 1): |
| 222 | + increment_btn.click() |
| 223 | + expected = f"{step * i:.{decimal_places}f}".rstrip("0").rstrip(".") |
| 224 | + dash_dcc.wait_for_text_to_equal("#output", expected) |
| 225 | + |
| 226 | + # Test decrement |
| 227 | + for i in range(num_clicks - 1, 0, -1): |
| 228 | + decrement_btn.click() |
| 229 | + expected = f"{step * i:.{decimal_places}f}".rstrip("0").rstrip(".") |
| 230 | + dash_dcc.wait_for_text_to_equal("#output", expected) |
| 231 | + |
| 232 | + # One more decrement to get back to 0 |
| 233 | + decrement_btn.click() |
| 234 | + dash_dcc.wait_for_text_to_equal("#output", "0") |
| 235 | + |
| 236 | + assert dash_dcc.get_logs() == [] |
| 237 | + |
| 238 | + |
151 | 239 | def test_inni010_valid_numbers(dash_dcc, ninput_app): |
152 | 240 | dash_dcc.start_server(ninput_app) |
153 | 241 | for num, op in ( |
|
0 commit comments