2025-03-08 08:31:15 +01:00
|
|
|
import { configureStore } from "@reduxjs/toolkit"
|
2025-05-23 15:57:53 +02:00
|
|
|
import type { AxiosResponse } from "axios"
|
|
|
|
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
2025-06-27 16:29:31 +02:00
|
|
|
import { client } from "@/app/client"
|
|
|
|
|
import { loadEntries } from "@/app/entries/thunks"
|
|
|
|
|
import { type RootState, reducers } from "@/app/store"
|
|
|
|
|
import { newFeedEntriesDiscovered, selectNextUnreadTreeItem } from "@/app/tree/thunks"
|
|
|
|
|
import type { Category, Entries, Entry, Subscription } from "@/app/types"
|
2025-05-23 15:57:53 +02:00
|
|
|
|
2025-06-27 16:29:31 +02:00
|
|
|
vi.mock(import("@/app/client"))
|
2025-03-08 08:31:15 +01:00
|
|
|
|
|
|
|
|
const createCategory = (id: string): Category => ({
|
|
|
|
|
id,
|
|
|
|
|
name: id,
|
|
|
|
|
children: [],
|
|
|
|
|
feeds: [],
|
|
|
|
|
expanded: true,
|
|
|
|
|
position: 0,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const createFeed = (id: number, unread: number): Subscription => ({
|
|
|
|
|
id,
|
|
|
|
|
name: String(id),
|
|
|
|
|
unread,
|
|
|
|
|
errorCount: 0,
|
|
|
|
|
position: 0,
|
|
|
|
|
feedUrl: "",
|
|
|
|
|
feedLink: "",
|
|
|
|
|
iconUrl: "",
|
2026-02-18 16:03:43 +01:00
|
|
|
pushNotificationsEnabled: true,
|
2025-03-08 08:31:15 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const root = createCategory("root")
|
|
|
|
|
|
|
|
|
|
const catA = createCategory("catA")
|
|
|
|
|
catA.feeds.push(createFeed(1, 0), createFeed(2, 0), createFeed(3, 1))
|
|
|
|
|
|
|
|
|
|
const catB = createCategory("catB")
|
|
|
|
|
|
|
|
|
|
const catC = createCategory("catC")
|
|
|
|
|
catC.feeds.push(createFeed(4, 1))
|
|
|
|
|
|
|
|
|
|
root.children.push(catA, catB, catC)
|
|
|
|
|
|
|
|
|
|
describe("selectNextUnreadTreeItem", () => {
|
|
|
|
|
it("selects the next unread category", async () => {
|
|
|
|
|
const store = configureStore({
|
|
|
|
|
reducer: reducers,
|
|
|
|
|
preloadedState: {
|
|
|
|
|
tree: {
|
|
|
|
|
rootCategory: root,
|
|
|
|
|
},
|
|
|
|
|
entries: {
|
|
|
|
|
source: {
|
|
|
|
|
type: "category",
|
|
|
|
|
id: "catA",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} as RootState,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await store.dispatch(selectNextUnreadTreeItem({ direction: "forward" }))
|
|
|
|
|
expect(store.getState().redirect.to).toBe("/app/category/catC")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("selects the previous unread category", async () => {
|
|
|
|
|
const store = configureStore({
|
|
|
|
|
reducer: reducers,
|
|
|
|
|
preloadedState: {
|
|
|
|
|
tree: {
|
|
|
|
|
rootCategory: root,
|
|
|
|
|
},
|
|
|
|
|
entries: {
|
|
|
|
|
source: {
|
|
|
|
|
type: "category",
|
|
|
|
|
id: "catC",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} as RootState,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await store.dispatch(selectNextUnreadTreeItem({ direction: "backward" }))
|
|
|
|
|
expect(store.getState().redirect.to).toBe("/app/category/catA")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("selects the next unread feed", async () => {
|
|
|
|
|
const store = configureStore({
|
|
|
|
|
reducer: reducers,
|
|
|
|
|
preloadedState: {
|
|
|
|
|
tree: {
|
|
|
|
|
rootCategory: root,
|
|
|
|
|
},
|
|
|
|
|
entries: {
|
|
|
|
|
source: {
|
|
|
|
|
type: "feed",
|
|
|
|
|
id: "1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} as RootState,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await store.dispatch(selectNextUnreadTreeItem({ direction: "forward" }))
|
|
|
|
|
expect(store.getState().redirect.to).toBe("/app/feed/3")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("selects the previous unread feed", async () => {
|
|
|
|
|
const store = configureStore({
|
|
|
|
|
reducer: reducers,
|
|
|
|
|
preloadedState: {
|
|
|
|
|
tree: {
|
|
|
|
|
rootCategory: root,
|
|
|
|
|
},
|
|
|
|
|
entries: {
|
|
|
|
|
source: {
|
|
|
|
|
type: "feed",
|
|
|
|
|
id: "4",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} as RootState,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await store.dispatch(selectNextUnreadTreeItem({ direction: "backward" }))
|
|
|
|
|
expect(store.getState().redirect.to).toBe("/app/feed/3")
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-05-23 15:57:53 +02:00
|
|
|
|
|
|
|
|
describe("hasNewEntries", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.resetAllMocks()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("sets and clear flag for a feed", async () => {
|
|
|
|
|
vi.mocked(client.feed.getEntries).mockResolvedValue({
|
|
|
|
|
data: {
|
|
|
|
|
entries: [{ id: "3" } as Entry],
|
|
|
|
|
hasMore: false,
|
|
|
|
|
name: "my-feed",
|
|
|
|
|
errorCount: 3,
|
|
|
|
|
feedLink: "https://mysite.com/feed",
|
|
|
|
|
timestamp: 123,
|
|
|
|
|
ignoredReadStatus: false,
|
|
|
|
|
},
|
|
|
|
|
} as AxiosResponse<Entries>)
|
|
|
|
|
|
|
|
|
|
const store = configureStore({
|
|
|
|
|
reducer: reducers,
|
|
|
|
|
preloadedState: {
|
|
|
|
|
tree: {
|
|
|
|
|
rootCategory: root,
|
|
|
|
|
},
|
|
|
|
|
entries: {
|
|
|
|
|
source: {
|
|
|
|
|
type: "feed",
|
|
|
|
|
id: "1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} as RootState,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// initial state
|
|
|
|
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].unread).toBe(0)
|
|
|
|
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBeFalsy()
|
|
|
|
|
|
|
|
|
|
// increments unread count and sets hasNewEntries to true
|
|
|
|
|
await store.dispatch(newFeedEntriesDiscovered({ feedId: 1, amount: 3 }))
|
|
|
|
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].unread).toBe(3)
|
|
|
|
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBe(true)
|
|
|
|
|
|
|
|
|
|
// reload entries and sets hasNewEntries to false
|
|
|
|
|
await store.dispatch(loadEntries({ source: { type: "feed", id: "1" }, clearSearch: true }))
|
|
|
|
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|