- A tag counts the followed channels the Subscriptions tab shows: hidden ones are left out, and a subscribe, unsubscribe, hide or tag toggle refreshes the count. The Tag manager's row and its hover list count the same channels as the chip; /api/tags sends both counts under their names. - A chip's count keeps the chip's colour after a dot (a dimmed count fell under 4.5:1). A pick in the open "+N" fold closes it; a chip that leaves the list hands the focus to the one in its place. The fold is a dialog of toggle buttons, not an empty menu. - Upload: a folder name left by a press is made once the press is over, if the dialog is still open, instead of guessing a dismissal from Modal's markup. While the folder is made, its place holds the focus and a live region says so. - Share: an expired link has no Copy and a warn badge; the link made on open goes into the cached list, so a reopen makes no second one; the New link form survives the link made on open. - Watch: a repeated Enter sends one unlock while one is on its way. See all buttons are named with their rail. - An unsized clip of a rotated file gets the upright size its crop cut.
55 lines
2.5 KiB
TypeScript
55 lines
2.5 KiB
TypeScript
import { readdirSync, readFileSync } from "node:fs";
|
|
import { QueryClient } from "@tanstack/react-query";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { qk } from "./queryKeys";
|
|
import { invalidateSubscription } from "./subscriptionCache";
|
|
|
|
// A subscription change touches the same reads wherever it is made, and each site kept
|
|
// its own list — the channel menu's unsubscribe left the feed showing the channel's videos, the
|
|
// channel page's left the status counts stale. One set, one function, every site on it.
|
|
describe("a subscription change", () => {
|
|
it("refreshes the channel, the lists, the counts and the feed", () => {
|
|
const qc = new QueryClient();
|
|
const spy = vi.spyOn(qc, "invalidateQueries");
|
|
invalidateSubscription(qc, "UC1");
|
|
expect(spy.mock.calls.map(([f]) => f?.queryKey)).toEqual([
|
|
qk.channel("UC1"),
|
|
qk.channels(),
|
|
qk.myStatus(),
|
|
qk.feed(),
|
|
qk.feedSummary(),
|
|
qk.discoveredChannels(),
|
|
qk.watchedChannels(),
|
|
qk.tags(),
|
|
]);
|
|
});
|
|
|
|
// A tag counts the followed channels it is on, not hidden: a hide changes the number as an
|
|
// unsubscribe does, and both places that hide a channel must refresh it.
|
|
it("a hide refreshes the tags wherever it is made", () => {
|
|
const root = new URL("../", import.meta.url);
|
|
const sites = readdirSync(root, { recursive: true, encoding: "utf8" })
|
|
.map((f) => f.split("\\").join("/"))
|
|
.filter((f) => f.endsWith(".tsx") && !f.includes(".test.") && !f.startsWith("design-lab/"))
|
|
.map((f) => [f, readFileSync(new URL(f, root), "utf8")] as const)
|
|
.filter(([, src]) => /api\.updateChannel\([^)]*hidden/.test(src));
|
|
expect(sites.length).toBeGreaterThanOrEqual(2);
|
|
for (const [f, src] of sites) expect(src, f).toContain("queryKey: qk.tags()");
|
|
});
|
|
|
|
it("is refreshed through that one function wherever it is made", () => {
|
|
const root = new URL("../", import.meta.url);
|
|
const sites = readdirSync(root, { recursive: true, encoding: "utf8" })
|
|
.map((f) => f.split("\\").join("/"))
|
|
.filter((f) => f.endsWith(".tsx") && !f.includes(".test.") && !f.startsWith("design-lab/"))
|
|
.map((f) => [f, readFileSync(new URL(f, root), "utf8")] as const)
|
|
.filter(([, src]) => /api\.(un)?subscribeChannel\(/.test(src));
|
|
expect(sites.length).toBeGreaterThanOrEqual(3);
|
|
for (const [f, src] of sites) {
|
|
const calls = src.match(/api\.(un)?subscribeChannel\(/g)!.length;
|
|
expect(src.match(/invalidateSubscription\(/g)?.length ?? 0, f).toBe(calls);
|
|
}
|
|
});
|
|
});
|