task: metadata: install: pip install -e .[dev]
patch: diff --git a/LICENSE b/LICENSE
index 499aa22..3f76a9e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2019-2024 Marc Wouts
+Copyright (c) 2019-2025 Marc Wouts
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/docs/changelog.md b/docs/changelog.md
index 2d03828..5ace749 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,6 +1,13 @@
 ITables ChangeLog
 =================
 
+2.2.5 (2025-02-22)
+------------------
+
+**Fixed**
+- We have removed an erroneous assertion and added tests on the warnings issued when selected rows are not displayed ([[#351](https://github.com/mwouts/itables/issues/351)])
+
+
 2.2.4 (2024-12-07)
 ------------------
 
diff --git a/src/itables/downsample.py b/src/itables/downsample.py
index 2fdec64..c9f5ce5 100644
--- a/src/itables/downsample.py
+++ b/src/itables/downsample.py
@@ -96,6 +96,7 @@ def _downsample(df, max_rows=0, max_columns=0, max_bytes=0, target_aspect_ratio=
     if len(df) > max_rows > 0:
         second_half = max_rows // 2
         first_half = max_rows - second_half
+        assert first_half >= second_half
         if second_half:
             try:
                 df = pd.concat((df.iloc[:first_half], df.iloc[-second_half:]))
@@ -110,6 +111,7 @@ def _downsample(df, max_rows=0, max_columns=0, max_bytes=0, target_aspect_ratio=
     if len(df.columns) > max_columns > 0:
         second_half = max_columns // 2
         first_half = max_columns - second_half
+        assert first_half >= second_half
         if second_half:
             try:
                 df = pd.concat(
diff --git a/src/itables/javascript.py b/src/itables/javascript.py
index cf69b55..5f9def1 100644
--- a/src/itables/javascript.py
+++ b/src/itables/javascript.py
@@ -599,11 +599,12 @@ def warn_if_selected_rows_are_not_visible(
     if full_row_count == data_row_count:
         return selected_rows
 
-    half = data_row_count // 2
-    assert data_row_count == 2 * half, data_row_count
+    second_half = data_row_count // 2
+    first_half = data_row_count - second_half
+    assert first_half >= second_half
 
-    bottom_limit = half
-    top_limit = full_row_count - half
+    bottom_limit = first_half
+    top_limit = full_row_count - second_half
 
     if warn_on_selected_rows_not_rendered and any(
         bottom_limit <= i < top_limit for i in selected_rows
diff --git a/src/itables/version.py b/src/itables/version.py
index 3468a8f..cd228ab 100644
--- a/src/itables/version.py
+++ b/src/itables/version.py
@@ -1,3 +1,3 @@
 """ITables' version number"""
 
-__version__ = "2.2.4"
+__version__ = "2.2.5"

problem_statement: AssertionError because downsampled row count is odd
If the row selection is enabled and the table is downsampled, then the number of downsampled rows must be even. Otherwise `warn_if_selected_rows_are_not_visible` throws an assertion error.

I don't see a way to control the number of downsampled rows (except by using `maxRows` instead of `maxBytes`). And even the assertion error is opaque. So effectively `maxBytes` can't safely be used with row selection.


```{python}
import itables
from itables.sample_dfs import get_dict_of_test_dfs
from itables.shiny import DT

print(itables.__version__)  # 2.2.4

df = get_dict_of_test_dfs()['wide']

DT(df, maxBytes = 1e2, selected_rows = [])
```

```
2.2.4
Traceback (most recent call last):
  File "c:\Users\bevilla\projects\satk\tmp2.py", line 9, in <module>
    DT(df, maxBytes = 1e2, selected_rows = [])
  File "C:\Python311\satk_dev_venv\Lib\site-packages\itables\shiny.py", line 35, in DT
    html = to_html_datatable(
           ^^^^^^^^^^^^^^^^^^
  File "C:\Python311\satk_dev_venv\Lib\site-packages\itables\javascript.py", line 366, in to_html_datatable
    kwargs["selected_rows"] = warn_if_selected_rows_are_not_visible(
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\satk_dev_venv\Lib\site-packages\itables\javascript.py", line 603, in warn_if_selected_rows_are_not_visible
    assert data_row_count == 2 * half, data_row_count
AssertionError: 3
```
repo: mwouts/itables
test_cmd: pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
test_patch: diff --git a/tests/test_downsample.py b/tests/test_downsample.py
index 5fa2ad8..2fd6d56 100644
--- a/tests/test_downsample.py
+++ b/tests/test_downsample.py
@@ -117,3 +117,10 @@ def test_df_with_many_columns_is_downsampled_preferentially_on_columns(df, max_b
     else:
         # aspect ratio is close to 1
         assert 0.5 < len(dn) / len(dn.columns) < 2
+
+
+def test_downsample_to_odd_number_of_rows():
+    df = pd.DataFrame({"x": range(17)})
+    dn, _ = downsample(df, max_rows=3)
+    assert len(dn) == 3
+    assert dn["x"].to_list() == [0, 1, 16]
diff --git a/tests/test_shiny.py b/tests/test_shiny.py
new file mode 100644
index 0000000..fcd947b
--- /dev/null
+++ b/tests/test_shiny.py
@@ -0,0 +1,29 @@
+import warnings
+
+import pandas as pd
+import pytest
+
+from itables.downsample import downsample
+from itables.shiny import DT
+
+
+def test_select_on_downsampled_df():
+    """
+    When a DF of 17 rows is downsampled to 3 rows,
+    we can only select rows 0, 1, 16
+    """
+    df = pd.DataFrame({"x": range(17)})
+    dn, _ = downsample(df, max_rows=3)
+    assert len(dn) == 3
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("error")
+        DT(df, maxRows=3, selected_rows=[0, 1, 16])
+
+    for row in [-1, 17]:
+        with pytest.raises(IndexError, match="Selected rows out of range"):
+            DT(df, maxRows=3, selected_rows=[row])
+
+    for row in [2, 15]:
+        with pytest.warns(match="no row with index between 2 and 15 can be selected"):
+            DT(df, maxRows=3, selected_rows=[row])