mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-01 02:12:26 +00:00
Refactor feed loading logic into shared loadFeedVideos method
- Introduce `loadFeedVideos(feedType, forceFetch)` in `js/app/feedCoordinator.js` to consolidate video loading logic. - Update `loadForYouVideos`, `loadKidsVideos`, `loadExploreVideos`, and `loadVideos` to use the shared method. - Parameterize feed-specific configuration (telemetry context, tags, loading messages, relay health checks) based on feed type. - Add `tests/app/feedCoordinator.test.mjs` to verify the new logic. Signed-off-by: Jules <[email protected]> Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
c539fef553
commit
afd64ba1ce
2 files changed
+183
-182
No files matched your search
+94
-182
@@ -1085,6 +1085,96 @@ export function createFeedCoordinator(deps) {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unified loader for all feed types.
|
||||
*/
|
||||
async loadFeedVideos(feedType, forceFetch = false) {
|
||||
devLogger.log(`Starting loadFeedVideos(${feedType})... (forceFetch =`, forceFetch, ")");
|
||||
this.setFeedTelemetryContext(feedType);
|
||||
|
||||
let includeTags = false;
|
||||
let loadingMessage = "Fetching videos\u2026";
|
||||
let refreshMethod = null;
|
||||
let shouldCheckRelayHealth = false;
|
||||
|
||||
switch (feedType) {
|
||||
case "recent":
|
||||
includeTags = true;
|
||||
loadingMessage = "Fetching recent videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshRecentFeed(opts);
|
||||
shouldCheckRelayHealth = true;
|
||||
break;
|
||||
case FEED_TYPES.FOR_YOU:
|
||||
includeTags = false;
|
||||
loadingMessage = "Fetching for-you videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshForYouFeed(opts);
|
||||
shouldCheckRelayHealth = true;
|
||||
break;
|
||||
case FEED_TYPES.KIDS:
|
||||
includeTags = true;
|
||||
loadingMessage = "Fetching kids videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshKidsFeed(opts);
|
||||
shouldCheckRelayHealth = false;
|
||||
break;
|
||||
case FEED_TYPES.EXPLORE:
|
||||
includeTags = false;
|
||||
loadingMessage = "Fetching explore videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshExploreFeed(opts);
|
||||
shouldCheckRelayHealth = false;
|
||||
break;
|
||||
default:
|
||||
devLogger.warn(`[Application] Unknown feed type for loadFeedVideos: ${feedType}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldCheckRelayHealth) {
|
||||
this.checkRelayHealthWarning();
|
||||
}
|
||||
|
||||
const container = this.mountVideoListView({ includeTags });
|
||||
const hasCachedVideos =
|
||||
this.nostrService &&
|
||||
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
|
||||
this.nostrService.getFilteredActiveVideos().length > 0;
|
||||
|
||||
if (!hasCachedVideos) {
|
||||
if (this.videoListView && container) {
|
||||
this.videoListView.showLoading(loadingMessage);
|
||||
} else if (container) {
|
||||
container.innerHTML = getSidebarLoadingMarkup(loadingMessage);
|
||||
}
|
||||
}
|
||||
|
||||
let initialRefreshPromise = null;
|
||||
|
||||
const videos = await this.nostrService.loadVideos({
|
||||
forceFetch,
|
||||
blacklistedEventIds: this.blacklistedEventIds,
|
||||
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
|
||||
onVideos: (payload, detail = {}) => {
|
||||
const promise = refreshMethod({
|
||||
reason: detail?.reason,
|
||||
fallbackVideos: payload,
|
||||
});
|
||||
if (!initialRefreshPromise) {
|
||||
initialRefreshPromise = promise;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (initialRefreshPromise) {
|
||||
await initialRefreshPromise;
|
||||
} else if (!Array.isArray(videos) || videos.length === 0) {
|
||||
await refreshMethod({ reason: "initial", fallbackVideos: [] });
|
||||
}
|
||||
|
||||
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to videos (older + new) and render them as they come in.
|
||||
*/
|
||||
@@ -1102,52 +1192,7 @@ export function createFeedCoordinator(deps) {
|
||||
this.lastLoadVideosTime = now;
|
||||
|
||||
this.loadVideosPromise = (async () => {
|
||||
devLogger.log("Starting loadVideos... (forceFetch =", forceFetch, ")");
|
||||
this.setFeedTelemetryContext("recent");
|
||||
this.checkRelayHealthWarning();
|
||||
|
||||
const container = this.mountVideoListView();
|
||||
const hasCachedVideos =
|
||||
this.nostrService &&
|
||||
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
|
||||
this.nostrService.getFilteredActiveVideos().length > 0;
|
||||
|
||||
if (!hasCachedVideos) {
|
||||
if (this.videoListView && container) {
|
||||
this.videoListView.showLoading("Fetching recent videos\u2026");
|
||||
} else if (container) {
|
||||
container.innerHTML = getSidebarLoadingMarkup("Fetching recent videos\u2026");
|
||||
}
|
||||
}
|
||||
|
||||
let initialRefreshPromise = null;
|
||||
|
||||
const videos = await this.nostrService.loadVideos({
|
||||
forceFetch,
|
||||
blacklistedEventIds: this.blacklistedEventIds,
|
||||
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
|
||||
onVideos: (payload, detail = {}) => {
|
||||
const promise = this.refreshRecentFeed({
|
||||
reason: detail?.reason,
|
||||
fallbackVideos: payload,
|
||||
});
|
||||
if (!initialRefreshPromise) {
|
||||
initialRefreshPromise = promise;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (initialRefreshPromise) {
|
||||
await initialRefreshPromise;
|
||||
} else if (!Array.isArray(videos) || videos.length === 0) {
|
||||
await this.refreshRecentFeed({ reason: "initial", fallbackVideos: [] });
|
||||
}
|
||||
|
||||
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
await this.loadFeedVideos("recent", forceFetch);
|
||||
})();
|
||||
|
||||
try {
|
||||
@@ -1158,148 +1203,15 @@ export function createFeedCoordinator(deps) {
|
||||
},
|
||||
|
||||
async loadForYouVideos(forceFetch = false) {
|
||||
devLogger.log("Starting loadForYouVideos... (forceFetch =", forceFetch, ")");
|
||||
this.setFeedTelemetryContext(FEED_TYPES.FOR_YOU);
|
||||
this.checkRelayHealthWarning();
|
||||
|
||||
const container = this.mountVideoListView({ includeTags: false });
|
||||
const hasCachedVideos =
|
||||
this.nostrService &&
|
||||
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
|
||||
this.nostrService.getFilteredActiveVideos().length > 0;
|
||||
|
||||
if (!hasCachedVideos) {
|
||||
if (this.videoListView && container) {
|
||||
this.videoListView.showLoading("Fetching for-you videos\u2026");
|
||||
} else if (container) {
|
||||
container.innerHTML = getSidebarLoadingMarkup("Fetching for-you videos\u2026");
|
||||
}
|
||||
}
|
||||
|
||||
let initialRefreshPromise = null;
|
||||
|
||||
const videos = await this.nostrService.loadVideos({
|
||||
forceFetch,
|
||||
blacklistedEventIds: this.blacklistedEventIds,
|
||||
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
|
||||
onVideos: (payload, detail = {}) => {
|
||||
const promise = this.refreshForYouFeed({
|
||||
reason: detail?.reason,
|
||||
fallbackVideos: payload,
|
||||
});
|
||||
if (!initialRefreshPromise) {
|
||||
initialRefreshPromise = promise;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (initialRefreshPromise) {
|
||||
await initialRefreshPromise;
|
||||
} else if (!Array.isArray(videos) || videos.length === 0) {
|
||||
await this.refreshForYouFeed({ reason: "initial", fallbackVideos: [] });
|
||||
}
|
||||
|
||||
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
return this.loadFeedVideos(FEED_TYPES.FOR_YOU, forceFetch);
|
||||
},
|
||||
|
||||
async loadKidsVideos(forceFetch = false) {
|
||||
devLogger.log("Starting loadKidsVideos... (forceFetch =", forceFetch, ")");
|
||||
this.setFeedTelemetryContext(FEED_TYPES.KIDS);
|
||||
|
||||
const container = this.mountVideoListView({ includeTags: true });
|
||||
const hasCachedVideos =
|
||||
this.nostrService &&
|
||||
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
|
||||
this.nostrService.getFilteredActiveVideos().length > 0;
|
||||
|
||||
if (!hasCachedVideos) {
|
||||
if (this.videoListView && container) {
|
||||
this.videoListView.showLoading("Fetching kids videos\u2026");
|
||||
} else if (container) {
|
||||
container.innerHTML = getSidebarLoadingMarkup("Fetching kids videos\u2026");
|
||||
}
|
||||
}
|
||||
|
||||
let initialRefreshPromise = null;
|
||||
|
||||
const videos = await this.nostrService.loadVideos({
|
||||
forceFetch,
|
||||
blacklistedEventIds: this.blacklistedEventIds,
|
||||
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
|
||||
onVideos: (payload, detail = {}) => {
|
||||
const promise = this.refreshKidsFeed({
|
||||
reason: detail?.reason,
|
||||
fallbackVideos: payload,
|
||||
});
|
||||
if (!initialRefreshPromise) {
|
||||
initialRefreshPromise = promise;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (initialRefreshPromise) {
|
||||
await initialRefreshPromise;
|
||||
} else if (!Array.isArray(videos) || videos.length === 0) {
|
||||
await this.refreshKidsFeed({ reason: "initial", fallbackVideos: [] });
|
||||
}
|
||||
|
||||
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
return this.loadFeedVideos(FEED_TYPES.KIDS, forceFetch);
|
||||
},
|
||||
|
||||
async loadExploreVideos(forceFetch = false) {
|
||||
devLogger.log("Starting loadExploreVideos... (forceFetch =", forceFetch, ")");
|
||||
this.setFeedTelemetryContext(FEED_TYPES.EXPLORE);
|
||||
|
||||
const container = this.mountVideoListView({ includeTags: false });
|
||||
const hasCachedVideos =
|
||||
this.nostrService &&
|
||||
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
|
||||
this.nostrService.getFilteredActiveVideos().length > 0;
|
||||
|
||||
if (!hasCachedVideos) {
|
||||
if (this.videoListView && container) {
|
||||
this.videoListView.showLoading("Fetching explore videos\u2026");
|
||||
} else if (container) {
|
||||
container.innerHTML = getSidebarLoadingMarkup("Fetching explore videos\u2026");
|
||||
}
|
||||
}
|
||||
|
||||
let initialRefreshPromise = null;
|
||||
|
||||
const videos = await this.nostrService.loadVideos({
|
||||
forceFetch,
|
||||
blacklistedEventIds: this.blacklistedEventIds,
|
||||
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
|
||||
onVideos: (payload, detail = {}) => {
|
||||
const promise = this.refreshExploreFeed({
|
||||
reason: detail?.reason,
|
||||
fallbackVideos: payload,
|
||||
});
|
||||
if (!initialRefreshPromise) {
|
||||
initialRefreshPromise = promise;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (initialRefreshPromise) {
|
||||
await initialRefreshPromise;
|
||||
} else if (!Array.isArray(videos) || videos.length === 0) {
|
||||
await this.refreshExploreFeed({ reason: "initial", fallbackVideos: [] });
|
||||
}
|
||||
|
||||
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
return this.loadFeedVideos(FEED_TYPES.EXPLORE, forceFetch);
|
||||
},
|
||||
|
||||
async loadOlderVideos(lastTimestamp) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { test, mock } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createFeedCoordinator } from '../../js/app/feedCoordinator.js';
|
||||
import { FEED_TYPES } from '../../js/constants.js';
|
||||
|
||||
test('createFeedCoordinator - loadForYouVideos', async (t) => {
|
||||
const mockDeps = {
|
||||
devLogger: { log: mock.fn(), warn: mock.fn() },
|
||||
userLogger: { warn: mock.fn() },
|
||||
nostrClient: {},
|
||||
getSidebarLoadingMarkup: mock.fn(() => '<div>Loading...</div>'),
|
||||
watchHistoryService: {},
|
||||
subscriptions: {},
|
||||
createActiveNostrSource: mock.fn(),
|
||||
createBlacklistFilterStage: mock.fn(),
|
||||
createDisinterestFilterStage: mock.fn(),
|
||||
createDedupeByRootStage: mock.fn(),
|
||||
createExploreDiversitySorter: mock.fn(),
|
||||
createExploreScorerStage: mock.fn(),
|
||||
createKidsAudienceFilterStage: mock.fn(),
|
||||
createKidsScorerStage: mock.fn(),
|
||||
createKidsScoreSorter: mock.fn(),
|
||||
createModerationStage: mock.fn(),
|
||||
createResolvePostedAtStage: mock.fn(),
|
||||
createTagPreferenceFilterStage: mock.fn(),
|
||||
createWatchHistorySuppressionStage: mock.fn(),
|
||||
createChronologicalSorter: mock.fn(),
|
||||
createSubscriptionAuthorsSource: mock.fn(),
|
||||
registerWatchHistoryFeed: mock.fn(),
|
||||
};
|
||||
|
||||
const coordinator = createFeedCoordinator(mockDeps);
|
||||
|
||||
// Mock application context
|
||||
const app = {
|
||||
...coordinator,
|
||||
nostrService: {
|
||||
getFilteredActiveVideos: mock.fn(() => []), // Simulate no cached videos
|
||||
loadVideos: mock.fn(async ({ onVideos }) => {
|
||||
// Simulate fetch completing with videos
|
||||
onVideos([], { reason: 'test-reason' });
|
||||
return [];
|
||||
}),
|
||||
getVideoSubscription: mock.fn(() => 'mock-subscription'),
|
||||
getVideosMap: mock.fn(() => new Map()),
|
||||
},
|
||||
videoListView: {
|
||||
showLoading: mock.fn(),
|
||||
state: {},
|
||||
},
|
||||
mountVideoListView: mock.fn(() => ({ innerHTML: '' })),
|
||||
checkRelayHealthWarning: mock.fn(),
|
||||
setFeedTelemetryContext: mock.fn(),
|
||||
isAuthorBlocked: mock.fn(() => false),
|
||||
refreshForYouFeed: mock.fn(async () => {}),
|
||||
refreshRecentFeed: mock.fn(async () => {}),
|
||||
refreshKidsFeed: mock.fn(async () => {}),
|
||||
refreshExploreFeed: mock.fn(async () => {}),
|
||||
blacklistedEventIds: new Set(),
|
||||
};
|
||||
|
||||
// Bind methods to app context
|
||||
const loadForYouVideos = app.loadForYouVideos.bind(app);
|
||||
|
||||
await t.test('loadForYouVideos executes successfully', async () => {
|
||||
await loadForYouVideos(false);
|
||||
|
||||
// Verify devLogger was called
|
||||
assert.strictEqual(mockDeps.devLogger.log.mock.callCount() > 0, true, 'devLogger.log should be called');
|
||||
|
||||
// Verify telemetry context set
|
||||
assert.strictEqual(app.setFeedTelemetryContext.mock.calls[0].arguments[0], FEED_TYPES.FOR_YOU, 'Should set telemetry context to for-you');
|
||||
|
||||
// Verify mountVideoListView called with includeTags: false
|
||||
assert.deepStrictEqual(app.mountVideoListView.mock.calls[0].arguments[0], { includeTags: false }, 'Should mount list view with includeTags: false');
|
||||
|
||||
// Verify checkRelayHealthWarning called
|
||||
assert.strictEqual(app.checkRelayHealthWarning.mock.callCount(), 1, 'Should check relay health warning');
|
||||
|
||||
// Verify nostrService.loadVideos called
|
||||
assert.strictEqual(app.nostrService.loadVideos.mock.callCount(), 1, 'Should call nostrService.loadVideos');
|
||||
|
||||
// Verify refreshForYouFeed called via callback
|
||||
assert.strictEqual(app.refreshForYouFeed.mock.callCount(), 1, 'Should call refreshForYouFeed via callback');
|
||||
|
||||
// Verify subscription updated
|
||||
assert.strictEqual(app.videoSubscription, 'mock-subscription', 'Should update videoSubscription');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user