mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Increases the timeout for `waitForFeedItems` from 15s to 30s across multiple E2E test files to prevent test flakiness in slower environments (e.g. Firefox in GitHub Actions). Co-authored-by: PR0M3TH3AN <[email protected]>
313 lines
9.3 KiB
TypeScript
313 lines
9.3 KiB
TypeScript
/**
|
|
* View navigation scenarios: sidebar links, hash routing, view switching.
|
|
*
|
|
* Scenarios covered:
|
|
* - SCN-nav-sidebar-recent: Recently Added link navigates to the recent videos view
|
|
* - SCN-nav-sidebar-kids: For Kids link navigates to the kids view
|
|
* - SCN-nav-sidebar-history: History link navigates to the history view
|
|
* - SCN-nav-hash-direct: Direct hash navigation renders the correct view
|
|
* - SCN-nav-login-reveals: Login reveals For You, Explore, and Subscriptions links
|
|
* - SCN-nav-sidebar-collapse: Sidebar collapse toggle works
|
|
* - SCN-nav-view-container: Each view has a video-list container
|
|
* - SCN-nav-feed-populates: Seeded videos appear in the most-recent-videos view
|
|
*/
|
|
|
|
import { test, expect } from "./helpers/bitvidTestFixture";
|
|
|
|
test.describe("View navigation — sidebar links", () => {
|
|
test("Recently Added sidebar link navigates to the recent videos view", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded
|
|
await gotoApp();
|
|
|
|
// When: user clicks the Recently Added link
|
|
const recentLink = page.locator('a[href="#view=most-recent-videos"]');
|
|
await expect(recentLink).toBeAttached();
|
|
await recentLink.click();
|
|
|
|
// Then: the URL hash should reflect the view
|
|
await page.waitForFunction(
|
|
() => window.location.hash.includes("view=most-recent-videos"),
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
// And: the video list container should be present
|
|
const videoList = page.locator('[data-testid="video-list"]');
|
|
await expect(videoList).toBeAttached({ timeout: 10000 });
|
|
});
|
|
|
|
test("For Kids sidebar link navigates to the kids view", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded
|
|
await gotoApp();
|
|
|
|
// When: user clicks the For Kids link
|
|
const kidsLink = page.locator('a[href="#view=kids"]');
|
|
await expect(kidsLink).toBeAttached();
|
|
await kidsLink.click();
|
|
|
|
// Then: the URL hash should reflect the kids view
|
|
await page.waitForFunction(
|
|
() => window.location.hash.includes("view=kids"),
|
|
{ timeout: 5000 },
|
|
);
|
|
});
|
|
|
|
test("History sidebar link navigates to the history view", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded
|
|
await gotoApp();
|
|
|
|
// When: user clicks the History link
|
|
const historyLink = page.locator('a[href="#view=history"]');
|
|
await expect(historyLink).toBeAttached();
|
|
await historyLink.click();
|
|
|
|
// Then: the URL hash should reflect the history view
|
|
await page.waitForFunction(
|
|
() => window.location.hash.includes("view=history"),
|
|
{ timeout: 5000 },
|
|
);
|
|
});
|
|
});
|
|
|
|
test.describe("View navigation — login-gated links", () => {
|
|
test("For You, Explore, and Subscriptions links are hidden before login", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded without login
|
|
await gotoApp();
|
|
|
|
// Then: login-gated links should be hidden
|
|
const forYouLink = page.locator("#forYouLink");
|
|
const exploreLink = page.locator("#exploreLink");
|
|
const subscriptionsLink = page.locator("#subscriptionsLink");
|
|
|
|
await expect(forYouLink).toHaveClass(/hidden/);
|
|
await expect(exploreLink).toHaveClass(/hidden/);
|
|
await expect(subscriptionsLink).toHaveClass(/hidden/);
|
|
});
|
|
|
|
test("login reveals For You, Explore, and Subscriptions sidebar links", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
}) => {
|
|
// Given: the app is loaded
|
|
await gotoApp();
|
|
|
|
// When: user logs in
|
|
await loginAs(page);
|
|
|
|
// Then: the login-gated links should become visible
|
|
// Allow time for the UI to update after login
|
|
const forYouLink = page.locator("#forYouLink");
|
|
const exploreLink = page.locator("#exploreLink");
|
|
const subscriptionsLink = page.locator("#subscriptionsLink");
|
|
|
|
await expect(forYouLink).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
await expect(exploreLink).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
await expect(subscriptionsLink).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
});
|
|
|
|
test("For You link navigates to the for-you view after login", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
}) => {
|
|
// Given: user is logged in
|
|
await gotoApp();
|
|
await loginAs(page);
|
|
|
|
const forYouLink = page.locator("#forYouLink");
|
|
await expect(forYouLink).not.toHaveClass(/hidden/, { timeout: 10000 });
|
|
|
|
// When: user clicks For You
|
|
await forYouLink.click();
|
|
|
|
// Then: hash should reflect the for-you view
|
|
await page.waitForFunction(
|
|
() => window.location.hash.includes("view=for-you"),
|
|
{ timeout: 5000 },
|
|
);
|
|
});
|
|
});
|
|
|
|
test.describe("View navigation — direct hash routing", () => {
|
|
test("navigating to #view=kids directly loads the kids view", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded at a specific hash
|
|
await gotoApp("/#view=kids");
|
|
|
|
// Then: the hash should be set
|
|
const hash = await page.evaluate(() => window.location.hash);
|
|
expect(hash).toContain("view=kids");
|
|
});
|
|
|
|
test("navigating to #view=most-recent-videos shows the feed", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: a video exists
|
|
await seedEvent({
|
|
title: "Navigation Feed Video",
|
|
url: "https://example.com/nav-feed.mp4",
|
|
dTag: "nav-feed-001",
|
|
});
|
|
|
|
await gotoApp("/#view=most-recent-videos");
|
|
await loginAs(page);
|
|
|
|
// Then: the feed should populate with the seeded video
|
|
const items = await page.evaluate(() => {
|
|
return (window as any).__bitvidTest__.waitForFeedItems(1, 30000);
|
|
});
|
|
expect(items.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
});
|
|
|
|
test.describe("View navigation — sidebar collapse", () => {
|
|
test("sidebar collapse toggle changes sidebar state", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded with sidebar collapsed (default)
|
|
await gotoApp();
|
|
|
|
const sidebar = page.locator("#sidebar");
|
|
const toggle = page.locator("#sidebarCollapseToggle");
|
|
|
|
await expect(sidebar).toBeAttached();
|
|
await expect(toggle).toBeAttached();
|
|
|
|
// Record initial state
|
|
const initialState = await sidebar.getAttribute("data-state");
|
|
|
|
// When: user clicks the collapse toggle
|
|
await toggle.click();
|
|
|
|
// Then: the sidebar state should change
|
|
await page.waitForFunction(
|
|
(initial) => {
|
|
const sidebar = document.querySelector("#sidebar");
|
|
return sidebar?.getAttribute("data-state") !== initial;
|
|
},
|
|
initialState,
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
const newState = await sidebar.getAttribute("data-state");
|
|
expect(newState).not.toBe(initialState);
|
|
});
|
|
|
|
test("sidebar toggle updates aria-expanded attribute", async ({
|
|
page,
|
|
gotoApp,
|
|
}) => {
|
|
// Given: the app is loaded
|
|
await gotoApp();
|
|
|
|
const toggle = page.locator("#sidebarCollapseToggle");
|
|
const initialExpanded = await toggle.getAttribute("aria-expanded");
|
|
|
|
// When: user clicks the toggle
|
|
await toggle.click();
|
|
|
|
// Then: aria-expanded should flip
|
|
const expected = initialExpanded === "true" ? "false" : "true";
|
|
await expect(toggle).toHaveAttribute("aria-expanded", expected, {
|
|
timeout: 5000,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("View navigation — feed with seeded content", () => {
|
|
test("seeded videos appear in the feed after navigation", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: multiple videos are seeded
|
|
await seedEvent({
|
|
title: "Nav Video One",
|
|
url: "https://example.com/nav1.mp4",
|
|
dTag: "nav-multi-001",
|
|
});
|
|
await seedEvent({
|
|
title: "Nav Video Two",
|
|
url: "https://example.com/nav2.mp4",
|
|
dTag: "nav-multi-002",
|
|
});
|
|
|
|
await gotoApp();
|
|
await loginAs(page);
|
|
|
|
// Then: both videos should appear in the feed
|
|
const items = await page.evaluate(() => {
|
|
return (window as any).__bitvidTest__.waitForFeedItems(2, 30000);
|
|
});
|
|
expect(items.length).toBeGreaterThanOrEqual(2);
|
|
|
|
const titles = items.map((i: any) => i.title);
|
|
expect(titles).toContain("Nav Video One");
|
|
expect(titles).toContain("Nav Video Two");
|
|
});
|
|
|
|
test("switching views and returning preserves app state", async ({
|
|
page,
|
|
gotoApp,
|
|
loginAs,
|
|
seedEvent,
|
|
}) => {
|
|
// Given: user is logged in and viewing the feed
|
|
await seedEvent({
|
|
title: "Persistent Video",
|
|
url: "https://example.com/persist.mp4",
|
|
dTag: "nav-persist-001",
|
|
});
|
|
|
|
await gotoApp();
|
|
await loginAs(page);
|
|
|
|
await page.evaluate(() => {
|
|
return (window as any).__bitvidTest__.waitForFeedItems(1, 30000);
|
|
});
|
|
|
|
// When: user navigates to kids view and back
|
|
await page.locator('a[href="#view=kids"]').click();
|
|
await page.waitForFunction(
|
|
() => window.location.hash.includes("view=kids"),
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
await page.locator('a[href="#view=most-recent-videos"]').click();
|
|
await page.waitForFunction(
|
|
() => window.location.hash.includes("view=most-recent-videos"),
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
// Then: app state should remain valid (login preserved across view switches)
|
|
const state = await page.evaluate(() => {
|
|
return (window as any).__bitvidTest__.getAppState();
|
|
});
|
|
expect(state.isLoggedIn).toBe(true);
|
|
expect(state.activePubkey).toBeTruthy();
|
|
|
|
// And: the video list container should be present in the returned view
|
|
const videoList = page.locator('[data-testid="video-list"]');
|
|
await expect(videoList).toBeAttached({ timeout: 10000 });
|
|
});
|
|
});
|