Skip to content

Commit

Permalink
Create a Service for providing video/category data #10 (#19)
Browse files Browse the repository at this point in the history
* Create a Service for providing video/category data #10

(Demo-rebase with @OGTor & @mblomdahl!)

* #10 Update after reviews

Changes:
- Removed root-level package.json and package-lock.json
- Removed package.json dependency on `peertube/embed-api`
- Moved testData.json from root level to `OwnTube.tv/` Node project directory
- Load testData.json via CommonJS import instead of HTTP request to web server

From commits 4/3 to 6/3:
- github actions testData fix v11
- github actions testData fix v10
- revert
- test
- github actions testData fix v9
- github actions testData fix v8
- github actions testData fix v7
- github actions testData fix v6
- github actions testData fix v5
- github actions testData fix v4
- github actions testData fix v3
- github actions testData fix
- github actions testData fix
- Changes information in testData.json
- Fixes: Changed to using testData instead of test API
- Create a Service for providing video/category data #10

(Rebase-demo med @OGTor och @mblomdahl!)

* PR #19 contribution on service tests (OGTor/web-client#1)

This PR adds test coverage for the behavior described in task #10 using Jest, which seems to be the test framework of choice for React Native (see [Testing docs here](https://reactnative.dev/docs/testing-overview#unit-tests)).

@OGTor please merge this in and update your VideoService to pass all tests without test suite modifications!

(Demo-rebase med @OGTor och @mblomdahl!)

* Pass tests from OGTor/web-client#1 + PR comments

Changes:
- Updated `videoService.tsx` to pass all tests
- Updated `VideoDataService.tsx` to render app landing page with videos **in** categories

From squashed updates from 15/3 to 22/3:
- final commit v2
- Revert "Final commit", This reverts commit 5b94307.
- Final commit
- test fix v3
- test fix v2
- fix for testing
- Create a Service for providing video/category data #10
- final changes
- changed category

* Remove type Category

* Prettier & remove extra testData.json

* update after review 2

* changes after eslint errors

* Moved VideoServiceState, Fixed thumbnail Render

* Removed inline styling for eslint error
  • Loading branch information
OGTor authored Apr 21, 2024
1 parent d034fca commit 1ed0943
Show file tree
Hide file tree
Showing 9 changed files with 7,740 additions and 4,213 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy-static-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ jobs:
run: cd OwnTube.tv/ && npx eslint .
- name: Check Prettier Formatting
run: cd OwnTube.tv/ && npx prettier --check ../
- name: Run Tests
run: cd OwnTube.tv/ && npm run test
- name: Inject Build Info
run: |
# Overwrite build-info.json in source root dir
Expand Down
3 changes: 3 additions & 0 deletions OwnTube.tv/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
import VideoDataService from "./components/videosOverview";

import build_info from "./build-info.json";

Expand All @@ -22,6 +23,7 @@ export default function App() {
🙋‍♀️ was here!)
</Text>
<StatusBar style="auto" />
<VideoDataService></VideoDataService>
</View>
);
}
Expand All @@ -31,5 +33,6 @@ const styles = StyleSheet.create({
alignItems: "center",
flex: 1,
justifyContent: "center",
overflow: "scroll",
},
});
84 changes: 84 additions & 0 deletions OwnTube.tv/components/videosOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState, useEffect } from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import VideoService from "../lib/videoServices";
import { Video } from "../types";

export interface VideoServiceState {
videos: Video[];
categories: string[];
error: string | null;
}
const useVideoService = () => {
const [state, setState] = useState<VideoServiceState>({
videos: [],
categories: [],
error: null,
});

useEffect(() => {
const videoService = new VideoService();

const fetchVideos = async () => {
try {
videoService.loadVideosFromJson();
const categoryLabels = videoService.getVideoCategoryLabels();
const videosWithThumbnails = videoService.completeThumbnailUrls();
setState({
videos: videosWithThumbnails,
categories: categoryLabels,
error: null,
});
} catch (error) {
console.error("Error fetching videos:", (error as Error).message);
setState((prev) => ({ ...prev, error: (error as Error).message }));
}
};

fetchVideos();
}, []);
return state;
};

