1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15- from __future__ import annotations
16-
17- from unittest import mock
1815
1916import pandas as pd
2017import pytest
2118
2219import bigframes as bf
2320
21+ pytest .importorskip ("anywidget" )
22+
2423# Test constants to avoid change detector tests
2524EXPECTED_ROW_COUNT = 6
2625EXPECTED_PAGE_SIZE = 2
26+ EXPECTED_TOTAL_PAGES = 3
2727
2828
2929@pytest .fixture (scope = "module" )
@@ -62,7 +62,8 @@ def table_widget(paginated_bf_df: bf.dataframe.DataFrame):
6262 Helper fixture to create a TableWidget instance with a fixed page size.
6363 This reduces duplication across tests that use the same widget configuration.
6464 """
65- from bigframes .display .anywidget import TableWidget
65+
66+ from bigframes .display import TableWidget
6667
6768 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
6869 # Delay context manager cleanup of `max_rows` until after tests finish.
@@ -91,7 +92,7 @@ def small_bf_df(
9192@pytest .fixture
9293def small_widget (small_bf_df ):
9394 """Helper fixture for tests using a DataFrame smaller than the page size."""
94- from bigframes .display . anywidget import TableWidget
95+ from bigframes .display import TableWidget
9596
9697 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 5 ):
9798 yield TableWidget (small_bf_df )
@@ -110,19 +111,21 @@ def empty_bf_df(
110111 return session .read_pandas (empty_pandas_df )
111112
112113
113- @pytest .fixture (scope = "module" )
114- def json_df (session : bf .Session ) -> bf .dataframe .DataFrame :
115- """Create a DataFrame with a JSON column for testing."""
116- import bigframes .dtypes
114+ def mock_execute_result_with_params (
115+ self , schema , total_rows_val , arrow_batches_val , * args , ** kwargs
116+ ):
117+ """
118+ Mocks an execution result with configurable total_rows and arrow_batches.
119+ """
120+ from bigframes .session .executor import ExecuteResult
117121
118- pandas_df = pd .DataFrame (
119- {
120- "a" : [1 ],
121- "b" : ['{"c": 2, "d": 3}' ],
122- }
122+ return ExecuteResult (
123+ iter (arrow_batches_val ),
124+ schema = schema ,
125+ query_job = None ,
126+ total_bytes = None ,
127+ total_rows = total_rows_val ,
123128 )
124- pandas_df ["b" ] = pandas_df ["b" ].astype (bigframes .dtypes .JSON_DTYPE )
125- return session .read_pandas (pandas_df )
126129
127130
128131def _assert_html_matches_pandas_slice (
@@ -151,11 +154,10 @@ def test_widget_initialization_should_calculate_total_row_count(
151154 paginated_bf_df : bf .dataframe .DataFrame ,
152155):
153156 """A TableWidget should correctly calculate the total row count on creation."""
154- from bigframes .display . anywidget import TableWidget
157+ from bigframes .display import TableWidget
155158
156159 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
157160 widget = TableWidget (paginated_bf_df )
158- widget = TableWidget (paginated_bf_df )
159161
160162 assert widget .row_count == EXPECTED_ROW_COUNT
161163
@@ -266,7 +268,7 @@ def test_widget_pagination_should_work_with_custom_page_size(
266268 A widget should paginate correctly with a custom page size of 3.
267269 """
268270 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 3 ):
269- from bigframes .display . anywidget import TableWidget
271+ from bigframes .display import TableWidget
270272
271273 widget = TableWidget (paginated_bf_df )
272274 assert widget .page_size == 3
@@ -312,7 +314,7 @@ def test_widget_page_size_should_be_immutable_after_creation(
312314 by subsequent changes to global options.
313315 """
314316 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
315- from bigframes .display . anywidget import TableWidget
317+ from bigframes .display import TableWidget
316318
317319 widget = TableWidget (paginated_bf_df )
318320 assert widget .page_size == 2
@@ -331,7 +333,7 @@ def test_widget_page_size_should_be_immutable_after_creation(
331333def test_empty_widget_should_have_zero_row_count (empty_bf_df : bf .dataframe .DataFrame ):
332334 """Given an empty DataFrame, the widget's row count should be 0."""
333335 with bf .option_context ("display.repr_mode" , "anywidget" ):
334- from bigframes .display . anywidget import TableWidget
336+ from bigframes .display import TableWidget
335337
336338 widget = TableWidget (empty_bf_df )
337339
@@ -341,7 +343,7 @@ def test_empty_widget_should_have_zero_row_count(empty_bf_df: bf.dataframe.DataF
341343def test_empty_widget_should_render_table_headers (empty_bf_df : bf .dataframe .DataFrame ):
342344 """Given an empty DataFrame, the widget should still render table headers."""
343345 with bf .option_context ("display.repr_mode" , "anywidget" ):
344- from bigframes .display . anywidget import TableWidget
346+ from bigframes .display import TableWidget
345347
346348 widget = TableWidget (empty_bf_df )
347349
@@ -436,112 +438,21 @@ def test_setting_page_size_above_max_should_be_clamped(table_widget):
436438 assert table_widget .page_size == expected_clamped_size
437439
438440
439- @mock .patch ("bigframes.display.TableWidget" )
440- def test_sql_anywidget_mode (mock_table_widget , session : bf .Session ):
441- """
442- Test that a SQL query runs in anywidget mode.
443- """
444- sql = "SELECT * FROM `bigquery-public-data.usa_names.usa_1910_current` LIMIT 5"
445-
446- with bf .option_context ("display.repr_mode" , "anywidget" ):
447- df = session .read_gbq (sql )
448- # In a real environment, this would display a widget.
449- # For testing, we just want to make sure we're in the anywidget code path.
450- df ._repr_html_ ()
451- mock_table_widget .assert_called_once ()
452-
453-
454- @mock .patch ("IPython.display.display" )
455- def test_struct_column_anywidget_mode (mock_display , session : bf .Session ):
456- """
457- Test that a DataFrame with a STRUCT column is displayed in anywidget mode
458- and does not fall back to the deferred representation. This confirms that
459- anywidget can handle complex types without raising an exception that would
460- trigger the fallback mechanism.
461- """
462- pandas_df = pd .DataFrame (
463- {
464- "a" : [1 ],
465- "b" : [{"c" : 2 , "d" : 3 }],
466- }
467- )
468- bf_df = session .read_pandas (pandas_df )
469-
470- with bf .option_context ("display.repr_mode" , "anywidget" ):
471- with mock .patch (
472- "bigframes.dataframe.formatter.repr_query_job"
473- ) as mock_repr_query_job :
474- # Trigger the display logic.
475- result = bf_df ._repr_html_ ()
476-
477- # Assert that we did NOT fall back to the deferred representation.
478- mock_repr_query_job .assert_not_called ()
479-
480- widget = mock_display .call_args [0 ][0 ]
481- from bigframes .display .anywidget import TableWidget
482-
483- assert isinstance (widget , TableWidget )
484-
485- # Assert that the widget's html contains the struct
486- html = widget .table_html
487- assert "{'c': 2, 'd': 3}" in html
488-
489- # Assert that _repr_html_ returns an empty string
490- assert result == ""
491-
492-
493441def test_widget_creation_should_load_css_for_rendering (table_widget ):
494442 """
495- Test that the widget's CSS is loaded correctly.
443+ Given a TableWidget is created, when its resources are accessed,
444+ it should contain the CSS content required for styling.
496445 """
497- css_content = table_widget . _css
498- assert ".bigframes-widget .footer" in css_content
446+ # The table_widget fixture creates the widget.
447+ # No additional setup is needed.
499448
449+ # Access the CSS content.
450+ css_content = table_widget ._css
500451
501- @mock .patch ("IPython.display.display" )
502- def test_json_column_anywidget_mode (mock_display , json_df : bf .dataframe .DataFrame ):
503- """
504- Test that a DataFrame with a JSON column is displayed in anywidget mode
505- by converting JSON to string, and does not fall back to deferred representation.
506- """
507- with bf .option_context ("display.repr_mode" , "anywidget" ):
508- with mock .patch (
509- "bigframes.dataframe.formatter.repr_query_job"
510- ) as mock_repr_query_job :
511- result = json_df ._repr_html_ ()
512-
513- # Assert no fallback
514- mock_repr_query_job .assert_not_called ()
515-
516- # Assert TableWidget was created and displayed
517- mock_display .assert_called_once ()
518- widget = mock_display .call_args [0 ][0 ]
519- from bigframes .display .anywidget import TableWidget
520-
521- assert isinstance (widget , TableWidget )
522-
523- # Assert JSON was converted to string in the HTML
524- html = widget .table_html
525- assert "{"c":2,"d":3}" in html
526-
527- assert result == ""
528-
529-
530- def mock_execute_result_with_params (
531- self , schema , total_rows_val , arrow_batches_val , * args , ** kwargs
532- ):
533- """
534- Mocks an execution result with configurable total_rows and arrow_batches.
535- """
536- from bigframes .session .executor import ExecuteResult
537-
538- return ExecuteResult (
539- iter (arrow_batches_val ),
540- schema = schema ,
541- query_job = None ,
542- total_bytes = None ,
543- total_rows = total_rows_val ,
544- )
452+ # The content is a non-empty string containing a known selector.
453+ assert isinstance (css_content , str )
454+ assert len (css_content ) > 0
455+ assert ".bigframes-widget .footer" in css_content
545456
546457
547458def test_widget_row_count_should_be_immutable_after_creation (
@@ -552,7 +463,7 @@ def test_widget_row_count_should_be_immutable_after_creation(
552463 options are changed later, the widget's original row_count should remain
553464 unchanged.
554465 """
555- from bigframes .display . anywidget import TableWidget
466+ from bigframes .display import TableWidget
556467
557468 # Use a context manager to ensure the option is reset
558469 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
@@ -592,7 +503,7 @@ def test_widget_should_fallback_to_zero_rows_with_invalid_total_rows(
592503
593504 # Create the TableWidget under the error condition.
594505 with bf .option_context ("display.repr_mode" , "anywidget" ):
595- from bigframes .display . anywidget import TableWidget
506+ from bigframes .display import TableWidget
596507
597508 # The widget should handle the faulty data from the mock without crashing.
598509 widget = TableWidget (paginated_bf_df )
@@ -611,7 +522,7 @@ def test_widget_row_count_reflects_actual_data_available(
611522 Test that widget row_count reflects the actual data available,
612523 regardless of theoretical limits.
613524 """
614- from bigframes .display . anywidget import TableWidget
525+ from bigframes .display import TableWidget
615526
616527 # Set up display options that define a page size.
617528 with bf .option_context ("display.repr_mode" , "anywidget" , "display.max_rows" , 2 ):
@@ -626,38 +537,3 @@ def test_widget_row_count_reflects_actual_data_available(
626537# TODO(shuowei): Add tests for custom index and multiindex
627538# This may not be necessary for the SQL Cell use case but should be
628539# considered for completeness.
629-
630-
631- @pytest .fixture (scope = "module" )
632- def empty_json_df (session : bf .Session ) -> bf .dataframe .DataFrame :
633- """Create an empty DataFrame with a JSON column for testing."""
634- import bigframes .dtypes
635-
636- pandas_df = pd .DataFrame (
637- {
638- "a" : pd .Series (dtype = "int64" ),
639- "b" : pd .Series (dtype = bigframes .dtypes .JSON_DTYPE ),
640- }
641- )
642- return session .read_pandas (pandas_df )
643-
644-
645- def test_empty_widget_with_json_column (empty_json_df : bf .dataframe .DataFrame ):
646- """Given an empty DataFrame with a JSON column, the widget should render table headers."""
647- with bf .option_context ("display.repr_mode" , "anywidget" ):
648- from bigframes .display .anywidget import TableWidget
649-
650- widget = TableWidget (empty_json_df )
651- html = widget .table_html
652-
653- assert widget .row_count == 0
654- assert "<table" in html
655- assert "a" in html
656- assert "b" in html
657-
658-
659- def test_json_column_conversion_warning (json_df : bf .dataframe .DataFrame ):
660- """Test that a warning is shown when converting JSON columns."""
661- with bf .option_context ("display.repr_mode" , "anywidget" ):
662- with pytest .warns (UserWarning , match = "Converting JSON columns to strings" ):
663- json_df ._repr_html_ ()
0 commit comments