summaryrefslogtreecommitdiff
path: root/app/src/test/java/org/terst
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-24 07:58:47 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-24 07:58:47 +0000
commit209da9e628112a5a25ac4519370cfdc072462547 (patch)
treeb88d207bc2f1dafe9a04d4621e9fb9ccd2e3906b /app/src/test/java/org/terst
parentda942624a4abd82797f563cf8c57f1124c5de435 (diff)
Let filter rules be scoped to one feed; fix coverage-gate report path bug
The data model and FilterRuleEngine already supported a per-feed scope (FilterRuleEntity.feedId), but Settings' "Add rule" form never let a user set it -- only a hardcoded migration had ever created one. Add a FeedScopePicker so a rule can target "All feeds" (unchanged default) or one specific feed, and show each existing rule's scope in the list. Added the first real unit tests in this repo (FilterRuleEngineTest), covering global vs. feed-scoped matching and mute-wins-over-highlight priority -- the exact behavior this UI gap left unexercised outside one migration-seeded row. First real jacoco coverage number: 2.31%. While verifying, found the pre-commit hook's coverage step was silently a no-op: it pointed at app/build/reports/jacoco/report.xml, but the jacoco setup that landed here uses the standard Gradle JacocoReport task, which writes to reports/jacoco/jacocoTestReport/jacocoTestReport.xml instead. Fixed the path so the gate actually reads real numbers now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017GMEkHeqKz6FLkmizowBTK
Diffstat (limited to 'app/src/test/java/org/terst')
-rw-r--r--app/src/test/java/org/terst/feedreader/data/FilterRuleEngineTest.kt83
1 files changed, 83 insertions, 0 deletions
diff --git a/app/src/test/java/org/terst/feedreader/data/FilterRuleEngineTest.kt b/app/src/test/java/org/terst/feedreader/data/FilterRuleEngineTest.kt
new file mode 100644
index 0000000..2f9dbd3
--- /dev/null
+++ b/app/src/test/java/org/terst/feedreader/data/FilterRuleEngineTest.kt
@@ -0,0 +1,83 @@
+package org.terst.feedreader.data
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+private data class FakeArticle(override val title: String, override val feedId: String?) : Titled
+
+class FilterRuleEngineTest {
+
+ private fun rule(pattern: String, action: FilterAction, feedId: String? = null) =
+ FilterRuleEntity(pattern = pattern, action = action, feedId = feedId, createdAt = 0L)
+
+ @Test
+ fun `global rule matches an article from any feed`() {
+ val rules = FilterRuleEngine.compile(listOf(rule("politics", FilterAction.MUTE)))
+
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-a"), rules))
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-b"), rules))
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", null), rules))
+ }
+
+ @Test
+ fun `feed-scoped rule only matches an article from that feed`() {
+ val rules = FilterRuleEngine.compile(listOf(rule("politics", FilterAction.MUTE, feedId = "feed-a")))
+
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-a"), rules))
+ }
+
+ @Test
+ fun `feed-scoped rule does not match an article from a different feed`() {
+ val rules = FilterRuleEngine.compile(listOf(rule("politics", FilterAction.MUTE, feedId = "feed-a")))
+
+ assertEquals(FilterResult.NORMAL, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-b"), rules))
+ assertEquals(FilterResult.NORMAL, FilterRuleEngine.evaluate(FakeArticle("Politics today", null), rules))
+ }
+
+ @Test
+ fun `mute wins over highlight when both match`() {
+ val rules = FilterRuleEngine.compile(
+ listOf(
+ rule("politics", FilterAction.HIGHLIGHT),
+ rule("politics", FilterAction.MUTE)
+ )
+ )
+
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-a"), rules))
+ }
+
+ @Test
+ fun `highlight applies when no mute rule matches`() {
+ val rules = FilterRuleEngine.compile(listOf(rule("politics", FilterAction.HIGHLIGHT)))
+
+ assertEquals(FilterResult.HIGHLIGHTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-a"), rules))
+ }
+
+ @Test
+ fun `matching is case-insensitive`() {
+ val rules = FilterRuleEngine.compile(listOf(rule("POLITICS", FilterAction.MUTE)))
+
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("politics today", "feed-a"), rules))
+ }
+
+ @Test
+ fun `no match returns normal`() {
+ val rules = FilterRuleEngine.compile(listOf(rule("politics", FilterAction.MUTE)))
+
+ assertEquals(FilterResult.NORMAL, FilterRuleEngine.evaluate(FakeArticle("Sports today", "feed-a"), rules))
+ }
+
+ @Test
+ fun `a feed-scoped rule for one feed and a global rule both apply independently`() {
+ val rules = FilterRuleEngine.compile(
+ listOf(
+ rule("politics", FilterAction.MUTE, feedId = "feed-a"),
+ rule("sports", FilterAction.HIGHLIGHT)
+ )
+ )
+
+ assertEquals(FilterResult.MUTED, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-a"), rules))
+ assertEquals(FilterResult.NORMAL, FilterRuleEngine.evaluate(FakeArticle("Politics today", "feed-b"), rules))
+ assertEquals(FilterResult.HIGHLIGHTED, FilterRuleEngine.evaluate(FakeArticle("Sports today", "feed-b"), rules))
+ }
+}