Add more filters
This commit is contained in:
@@ -219,3 +219,41 @@ class FilterBarRenderingTest(TestCase):
|
||||
)
|
||||
)
|
||||
self._assert_shell(html, "/presets/playevents/list", "/presets/playevents/save")
|
||||
|
||||
def test_game_filter_bar_has_new_widgets(self):
|
||||
"""The expanded games FilterBar exposes platform_group, device, playevent_note,
|
||||
purchase_type / purchase_ownership_type, plus count and aggregate-playtime
|
||||
range sliders and the new boolean checkboxes."""
|
||||
html = str(
|
||||
FilterBar(
|
||||
filter_json="",
|
||||
preset_list_url="/l",
|
||||
preset_save_url="/s",
|
||||
)
|
||||
)
|
||||
# New search-backed selects
|
||||
self.assertIn('data-search-url="/api/devices/search"', html)
|
||||
self.assertIn('data-search-url="/api/platforms/groups"', html)
|
||||
# New enum selects (purchase type / ownership)
|
||||
self.assertIn('data-name="purchase_type"', html)
|
||||
self.assertIn('data-name="purchase_ownership_type"', html)
|
||||
# Free-text widget for playevent notes
|
||||
self.assertIn('data-name="playevent_note"', html)
|
||||
self.assertIn('data-search-select-free-text="true"', html)
|
||||
# New range slider input prefixes
|
||||
self.assertIn('name="filter-purchase-count-min"', html)
|
||||
self.assertIn('name="filter-playevent-count-min"', html)
|
||||
self.assertIn('name="filter-manual-playtime-minutes-min"', html)
|
||||
self.assertIn('name="filter-calculated-playtime-minutes-min"', html)
|
||||
self.assertIn('name="filter-original-year-min"', html)
|
||||
self.assertIn('name="filter-purchase-price-total-min"', html)
|
||||
self.assertIn('name="filter-purchase-price-any-min"', html)
|
||||
# New boolean checkboxes
|
||||
self.assertIn('name="filter-purchase-refunded"', html)
|
||||
self.assertIn('name="filter-purchase-infinite"', html)
|
||||
self.assertIn('name="filter-session-emulated"', html)
|
||||
# Removed boolean checkboxes
|
||||
self.assertNotIn('name="filter-has-purchases"', html)
|
||||
self.assertNotIn('name="filter-has-playevents"', html)
|
||||
# Playtime label renamed
|
||||
self.assertIn("Total playtime", html)
|
||||
|
||||
+174
-2
@@ -787,9 +787,9 @@ class TestExpandedFiltersAgainstDB:
|
||||
|
||||
data = self._setup_entities()
|
||||
|
||||
# has_purchases = True
|
||||
# purchase_count == 1 (replaces removed has_purchases boolean)
|
||||
gf_pur = GameFilter.from_json({
|
||||
"has_purchases": {"value": True, "modifier": "EQUALS"}
|
||||
"purchase_count": {"value": 1, "modifier": "EQUALS"}
|
||||
})
|
||||
assert data["game"] in list(Game.objects.filter(gf_pur.to_q()))
|
||||
assert data["game2"] not in list(Game.objects.filter(gf_pur.to_q()))
|
||||
@@ -799,3 +799,175 @@ class TestExpandedFiltersAgainstDB:
|
||||
"session_count": {"value": 1, "modifier": "EQUALS"}
|
||||
})
|
||||
assert data["game"] in list(Game.objects.filter(gf_cnt.to_q()))
|
||||
|
||||
def test_game_filter_purchase_count_range(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
|
||||
# game has 1 purchase, game2 has 0
|
||||
gf = GameFilter.from_json({
|
||||
"purchase_count": {"value": 1, "modifier": "EQUALS"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf.to_q()))
|
||||
assert data["game"] in results
|
||||
assert data["game2"] not in results
|
||||
|
||||
def test_game_filter_playevent_count(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
gf = GameFilter.from_json({
|
||||
"playevent_count": {"value": 1, "modifier": "EQUALS"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf.to_q()))
|
||||
assert data["game"] in results
|
||||
assert data["game2"] not in results
|
||||
|
||||
def test_game_filter_device(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
gf = GameFilter.from_json({
|
||||
"device": {"value": [data["dev"].id], "modifier": "INCLUDES"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf.to_q()))
|
||||
assert data["game"] in results
|
||||
assert data["game2"] not in results
|
||||
|
||||
def test_game_filter_platform_group(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
gf = GameFilter.from_json({
|
||||
"platform_group": {"value": ["Nintendo"], "modifier": "INCLUDES"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf.to_q()))
|
||||
# both games are on the same Nintendo platform
|
||||
assert data["game"] in results
|
||||
assert data["game2"] in results
|
||||
|
||||
def test_game_filter_session_emulated(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game, Session
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
data = self._setup_entities()
|
||||
Session.objects.create(
|
||||
game=data["game2"],
|
||||
device=data["dev"],
|
||||
timestamp_start=datetime.datetime(2026, 6, 2, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
timestamp_end=datetime.datetime(2026, 6, 2, 12, 30, 0, tzinfo=datetime.timezone.utc),
|
||||
duration_manual=timedelta(0),
|
||||
emulated=True,
|
||||
)
|
||||
gf = GameFilter.from_json({
|
||||
"session_emulated": {"value": True, "modifier": "EQUALS"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf.to_q()))
|
||||
assert data["game2"] in results
|
||||
assert data["game"] not in results
|
||||
|
||||
def test_game_filter_purchase_refunded_and_infinite(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game, Purchase
|
||||
import datetime
|
||||
|
||||
data = self._setup_entities()
|
||||
# data["pur"] is infinite=True, non-refunded.
|
||||
gf_inf = GameFilter.from_json({
|
||||
"purchase_infinite": {"value": True, "modifier": "EQUALS"}
|
||||
})
|
||||
assert data["game"] in set(Game.objects.filter(gf_inf.to_q()))
|
||||
assert data["game2"] not in set(Game.objects.filter(gf_inf.to_q()))
|
||||
|
||||
# Add a refunded purchase for game2.
|
||||
refunded = Purchase.objects.create(
|
||||
platform=data["plat"],
|
||||
date_purchased=datetime.date(2026, 1, 1),
|
||||
date_refunded=datetime.date(2026, 2, 1),
|
||||
price=10.0,
|
||||
price_currency="USD",
|
||||
converted_price=10.0,
|
||||
converted_currency="USD",
|
||||
)
|
||||
refunded.games.add(data["game2"])
|
||||
gf_ref = GameFilter.from_json({
|
||||
"purchase_refunded": {"value": True, "modifier": "EQUALS"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf_ref.to_q()))
|
||||
assert data["game2"] in results
|
||||
assert data["game"] not in results
|
||||
|
||||
def test_game_filter_purchase_type_and_ownership(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
# data["pur"] defaults to type=game, ownership_type=digital
|
||||
gf = GameFilter.from_json({
|
||||
"purchase_type": {"value": ["game"], "modifier": "INCLUDES"}
|
||||
})
|
||||
assert data["game"] in set(Game.objects.filter(gf.to_q()))
|
||||
|
||||
gf = GameFilter.from_json({
|
||||
"purchase_ownership_type": {"value": ["di"], "modifier": "INCLUDES"}
|
||||
})
|
||||
assert data["game"] in set(Game.objects.filter(gf.to_q()))
|
||||
|
||||
def test_game_filter_purchase_price_any_and_total(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
# data["pur"] has converted_price=45.00 linked to data["game"]
|
||||
gf_any = GameFilter.from_json({
|
||||
"purchase_price_any": {"value": 40.0, "value2": 50.0, "modifier": "BETWEEN"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf_any.to_q()))
|
||||
assert data["game"] in results
|
||||
assert data["game2"] not in results
|
||||
|
||||
gf_total = GameFilter.from_json({
|
||||
"purchase_price_total": {"value": 40.0, "value2": 50.0, "modifier": "BETWEEN"}
|
||||
})
|
||||
results = set(Game.objects.filter(gf_total.to_q()))
|
||||
assert data["game"] in results
|
||||
assert data["game2"] not in results
|
||||
|
||||
def test_game_filter_playevent_note_includes(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
# data["pe"] has note="Completed 100%" on data["game"]
|
||||
gf = GameFilter.from_json({
|
||||
"playevent_note": {
|
||||
"value": [{"id": "Completed", "label": "Completed"}],
|
||||
"modifier": "INCLUDES",
|
||||
}
|
||||
})
|
||||
results = set(Game.objects.filter(gf.to_q()))
|
||||
assert data["game"] in results
|
||||
assert data["game2"] not in results
|
||||
|
||||
def test_game_filter_manual_and_calculated_playtime(self):
|
||||
from games.filters import GameFilter
|
||||
from games.models import Game
|
||||
|
||||
data = self._setup_entities()
|
||||
# data["s1"] has 10 minutes manual + 30 minutes calculated
|
||||
gf_manual = GameFilter.from_json({
|
||||
"manual_playtime_minutes": {"value": 10, "modifier": "EQUALS"}
|
||||
})
|
||||
assert data["game"] in set(Game.objects.filter(gf_manual.to_q()))
|
||||
|
||||
gf_calc = GameFilter.from_json({
|
||||
"calculated_playtime_minutes": {"value": 30, "modifier": "EQUALS"}
|
||||
})
|
||||
assert data["game"] in set(Game.objects.filter(gf_calc.to_q()))
|
||||
|
||||
@@ -60,3 +60,16 @@ class PathWorksTest(TestCase):
|
||||
def test_list_purchases_returns_200(self):
|
||||
response = self.client.get(reverse("games:list_purchases"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_platform_groups_api_returns_200(self):
|
||||
# Distinct platform groups are returned as string-valued options.
|
||||
Platform.objects.create(name="Switch", icon="switch", group="Nintendo")
|
||||
response = self.client.get("/api/platforms/groups")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = response.json()
|
||||
groups = {item["value"] for item in body}
|
||||
self.assertIn("Nintendo", groups)
|
||||
|
||||
filtered = self.client.get("/api/platforms/groups?q=nin")
|
||||
self.assertEqual(filtered.status_code, 200)
|
||||
self.assertEqual({item["value"] for item in filtered.json()}, {"Nintendo"})
|
||||
|
||||
Reference in New Issue
Block a user