feat(design): a library row keeps Share (the link copy) in sight, ahead of save (E28 S3e3)

The owner's call: sharing a download's link is the library's main errand, more
important than saving the file, so it does not hide in the row's "…". Manage
sharing stays in the menu. The "…" tests press as a mouse, so a stray long-press
timer no longer fires after teardown.
This commit is contained in:
2026-09-26 07:21:21 +02:00
parent 2df60fbf07
commit 9c91eb5d22
7 changed files with 54 additions and 23 deletions
@@ -139,7 +139,7 @@ describe("DownloadCenter · the queue row", () => {
// The press lands outside the panel, which floating-ui's dismiss closes on; the click after
// it must not open it again.
act(() => {
fireEvent.pointerDown(moreOf("Clip 1"));
fireEvent.pointerDown(moreOf("Clip 1"), { pointerType: "mouse" });
fireEvent.mouseDown(moreOf("Clip 1"));
fireEvent.click(moreOf("Clip 1"), { detail: 1 });
});
@@ -159,7 +159,7 @@ describe("DownloadCenter · the queue row", () => {
target: { value: "x" },
});
act(() => {
fireEvent.pointerDown(moreOf("Clip 1"));
fireEvent.pointerDown(moreOf("Clip 1"), { pointerType: "mouse" });
fireEvent.mouseDown(moreOf("Clip 1"));
fireEvent.click(moreOf("Clip 1"), { detail: 1 });
});
@@ -191,23 +191,24 @@ describe("DownloadCenter · the queue row", () => {
});
describe("DownloadCenter · the library row", () => {
it("shows play, save and its '…'; the editors, sharing and delete are in the menu", async () => {
it("shows play, share, save and its '…'; the editors, managing shares and delete are in the menu", async () => {
mocks.jobs = [job(2, "done", { source_url: "https://youtu.be/x" })];
mount("done");
await screen.findByText("Clip 2");
expect(rowButtons("Clip 2")).toEqual([
"downloads.actions.play",
"downloads.actions.share",
"downloads.actions.download",
"downloads.rowMenu",
]);
await openMenu(() => fireEvent.contextMenu(rowOf("Clip 2")));
expect(menuRows()).toEqual([
"downloads.actions.play",
"downloads.actions.share",
"downloads.actions.download",
"downloads.actions.edit",
"downloads.actions.editDetails",
"downloads.actions.redownload",
"downloads.actions.share",
"downloads.actions.shareManage",
"common.copy.title",
"common.copy.link",
@@ -215,6 +216,21 @@ describe("DownloadCenter · the library row", () => {
]);
});
it("copies the share link from the row's own Share button", async () => {
mocks.jobs = [job(2, "done")];
vi.spyOn(api, "downloadLinks").mockResolvedValue([] as never);
const create = vi
.spyOn(api, "createDownloadLink")
.mockResolvedValue({ id: 1, url: "/s/tok" } as never);
mount("done");
await screen.findByText("Clip 2");
fireEvent.click(
rowOf("Clip 2").querySelector<HTMLElement>('button[aria-label="downloads.actions.share"]')!,
);
await waitFor(() => expect(create).toHaveBeenCalledWith(2, {}));
expect(screen.queryAllByRole("menuitem", { hidden: true })).toHaveLength(0);
});
it("saves the file through the same link as the row's save button", async () => {
mocks.jobs = [job(2, "done")];
const followed: string[] = [];
@@ -243,8 +259,8 @@ describe("DownloadCenter · the library row", () => {
await screen.findByText("Clip 3");
await openMenu(() => fireEvent.click(moreOf("Clip 3")));
expect(menuRows()).toEqual([
"downloads.actions.download",
"downloads.actions.share",
"downloads.actions.download",
"downloads.actions.shareManage",
"common.copy.title",
"downloads.actions.delete",