|
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | 15 |
|
16 | | -import unittest.mock as mock |
| 16 | +from typing import Any |
| 17 | +from unittest import mock |
17 | 18 |
|
18 | 19 | import pandas as pd |
19 | 20 | import pytest |
@@ -100,6 +101,33 @@ def small_widget(small_bf_df): |
100 | 101 | yield TableWidget(small_bf_df) |
101 | 102 |
|
102 | 103 |
|
| 104 | +@pytest.fixture |
| 105 | +def unknown_row_count_widget(session): |
| 106 | + """Fixture to create a TableWidget with an unknown row count.""" |
| 107 | + from bigframes.core import blocks |
| 108 | + from bigframes.display import TableWidget |
| 109 | + |
| 110 | + # Create a small DataFrame with known content |
| 111 | + test_data = pd.DataFrame( |
| 112 | + { |
| 113 | + "id": [0, 1, 2, 3, 4], |
| 114 | + "value": ["row_0", "row_1", "row_2", "row_3", "row_4"], |
| 115 | + } |
| 116 | + ) |
| 117 | + bf_df = session.read_pandas(test_data) |
| 118 | + |
| 119 | + # Simulate a scenario where total_rows is not available from the iterator |
| 120 | + with mock.patch.object(bf_df, "_to_pandas_batches") as mock_batches: |
| 121 | + # We need to provide an iterator of DataFrames, not Series |
| 122 | + batches_iterator = iter([test_data]) |
| 123 | + mock_batches.return_value = blocks.PandasBatches( |
| 124 | + batches_iterator, total_rows=None |
| 125 | + ) |
| 126 | + with bf.option_context("display.repr_mode", "anywidget", "display.max_rows", 2): |
| 127 | + widget = TableWidget(bf_df) |
| 128 | + yield widget |
| 129 | + |
| 130 | + |
103 | 131 | @pytest.fixture(scope="module") |
104 | 132 | def empty_pandas_df() -> pd.DataFrame: |
105 | 133 | """Create an empty DataFrame for edge case testing.""" |
@@ -131,7 +159,7 @@ def execution_metadata(self) -> ExecutionMetadata: |
131 | 159 | return ExecutionMetadata() |
132 | 160 |
|
133 | 161 | @property |
134 | | - def schema(self): |
| 162 | + def schema(self) -> Any: |
135 | 163 | return schema |
136 | 164 |
|
137 | 165 | def batches(self) -> ResultsIterator: |
@@ -183,10 +211,10 @@ def test_widget_initialization_should_default_to_page_zero( |
183 | 211 | table_widget, |
184 | 212 | ): |
185 | 213 | """ |
186 | | - Given a new TableWidget, when it is initialized, |
187 | | - then its page number should default to 0. |
| 214 | + A TableWidget should initialize with page 0 and the correct page size. |
188 | 215 | """ |
189 | | - |
| 216 | + # The `table_widget` fixture already creates the widget. |
| 217 | + # Assert its state. |
190 | 218 | assert table_widget.page == 0 |
191 | 219 | assert table_widget.page_size == EXPECTED_PAGE_SIZE |
192 | 220 |
|
@@ -309,15 +337,21 @@ def test_widget_with_few_rows_should_display_all_rows(small_widget, small_pandas |
309 | 337 |
|
310 | 338 | def test_navigation_beyond_last_page_should_be_clamped(small_widget): |
311 | 339 | """ |
312 | | - Given a DataFrame smaller than the page size, |
313 | | - when navigating beyond the last page, |
314 | | - then the page should be clamped to the last valid page (page 0). |
| 340 | + Given a DataFrame with a small number of rows, the widget should |
| 341 | + report the correct total row count and prevent navigation beyond |
| 342 | + the first page, ensuring the frontend correctly displays "Page 1 of 1". |
315 | 343 | """ |
316 | | - assert small_widget.page == 0 |
| 344 | + # For a DataFrame with 2 rows and page_size 5 (from small_widget fixture), |
| 345 | + # the frontend should calculate 1 total page. |
| 346 | + assert small_widget.row_count == 2 |
317 | 347 |
|
318 | | - small_widget.page = 1 # Attempt to navigate past the end |
| 348 | + # The widget should always be on page 0 for a single-page dataset. |
| 349 | + assert small_widget.page == 0 |
319 | 350 |
|
320 | | - assert small_widget.page == 0 # Should be clamped back to the only valid page |
| 351 | + # Attempting to navigate to page 1 should be clamped back to page 0, |
| 352 | + # confirming that only one page is recognized by the backend. |
| 353 | + small_widget.page = 1 |
| 354 | + assert small_widget.page == 0 |
321 | 355 |
|
322 | 356 |
|
323 | 357 | def test_global_options_change_should_not_affect_existing_widget_page_size( |
@@ -434,8 +468,10 @@ def test_navigation_after_page_size_change_should_use_new_size( |
434 | 468 |
|
435 | 469 | @pytest.mark.parametrize("invalid_size", [0, -5], ids=["zero", "negative"]) |
436 | 470 | def test_setting_invalid_page_size_should_be_ignored(table_widget, invalid_size: int): |
437 | | - """When the page size is set to an invalid number (<=0), the change should |
438 | | - be ignored.""" |
| 471 | + """ |
| 472 | + When the page size is set to an invalid number (<=0), the change should |
| 473 | + be ignored. |
| 474 | + """ |
439 | 475 | # Set the initial page to 2. |
440 | 476 | initial_size = table_widget.page_size |
441 | 477 | assert initial_size == 2 |
|
0 commit comments