v4: a playlist's editor has a URL, as a dump's has — it opened over the playlist's header as a state of the page, which a reload or a shared link lost, while a dump's editor is /@name/dumps/<slug>/edit. It is now /@name/playlists/<slug>/edit: Edit goes there, and Cancel or Save come back to the page in its place. Both paths render PlaylistDetail at the same place, so the playlist stays loaded, and a new slug keeps the view. Anyone who may not edit it gets the page, a guest the sign-in first. playlistPath takes the view, and parsePagePath reads it, so its link has no preview, as a dump editor's has none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,15 @@ function open(
|
||||
path: "/:handle/playlists/:slug",
|
||||
session,
|
||||
categories,
|
||||
extraRoutes: <Route path="/" element={<p>home</p>} />,
|
||||
extraRoutes: (
|
||||
<>
|
||||
<Route path="/" element={<p>home</p>} />
|
||||
<Route
|
||||
path="/:handle/playlists/:slug/edit"
|
||||
element={<PlaylistDetail editing />}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
});
|
||||
const socket = FakeWebSocket.last;
|
||||
socket.welcome();
|
||||
@@ -142,12 +150,39 @@ describe("PlaylistDetail", () => {
|
||||
await user.clear(box);
|
||||
await user.type(box, "Better tunes");
|
||||
await user.click(screen.getByRole("button", { name: "Save" }));
|
||||
expect(await screen.findByText("Better tunes")).toBeInTheDocument();
|
||||
// Saving closes the editor, whose text would be found first.
|
||||
await waitFor(() => expect(currentPath()).toBe("/@owner/playlists/mix"));
|
||||
expect(screen.getByText("Better tunes")).toBeInTheDocument();
|
||||
expect(calls("PUT /api/playlists/:id")[0].body).toEqual({
|
||||
description: "Better tunes",
|
||||
isPublic: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("the editor is a page: /edit opens it, closing goes back to the page", async () => {
|
||||
const { user } = open({ url: "/@owner/playlists/mix/edit" });
|
||||
expect(await screen.findByRole("textbox", { name: "Description" }))
|
||||
.toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
expect(currentPath()).toBe("/@owner/playlists/mix");
|
||||
await user.click(screen.getByRole("button", { name: "Edit" }));
|
||||
expect(currentPath()).toBe("/@owner/playlists/mix/edit");
|
||||
expect(calls("GET /api/users/:u/playlists/:slug")).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("at /edit, anyone else gets the page", async () => {
|
||||
open({ session: visitor, url: "/@owner/playlists/mix/edit" });
|
||||
await waitFor(() => expect(currentPath()).toBe("/@owner/playlists/mix"));
|
||||
expect(screen.queryByRole("textbox", { name: "Description" })).toBeNull();
|
||||
});
|
||||
|
||||
test("at /edit, a guest is asked to sign in first", async () => {
|
||||
open({ session: null, url: "/@owner/playlists/mix/edit" });
|
||||
await waitFor(() =>
|
||||
expect(currentPath()).toBe(
|
||||
"/login?next=%2F%40owner%2Fplaylists%2Fmix%2Fedit",
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("its categories show, and editing them sends the new set", async () => {
|
||||
@@ -444,7 +479,7 @@ describe("PlaylistDetail", () => {
|
||||
}),
|
||||
);
|
||||
expect(await screen.findByRole("alert")).toHaveTextContent("Forbidden");
|
||||
expect(currentPath()).toBe("/@owner/playlists/mix");
|
||||
expect(currentPath()).toBe("/@owner/playlists/mix/edit");
|
||||
});
|
||||
|
||||
test("live: added dumps appear, removed ones fade, a deleted playlist sends you home", async () => {
|
||||
|
||||
Reference in New Issue
Block a user