From 8de18eb3c603772ec551b1501084d55b0da493e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 6 Jun 2026 16:00:29 +0100 Subject: [PATCH 01/10] Use pytest --- test/dbcore/test_sort.py | 194 +++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 99 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index d87784d199..f644ecbd93 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -17,6 +17,8 @@ import os from unittest.mock import patch +import pytest + import beets.library from beets import config, util from beets.dbcore import types @@ -24,98 +26,93 @@ from beets.dbcore.sort import FixedFieldSort, MultipleSort, SlowFieldSort from beets.library import Album from beets.test import _common -from beets.test.helper import BeetsTestCase def abs_test_path(path: str) -> str: return os.fsdecode(util.normpath(path)) -# A test case class providing a library with some dummy data and some -# assertions involving that data. -class DummyDataTestCase(BeetsTestCase): - def setUp(self): - super().setUp() +@pytest.fixture(scope="class") +def helper(class_helper): + return class_helper - albums = [ - Album( - album="Album A", - genres=["Rock"], - year=2001, - flex1="Flex1-1", - flex2="Flex2-A", - albumartist="Foo", - ), - Album( - album="Album B", - genres=["Rock"], - year=2001, - flex1="Flex1-2", - flex2="Flex2-A", - albumartist="Bar", - ), + +@pytest.fixture(autouse=True, scope="class") +def setup_library(request: pytest.FixtureRequest, helper): + album_ids = [ + helper.lib.add( Album( - album="Album C", - genres=["Jazz"], - year=2005, - flex1="Flex1-1", - flex2="Flex2-B", - albumartist="Baz", - ), - ] - for album in albums: - self.lib.add(album) - - items = [_common.item() for _ in range(4)] - items[0].title = "Foo bar" - items[0].artist = "One" - items[0].album = "Baz" - items[0].year = 2001 - items[0].comp = True - items[0].flex1 = "Flex1-0" - items[0].flex2 = "Flex2-A" - items[0].album_id = albums[0].id - items[0].artist_sort = None - items[0].path = abs_test_path("/path0.mp3") - items[0].track = 1 - items[1].title = "Baz qux" - items[1].artist = "Two" - items[1].album = "Baz" - items[1].year = 2002 - items[1].comp = True - items[1].flex1 = "Flex1-1" - items[1].flex2 = "Flex2-A" - items[1].album_id = albums[0].id - items[1].artist_sort = None - items[1].path = abs_test_path("/patH1.mp3") - items[1].track = 2 - items[2].title = "Beets 4 eva" - items[2].artist = "Three" - items[2].album = "Foo" - items[2].year = 2003 - items[2].comp = False - items[2].flex1 = "Flex1-2" - items[2].flex2 = "Flex1-B" - items[2].album_id = albums[1].id - items[2].artist_sort = None - items[2].path = abs_test_path("/paTH2.mp3") - items[2].track = 3 - items[3].title = "Beets 4 eva" - items[3].artist = "Three" - items[3].album = "Foo2" - items[3].year = 2004 - items[3].comp = False - items[3].flex1 = "Flex1-2" - items[3].flex2 = "Flex1-C" - items[3].album_id = albums[2].id - items[3].artist_sort = None - items[3].path = abs_test_path("/PATH3.mp3") - items[3].track = 4 - for item in items: - self.lib.add(item) - - -class SortFixedFieldTest(DummyDataTestCase): + album=album, + genres=genres, + year=year, + flex1=flex1, + flex2=flex2, + albumartist=albumartist, + ) + ) + for album, genres, year, flex1, flex2, albumartist in ( + ["Album A", ["Rock"], 2001, "Flex1-1", "Flex2-A", "Foo"], + ["Album B", ["Rock"], 2001, "Flex1-2", "Flex2-A", "Bar"], + ["Album C", ["Jazz"], 2005, "Flex1-1", "Flex2-B", "Baz"], + ) + ] + + for item in [ + _common.item( + title="Foo bar", + artist="One", + album="Baz", + year=2001, + comp=True, + flex1="Flex1-0", + flex2="Flex2-A", + album_id=album_ids[0], + path=abs_test_path("/path0.mp3"), + track=1, + ), + _common.item( + title="Baz qux", + artist="Two", + album="Baz", + year=2002, + comp=True, + flex1="Flex1-1", + flex2="Flex2-A", + album_id=album_ids[0], + path=abs_test_path("/patH1.mp3"), + track=2, + ), + _common.item( + title="Beets 4 eva", + artist="Three", + album="Foo", + year=2003, + comp=False, + flex1="Flex1-2", + flex2="Flex1-B", + album_id=album_ids[1], + path=abs_test_path("/paTH2.mp3"), + track=3, + ), + _common.item( + title="Beets 4 eva", + artist="Three", + album="Foo2", + year=2004, + comp=False, + flex1="Flex1-2", + flex2="Flex1-C", + album_id=album_ids[2], + path=abs_test_path("/PATH3.mp3"), + track=4, + ), + ]: + helper.lib.add(item) + + request.cls.lib = helper.lib + + +class TestSortFixedField: def test_sort_asc(self): q = "" sort = FixedFieldSort("year", True) @@ -169,7 +166,7 @@ def test_sort_path_field(self): assert results[3]["path"] == util.normpath("/PATH3.mp3") -class SortFlexFieldTest(DummyDataTestCase): +class TestSortFlexField: def test_sort_asc(self): q = "" sort = SlowFieldSort("flex1", True) @@ -216,7 +213,7 @@ def test_sort_two_field(self): assert r1.id == r2.id -class SortAlbumFixedFieldTest(DummyDataTestCase): +class TestSortAlbumFixedField: def test_sort_asc(self): q = "" sort = FixedFieldSort("year", True) @@ -261,7 +258,7 @@ def test_sort_two_field_asc(self): assert r1.id == r2.id -class SortAlbumFlexFieldTest(DummyDataTestCase): +class TestSortAlbumFlexField: def test_sort_asc(self): q = "" sort = SlowFieldSort("flex1", True) @@ -306,7 +303,7 @@ def test_sort_two_field_asc(self): assert r1.id == r2.id -class SortAlbumComputedFieldTest(DummyDataTestCase): +class TestSortAlbumComputedField: def test_sort_asc(self): q = "" sort = SlowFieldSort("path", True) @@ -332,7 +329,7 @@ def test_sort_desc(self): assert r1.id == r2.id -class SortCombinedFieldTest(DummyDataTestCase): +class TestSortCombinedField: def test_computed_first(self): q = "" s1 = SlowFieldSort("path", True) @@ -365,7 +362,7 @@ def test_computed_second(self): assert r1.id == r2.id -class ConfigSortTest(DummyDataTestCase): +class TestConfigSort: def test_default_sort_item(self): results = list(self.lib.items()) assert results[0].artist < results[1].artist @@ -385,14 +382,13 @@ def test_config_opposite_sort_album(self): assert results[0].albumartist > results[1].albumartist -class CaseSensitivityTest(DummyDataTestCase): +class TestCaseSensitivity: """If case_insensitive is false, lower-case values should be placed after all upper-case values. E.g., `Foo Qux bar` """ - def setUp(self): - super().setUp() - + @pytest.fixture(autouse=True) + def setup(self, helper): album = Album( album="album", genres=["alternative"], @@ -401,7 +397,7 @@ def setUp(self): flex2="flex2-A", albumartist="bar", ) - self.lib.add(album) + helper.lib.add(album) item = _common.item() item.title = "another" @@ -414,15 +410,15 @@ def setUp(self): item.album_id = album.id item.artist_sort = None item.track = 10 - self.lib.add(item) + helper.lib.add(item) self.new_album = album self.new_item = item - def tearDown(self): + yield + self.new_item.remove(delete=True) self.new_album.remove(delete=True) - super().tearDown() def test_smart_artist_case_insensitive(self): config["sort_case_insensitive"] = True @@ -478,7 +474,7 @@ def test_case_sensitive_only_affects_text(self): assert results[-1].track == 10 -class NonExistingFieldTest(DummyDataTestCase): +class TestNonExistingField: """Test sorting by non-existing fields""" def test_non_existing_fields_not_fail(self): From 89d44a52050997c385f63904d046c8b6c20ce15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 6 Jun 2026 18:05:55 +0100 Subject: [PATCH 02/10] Only set required fields --- test/dbcore/test_sort.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index f644ecbd93..93a1226f99 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -59,11 +59,9 @@ def setup_library(request: pytest.FixtureRequest, helper): for item in [ _common.item( - title="Foo bar", artist="One", - album="Baz", + album="Album A", year=2001, - comp=True, flex1="Flex1-0", flex2="Flex2-A", album_id=album_ids[0], @@ -71,11 +69,9 @@ def setup_library(request: pytest.FixtureRequest, helper): track=1, ), _common.item( - title="Baz qux", artist="Two", - album="Baz", + album="Album A", year=2002, - comp=True, flex1="Flex1-1", flex2="Flex2-A", album_id=album_ids[0], @@ -83,11 +79,9 @@ def setup_library(request: pytest.FixtureRequest, helper): track=2, ), _common.item( - title="Beets 4 eva", artist="Three", - album="Foo", + album="Album B", year=2003, - comp=False, flex1="Flex1-2", flex2="Flex1-B", album_id=album_ids[1], @@ -95,11 +89,9 @@ def setup_library(request: pytest.FixtureRequest, helper): track=3, ), _common.item( - title="Beets 4 eva", artist="Three", - album="Foo2", + album="Album C", year=2004, - comp=False, flex1="Flex1-2", flex2="Flex1-C", album_id=album_ids[2], @@ -147,8 +139,8 @@ def test_sort_two_field_asc(self): results = self.lib.items(q, sort) assert results[0]["album"] <= results[1]["album"] assert results[1]["album"] <= results[2]["album"] - assert results[0]["album"] == "Baz" - assert results[1]["album"] == "Baz" + assert results[0]["album"] == "Album A" + assert results[1]["album"] == "Album A" assert results[0]["year"] <= results[1]["year"] # same thing with query string q = "album+ year+" From 4e66b5ec340b8756dfa48ca632c80cf3e10a26f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 6 Jun 2026 17:20:59 +0100 Subject: [PATCH 03/10] Parametrize ascending sort coverage - Consolidate duplicate ascending sort tests across item and album fixed and flexible fields. --- test/dbcore/test_sort.py | 101 ++++++++++++++------------------------- 1 file changed, 36 insertions(+), 65 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index 93a1226f99..c8f3d17a13 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -24,9 +24,11 @@ from beets.dbcore import types from beets.dbcore.query import TrueQuery from beets.dbcore.sort import FixedFieldSort, MultipleSort, SlowFieldSort -from beets.library import Album +from beets.library import Album, Item from beets.test import _common +_p = pytest.param + def abs_test_path(path: str) -> str: return os.fsdecode(util.normpath(path)) @@ -50,15 +52,17 @@ def setup_library(request: pytest.FixtureRequest, helper): albumartist=albumartist, ) ) - for album, genres, year, flex1, flex2, albumartist in ( - ["Album A", ["Rock"], 2001, "Flex1-1", "Flex2-A", "Foo"], - ["Album B", ["Rock"], 2001, "Flex1-2", "Flex2-A", "Bar"], - ["Album C", ["Jazz"], 2005, "Flex1-1", "Flex2-B", "Baz"], + for id_, album, genres, year, flex1, flex2, albumartist in ( + [1, "Album A", ["Rock"], 2001, "Flex1-1", "Flex2-A", "Foo"], + [2, "Album B", ["Rock"], 2001, "Flex1-2", "Flex2-A", "Bar"], + [3, "Album C", ["Jazz"], 2005, "Flex1-1", "Flex2-B", "Baz"], ) ] for item in [ _common.item( + id=1, + title="first", artist="One", album="Album A", year=2001, @@ -69,6 +73,8 @@ def setup_library(request: pytest.FixtureRequest, helper): track=1, ), _common.item( + id=2, + title="second", artist="Two", album="Album A", year=2002, @@ -79,6 +85,8 @@ def setup_library(request: pytest.FixtureRequest, helper): track=2, ), _common.item( + id=3, + title="third", artist="Three", album="Album B", year=2003, @@ -89,6 +97,8 @@ def setup_library(request: pytest.FixtureRequest, helper): track=3, ), _common.item( + id=4, + title="fourth", artist="Three", album="Album C", year=2004, @@ -104,19 +114,28 @@ def setup_library(request: pytest.FixtureRequest, helper): request.cls.lib = helper.lib -class TestSortFixedField: - def test_sort_asc(self): - q = "" - sort = FixedFieldSort("year", True) - results = self.lib.items(q, sort) - assert results[0]["year"] <= results[1]["year"] - assert results[0]["year"] == 2001 - # same thing with query string - q = "year+" - results2 = self.lib.items(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id +class TestSort: + @pytest.mark.parametrize( + "model,sort,expected_ids", + [ + _p(Album, FixedFieldSort("year", True), [1, 2, 3], id="album-fixed"), + _p(Item, FixedFieldSort("year", True), [1, 2, 3, 4], id="item-fixed"), + _p(Album, SlowFieldSort("flex1", True), [1, 3, 2], id="album-flex"), + _p(Item, SlowFieldSort("flex1", True), [1, 2, 3, 4], id="item-flex"), + _p(Album, SlowFieldSort("path", True), [1, 2, 3], id="album-calculated"), + ], + ) # fmt: skip + def test_sort_asc(self, model, sort, expected_ids): + results = self.lib._fetch(model, "", sort) + assert [r.id for r in results] == expected_ids + # same thing with qery string + sort_q = f"{sort.field}{'+' if sort.ascending else '-'}" + results2 = self.lib._fetch(model, sort_q, None) + assert [r.id for r in results2] == expected_ids + + +class TestSortFixedField: def test_sort_desc(self): q = "" sort = FixedFieldSort("year", False) @@ -159,18 +178,6 @@ def test_sort_path_field(self): class TestSortFlexField: - def test_sort_asc(self): - q = "" - sort = SlowFieldSort("flex1", True) - results = self.lib.items(q, sort) - assert results[0]["flex1"] <= results[1]["flex1"] - assert results[0]["flex1"] == "Flex1-0" - # same thing with query string - q = "flex1+" - results2 = self.lib.items(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_desc(self): q = "" sort = SlowFieldSort("flex1", False) @@ -206,18 +213,6 @@ def test_sort_two_field(self): class TestSortAlbumFixedField: - def test_sort_asc(self): - q = "" - sort = FixedFieldSort("year", True) - results = self.lib.albums(q, sort) - assert results[0]["year"] <= results[1]["year"] - assert results[0]["year"] == 2001 - # same thing with query string - q = "year+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_desc(self): q = "" sort = FixedFieldSort("year", False) @@ -251,18 +246,6 @@ def test_sort_two_field_asc(self): class TestSortAlbumFlexField: - def test_sort_asc(self): - q = "" - sort = SlowFieldSort("flex1", True) - results = self.lib.albums(q, sort) - assert results[0]["flex1"] <= results[1]["flex1"] - assert results[1]["flex1"] <= results[2]["flex1"] - # same thing with query string - q = "flex1+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_desc(self): q = "" sort = SlowFieldSort("flex1", False) @@ -296,18 +279,6 @@ def test_sort_two_field_asc(self): class TestSortAlbumComputedField: - def test_sort_asc(self): - q = "" - sort = SlowFieldSort("path", True) - results = self.lib.albums(q, sort) - assert results[0]["path"] <= results[1]["path"] - assert results[1]["path"] <= results[2]["path"] - # same thing with query string - q = "path+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_desc(self): q = "" sort = SlowFieldSort("path", False) From f0c861fb9238dbe44136c2f219e7732dbde842ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 6 Jun 2026 18:44:49 +0100 Subject: [PATCH 04/10] Parametrize descending sort coverage --- test/dbcore/test_sort.py | 71 ++++------------------------------------ 1 file changed, 6 insertions(+), 65 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index c8f3d17a13..20e14b9654 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -123,9 +123,14 @@ class TestSort: _p(Album, SlowFieldSort("flex1", True), [1, 3, 2], id="album-flex"), _p(Item, SlowFieldSort("flex1", True), [1, 2, 3, 4], id="item-flex"), _p(Album, SlowFieldSort("path", True), [1, 2, 3], id="album-calculated"), + _p(Album, FixedFieldSort("year", False), [3, 1, 2], id="album-fixed-desc"), + _p(Item, FixedFieldSort("year", False), [4, 3, 2, 1], id="item-fixed-desc"), + _p(Album, SlowFieldSort("flex1", False), [2, 1, 3], id="album-flex-desc"), + _p(Item, SlowFieldSort("flex1", False), [3, 4, 2, 1], id="item-flex-desc"), + _p(Album, SlowFieldSort("path", False), [1, 2, 3], id="album-calculated-desc"), ], ) # fmt: skip - def test_sort_asc(self, model, sort, expected_ids): + def test_sort(self, model, sort, expected_ids): results = self.lib._fetch(model, "", sort) assert [r.id for r in results] == expected_ids @@ -136,18 +141,6 @@ def test_sort_asc(self, model, sort, expected_ids): class TestSortFixedField: - def test_sort_desc(self): - q = "" - sort = FixedFieldSort("year", False) - results = self.lib.items(q, sort) - assert results[0]["year"] >= results[1]["year"] - assert results[0]["year"] == 2004 - # same thing with query string - q = "year-" - results2 = self.lib.items(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_two_field_asc(self): q = "" s1 = FixedFieldSort("album", True) @@ -178,20 +171,6 @@ def test_sort_path_field(self): class TestSortFlexField: - def test_sort_desc(self): - q = "" - sort = SlowFieldSort("flex1", False) - results = self.lib.items(q, sort) - assert results[0]["flex1"] >= results[1]["flex1"] - assert results[1]["flex1"] >= results[2]["flex1"] - assert results[2]["flex1"] >= results[3]["flex1"] - assert results[0]["flex1"] == "Flex1-2" - # same thing with query string - q = "flex1-" - results2 = self.lib.items(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_two_field(self): q = "" s1 = SlowFieldSort("flex2", False) @@ -213,18 +192,6 @@ def test_sort_two_field(self): class TestSortAlbumFixedField: - def test_sort_desc(self): - q = "" - sort = FixedFieldSort("year", False) - results = self.lib.albums(q, sort) - assert results[0]["year"] >= results[1]["year"] - assert results[0]["year"] == 2005 - # same thing with query string - q = "year-" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_two_field_asc(self): q = "" s1 = FixedFieldSort("genres", True) @@ -246,18 +213,6 @@ def test_sort_two_field_asc(self): class TestSortAlbumFlexField: - def test_sort_desc(self): - q = "" - sort = SlowFieldSort("flex1", False) - results = self.lib.albums(q, sort) - assert results[0]["flex1"] >= results[1]["flex1"] - assert results[1]["flex1"] >= results[2]["flex1"] - # same thing with query string - q = "flex1-" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_two_field_asc(self): q = "" s1 = SlowFieldSort("flex2", True) @@ -278,20 +233,6 @@ def test_sort_two_field_asc(self): assert r1.id == r2.id -class TestSortAlbumComputedField: - def test_sort_desc(self): - q = "" - sort = SlowFieldSort("path", False) - results = self.lib.albums(q, sort) - assert results[0]["path"] >= results[1]["path"] - assert results[1]["path"] >= results[2]["path"] - # same thing with query string - q = "path-" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - - class TestSortCombinedField: def test_computed_first(self): q = "" From 86980fb4b177293b7ef7477b00f94ae9310e0fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 6 Jun 2026 19:26:44 +0100 Subject: [PATCH 05/10] Consolidate sort query tests - Replace duplicate direct MultipleSort checks with parametrized query-string cases covering single-field, multi-field, fixed, flex, and computed sort behavior. - Reduce test repetition while keeping the same sort behavior coverage. --- test/dbcore/test_sort.py | 156 ++++++--------------------------------- 1 file changed, 21 insertions(+), 135 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index 20e14b9654..7744fee3e2 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -23,7 +23,7 @@ from beets import config, util from beets.dbcore import types from beets.dbcore.query import TrueQuery -from beets.dbcore.sort import FixedFieldSort, MultipleSort, SlowFieldSort +from beets.dbcore.sort import FixedFieldSort, SlowFieldSort from beets.library import Album, Item from beets.test import _common @@ -54,7 +54,7 @@ def setup_library(request: pytest.FixtureRequest, helper): ) for id_, album, genres, year, flex1, flex2, albumartist in ( [1, "Album A", ["Rock"], 2001, "Flex1-1", "Flex2-A", "Foo"], - [2, "Album B", ["Rock"], 2001, "Flex1-2", "Flex2-A", "Bar"], + [2, "Album B", ["Rock"], 2002, "Flex1-2", "Flex2-A", "Bar"], [3, "Album C", ["Jazz"], 2005, "Flex1-1", "Flex2-B", "Baz"], ) ] @@ -116,50 +116,32 @@ def setup_library(request: pytest.FixtureRequest, helper): class TestSort: @pytest.mark.parametrize( - "model,sort,expected_ids", + "model,query,expected_ids", [ - _p(Album, FixedFieldSort("year", True), [1, 2, 3], id="album-fixed"), - _p(Item, FixedFieldSort("year", True), [1, 2, 3, 4], id="item-fixed"), - _p(Album, SlowFieldSort("flex1", True), [1, 3, 2], id="album-flex"), - _p(Item, SlowFieldSort("flex1", True), [1, 2, 3, 4], id="item-flex"), - _p(Album, SlowFieldSort("path", True), [1, 2, 3], id="album-calculated"), - _p(Album, FixedFieldSort("year", False), [3, 1, 2], id="album-fixed-desc"), - _p(Item, FixedFieldSort("year", False), [4, 3, 2, 1], id="item-fixed-desc"), - _p(Album, SlowFieldSort("flex1", False), [2, 1, 3], id="album-flex-desc"), - _p(Item, SlowFieldSort("flex1", False), [3, 4, 2, 1], id="item-flex-desc"), - _p(Album, SlowFieldSort("path", False), [1, 2, 3], id="album-calculated-desc"), + _p(Album, "year+", [1, 2, 3], id="album-fixed"), + _p(Album, "flex1+", [1, 3, 2], id="album-flex"), + _p(Album, "path+", [1, 2, 3], id="album-calculated"), + _p(Album, "year-", [3, 2, 1], id="album-fixed-desc"), + _p(Album, "flex1-", [2, 1, 3], id="album-flex-desc"), + _p(Album, "path-", [1, 2, 3], id="album-calculated-desc"), + _p(Album, "genres+ album+", [3, 1, 2], id="multi-album-fixed-field-asc"), + _p(Album, "flex2+ flex1+", [1, 2, 3], id="multi-album-flex-field-asc"), + _p(Album, "path+ year+", [1, 2, 3], id="computed"), + _p(Album, "year+ path+", [1, 2, 3], id="computed-reverse"), + _p(Item, "year+", [1, 2, 3, 4], id="item-fixed"), + _p(Item, "flex1+", [1, 2, 3, 4], id="item-flex"), + _p(Item, "year-", [4, 3, 2, 1], id="item-fixed-desc"), + _p(Item, "flex1-", [3, 4, 2, 1], id="item-flex-desc"), + _p(Item, "album+ year+", [1, 2, 3, 4], id="multi-item-fixed-field-asc"), + _p(Item, "flex2- flex1+", [1, 2, 4, 3], id="multi-flex-field-mixed"), ], ) # fmt: skip - def test_sort(self, model, sort, expected_ids): - results = self.lib._fetch(model, "", sort) + def test_sort(self, model, query, expected_ids): + results = self.lib._fetch(model, query, None) assert [r.id for r in results] == expected_ids - # same thing with qery string - sort_q = f"{sort.field}{'+' if sort.ascending else '-'}" - results2 = self.lib._fetch(model, sort_q, None) - assert [r.id for r in results2] == expected_ids - class TestSortFixedField: - def test_sort_two_field_asc(self): - q = "" - s1 = FixedFieldSort("album", True) - s2 = FixedFieldSort("year", True) - sort = MultipleSort() - sort.add_sort(s1) - sort.add_sort(s2) - results = self.lib.items(q, sort) - assert results[0]["album"] <= results[1]["album"] - assert results[1]["album"] <= results[2]["album"] - assert results[0]["album"] == "Album A" - assert results[1]["album"] == "Album A" - assert results[0]["year"] <= results[1]["year"] - # same thing with query string - q = "album+ year+" - results2 = self.lib.items(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - def test_sort_path_field(self): q = "" sort = FixedFieldSort("path", True) @@ -170,102 +152,6 @@ def test_sort_path_field(self): assert results[3]["path"] == util.normpath("/PATH3.mp3") -class TestSortFlexField: - def test_sort_two_field(self): - q = "" - s1 = SlowFieldSort("flex2", False) - s2 = SlowFieldSort("flex1", True) - sort = MultipleSort() - sort.add_sort(s1) - sort.add_sort(s2) - results = self.lib.items(q, sort) - assert results[0]["flex2"] >= results[1]["flex2"] - assert results[1]["flex2"] >= results[2]["flex2"] - assert results[0]["flex2"] == "Flex2-A" - assert results[1]["flex2"] == "Flex2-A" - assert results[0]["flex1"] <= results[1]["flex1"] - # same thing with query string - q = "flex2- flex1+" - results2 = self.lib.items(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - - -class TestSortAlbumFixedField: - def test_sort_two_field_asc(self): - q = "" - s1 = FixedFieldSort("genres", True) - s2 = FixedFieldSort("album", True) - sort = MultipleSort() - sort.add_sort(s1) - sort.add_sort(s2) - results = self.lib.albums(q, sort) - assert results[0]["genres"] <= results[1]["genres"] - assert results[1]["genres"] <= results[2]["genres"] - assert results[1]["genres"] == ["Rock"] - assert results[2]["genres"] == ["Rock"] - assert results[1]["album"] <= results[2]["album"] - # same thing with query string - q = "genres+ album+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - - -class TestSortAlbumFlexField: - def test_sort_two_field_asc(self): - q = "" - s1 = SlowFieldSort("flex2", True) - s2 = SlowFieldSort("flex1", True) - sort = MultipleSort() - sort.add_sort(s1) - sort.add_sort(s2) - results = self.lib.albums(q, sort) - assert results[0]["flex2"] <= results[1]["flex2"] - assert results[1]["flex2"] <= results[2]["flex2"] - assert results[0]["flex2"] == "Flex2-A" - assert results[1]["flex2"] == "Flex2-A" - assert results[0]["flex1"] <= results[1]["flex1"] - # same thing with query string - q = "flex2+ flex1+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - - -class TestSortCombinedField: - def test_computed_first(self): - q = "" - s1 = SlowFieldSort("path", True) - s2 = FixedFieldSort("year", True) - sort = MultipleSort() - sort.add_sort(s1) - sort.add_sort(s2) - results = self.lib.albums(q, sort) - assert results[0]["path"] <= results[1]["path"] - assert results[1]["path"] <= results[2]["path"] - q = "path+ year+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - - def test_computed_second(self): - q = "" - s1 = FixedFieldSort("year", True) - s2 = SlowFieldSort("path", True) - sort = MultipleSort() - sort.add_sort(s1) - sort.add_sort(s2) - results = self.lib.albums(q, sort) - assert results[0]["year"] <= results[1]["year"] - assert results[1]["year"] <= results[2]["year"] - assert results[0]["path"] <= results[1]["path"] - q = "year+ path+" - results2 = self.lib.albums(q) - for r1, r2 in zip(results, results2): - assert r1.id == r2.id - - class TestConfigSort: def test_default_sort_item(self): results = list(self.lib.items()) From 32647d2e4b294f8489cec4cdbbf746fa13dddc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 7 Jun 2026 19:07:32 +0100 Subject: [PATCH 06/10] Simplify paths --- test/dbcore/test_sort.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index 7744fee3e2..3aa9d1ee70 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -44,6 +44,7 @@ def setup_library(request: pytest.FixtureRequest, helper): album_ids = [ helper.lib.add( Album( + id=id_, album=album, genres=genres, year=year, @@ -140,16 +141,16 @@ def test_sort(self, model, query, expected_ids): results = self.lib._fetch(model, query, None) assert [r.id for r in results] == expected_ids - -class TestSortFixedField: def test_sort_path_field(self): - q = "" - sort = FixedFieldSort("path", True) - results = self.lib.items(q, sort) - assert results[0]["path"] == util.normpath("/path0.mp3") - assert results[1]["path"] == util.normpath("/patH1.mp3") - assert results[2]["path"] == util.normpath("/paTH2.mp3") - assert results[3]["path"] == util.normpath("/PATH3.mp3") + results = self.lib.items("", FixedFieldSort("path", True)) + expected_paths = [ + b"/path0.mp3", + b"/patH1.mp3", + b"/paTH2.mp3", + b"/PATH3.mp3", + ] + expected_paths_with_prefix = list(map(util.normpath, expected_paths)) + assert [i.path for i in results] == expected_paths_with_prefix class TestConfigSort: From 0539c32d643c851411cb1ab8bc4cc6a8ff0f49ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 6 Jun 2026 21:02:12 +0100 Subject: [PATCH 07/10] Simplify TestCaseSensitivity --- test/dbcore/test_sort.py | 134 ++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 78 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index 3aa9d1ee70..8b88cdf220 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -39,7 +39,7 @@ def helper(class_helper): return class_helper -@pytest.fixture(autouse=True, scope="class") +@pytest.fixture(scope="class") def setup_library(request: pytest.FixtureRequest, helper): album_ids = [ helper.lib.add( @@ -115,6 +115,7 @@ def setup_library(request: pytest.FixtureRequest, helper): request.cls.lib = helper.lib +@pytest.mark.usefixtures("setup_library") class TestSort: @pytest.mark.parametrize( "model,query,expected_ids", @@ -153,6 +154,7 @@ def test_sort_path_field(self): assert [i.path for i in results] == expected_paths_with_prefix +@pytest.mark.usefixtures("setup_library") class TestConfigSort: def test_default_sort_item(self): results = list(self.lib.items()) @@ -178,93 +180,69 @@ class TestCaseSensitivity: after all upper-case values. E.g., `Foo Qux bar` """ - @pytest.fixture(autouse=True) + @pytest.fixture(autouse=True, scope="class") def setup(self, helper): - album = Album( - album="album", - genres=["alternative"], - year="2001", - flex1="flex1", - flex2="flex2-A", - albumartist="bar", - ) - helper.lib.add(album) - - item = _common.item() - item.title = "another" - item.artist = "lowercase" - item.album = "album" - item.year = 2001 - item.comp = True - item.flex1 = "flex1" - item.flex2 = "flex2-A" - item.album_id = album.id - item.artist_sort = None - item.track = 10 - helper.lib.add(item) - - self.new_album = album - self.new_item = item - - yield - - self.new_item.remove(delete=True) - self.new_album.remove(delete=True) - - def test_smart_artist_case_insensitive(self): - config["sort_case_insensitive"] = True - q = "artist+" - results = list(self.lib.items(q)) - assert results[0].artist == "lowercase" - assert results[1].artist == "One" - - def test_smart_artist_case_sensitive(self): - config["sort_case_insensitive"] = False - q = "artist+" - results = list(self.lib.items(q)) - assert results[0].artist == "One" - assert results[-1].artist == "lowercase" - - def test_fixed_field_case_insensitive(self): - config["sort_case_insensitive"] = True - q = "album+" - results = list(self.lib.albums(q)) - assert results[0].album == "album" - assert results[1].album == "Album A" + helper.lib.add(Album(album="album", albumartist="bar")) + helper.lib.add(Album(album="Album", albumartist="Bar")) + helper.add_item(artist="artist", flex1="flex1", track=10) + helper.add_item(artist="Artist", flex1="Flex1", track=2) - def test_fixed_field_case_sensitive(self): - config["sort_case_insensitive"] = False - q = "album+" - results = list(self.lib.albums(q)) - assert results[0].album == "Album A" - assert results[-1].album == "album" - - def test_flex_field_case_insensitive(self): + @pytest.mark.parametrize( + "getter,query,attr,expected_insensitive,expected_sensitive", + [ + _p( + "items", + "artist+", + "artist", + ["artist", "Artist"], + ["Artist", "artist"], + id="smart-artist-case", + ), + _p( + "albums", + "album+", + "album", + ["album", "Album"], + ["Album", "album"], + id="fixed-field-case", + ), + _p( + "items", + "flex1+", + "flex1", + ["flex1", "Flex1"], + ["Flex1", "flex1"], + id="flex-field-case", + ), + ], + ) + def test_text_field_case_sorting( + self, + config, + getter, + query, + attr, + expected_insensitive, + expected_sensitive, + helper, + ): config["sort_case_insensitive"] = True - q = "flex1+" - results = list(self.lib.items(q)) - assert results[0].flex1 == "flex1" - assert results[1].flex1 == "Flex1-0" + results = getattr(helper.lib, getter)(query) + assert [r[attr] for r in results] == expected_insensitive - def test_flex_field_case_sensitive(self): config["sort_case_insensitive"] = False - q = "flex1+" - results = list(self.lib.items(q)) - assert results[0].flex1 == "Flex1-0" - assert results[-1].flex1 == "flex1" + results = getattr(helper.lib, getter)(query) + assert [r[attr] for r in results] == expected_sensitive - def test_case_sensitive_only_affects_text(self): + def test_case_sensitive_only_affects_text(self, config, helper): config["sort_case_insensitive"] = True - q = "track+" - results = list(self.lib.items(q)) + results = helper.lib.items("track+") # If the numerical values were sorted as strings, - # then ['1', '10', '2'] would be valid. - # print([r.track for r in results]) - assert results[0].track == 1 - assert results[1].track == 2 - assert results[-1].track == 10 + # then ['10', '2'] would be valid. + assert [r.track for r in results] == [2, 10] +@pytest.mark.usefixtures("setup_library") class TestNonExistingField: """Test sorting by non-existing fields""" From fe954da2d4064474e9428a7de94dbd1093b9b297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 7 Jun 2026 00:07:09 +0100 Subject: [PATCH 08/10] Refactor TestNonExistingField - Consolidate duplicate sort test coverage with parametrized cases. - Keep missing-field and mixed-presence behavior covered while reducing repeated assertions. --- test/dbcore/test_sort.py | 109 ++++++++++++--------------------------- 1 file changed, 34 insertions(+), 75 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index 8b88cdf220..d31b1df79f 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -15,7 +15,6 @@ """Various tests for querying the library database.""" import os -from unittest.mock import patch import pytest @@ -246,84 +245,44 @@ def test_case_sensitive_only_affects_text(self, config, helper): class TestNonExistingField: """Test sorting by non-existing fields""" - def test_non_existing_fields_not_fail(self): - qs = ["foo+", "foo-", "--", "-+", "+-", "++", "-foo-", "-foo+", "---"] - - q0 = "foo+" - results0 = list(self.lib.items(q0)) - for q1 in qs: - results1 = list(self.lib.items(q1)) - for r1, r2 in zip(results0, results1): - assert r1.id == r2.id - - def test_combined_non_existing_field_asc(self): - all_results = list(self.lib.items("id+")) - q = "foo+ id+" - results = list(self.lib.items(q)) - assert len(all_results) == len(results) - for r1, r2 in zip(all_results, results): - assert r1.id == r2.id - - def test_combined_non_existing_field_desc(self): - all_results = list(self.lib.items("id+")) - q = "foo- id+" - results = list(self.lib.items(q)) - assert len(all_results) == len(results) - for r1, r2 in zip(all_results, results): - assert r1.id == r2.id - - def test_field_present_in_some_items(self): - """Test ordering by a (string) field not present on all items.""" - # append 'foo' to two items (1,2) - lower_foo_item, higher_foo_item, *items_without_foo = self.lib.items( - "id+" - ) - lower_foo_item.foo, higher_foo_item.foo = "bar1", "bar2" - lower_foo_item.store() - higher_foo_item.store() - - results_asc = list(self.lib.items("foo+ id+")) - assert [i.id for i in results_asc] == [ - # items without field first - *[i.id for i in items_without_foo], - lower_foo_item.id, - higher_foo_item.id, - ] + @pytest.mark.parametrize( + "q", ["foo+", "foo-", "--", "-+", "+-", "++", "-foo-", "-foo+", "---"] + ) + def test_non_existing_fields_not_fail(self, q): + expected_ids = [i.id for i in self.lib.items("foo+")] - results_desc = list(self.lib.items("foo- id+")) - assert [i.id for i in results_desc] == [ - higher_foo_item.id, - lower_foo_item.id, - # items without field last - *[i.id for i in items_without_foo], - ] + actual_ids = [i.id for i in self.lib.items(q)] + + assert actual_ids == expected_ids + + @pytest.mark.parametrize("q", ["foo+ id+", "foo- id+"], ids=["asc", "desc"]) + def test_combined_non_existing_field(self, q): + expected_ids = [i.id for i in self.lib.items("id+")] + + actual_ids = [i.id for i in self.lib.items(q)] + + assert actual_ids == expected_ids - @patch("beets.library.Item._types", {"myint": types.Integer()}) - def test_int_field_present_in_some_items(self): + @pytest.mark.parametrize( + "field,values", + [_p("myint", (2, 10), id="int"), _p("foo", ("bar1", "bar2"), id="str")], + ) + def test_field_present_in_some_items(self, monkeypatch, field, values): """Test ordering by an int-type field not present on all items.""" - # append int-valued 'myint' to two items (1,2) - lower_myint_item, higher_myint_item, *items_without_myint = ( - self.lib.items("id+") - ) - lower_myint_item.myint, higher_myint_item.myint = 1, 2 - lower_myint_item.store() - higher_myint_item.store() - - results_asc = list(self.lib.items("myint+ id+")) - assert [i.id for i in results_asc] == [ - # items without field first - *[i.id for i in items_without_myint], - lower_myint_item.id, - higher_myint_item.id, - ] + monkeypatch.setitem(Item._types, "myint", types.Integer()) - results_desc = list(self.lib.items("myint- id+")) - assert [i.id for i in results_desc] == [ - higher_myint_item.id, - lower_myint_item.id, - # items without field last - *[i.id for i in items_without_myint], - ] + lower_item, higher_item, *items_without_val = self.lib.items("id+") + for item, value in zip((lower_item, higher_item), values): + setattr(item, field, value) + item.store() + + null_values_ids = [i.id for i in items_without_val] + + ids_asc = [i.id for i in self.lib.items(f"{field}+ id+")] + ids_desc = [i.id for i in self.lib.items(f"{field}- id+")] + + assert ids_asc == [*null_values_ids, lower_item.id, higher_item.id] + assert ids_desc == [higher_item.id, lower_item.id, *null_values_ids] def test_negation_interaction(self): """Test the handling of negation and sorting together. From bf90a891b7082980c9361b0c6e508e3e2be3421f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 7 Jun 2026 01:41:12 +0100 Subject: [PATCH 09/10] Tighten config sort tests - Combine default and override sort assertions for items and albums. - Use the config fixture and exact ordered results to avoid global config state. --- test/dbcore/test_sort.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index d31b1df79f..749dfbf151 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -19,7 +19,7 @@ import pytest import beets.library -from beets import config, util +from beets import util from beets.dbcore import types from beets.dbcore.query import TrueQuery from beets.dbcore.sort import FixedFieldSort, SlowFieldSort @@ -152,26 +152,21 @@ def test_sort_path_field(self): expected_paths_with_prefix = list(map(util.normpath, expected_paths)) assert [i.path for i in results] == expected_paths_with_prefix + def test_config_defaults(self): + artists = [r.artist for r in self.lib.items()] + albumartists = [r.albumartist for r in self.lib.albums()] -@pytest.mark.usefixtures("setup_library") -class TestConfigSort: - def test_default_sort_item(self): - results = list(self.lib.items()) - assert results[0].artist < results[1].artist - - def test_config_opposite_sort_item(self): - config["sort_item"] = "artist-" - results = list(self.lib.items()) - assert results[0].artist > results[1].artist - - def test_default_sort_album(self): - results = list(self.lib.albums()) - assert results[0].albumartist < results[1].albumartist - - def test_config_opposite_sort_album(self): - config["sort_album"] = "albumartist-" - results = list(self.lib.albums()) - assert results[0].albumartist > results[1].albumartist + assert artists == ["One", "Three", "Three", "Two"] + assert albumartists == ["Bar", "Baz", "Foo"] + + def test_config_overrides(self, config): + config.set({"sort_item": "artist-", "sort_album": "albumartist-"}) + + artists = [r.artist for r in self.lib.items()] + albumartists = [r.albumartist for r in self.lib.albums()] + + assert artists == ["Two", "Three", "Three", "One"] + assert albumartists == ["Foo", "Baz", "Bar"] class TestCaseSensitivity: From 345829c6c282835897d79fecf61b8d056f59924d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 7 Jun 2026 16:38:00 +0100 Subject: [PATCH 10/10] Remove duplicate branches --- test/dbcore/test_sort.py | 42 ++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/test/dbcore/test_sort.py b/test/dbcore/test_sort.py index 749dfbf151..d0a97497d1 100644 --- a/test/dbcore/test_sort.py +++ b/test/dbcore/test_sort.py @@ -119,24 +119,17 @@ class TestSort: @pytest.mark.parametrize( "model,query,expected_ids", [ - _p(Album, "year+", [1, 2, 3], id="album-fixed"), - _p(Album, "flex1+", [1, 3, 2], id="album-flex"), - _p(Album, "path+", [1, 2, 3], id="album-calculated"), - _p(Album, "year-", [3, 2, 1], id="album-fixed-desc"), - _p(Album, "flex1-", [2, 1, 3], id="album-flex-desc"), - _p(Album, "path-", [1, 2, 3], id="album-calculated-desc"), - _p(Album, "genres+ album+", [3, 1, 2], id="multi-album-fixed-field-asc"), - _p(Album, "flex2+ flex1+", [1, 2, 3], id="multi-album-flex-field-asc"), + _p(Album, "year+", [1, 2, 3], id="fixed"), + _p(Album, "flex1-", [2, 1, 3], id="flex"), + _p(Album, "path+", [1, 2, 3], id="calculated"), + _p(Album, "year-", [3, 2, 1], id="fixed-desc"), + _p(Album, "genres+ album+", [3, 1, 2], id="multi-fixed-field"), + _p(Album, "flex2+ flex1+", [1, 2, 3], id="multi-flex-field"), _p(Album, "path+ year+", [1, 2, 3], id="computed"), _p(Album, "year+ path+", [1, 2, 3], id="computed-reverse"), - _p(Item, "year+", [1, 2, 3, 4], id="item-fixed"), - _p(Item, "flex1+", [1, 2, 3, 4], id="item-flex"), - _p(Item, "year-", [4, 3, 2, 1], id="item-fixed-desc"), - _p(Item, "flex1-", [3, 4, 2, 1], id="item-flex-desc"), - _p(Item, "album+ year+", [1, 2, 3, 4], id="multi-item-fixed-field-asc"), - _p(Item, "flex2- flex1+", [1, 2, 4, 3], id="multi-flex-field-mixed"), + _p(Item, "flex2- flex1+", [1, 2, 4, 3], id="item-multi-flex-field"), ], - ) # fmt: skip + ) def test_sort(self, model, query, expected_ids): results = self.lib._fetch(model, query, None) assert [r.id for r in results] == expected_ids @@ -250,31 +243,26 @@ def test_non_existing_fields_not_fail(self, q): assert actual_ids == expected_ids - @pytest.mark.parametrize("q", ["foo+ id+", "foo- id+"], ids=["asc", "desc"]) - def test_combined_non_existing_field(self, q): + def test_combined_non_existing_field(self): expected_ids = [i.id for i in self.lib.items("id+")] - actual_ids = [i.id for i in self.lib.items(q)] + actual_ids = [i.id for i in self.lib.items("foo+ id+")] assert actual_ids == expected_ids - @pytest.mark.parametrize( - "field,values", - [_p("myint", (2, 10), id="int"), _p("foo", ("bar1", "bar2"), id="str")], - ) - def test_field_present_in_some_items(self, monkeypatch, field, values): + def test_field_present_in_some_items(self, monkeypatch): """Test ordering by an int-type field not present on all items.""" monkeypatch.setitem(Item._types, "myint", types.Integer()) lower_item, higher_item, *items_without_val = self.lib.items("id+") - for item, value in zip((lower_item, higher_item), values): - setattr(item, field, value) + for item, value in zip((lower_item, higher_item), (2, 10)): + item.myint = value item.store() null_values_ids = [i.id for i in items_without_val] - ids_asc = [i.id for i in self.lib.items(f"{field}+ id+")] - ids_desc = [i.id for i in self.lib.items(f"{field}- id+")] + ids_asc = [i.id for i in self.lib.items("myint+ id+")] + ids_desc = [i.id for i in self.lib.items("myint- id+")] assert ids_asc == [*null_values_ids, lower_item.id, higher_item.id] assert ids_desc == [higher_item.id, lower_item.id, *null_values_ids]