memos/web/tests/attachment-library-source-link.test.tsx
johnnyjoygh af008b2153 feat(web): scope Spaces by URL path instead of a remembered selection
The selected Space used to live in sessionStorage and ride along invisibly
with every collection route. It now lives in the URL: collection pages exist
both globally and beneath /spaces/:spaceUid, and the Space context derives
its scope from the current location.

- Add a Space route branch (Home, Explore, Calendar, Attachments) guarded by
  a SpaceRoute that shows loading, unavailable, and retry states.
- Resolve collection routes through one matchPath-based helper built from the
  route constants; build Space-scoped paths with buildCollectionPath.
- Fetch the route Space directly, seeded from the switcher list so pages are
  not held behind a second round-trip.
- Turn the Space switcher into plain links that preserve the current
  collection view and query.
- Drop the remembered-Space machinery: clearSelectedSpace/selectMemos,
  the compose-placement policy, routeSupportsCollectionScope, and the
  quick-find switchToAll flag are all implied by the URL now.
- Return from memo details to the full origin URL, including its Space.
- Add space.unavailable and space.back-to-memos strings; align the
  Azerbaijani catalog with English.
2026-09-07 09:29:50 +08:00

126 lines
4.3 KiB
TypeScript

import { create } from "@bufbuild/protobuf";
import { fireEvent, render, screen } from "@testing-library/react";
import { MemoryRouter, useLocation } from "react-router-dom";
import { describe, expect, it, vi } from "vitest";
import AttachmentMediaGrid from "@/components/AttachmentLibrary/AttachmentMediaGrid";
import type { AttachmentLibraryMonthGroup } from "@/hooks/useAttachmentLibrary";
import { AttachmentSchema, MediaMetadataSchema, VideoMetadataSchema } from "@/types/proto/api/v1/attachment_service_pb";
vi.mock("@/utils/i18n", () => ({ useTranslate: () => (key: string) => key }));
const LocationStateProbe = () => {
const location = useLocation();
return <output data-testid="location-state">{JSON.stringify(location.state)}</output>;
};
describe("<AttachmentMediaGrid>", () => {
it("links a media attachment to its source memo without replacing the direct file action", () => {
const attachment = create(AttachmentSchema, {
name: "attachments/photo",
filename: "photo.jpg",
type: "image/jpeg",
memo: "memos/source-memo",
});
const groups: AttachmentLibraryMonthGroup[] = [
{
key: "2026-07",
label: "Jul 2026",
items: [
{
id: attachment.name,
kind: "image",
filename: attachment.filename,
posterUrl: "/file/attachments/photo/photo.jpg?thumbnail=1",
sourceUrl: "/file/attachments/photo/photo.jpg",
attachmentNames: [attachment.name],
attachments: [attachment],
previewItem: {
id: attachment.name,
kind: "image",
filename: attachment.filename,
sourceUrl: "/file/attachments/photo/photo.jpg",
},
mimeType: attachment.type,
primaryAttachment: attachment,
createdLabel: "Jul 29, 2026",
fileTypeLabel: "JPEG",
memoName: attachment.memo,
},
],
},
];
const onPreview = vi.fn();
render(
<MemoryRouter initialEntries={["/spaces/product/attachments?filter=tagSearch%3Awork"]}>
<AttachmentMediaGrid groups={groups} onPreview={onPreview} />
<LocationStateProbe />
</MemoryRouter>,
);
const memoLink = screen.getByRole("link", { name: "attachment-library.labels.memo" });
expect(memoLink).toHaveAttribute("href", "/memos/source-memo");
const fileLink = screen.getByRole("link", { name: "attachment-library.actions.open" });
expect(fileLink).toHaveAttribute("href", "/file/attachments/photo/photo.jpg");
expect(fileLink).toHaveAttribute("target", "_blank");
fireEvent.click(memoLink);
expect(onPreview).not.toHaveBeenCalled();
expect(screen.getByTestId("location-state")).toHaveTextContent(
'{"from":"/spaces/product/attachments?filter=tagSearch%3Awork","fromScope":"preserve"}',
);
});
it("keeps video duration inside the existing play badge", () => {
const attachment = create(AttachmentSchema, {
name: "attachments/video",
filename: "clip.mp4",
type: "video/mp4",
memo: "memos/source-memo",
mediaMetadata: create(MediaMetadataSchema, {
details: {
case: "video",
value: create(VideoMetadataSchema, { durationSeconds: 12.5 }),
},
}),
});
const groups: AttachmentLibraryMonthGroup[] = [
{
key: "2026-07",
label: "Jul 2026",
items: [
{
id: attachment.name,
kind: "video",
filename: attachment.filename,
posterUrl: "/poster.jpg",
sourceUrl: "/clip.mp4",
attachmentNames: [attachment.name],
attachments: [attachment],
previewItem: {
id: attachment.name,
kind: "video",
filename: attachment.filename,
sourceUrl: "/clip.mp4",
attachments: [attachment],
},
mimeType: attachment.type,
primaryAttachment: attachment,
createdLabel: "Jul 29, 2026",
fileTypeLabel: "MP4",
memoName: attachment.memo,
},
],
},
];
render(
<MemoryRouter>
<AttachmentMediaGrid groups={groups} onPreview={vi.fn()} />
</MemoryRouter>,
);
expect(screen.getByText("0:13")).toBeInTheDocument();
});
});