Skip to content

Commit 91d5414

Browse files
authored
Merge pull request #2 from neweracy/feat/news-page-api
Feat/news page api
2 parents b9d6981 + 1b125d3 commit 91d5414

File tree

19 files changed

+1893
-202
lines changed

19 files changed

+1893
-202
lines changed

.github/workflow/deploy.yml

Whitespace-only changes.

.txt

Whitespace-only changes.

app/app.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import { customFontsToLoad } from "./theme";
3333
import { KeyboardProvider } from "react-native-keyboard-controller";
3434
import { loadDateFnsLocale } from "./utils/formatDate";
3535
import "react-native-get-random-values";
36+
import { api } from "./services/api";
37+
38+
3639

3740
export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE";
3841

@@ -72,7 +75,16 @@ export function App() {
7275
const [areFontsLoaded, fontLoadError] = useFonts(customFontsToLoad);
7376
const [isI18nInitialized, setIsI18nInitialized] = useState(false);
7477

78+
79+
const runTest = async () => {
80+
const result = await api.getGlobalHealthcareNews();
81+
if (result.kind === "ok") {
82+
console.log(result.articles); // Global health articles
83+
}
84+
};
85+
7586
useEffect(() => {
87+
7688
initI18n()
7789
.then(() => setIsI18nInitialized(true))
7890
.then(() => loadDateFnsLocale());

app/components/Icon.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export function Icon(props: IconProps) {
111111
export const iconRegistry = {
112112
back: require("../../assets/icons/back.png"),
113113
bell: require("../../assets/icons/bell.png"),
114-
calendar: require("../../assets/icons/calendar.png"),
114+
calendar: require("../../assets/icons/schedule.png"),
115115
caretLeft: require("../../assets/icons/caretLeft.png"),
116116
caretRight: require("../../assets/icons/caretRight.png"),
117117
check: require("../../assets/icons/check.png"),
@@ -134,6 +134,7 @@ export const iconRegistry = {
134134
x: require("../../assets/icons/x.png"),
135135
google: require("../../assets/icons/google.png"),
136136
spot: require("../../assets/icons/black-circle.png"),
137+
news: require("../../assets/icons/newspaper.png"),
137138
}
138139

139140
const $imageStyleBase: ImageStyle = {

app/models/NewsArticle.test.ts

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { NewsArticleModel } from "./NewsArticle"
2+
3+
describe("NewsArticle", () => {
4+
const createTestArticle = (overrides = {}) => {
5+
return NewsArticleModel.create({
6+
article_id: "test-1",
7+
title: "Test Health Article",
8+
link: "https://example.com/article",
9+
pubDate: "2024-01-15T10:30:00Z",
10+
source_id: "test-source",
11+
source_name: "Healthcare Today",
12+
source_url: "https://healthcaretoday.com",
13+
keywords: ["health", "medicine"],
14+
creator: ["Dr. Jane Smith"],
15+
description: "This is a test article about healthcare developments and medical research findings.",
16+
content: "Full article content with detailed information about healthcare trends and medical breakthroughs.",
17+
country: ["US", "GH"],
18+
category: ["health", "medicine"],
19+
source_priority: 1500,
20+
sentiment: "positive",
21+
image_url: "https://example.com/image.jpg",
22+
...overrides,
23+
})
24+
}
25+
26+
test("can be created with minimal props", () => {
27+
const instance = NewsArticleModel.create({
28+
article_id: "1",
29+
title: "Test Article",
30+
link: "https://example.com/test",
31+
pubDate: new Date().toISOString(),
32+
source_id: "source1",
33+
source_name: "Test Source",
34+
source_url: "https://example.com/source",
35+
})
36+
37+
expect(instance).toBeTruthy()
38+
expect(instance.keywords).toEqual([])
39+
expect(instance.description).toBe("")
40+
expect(instance.language).toBe("en")
41+
})
42+
43+
describe("date views", () => {
44+
test("formats dates correctly", () => {
45+
const article = createTestArticle({ pubDate: "2024-01-15T10:30:00Z" })
46+
47+
expect(article.formattedDate).toMatch(/2024/)
48+
expect(article.formattedDateTime).toContain("2024")
49+
})
50+
51+
test("handles invalid dates", () => {
52+
const article = createTestArticle({ pubDate: "invalid-date" })
53+
expect(article.formattedDate).toBe("invalid-date")
54+
expect(article.timeAgo).toBe("")
55+
})
56+
57+
test("calculates time ago", () => {
58+
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000)
59+
const article = createTestArticle({ pubDate: oneHourAgo.toISOString() })
60+
expect(article.timeAgo).toBe("1h ago")
61+
})
62+
})
63+
64+
describe("multimedia views", () => {
65+
test("detects multimedia presence", () => {
66+
const withImage = createTestArticle({ image_url: "test.jpg", video_url: null })
67+
const withVideo = createTestArticle({ image_url: null, video_url: "test.mp4" })
68+
const withNeither = createTestArticle({ image_url: null, video_url: null })
69+
70+
expect(withImage.hasImage).toBe(true)
71+
expect(withImage.hasMultimedia).toBe(true)
72+
expect(withVideo.hasVideo).toBe(true)
73+
expect(withNeither.hasMultimedia).toBe(false)
74+
})
75+
})
76+
77+
describe("content views", () => {
78+
test("handles categories and countries", () => {
79+
const article = createTestArticle({ category: ["health", "medicine"], country: ["US", "CA"] })
80+
const empty = createTestArticle({ category: [], country: [] })
81+
82+
expect(article.primaryCategory).toBe("health")
83+
expect(article.allCategories).toBe("health, medicine")
84+
expect(article.primaryCountry).toBe("US")
85+
expect(empty.primaryCategory).toBe("General")
86+
expect(empty.allCountries).toBe("Global")
87+
})
88+
89+
test("handles authors", () => {
90+
const withAuthors = createTestArticle({ creator: ["John Doe", "Jane Smith"] })
91+
const withoutAuthors = createTestArticle({ creator: [] })
92+
93+
expect(withAuthors.authorName).toBe("John Doe, Jane Smith")
94+
expect(withAuthors.firstAuthor).toBe("John Doe")
95+
expect(withoutAuthors.authorName).toBe("Unknown")
96+
})
97+
98+
test("truncates descriptions", () => {
99+
const longDesc = "A".repeat(150)
100+
const article = createTestArticle({ description: longDesc })
101+
102+
expect(article.shortDescription).toBe("A".repeat(100) + "...")
103+
expect(article.mediumDescription).toBe("A".repeat(150))
104+
})
105+
})
106+
107+
describe("health detection", () => {
108+
test("detects health-related content", () => {
109+
const healthArticle = createTestArticle({
110+
title: "Medical Breakthrough",
111+
description: "",
112+
keywords: []
113+
})
114+
const nonHealthArticle = createTestArticle({
115+
title: "Tech Update",
116+
description: "Software news",
117+
keywords: ["tech"]
118+
})
119+
120+
expect(healthArticle.isHealthRelated).toBe(true)
121+
expect(nonHealthArticle.isHealthRelated).toBe(false)
122+
})
123+
})
124+
125+
describe("priority and sentiment", () => {
126+
test("categorizes source priority", () => {
127+
const highPriority = createTestArticle({ source_priority: 500 })
128+
const lowPriority = createTestArticle({ source_priority: 8000 })
129+
130+
expect(highPriority.sourcePriorityLabel).toBe("High Priority")
131+
expect(highPriority.sourcePriorityBadgeColor).toBe("green")
132+
expect(lowPriority.sourcePriorityLabel).toBe("Low Priority")
133+
expect(lowPriority.sourcePriorityBadgeColor).toBe("gray")
134+
})
135+
136+
test("handles sentiment", () => {
137+
const positive = createTestArticle({ sentiment: "positive" })
138+
const unknown = createTestArticle({ sentiment: "" })
139+
140+
expect(positive.sentimentLabel).toBe("Positive")
141+
expect(positive.sentimentColor).toBe("green")
142+
expect(unknown.sentimentLabel).toBe("Unknown")
143+
expect(unknown.sentimentColor).toBe("gray")
144+
})
145+
})
146+
147+
describe("Ghana detection", () => {
148+
test("identifies Ghana news", () => {
149+
const ghanaCode = createTestArticle({ country: ["GH"] })
150+
const ghanaName = createTestArticle({ country: ["Ghana"] })
151+
const nonGhana = createTestArticle({ country: ["US"] })
152+
153+
expect(ghanaCode.isGhanaNews).toBe(true)
154+
expect(ghanaName.isGhanaNews).toBe(true)
155+
expect(nonGhana.isGhanaNews).toBe(false)
156+
})
157+
})
158+
159+
describe("utility views", () => {
160+
test("provides utility properties", () => {
161+
const article = createTestArticle({
162+
content: "Test content",
163+
title: "Test Title",
164+
keywords: ["health", "test"]
165+
})
166+
const emptyTitle = createTestArticle({ title: "" })
167+
168+
expect(article.hasContent).toBe(true)
169+
expect(article.displayTitle).toBe("Test Title")
170+
expect(article.shareUrl).toBe("https://example.com/article")
171+
expect(article.keywordsList).toBe("health, test")
172+
expect(article.readingTimeEstimate).toMatch(/min read/)
173+
expect(emptyTitle.displayTitle).toBe("Untitled Article")
174+
})
175+
})
176+
177+
describe("actions", () => {
178+
test("updates from API data", () => {
179+
const article = createTestArticle({ title: "Original" })
180+
181+
article.updateFromApi({ title: "Updated", sentiment: "negative" })
182+
183+
expect(article.title).toBe("Updated")
184+
expect(article.sentiment).toBe("negative")
185+
})
186+
187+
test("updates sentiment and AI summary", () => {
188+
const article = createTestArticle()
189+
190+
article.updateSentiment("neutral")
191+
article.updateAiSummary("AI summary")
192+
193+
expect(article.sentiment).toBe("neutral")
194+
expect(article.ai_summary).toBe("AI summary")
195+
})
196+
197+
test("markAsRead executes without error", () => {
198+
const article = createTestArticle()
199+
expect(() => article.markAsRead()).not.toThrow()
200+
})
201+
})
202+
})

0 commit comments

Comments
 (0)