1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ import typing
16+
1517import pandas as pd
1618import pytest
1719
@@ -61,11 +63,12 @@ def table_widget(paginated_bf_df: bf.dataframe.DataFrame):
6163 Helper fixture to create a TableWidget instance with a fixed page size.
6264 This reduces duplication across tests that use the same widget configuration.
6365 """
64- from bigframes import display
66+
67+ from bigframes .display import TableWidget
6568
6669 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
6770 # Delay context manager cleanup of `max_rows` until after tests finish.
68- yield display . TableWidget (paginated_bf_df )
71+ yield TableWidget (paginated_bf_df )
6972
7073
7174@pytest .fixture (scope = "module" )
@@ -90,10 +93,10 @@ def small_bf_df(
9093@pytest .fixture
9194def small_widget (small_bf_df ):
9295 """Helper fixture for tests using a DataFrame smaller than the page size."""
93- from bigframes import display
96+ from bigframes . display import TableWidget
9497
9598 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 5 ):
96- yield display . TableWidget (small_bf_df )
99+ yield TableWidget (small_bf_df )
97100
98101
99102@pytest .fixture (scope = "module" )
@@ -135,10 +138,10 @@ def test_widget_initialization_should_calculate_total_row_count(
135138 paginated_bf_df : bf .dataframe .DataFrame ,
136139):
137140 """A TableWidget should correctly calculate the total row count on creation."""
138- from bigframes import display
141+ from bigframes . display import TableWidget
139142
140143 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
141- widget = display . TableWidget (paginated_bf_df )
144+ widget = TableWidget (paginated_bf_df )
142145
143146 assert widget .row_count == EXPECTED_ROW_COUNT
144147
@@ -436,6 +439,107 @@ def test_widget_creation_should_load_css_for_rendering(table_widget):
436439 assert ".bigframes-widget .footer" in css_content
437440
438441
442+ def test_widget_row_count_should_be_immutable_after_creation (
443+ paginated_bf_df : bf .dataframe .DataFrame ,
444+ ):
445+ """
446+ Given a widget created with a specific configuration when global display
447+ options are changed later, the widget's original row_count should remain
448+ unchanged.
449+ """
450+ from bigframes .display import TableWidget
451+
452+ with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
453+ widget = TableWidget (paginated_bf_df )
454+ initial_row_count = widget .row_count
455+ assert initial_row_count == EXPECTED_ROW_COUNT
456+
457+ # Change a global option that could influence row count
458+ bf .options .display .max_rows = 10
459+
460+ # The widget's row count was fixed at creation and should not change.
461+ assert widget .row_count == initial_row_count
462+
463+
464+ def test_widget_should_fallback_to_zero_rows_when_total_rows_is_none (
465+ paginated_bf_df : bf .dataframe .DataFrame , monkeypatch : pytest .MonkeyPatch
466+ ):
467+ """
468+ Given an internal component that fails to provide a total row count,
469+ when the widget is created, the row_count should safely fall back to 0.
470+ """
471+ from bigframes .core .blocks import PandasBatches
472+
473+ # Simulate an internal failure where total_rows returns None
474+ monkeypatch .setattr (PandasBatches , "total_rows" , property (lambda self : None ))
475+
476+ with bf .option_context ("display.repr_mode" , "anywidget" ):
477+ from bigframes .display import TableWidget
478+
479+ widget = TableWidget (paginated_bf_df )
480+
481+ assert widget .row_count == 0
482+
483+
484+ def test_widget_should_fallback_to_zero_rows_when_batches_are_invalid_type (
485+ paginated_bf_df : bf .dataframe .DataFrame , monkeypatch : pytest .MonkeyPatch
486+ ):
487+ """
488+ Given an internal component that returns an unexpected data type,
489+ when the widget is created, the row_count should safely fall back to 0.
490+ """
491+ # Simulate internal method returning an unexpected type (a simple iterator)
492+ def mock_to_pandas_batches (self , ** kwargs ):
493+ return iter ([paginated_bf_df .to_pandas ().iloc [:2 ]])
494+
495+ monkeypatch .setattr (
496+ "bigframes.dataframe.DataFrame.to_pandas_batches" , mock_to_pandas_batches
497+ )
498+
499+ with bf .option_context ("display.repr_mode" , "anywidget" ):
500+ from bigframes .display import TableWidget
501+
502+ widget = TableWidget (paginated_bf_df )
503+
504+ assert widget .row_count == 0
505+
506+
507+ @pytest .mark .parametrize (
508+ "max_results, expected_rows" ,
509+ [
510+ (None , EXPECTED_ROW_COUNT ),
511+ (3 , 3 ),
512+ (10 , EXPECTED_ROW_COUNT ),
513+ ],
514+ ids = ["no_limit" , "limit_is_less_than_total" , "limit_is_greater_than_total" ],
515+ )
516+ def test_widget_row_count_should_respect_max_results_on_creation (
517+ paginated_bf_df : bf .dataframe .DataFrame ,
518+ max_results : typing .Optional [int ],
519+ expected_rows : int ,
520+ ):
521+ """
522+ Given a max_results value, when a TableWidget is created with custom batches,
523+ its row_count should be correctly capped by that value.
524+ """
525+ with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
526+ from bigframes .core .blocks import PandasBatches
527+ from bigframes .display import TableWidget
528+
529+ widget = TableWidget (paginated_bf_df )
530+
531+ # Override batches with max_results to test the behavior
532+ widget ._batches = paginated_bf_df .to_pandas_batches (
533+ page_size = widget .page_size , max_results = max_results
534+ )
535+
536+ # Re-apply thelogic to update row_count
537+ if isinstance (widget ._batches , PandasBatches ):
538+ widget .row_count = widget ._batches .total_rows or 0
539+
540+ assert widget .row_count == expected_rows
541+
542+
439543# TODO(shuowei): Add tests for custom index and multiindex
440544# This may not be necessary for the SQL Cell use case but should be
441545# considered for completeness.
0 commit comments