-
Guides
-
- Welcome to the guides section! Here you can find various guides to help
- you navigate through our application.
-
-
-
-
-
- -
- {{ guide.title }}
-
-
+
+
Guider
+
+
+
+
+ {{ visible.length }} av {{ guides.length }}
+
+
+
Laddar guider…
+
Kunde inte hämta guider: {{ error }}
+
Inga guider matchar sökningen.
+
+
+
+
-
+
From 3270223e1df451f06b65321786a63488cab28517 Mon Sep 17 00:00:00 2001
From: Henrik <000henrik@gmail.com>
Date: Fri, 2 Oct 2026 13:51:10 +0200
Subject: [PATCH 3/4] Added all tests from this morning
---
client/src/lib/tours.test.ts | 27 +++++++++++++++++++++++++++
client/src/lib/tours.ts | 19 +++++++++++++++++++
client/vitest.config.js | 9 +++++----
shared/src/types.ts | 19 ++++++++-----------
4 files changed, 59 insertions(+), 15 deletions(-)
create mode 100644 client/src/lib/tours.test.ts
create mode 100644 client/src/lib/tours.ts
diff --git a/client/src/lib/tours.test.ts b/client/src/lib/tours.test.ts
new file mode 100644
index 0000000..0ae7cea
--- /dev/null
+++ b/client/src/lib/tours.test.ts
@@ -0,0 +1,27 @@
+import { describe, it, expect } from 'vitest'
+import type { TourLog } from '@utpost/shared'
+import { elevationGain } from './tours'
+
+const log = (elevation_m: number | null, id = 0): TourLog => ({
+ id,
+ tour_id: 1,
+ recorded_at: '2026-09-01T08:00:00.000Z',
+ lat: 67.9,
+ lon: 18.5,
+ elevation_m,
+ heart_rate: null,
+ note: null,
+})
+
+describe('elevationGain', () => {
+ it('summerar bara stigningar, inte nedförsbackar', () => {
+ expect(elevationGain([log(100), log(150), log(120), log(180)])).toBe(110)
+ })
+ it('ger 0 för en tur utan mätpunkter', () => {
+ expect(elevationGain([])).toBe(0)
+ })
+ // Regressionstest – skuld ur docs/debt.md: en mätpunkt utan höjd räknas som havsnivå
+ it('hoppar över mätpunkter utan höjd i stället för att räkna dem som noll', () => {
+ expect(elevationGain([log(100), log(null), log(150)])).toBe(50)
+ })
+})
diff --git a/client/src/lib/tours.ts b/client/src/lib/tours.ts
new file mode 100644
index 0000000..2f0c15d
--- /dev/null
+++ b/client/src/lib/tours.ts
@@ -0,0 +1,19 @@
+import type { TourLog } from '@utpost/shared'
+
+export const elevationGain = (logs: TourLog[]): number => {
+ let gain = 0
+ let previousElevation: number | null = null
+
+ for (const log of logs) {
+ const elevation = log.elevation_m
+ if (elevation == null) continue
+
+ if (previousElevation !== null && elevation > previousElevation) {
+ gain += elevation - previousElevation
+ }
+
+ previousElevation = elevation
+ }
+
+ return gain
+}
\ No newline at end of file
diff --git a/client/vitest.config.js b/client/vitest.config.js
index ba01629..f8a5538 100644
--- a/client/vitest.config.js
+++ b/client/vitest.config.js
@@ -6,9 +6,10 @@ export default mergeConfig(
viteConfig,
defineConfig({
test: {
- environment: 'jsdom',
- exclude: [...configDefaults.exclude, 'e2e/**'],
- root: fileURLToPath(new URL('./', import.meta.url)),
- },
+ globals: true,
+ environment: 'jsdom',
+ exclude: [...configDefaults.exclude, 'e2e/**'],
+ root: fileURLToPath(new URL('./', import.meta.url)),
+ },
}),
)
diff --git a/shared/src/types.ts b/shared/src/types.ts
index 2f56d6d..9b4b446 100644
--- a/shared/src/types.ts
+++ b/shared/src/types.ts
@@ -14,16 +14,13 @@ export interface Guide {
updated_at: string;
}
-export interface Guide {
+export interface TourLog {
id: number
- slug: string
- title: string
- region: string
- difficulty: Difficulty
- length_km: number
- body_html: string
- hero_image: string | null
- published: boolean
- author_id: number | null
- updated_at: string
+ tour_id: number
+ recorded_at: string
+ lat: number
+ lon: number
+ elevation_m: number | null
+ heart_rate: number | null
+ note: string | null
}
\ No newline at end of file
From 327c7007b1c37db007ff94c1296a89db0d792f6a Mon Sep 17 00:00:00 2001
From: Henrik <000henrik@gmail.com>
Date: Fri, 2 Oct 2026 13:54:19 +0200
Subject: [PATCH 4/4] Fixed format and lint
---
client/src/components/GuideCard.vue | 8 +-
client/src/lib/tours.test.ts | 32 ++++----
client/src/lib/tours.ts | 18 ++---
client/src/views/GuidesView.test.ts | 120 +++++++++++++++-------------
client/src/views/GuidesView.vue | 49 +++++++-----
5 files changed, 122 insertions(+), 105 deletions(-)
diff --git a/client/src/components/GuideCard.vue b/client/src/components/GuideCard.vue
index 51e772f..e7732b6 100644
--- a/client/src/components/GuideCard.vue
+++ b/client/src/components/GuideCard.vue
@@ -1,7 +1,7 @@
@@ -9,7 +9,9 @@ defineProps<{ guide: Guide }>()
{{ guide.title }}
- {{ guide.region }} · {{ guide.difficulty }} · {{ guide.length_km }} km
+
+ {{ guide.region }} · {{ guide.difficulty }} · {{ guide.length_km }} km
+
diff --git a/client/src/lib/tours.test.ts b/client/src/lib/tours.test.ts
index 0ae7cea..ae3aceb 100644
--- a/client/src/lib/tours.test.ts
+++ b/client/src/lib/tours.test.ts
@@ -1,27 +1,27 @@
-import { describe, it, expect } from 'vitest'
-import type { TourLog } from '@utpost/shared'
-import { elevationGain } from './tours'
+import { describe, it, expect } from "vitest";
+import type { TourLog } from "@utpost/shared";
+import { elevationGain } from "./tours";
const log = (elevation_m: number | null, id = 0): TourLog => ({
id,
tour_id: 1,
- recorded_at: '2026-09-01T08:00:00.000Z',
+ recorded_at: "2026-09-01T08:00:00.000Z",
lat: 67.9,
lon: 18.5,
elevation_m,
heart_rate: null,
note: null,
-})
+});
-describe('elevationGain', () => {
- it('summerar bara stigningar, inte nedförsbackar', () => {
- expect(elevationGain([log(100), log(150), log(120), log(180)])).toBe(110)
- })
- it('ger 0 för en tur utan mätpunkter', () => {
- expect(elevationGain([])).toBe(0)
- })
+describe("elevationGain", () => {
+ it("summerar bara stigningar, inte nedförsbackar", () => {
+ expect(elevationGain([log(100), log(150), log(120), log(180)])).toBe(110);
+ });
+ it("ger 0 för en tur utan mätpunkter", () => {
+ expect(elevationGain([])).toBe(0);
+ });
// Regressionstest – skuld ur docs/debt.md: en mätpunkt utan höjd räknas som havsnivå
- it('hoppar över mätpunkter utan höjd i stället för att räkna dem som noll', () => {
- expect(elevationGain([log(100), log(null), log(150)])).toBe(50)
- })
-})
+ it("hoppar över mätpunkter utan höjd i stället för att räkna dem som noll", () => {
+ expect(elevationGain([log(100), log(null), log(150)])).toBe(50);
+ });
+});
diff --git a/client/src/lib/tours.ts b/client/src/lib/tours.ts
index 2f0c15d..a8ed30e 100644
--- a/client/src/lib/tours.ts
+++ b/client/src/lib/tours.ts
@@ -1,19 +1,19 @@
-import type { TourLog } from '@utpost/shared'
+import type { TourLog } from "@utpost/shared";
export const elevationGain = (logs: TourLog[]): number => {
- let gain = 0
- let previousElevation: number | null = null
+ let gain = 0;
+ let previousElevation: number | null = null;
for (const log of logs) {
- const elevation = log.elevation_m
- if (elevation == null) continue
+ const elevation = log.elevation_m;
+ if (elevation == null) continue;
if (previousElevation !== null && elevation > previousElevation) {
- gain += elevation - previousElevation
+ gain += elevation - previousElevation;
}
- previousElevation = elevation
+ previousElevation = elevation;
}
- return gain
-}
\ No newline at end of file
+ return gain;
+};
diff --git a/client/src/views/GuidesView.test.ts b/client/src/views/GuidesView.test.ts
index baf352a..2402ff2 100644
--- a/client/src/views/GuidesView.test.ts
+++ b/client/src/views/GuidesView.test.ts
@@ -1,81 +1,89 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest'
-import { render, screen } from '@testing-library/vue'
-import userEvent from '@testing-library/user-event'
-import '@testing-library/jest-dom/vitest'
-import type { Guide } from '@utpost/shared'
-import GuidesView from './GuidesView.vue'
-import { get } from '../api.js'
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen } from "@testing-library/vue";
+import userEvent from "@testing-library/user-event";
+import "@testing-library/jest-dom/vitest";
+import type { Guide } from "@utpost/shared";
+import GuidesView from "./GuidesView.vue";
+import { get } from "../api.js";
-
-vi.mock('../api', () => ({ get: vi.fn() }))
-const mockedGet = vi.mocked(get)
+vi.mock("../api", () => ({ get: vi.fn() }));
+const mockedGet = vi.mocked(get);
// Kontraktet styr mockdatan: glömmer ni ett fält säger typecheck ifrån.
const guide = (overrides: Partial
): Guide => ({
id: 1,
- slug: 'kebnekaise',
- title: 'Kebnekaise',
- region: 'Lappland',
- difficulty: 'svår',
+ slug: "kebnekaise",
+ title: "Kebnekaise",
+ region: "Lappland",
+ difficulty: "svår",
length_km: 18,
- body_html: 'Sveriges tak
',
+ body_html: "Sveriges tak
",
hero_image: null,
published: true,
author_id: 1,
- updated_at: '2026-09-01T00:00:00.000Z',
+ updated_at: "2026-09-01T00:00:00.000Z",
...overrides,
-})
+});
const renderView = () =>
- render(GuidesView, { global: { stubs: { RouterLink: { template: '' } } } })
+ render(GuidesView, {
+ global: { stubs: { RouterLink: { template: "" } } },
+ });
-describe('GuidesView', () => {
+describe("GuidesView", () => {
beforeEach(() => {
mockedGet.mockResolvedValue([
- guide({ id: 1, title: 'Kebnekaise', region: 'Lappland' }),
- guide({ id: 2, slug: 'sodra-myrleden', title: 'Södra Myrleden', region: 'Småland' }),
- ])
- })
+ guide({ id: 1, title: "Kebnekaise", region: "Lappland" }),
+ guide({
+ id: 2,
+ slug: "sodra-myrleden",
+ title: "Södra Myrleden",
+ region: "Småland",
+ }),
+ ]);
+ });
//Test 1
- it('visar guiderna från API:et', async () => {
- renderView()
- expect(await screen.findByText('Kebnekaise')).toBeInTheDocument()
- expect(screen.getByText('2 av 2')).toBeInTheDocument()
- })
+ it("visar guiderna från API:et", async () => {
+ renderView();
+ expect(await screen.findByText("Kebnekaise")).toBeInTheDocument();
+ expect(screen.getByText("2 av 2")).toBeInTheDocument();
+ });
//Test 2
-it('filtrerar på landskap när användaren söker', async () => {
- const user = userEvent.setup()
- renderView()
- await screen.findByText('Kebnekaise')
-
- await user.type(screen.getByLabelText('Sök'), 'små')
-
- expect(screen.getByText('Södra Myrleden')).toBeInTheDocument()
- expect(screen.queryByText('Kebnekaise')).not.toBeInTheDocument()
- expect(screen.getByText('1 av 2')).toBeInTheDocument()
-})
+ it("filtrerar på landskap när användaren söker", async () => {
+ const user = userEvent.setup();
+ renderView();
+ await screen.findByText("Kebnekaise");
-//Test 3
-it('visar ett fel när API:et inte svarar', async () => {
- mockedGet.mockRejectedValueOnce(new Error('API svarade 500'))
- renderView()
- expect(await screen.findByRole('alert')).toHaveTextContent('API svarade 500')
-})
+ await user.type(screen.getByLabelText("Sök"), "små");
-//Test 4
-it('säger till när inget matchar', async () => {
- const user = userEvent.setup()
- renderView()
- await screen.findByText('Kebnekaise')
+ expect(screen.getByText("Södra Myrleden")).toBeInTheDocument();
+ expect(screen.queryByText("Kebnekaise")).not.toBeInTheDocument();
+ expect(screen.getByText("1 av 2")).toBeInTheDocument();
+ });
- await user.type(screen.getByLabelText('Sök'), 'öland')
+ //Test 3
+ it("visar ett fel när API:et inte svarar", async () => {
+ mockedGet.mockRejectedValueOnce(new Error("API svarade 500"));
+ renderView();
+ expect(await screen.findByRole("alert")).toHaveTextContent(
+ "API svarade 500",
+ );
+ });
- expect(screen.getByText('Inga guider matchar sökningen.')).toBeInTheDocument()
- expect(screen.getByText('0 av 2')).toBeInTheDocument()
- expect(screen.queryByText('Kebnekaise')).not.toBeInTheDocument()
-})
+ //Test 4
+ it("säger till när inget matchar", async () => {
+ const user = userEvent.setup();
+ renderView();
+ await screen.findByText("Kebnekaise");
-})
+ await user.type(screen.getByLabelText("Sök"), "öland");
+ expect(
+ screen.getByText("Inga guider matchar sökningen."),
+ ).toBeInTheDocument();
+ expect(screen.getByText("0 av 2")).toBeInTheDocument();
+ expect(screen.queryByText("Kebnekaise")).not.toBeInTheDocument();
+ });
+});
diff --git a/client/src/views/GuidesView.vue b/client/src/views/GuidesView.vue
index a2c8fc3..1a492ab 100644
--- a/client/src/views/GuidesView.vue
+++ b/client/src/views/GuidesView.vue
@@ -1,35 +1,37 @@
@@ -38,7 +40,12 @@ const visible = computed(() => {
-
+
{{ visible.length }} av {{ guides.length }}