mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 07:26:30 +00:00
test(tours): cover a bike tour's GPX activity and the trail grade cache's limit
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import type { BikeProfile, PaddleBoatType } from '@trek/shared';
|
||||
|
||||
/** What GPX calls the activity in a track's <type>; a paddle tour says what it is paddled in. */
|
||||
const PADDLE_ACTIVITY: Record<PaddleBoatType, string> = {
|
||||
kayak: 'kayaking',
|
||||
canoe: 'canoeing',
|
||||
sup: 'stand_up_paddling',
|
||||
other: 'paddling',
|
||||
};
|
||||
|
||||
/** A bike tour says what it is ridden on, in the words Garmin and Strava read. */
|
||||
const BIKE_ACTIVITY: Record<BikeProfile, string> = {
|
||||
touring: 'cycling',
|
||||
road: 'road_biking',
|
||||
gravel: 'gravel_cycling',
|
||||
mtb: 'mountain_biking',
|
||||
};
|
||||
|
||||
/** The activity a tour's track names in GPX: a paddle tour its boat, a bike tour its profile, a hike hiking; whatever is missing falls back to the plainest. */
|
||||
export function gpxActivityOf(tour: { tour_type: string; boat_type?: string | null; bike_profile?: string | null }): string {
|
||||
const { tour_type: type, boat_type: boat, bike_profile: bike } = tour;
|
||||
if (type === 'kayak') return PADDLE_ACTIVITY[(boat ?? 'other') as PaddleBoatType] ?? PADDLE_ACTIVITY.other;
|
||||
if (type === 'bike') return BIKE_ACTIVITY[(bike ?? 'touring') as BikeProfile] ?? BIKE_ACTIVITY.touring;
|
||||
return type === 'hike' ? 'hiking' : type;
|
||||
}
|
||||
@@ -7,32 +7,9 @@ import type { ToursRepository } from '../../db/repositories/Tours.repository';
|
||||
import { DomainError } from '../common/domain-error';
|
||||
import { toRowId } from '../common/row-id';
|
||||
import { PlaceImportService } from '../place-import/place-import.service';
|
||||
import { gpxActivityOf } from './tour-gpx-export.helpers';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import type { BikeProfile, PaddleBoatType } from '@trek/shared';
|
||||
|
||||
/** What GPX calls the activity in a track's <type>; a paddle tour says what it is paddled in. */
|
||||
const PADDLE_ACTIVITY: Record<PaddleBoatType, string> = {
|
||||
kayak: 'kayaking',
|
||||
canoe: 'canoeing',
|
||||
sup: 'stand_up_paddling',
|
||||
other: 'paddling',
|
||||
};
|
||||
|
||||
/** A bike tour says what it is ridden on, in the words Garmin and Strava read. */
|
||||
const BIKE_ACTIVITY: Record<BikeProfile, string> = {
|
||||
touring: 'cycling',
|
||||
road: 'road_biking',
|
||||
gravel: 'gravel_cycling',
|
||||
mtb: 'mountain_biking',
|
||||
};
|
||||
|
||||
function gpxActivityOf(tour: { tour_type: string; boat_type?: string | null; bike_profile?: string | null }): string {
|
||||
const { tour_type: type, boat_type: boat, bike_profile: bike } = tour;
|
||||
if (type === 'kayak') return PADDLE_ACTIVITY[(boat ?? 'other') as PaddleBoatType] ?? PADDLE_ACTIVITY.other;
|
||||
if (type === 'bike') return BIKE_ACTIVITY[(bike ?? 'touring') as BikeProfile] ?? BIKE_ACTIVITY.touring;
|
||||
return type === 'hike' ? 'hiking' : type;
|
||||
}
|
||||
|
||||
/**
|
||||
* PR1-06A: one saved tour as a GPX file, its whole saved line with the heights
|
||||
|
||||
@@ -21,7 +21,7 @@ const wayElementSchema = z.object({
|
||||
function step(value: string | undefined, top: number, signed: boolean): string | null {
|
||||
const match = /^\s*S?([0-9])([+-])?\s*$/i.exec(value ?? '');
|
||||
if (!match || Number(match[1]) > top) return null;
|
||||
return signed ? `${match[1]}${match[2] ?? ''}` : (match[1] ?? null);
|
||||
return signed ? `${match[1]}${match[2] ?? ''}` : match[1];
|
||||
}
|
||||
|
||||
function gradeOf(id: number, tags: Record<string, string> = {}): TourTrailGrade | null {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { gpxActivityOf } from '../../../src/nest/tours/tour-gpx-export.helpers';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
// TOUR-GPX-ACTIVITY-001 to TOUR-GPX-ACTIVITY-003
|
||||
|
||||
describe('gpxActivityOf (PR1-06A, BIK-01)', () => {
|
||||
it('TOUR-GPX-ACTIVITY-001: a bike tour names its profile, and the plainest riding without a known one', () => {
|
||||
expect(gpxActivityOf({ tour_type: 'bike', bike_profile: 'mtb' })).toBe('mountain_biking');
|
||||
expect(gpxActivityOf({ tour_type: 'bike', bike_profile: null })).toBe('cycling');
|
||||
expect(gpxActivityOf({ tour_type: 'bike', bike_profile: 'unicycle' })).toBe('cycling');
|
||||
});
|
||||
|
||||
it('TOUR-GPX-ACTIVITY-002: a paddle tour names its boat, and paddling without a known one', () => {
|
||||
expect(gpxActivityOf({ tour_type: 'kayak', boat_type: 'canoe' })).toBe('canoeing');
|
||||
expect(gpxActivityOf({ tour_type: 'kayak', boat_type: null })).toBe('paddling');
|
||||
expect(gpxActivityOf({ tour_type: 'kayak', boat_type: 'raft' })).toBe('paddling');
|
||||
});
|
||||
|
||||
it('TOUR-GPX-ACTIVITY-003: a hike is hiking, any other type its own key', () => {
|
||||
expect(gpxActivityOf({ tour_type: 'hike' })).toBe('hiking');
|
||||
expect(gpxActivityOf({ tour_type: 'walk' })).toBe('walk');
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// TOUR-GRADE-001 to TOUR-GRADE-005
|
||||
// TOUR-GRADE-001 to TOUR-GRADE-006
|
||||
|
||||
const query = vi.fn<(q: string, caller: string) => Promise<unknown[]>>();
|
||||
let service: TourTrailGradeService;
|
||||
@@ -75,4 +75,19 @@ describe('TourTrailGradeService (BIK-05)', () => {
|
||||
query.mockResolvedValue([{ type: 'node', id: 1, tags: { 'mtb:scale': '1' } }, { nonsense: true }]);
|
||||
expect(await service.grades({ way_ids: [1] })).toEqual({ ways: [], unanswered: [] });
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-006: keeps at most fifty thousand ways, forgetting the longest kept first', async () => {
|
||||
query.mockResolvedValue([]);
|
||||
for (let part = 0; part < 26; part += 1) {
|
||||
const ids = Array.from({ length: 2000 }, (_, index) => part * 2000 + index + 1);
|
||||
await service.grades({ way_ids: ids });
|
||||
}
|
||||
query.mockClear();
|
||||
// The first way was forgotten, so it is asked about again; the last one is still kept.
|
||||
await service.grades({ way_ids: [1] });
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
query.mockClear();
|
||||
await service.grades({ way_ids: [52_000] });
|
||||
expect(query).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user