12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
+ import typing
16
+
15
17
import pandas as pd
16
18
import pytest
17
19
@@ -61,11 +63,12 @@ def table_widget(paginated_bf_df: bf.dataframe.DataFrame):
61
63
Helper fixture to create a TableWidget instance with a fixed page size.
62
64
This reduces duplication across tests that use the same widget configuration.
63
65
"""
64
- from bigframes import display
66
+
67
+ from bigframes .display import TableWidget
65
68
66
69
with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
67
70
# Delay context manager cleanup of `max_rows` until after tests finish.
68
- yield display . TableWidget (paginated_bf_df )
71
+ yield TableWidget (paginated_bf_df )
69
72
70
73
71
74
@pytest .fixture (scope = "module" )
@@ -90,10 +93,10 @@ def small_bf_df(
90
93
@pytest .fixture
91
94
def small_widget (small_bf_df ):
92
95
"""Helper fixture for tests using a DataFrame smaller than the page size."""
93
- from bigframes import display
96
+ from bigframes . display import TableWidget
94
97
95
98
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 )
97
100
98
101
99
102
@pytest .fixture (scope = "module" )
@@ -135,10 +138,10 @@ def test_widget_initialization_should_calculate_total_row_count(
135
138
paginated_bf_df : bf .dataframe .DataFrame ,
136
139
):
137
140
"""A TableWidget should correctly calculate the total row count on creation."""
138
- from bigframes import display
141
+ from bigframes . display import TableWidget
139
142
140
143
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 )
142
145
143
146
assert widget .row_count == EXPECTED_ROW_COUNT
144
147
@@ -436,6 +439,107 @@ def test_widget_creation_should_load_css_for_rendering(table_widget):
436
439
assert ".bigframes-widget .footer" in css_content
437
440
438
441
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
+
439
543
# TODO(shuowei): Add tests for custom index and multiindex
440
544
# This may not be necessary for the SQL Cell use case but should be
441
545
# considered for completeness.
0 commit comments