Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/URIHandler.feeds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe("podNotesURIHandler with saved feeds", () => {
test("a link to the playing private episode by its secret URL seeks it without loading the feed", async () => {
const { feeds } = follow();
load.mockResolvedValue(feedDocument("Patron Show", [first]));
await feeds.ensureAll();
await feeds.ensureAll({ online: true });
const [subscribed] = get(feeds).get("Patron Show")?.episodes ?? [];
player.play(subscribed);
load.mockClear();
Expand Down Expand Up @@ -257,7 +257,7 @@ describe("podNotesURIHandler with saved feeds", () => {
async function lostPrivateUrl() {
const { feeds, feedUrls, patron } = follow();
load.mockResolvedValue(feedDocument("Patron Show", [first]));
await feeds.ensureAll();
await feeds.ensureAll({ online: true });
feedUrls.delete(patron.urlSecretId ?? "");
load.mockClear();
}
Expand Down Expand Up @@ -288,7 +288,7 @@ describe("podNotesURIHandler with saved feeds", () => {
vi.spyOn(console, "error").mockImplementation(() => {});
const { feeds } = follow();
load.mockResolvedValue(feedDocument("Patron Show", [first]));
await feeds.ensureAll();
await feeds.ensureAll({ online: true });
load.mockRejectedValue(new Error("offline"));

await open(privateLink, second.title);
Expand Down Expand Up @@ -365,7 +365,7 @@ describe("podNotesURIHandler with saved feeds", () => {
async (_, url, title) => {
const { feeds } = follow();
load.mockResolvedValue(feedDocument(title, [first]));
await feeds.ensureAll();
await feeds.ensureAll({ online: true });
load.mockResolvedValue(feedDocument(title, [second, first]));

await open(url, second.title);
Expand All @@ -383,7 +383,7 @@ describe("podNotesURIHandler with saved feeds", () => {
vi.spyOn(console, "error").mockImplementation(() => {});
const { feeds } = follow();
load.mockResolvedValue(feedDocument(title, [first]));
await feeds.ensureAll();
await feeds.ensureAll({ online: true });
load.mockRejectedValue(new Error("offline"));

await open(url, first.title);
Expand Down
95 changes: 70 additions & 25 deletions src/services/FeedRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async function loaded(
response: ParsedFeedEpisodes,
) {
t.repo.setSubscriptions(feeds);
const loading = t.repo.ensureAll();
const loading = t.repo.ensureAll({ online: true });
for (const request of t.requests) request.respond.resolve(response);
await loading;
}
Expand All @@ -99,7 +99,7 @@ describe("FeedRepository", () => {
"Alpha Alias": { ...alpha, title: "Alpha Alias" },
});

const loading = t.repo.ensureAll();
const loading = t.repo.ensureAll({ online: true });
expect(t.load).toHaveBeenCalledTimes(1);
t.requests[0].respond.resolve(doc("Alpha Channel", [item(1)]));
await loading;
Expand All @@ -118,7 +118,7 @@ describe("FeedRepository", () => {
const t = setup();
t.repo.setSubscriptions({ Alpha: alpha });

const first = t.repo.ensureAll();
const first = t.repo.ensureAll({ online: true });
const refresh = t.repo.load(["Alpha"], "network");
t.requests[0].respond.resolve(doc("Alpha", [item(1)]));
await expect(Promise.all([first, refresh])).resolves.toEqual([true, true]);
Expand All @@ -130,7 +130,7 @@ describe("FeedRepository", () => {
test("refreshes keep the shown episodes until they answer, in whatever order they answer", async () => {
const t = setup();
t.repo.setSubscriptions({ Alpha: alpha, Beta: beta });
const first = t.repo.ensureAll();
const first = t.repo.ensureAll({ online: true });
t.requests[0].respond.resolve(doc("Alpha", [item(1)]));
t.requests[1].respond.resolve(doc("Beta", [item(2)]));
await first;
Expand All @@ -152,7 +152,7 @@ describe("FeedRepository", () => {
await loaded(t, { Alpha: alpha }, doc("Alpha", [item(1)]));
t.advance(7 * HOUR);

const refresh = t.repo.ensureAll();
const refresh = t.repo.ensureAll({ online: true });
expect(t.load).toHaveBeenCalledTimes(2);
expect(t.titles("Alpha")).toEqual(["Episode 1"]);
t.requests[1].respond.reject(new Error("offline"));
Expand All @@ -166,7 +166,7 @@ describe("FeedRepository", () => {
test("a feed that never loaded reports the failure", async () => {
const t = setup();
t.repo.setSubscriptions({ Alpha: alpha });
const loading = t.repo.ensureAll();
const loading = t.repo.ensureAll({ online: true });
t.requests[0].respond.reject(new Error("offline"));
await expect(loading).resolves.toBe(false);

Expand All @@ -176,7 +176,7 @@ describe("FeedRepository", () => {
test("a response for a podcast removed while it loads is dropped and not cached", async () => {
const t = setup();
t.repo.setSubscriptions({ Alpha: alpha, Beta: beta });
const loading = t.repo.ensureAll();
const loading = t.repo.ensureAll({ online: true });
t.requests[0].respond.resolve(doc("Alpha", [item(1)]));

t.repo.setSubscriptions({ Alpha: alpha });
Expand All @@ -188,18 +188,18 @@ describe("FeedRepository", () => {

t.repo.setSubscriptions({ Alpha: alpha, Beta: beta });
expect(t.titles("Beta")).toEqual([]);
void t.repo.ensureAll();
void t.repo.ensureAll({ online: true });
expect(t.load).toHaveBeenCalledTimes(3);
});

test("a late response for a podcast's old URL never replaces its new feed", async () => {
const t = setup();
const moved = { ...alpha, url: "https://alpha.test/moved.xml" };
t.repo.setSubscriptions({ Alpha: alpha });
const before = t.repo.ensureAll();
const before = t.repo.ensureAll({ online: true });

t.repo.setSubscriptions({ Alpha: moved });
const after = t.repo.ensureAll();
const after = t.repo.ensureAll({ online: true });
t.requests[1].respond.resolve(doc("Alpha", [item(2)]));
t.requests[0].respond.resolve(doc("Alpha", [item(1)]));
await Promise.all([before, after]);
Expand All @@ -218,7 +218,7 @@ describe("FeedRepository", () => {

const t = setup({ storage: first.storage });
t.repo.setSubscriptions({ Alpha: alpha });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });
expect(t.load).not.toHaveBeenCalled();
expect(t.titles("Alpha")).toHaveLength(75);

Expand Down Expand Up @@ -269,7 +269,7 @@ describe("FeedRepository", () => {

const t = setup({ storage: first.storage });
t.repo.setSubscriptions({ Alpha: alpha });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(t.feed("Alpha")?.episodes).toEqual(first.feed("Alpha")?.episodes);
expect(t.feed("Alpha")?.episodes[0].episodeDate).toEqual(episode.episodeDate);
Expand All @@ -291,7 +291,7 @@ describe("FeedRepository", () => {

const t = setup({ storage: first.storage });
t.repo.setSubscriptions({ Alpha: alpha });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(t.load).not.toHaveBeenCalled();
expect(Object.keys(t.cached())).toEqual([alpha.url]);
Expand Down Expand Up @@ -386,23 +386,68 @@ describe("FeedRepository", () => {
const t = setup({ storage: seeded.storage });
t.advance(6 * HOUR);
t.repo.setSubscriptions({ Alpha: alpha });
void t.repo.ensureAll();
void t.repo.ensureAll({ online: true });
expect(t.load).not.toHaveBeenCalled();
expect(t.feed("Alpha")).toMatchObject({ status: "ready", episodes: [expect.anything()] });

t.advance(1);
void t.repo.ensureAll();
void t.repo.ensureAll({ online: true });
expect(t.load).toHaveBeenCalledTimes(1);
expect(t.feed("Alpha")).toMatchObject({ status: "loading", episodes: [expect.anything()] });
});

test("offline, an expired cache shows without a request", async () => {
const seeded = setup();
await loaded(seeded, { Alpha: alpha }, doc("Alpha", [item(1)]));

const t = setup({ storage: seeded.storage });
t.advance(7 * HOUR);
t.repo.setSubscriptions({ Alpha: alpha });
const loading = t.repo.ensureAll({ online: false });

expect(t.load).not.toHaveBeenCalled();
await expect(loading).resolves.toBe(true);
expect(t.feed("Alpha")).toMatchObject({ status: "stale", error: undefined });
expect(t.titles("Alpha")).toEqual(["Episode 1"]);
});

test("offline, a feed with no document waits idle without a request", async () => {
const t = setup();
t.repo.setSubscriptions({ Alpha: alpha });
const loading = t.repo.ensureAll({ online: false });

expect(t.load).not.toHaveBeenCalled();
await expect(loading).resolves.toBe(true);
expect(t.feed("Alpha")).toMatchObject({ status: "idle", error: undefined, episodes: [] });
});

test("reconnecting after an offline start fetches each URL once", async () => {
const t = setup();
t.repo.setSubscriptions({
Alpha: alpha,
"Alpha Alias": { ...alpha, title: "Alpha Alias" },
Beta: beta,
});
void t.repo.ensureAll({ online: false });
expect(t.load).not.toHaveBeenCalled();

const reconnect = t.repo.ensureAll({ online: true });
expect(t.requests.map(({ url }) => url)).toEqual([alpha.url, beta.url]);
t.requests[0].respond.resolve(doc("Alpha", [item(1)]));
t.requests[1].respond.resolve(doc("Beta", [item(2)]));
await expect(reconnect).resolves.toBe(true);

expect(t.titles("Alpha Alias")).toEqual(["Episode 1"]);
expect(t.titles("Beta")).toEqual(["Episode 2"]);
});

test("a private feed whose URL is not on this device is never fetched", async () => {
const resolveUrl = vi.fn(() => null);
const t = setup({ resolveUrl });
const placeholder = privateFeedPlaceholder("Private");
t.repo.setSubscriptions({ Private: { ...alpha, url: placeholder, urlSecretId: "id" } });

await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(t.load).not.toHaveBeenCalled();
expect(t.feed("Private")).toMatchObject({ status: "error", error: "unavailable" });
Expand All @@ -416,7 +461,7 @@ describe("FeedRepository", () => {
const resolveUrl = vi.fn(() => null);
const t = setup({ storage: seeded.storage, resolveUrl });
t.repo.setSubscriptions({ Private: privateFeed });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(resolveUrl).not.toHaveBeenCalled();
expect(t.titles("Private")).toEqual(["Episode 1"]);
Expand Down Expand Up @@ -492,7 +537,7 @@ describe("FeedRepository", () => {

const t = setup({ storage: first.storage });
t.repo.setSubscriptions({ Alpha: alpha });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(t.load).not.toHaveBeenCalled();
const guids = new Map(t.feed("Alpha")!.episodes.map(({ title, guid }) => [title, guid]));
Expand Down Expand Up @@ -550,7 +595,7 @@ describe("FeedRepository", () => {
await loaded(t, { Alpha: alpha, Beta: beta }, doc("Show", [item(1)]));
const episodes = t.feed("Alpha")?.episodes;

await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });
expect(t.feed("Alpha")?.episodes).toBe(episodes);

t.repo.setSubscriptions({ Alpha: { ...alpha, title: "Alpha Renamed" }, Beta: beta });
Expand All @@ -572,7 +617,7 @@ describe("FeedRepository", () => {
storage.saveLocalStorage(CACHE_KEY, stored);
const t = setup({ storage });
t.repo.setSubscriptions({ Alpha: alpha });
void t.repo.ensureAll();
void t.repo.ensureAll({ online: true });
expect(t.load).toHaveBeenCalledTimes(1);
}

Expand All @@ -585,7 +630,7 @@ describe("FeedRepository", () => {
);
const t = setup({ storage });
t.repo.setSubscriptions({ Alpha: alpha });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(t.load).not.toHaveBeenCalled();
expect(t.feed("Alpha")?.episodes).toEqual([
Expand Down Expand Up @@ -626,7 +671,7 @@ describe("FeedRepository", () => {
);
const t = setup({ storage });
t.repo.setSubscriptions({ Alpha: alpha });
await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });
const [first, second] = t.feed("Alpha")!.episodes;

expect(t.load).not.toHaveBeenCalled();
Expand All @@ -644,7 +689,7 @@ describe("FeedRepository", () => {
const t = setup({ storage });
t.repo.setSubscriptions({});

await t.repo.ensureAll();
await t.repo.ensureAll({ online: true });

expect(storage.values.has("podnotes:feed-cache:v5")).toBe(false);
expect([...legacyStorage.keys()]).toEqual([]);
Expand Down Expand Up @@ -695,7 +740,7 @@ describe("FeedRepository", () => {
const t = setup({ storage });
t.repo.setSubscriptions({ Alpha: alpha });

void t.repo.ensureAll();
void t.repo.ensureAll({ online: true });

expect(t.load).not.toHaveBeenCalled();
expect(t.titles("Alpha")).toEqual(["Episode 1"]);
Expand Down Expand Up @@ -732,7 +777,7 @@ describe("FeedRepository", () => {
test("a feed link for a subscribed feed shares its request", async () => {
const t = setup();
t.repo.setSubscriptions({ Alpha: alpha });
const loading = t.repo.ensureAll();
const loading = t.repo.ensureAll({ online: true });

const episodes = t.repo.episodesAt(alpha.url);
t.requests[0].respond.resolve(doc("Alpha Channel", [item(1)]));
Expand Down
4 changes: 3 additions & 1 deletion src/services/FeedRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ export class FeedRepository implements Readable<ReadonlyMap<FeedKey, FeedState>>
);
}

ensureAll(): Promise<boolean> {
ensureAll({ online }: { online: boolean }): Promise<boolean> {
this.hydrate();
if (!online) return Promise.resolve(true);
return this.load(this.subscriptions.keys(), "cached");
}

Expand Down
2 changes: 1 addition & 1 deletion src/ui/PodcastView/EpisodesPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ describe("a podcast's page", () => {
const answer = deferred<ParsedFeedEpisodes>();
load.mockReturnValueOnce(answer.promise);
services.online.set(true);
void services.feeds.ensureAll();
void services.feeds.ensureAll({ online: true });
await tick();

expect(screen.queryByRole("alert")).toBeNull();
Expand Down
2 changes: 1 addition & 1 deletion src/ui/PodcastView/LibraryPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe("the Library's podcast grid", () => {
await repository.load(["Art Show"], "network");

services.online.set(true);
void repository.ensureAll();
void repository.ensureAll({ online: true });
await tick();

expect(card("Art Show").querySelector(".pn-badge")).toBeNull();
Expand Down
5 changes: 3 additions & 2 deletions src/ui/PodcastView/PodcastView.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { fireEvent, render, screen, waitFor, within } from "@testing-library/svelte";
import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/svelte";
import * as obsidian from "obsidian";
import { get, readable } from "svelte/store";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
Expand Down Expand Up @@ -159,7 +159,7 @@ async function cacheFeeds(...entries: [PodcastFeed, Episode[]][]) {
now: Date.now,
});
earlier.setSubscriptions(Object.fromEntries(entries.map(([feed]) => [feed.title, feed])));
await earlier.ensureAll();
await earlier.ensureAll({ online: true });
}

function respondByUrl(responses: Record<string, Episode[]>) {
Expand Down Expand Up @@ -200,6 +200,7 @@ beforeEach(() => {
});

afterEach(() => {
cleanup();
resetStores();
mockLoad.mockReset();
delete (globalThis as Record<string, unknown>).app;
Expand Down
Loading
Loading