const VideoDataService: React.FC = () => {
const { videos, categories, error } = useVideoService();

if (error) {
return (
<View>
<Text>Error: {error}</Text>
</View>
);
}

return (
<View>
{/* Display Category Labels */}
{categories.map((category) => (
<View key={category}>
<Text>Category: {category}</Text>
<View>
{videos
.filter((video) => video.category.label === category)
.map((video) => (
<View key={video.id}>
<Text>ID: {video.id}</Text>
<Text>Name: {video.name}</Text>
<Text>Category ID: {video.category.id}</Text>
<Text>Category Label: {video.category.label}</Text>
<Image source={{ uri: video.thumbnailUrl }} style={styles.image} />
</View>
))}
</View>
</View>
))}
</View>
);
};

const styles = StyleSheet.create({
image: {
height: 180,
width: 200,
},
});
export default VideoDataService;
71 changes: 71 additions & 0 deletions OwnTube.tv/lib/videoServices.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import VideoService from "./videoServices";

describe("VideoService", () => {
it("returns a list of unique category label names from testData.json", () => {
const videoService = new VideoService();

const categoryLabels = videoService.getVideoCategoryLabels();
expect(categoryLabels).toBeInstanceOf(Array);
expect(categoryLabels.length).toBe(5);
expect(categoryLabels).toContain("Gaming");
expect(categoryLabels).toContain("Entertainment");
expect(categoryLabels).toContain("Unknown");
expect(categoryLabels).toContain("Art");
expect(categoryLabels).toContain("Education");
});

it("returns a list with the correct number of videos from testData.json, for each label", () => {
const videoService = new VideoService();

const gamingVideos = videoService.getVideosForCategory("Gaming");
expect(gamingVideos).toBeInstanceOf(Array);
expect(gamingVideos.length).toBe(6);

const entertainmentVideos = videoService.getVideosForCategory("Entertainment");
expect(entertainmentVideos).toBeInstanceOf(Array);
expect(entertainmentVideos.length).toBe(1);

const unknownVideos = videoService.getVideosForCategory("Unknown");
expect(unknownVideos).toBeInstanceOf(Array);
expect(unknownVideos.length).toBe(6);

const artVideos = videoService.getVideosForCategory("Art");
expect(artVideos).toBeInstanceOf(Array);
expect(artVideos.length).toBe(1);

const educationVideos = videoService.getVideosForCategory("Education");
expect(educationVideos).toBeInstanceOf(Array);
expect(educationVideos.length).toBe(1);

const undefinedLabelVideos = videoService.getVideosForCategory("Undefined, just made up from nowhere!");
expect(undefinedLabelVideos).toBeInstanceOf(Array);
expect(undefinedLabelVideos.length).toBe(0);
});

it("returns a total 15 videos with from testData.json, all with id, name, category, description, and thumbnailPath", () => {
const videoService = new VideoService();
const allVideos = [];
for (const label of videoService.getVideoCategoryLabels()) {
const categoryVideos = videoService.getVideosForCategory(label);
allVideos.push(...categoryVideos);
}

expect(allVideos.length).toBe(15);

for (const video of allVideos) {
expect(Number.isInteger(video.id)).toBe(true);
expect(typeof video.name).toBe("string");
if (video.category.id) {
expect(video.category).toMatchObject({ id: expect.any(Number), label: expect.any(String) });
} else {
expect(video.category).toMatchObject({ id: null, label: expect.any(String) });
}
if (video.description) {
expect(typeof video.description).toBe("string");
} else {
expect(video.description).toBeNull();
}
expect(typeof video.thumbnailPath).toBe("string");
}
});
});
38 changes: 38 additions & 0 deletions OwnTube.tv/lib/videoServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as testData from "../testData.json";
import { Video, Category } from "../types";

class VideoService {
private videos: Video[] = [];
private categories: Category[] = [];
private readonly baseThumbnailUrl = "https://peertube2.cpy.re";

constructor() {
this.loadVideosFromJson();
}

public getVideoCategoryLabels(): string[] {
return this.categories.map(({ label }) => label);
}

public completeThumbnailUrls(): Video[] {
return this.videos.map((video) => ({
...video,
thumbnailUrl: `${this.baseThumbnailUrl}${video.thumbnailPath}`,
}));
}

public getVideosForCategory(categoryLabel: string): Video[] {
return this.videos.filter((video) => video.category.label === categoryLabel);
}

public loadVideosFromJson(): void {
const data = testData;
this.videos = data.data;
const uniqueCategories: Category[] = Array.from(new Set(this.videos.map((video) => video.category.label))).map(
(label, index) => ({ id: index + 1, label }),
);
this.categories = uniqueCategories;
}
}

export default VideoService;
Loading

0 comments on commit 1ed0943

Please sign in to comment.