From 4dcc0a4e655b0178a7733954bf75df3cff9ef07a Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Mon, 24 Aug 2026 13:36:41 +0200 Subject: Ride type multiselect filter with a checkbox group across library and analytics --- web/ride-filters_test.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) (limited to 'web/ride-filters_test.go') diff --git a/web/ride-filters_test.go b/web/ride-filters_test.go index fd3ee13..e4efc02 100644 --- a/web/ride-filters_test.go +++ b/web/ride-filters_test.go @@ -38,7 +38,7 @@ func TestParseRideFilters(t *testing.T) { assert.Equal(t, 50, *filters.CotacolMinPoints) assert.Equal(t, 500, *filters.CotacolMaxPoints) assert.Equal(t, ride.BikeTypeRoad, *filters.BikeType) - assert.Equal(t, ride.RideTypeTraining, *filters.RideType) + assert.Equal(t, []ride.RideType{ride.RideTypeTraining}, filters.RideTypes) assert.True(t, filters.Active()) } @@ -68,7 +68,7 @@ func TestParseRideFiltersIgnoresInvalidValues(t *testing.T) { assert.Nil(t, filters.CotacolMinPoints) assert.Nil(t, filters.CotacolMaxPoints) assert.Nil(t, filters.BikeType) - assert.Nil(t, filters.RideType) + assert.Equal(t, defaultRideTypes(), filters.RideTypes) } func TestParseRideFiltersTreatsFullRangesAsInactive(t *testing.T) { @@ -114,7 +114,40 @@ func TestFilterRidesUsesInclusiveBoundsAndMovingTime(t *testing.T) { assert.Equal(t, "upper boundary", filtered[1].Name) } -func TestFilterRidesByBikeAndRideType(t *testing.T) { +func TestFilterRidesDefaultsToAllRideTypesExceptTransport(t *testing.T) { + items := []ride.StoredRide{ + {Name: "training", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining, GPXPath: "a.gpx"}, + {Name: "race", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeRace, GPXPath: "b.gpx"}, + {Name: "indoor", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeIndoor, GPXPath: "c.gpx"}, + {Name: "transport", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTransport, GPXPath: "d.gpx"}, + } + + filtered := filterRides(items, parseRideFilters(url.Values{})) + + require.Len(t, filtered, 3) + assert.False(t, parseRideFilters(url.Values{}).Active()) + for _, item := range filtered { + assert.NotEqual(t, ride.RideTypeTransport, item.RideType) + } +} + +func TestFilterRidesByMultipleRideTypes(t *testing.T) { + items := []ride.StoredRide{ + {Name: "training", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining, GPXPath: "a.gpx"}, + {Name: "race", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeRace, GPXPath: "b.gpx"}, + {Name: "indoor", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeIndoor, GPXPath: "c.gpx"}, + } + filters := parseRideFilters(url.Values{rideFilterRideTypeParam: {string(ride.RideTypeTraining), string(ride.RideTypeRace)}}) + + filtered := filterRides(items, filters) + + require.Len(t, filtered, 2) + assert.True(t, filters.Active()) + assert.Equal(t, ride.RideTypeTraining, filtered[0].RideType) + assert.Equal(t, ride.RideTypeRace, filtered[1].RideType) +} + +func TestFilterRidesByRideType(t *testing.T) { items := []ride.StoredRide{ {Name: "road training", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining, GPXPath: "a.gpx"}, {Name: "mtb race", BikeType: ride.BikeTypeMTB, RideType: ride.RideTypeRace, GPXPath: "b.gpx"}, @@ -204,9 +237,40 @@ func TestHandlerRendersRideFilterControls(t *testing.T) { assert.Contains(t, body, `name="cotacol_min"`) assert.Contains(t, body, `name="bike_type"`) assert.Contains(t, body, `name="ride_type"`) + assert.Contains(t, body, `value="training" checked`) + assert.NotContains(t, body, `value="transport" checked`) assert.Contains(t, body, ``) } +func TestHandlerHidesTransportRidesByDefault(t *testing.T) { + server, db := newWebTestServer(t) + save := func(externalID, name string, rideType ride.RideType) { + require.NoError(t, ride.Save(db, ride.StoredRide{ + ExternalID: externalID, + GPXPath: testGPXPath(t, externalID+".gpx"), + Name: name, + Type: "Ride", + BikeType: ride.BikeTypeRoad, + RideType: rideType, + StartDate: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC), + DistanceM: 20_000, + })) + } + save("strava:550e8400-e29b-41d4-a716-446655440000", "Training Ride", ride.RideTypeTraining) + save("strava:6ba7b810-9dad-41d1-80b4-00c04fd430c8", "Race Ride", ride.RideTypeRace) + save("strava:7ba7b810-9dad-41d1-80b4-00c04fd430c8", "Transport Ride", ride.RideTypeTransport) + + request := httptest.NewRequest(http.MethodGet, "/", nil) + response := httptest.NewRecorder() + server.Handler().ServeHTTP(response, request) + + body := response.Body.String() + assert.Equal(t, http.StatusOK, response.Code) + assert.Contains(t, body, "Training Ride") + assert.Contains(t, body, "Race Ride") + assert.NotContains(t, body, "Transport Ride") +} + func TestRideSortHeadersPreserveFilters(t *testing.T) { filters := parseRideFilters(url.Values{ rideFilterDistanceMinParam: {"50"}, @@ -219,6 +283,14 @@ func TestRideSortHeadersPreserveFilters(t *testing.T) { assert.Equal(t, "/?bike_type=road&dir=asc&distance_max=150&distance_min=50&sort=name", headers[0].URL) } +func TestRideSortHeadersPreserveRideTypeMultiselect(t *testing.T) { + filters := parseRideFilters(url.Values{rideFilterRideTypeParam: {string(ride.RideTypeRace), string(ride.RideTypeTransport)}}) + + headers := rideSortHeadersWithFilters(RideSort{Column: rideSortDistance, Descending: true}, filters) + + assert.Equal(t, "/?dir=asc&ride_type=race&ride_type=transport&sort=name", headers[0].URL) +} + func TestHandlerFiltersRidesBeforeRenderingSortedResults(t *testing.T) { server, db := newWebTestServer(t) save := func(externalID, name, path string, distance float64) { -- cgit v1.3