Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267<script module lang="ts"> import { defineMeta } from "@storybook/addon-svelte-csf"; import { expect, userEvent, waitFor, within } from "storybook/test"; import CommentCard from "#lib/components/comment/CommentCard.svelte"; import { sampleComments, sampleMarkup, sampleSubjectCid, sampleSubjectUri, sampleThread, soloThread } from "#fixtures/comments.ts"; import { reactionWrite, staleAfterChip, staleAfterPicker, thumbsUp } from "#fixtures/reactions.ts"; import { moderationPage } from "#storybook/moderation.ts"; import type { ModerationLevel, ModerationTarget } from "#lib/api/moderationLabels.ts"; import type { CommentView } from "#lib/components/comment/comments.ts";
// a thread the logged-in user owns, so the edit affordance shows const myThread = { self: sampleComments.mine, replies: [] };
const moderated = ( comment: CommentView, level: ModerationLevel, target: ModerationTarget ): CommentView => ({ ...comment, moderation: { level, target } });
const flaggedThread = { self: moderated(sampleComments.erin, "warn", "did:plc:erin"), replies: [sampleComments.frank, sampleComments.grace] }; const flaggedSoloThread = { self: moderated(sampleComments.erin, "warn", "did:plc:erin"), replies: [] }; const hiddenThread = { self: moderated(sampleComments.erin, "hide", "did:plc:erin"), replies: [sampleComments.frank] }; const flaggedReplyThread = { self: sampleComments.erin, replies: [moderated(sampleComments.frank, "warn", "did:plc:frank"), sampleComments.grace] }; const flaggedCommentThread = { self: sampleComments.erin, replies: [ moderated( sampleComments.frank, "warn", "at://did:plc:frank/org.tangled.feed.comment/frank" ), sampleComments.grace ] }; const myFlaggedThread = { self: moderated(sampleComments.mine, "hide", "did:plc:alice"), replies: [] };
const thumbed = { ...sampleComments.erin, reactions: [thumbsUp] };
const { Story } = defineMeta({ title: "Comment/CommentCard", component: CommentCard, tags: ["autodocs"], args: { thread: soloThread, subjectUri: sampleSubjectUri, subjectCid: sampleSubjectCid, markup: sampleMarkup } });</script>
<script lang="ts"> let read = $state({ self: thumbed, replies: [] }); let readBare = $state(soloThread);</script>
<!-- logged out: the reply affordance is a login prompt --><Story name="Top level only" /><Story name="With replies" args={{ thread: sampleThread }} />
<!-- logged in: the reply field is always there; its button only appears once there is text --><Story name="Replying" parameters={{ auth: true }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); const textarea = canvas.getByPlaceholderText("Leave a reply..."); expect(canvas.queryByRole("button", { name: "Reply" })).toBeNull(); expect(canvas.queryByRole("switch")).toBeNull(); await userEvent.type(textarea, "first line{Shift>}{Enter}{/Shift}second line"); await expect(canvas.getByRole("button", { name: "Reply" })).toBeInTheDocument(); expect(canvas.queryByRole("button", { name: "Cancel" })).toBeNull(); await userEvent.clear(textarea); await waitFor(() => expect(canvas.queryByRole("button", { name: "Reply" })).toBeNull()); }}/>
<!-- logged in as the author: the edit icon opens an inline editor prefilled with the body --><Story name="Editing" args={{ thread: myThread }} parameters={{ auth: true }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click( canvas.getByRole("button", { name: /more actions for alice\.tngl\.sh's comment/i }) ); await userEvent.click(await canvas.findByRole("menuitem", { name: "Edit comment" })); const textarea = await waitFor(() => canvas.getByPlaceholderText(/edit your comment/i)); await expect(textarea).toHaveValue(sampleComments.mine.body); await expect(canvas.getByRole("button", { name: "Save" })).toBeInTheDocument(); await expect(canvas.getByRole("button", { name: "Cancel" })).toBeInTheDocument(); }}/>
<Story name="Reporting a reply" args={{ thread: sampleThread }} parameters={{ auth: true, ...moderationPage }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click( canvas.getByRole("button", { name: /more actions for frank\.tngl\.sh's reply/i }) ); // the actions row fades in on real hover, which a synthetic pointer cannot do, so // assert the menu is in the tree rather than painted await expect( await canvas.findByRole("menuitem", { name: /report reply/i }) ).toBeInTheDocument(); await expect(canvas.getByRole("menuitem", { name: /report user/i })).toBeInTheDocument(); expect(canvas.queryByRole("button", { name: "Edit comment" })).toBeNull();
await userEvent.click( canvas.getByRole("button", { name: /more actions for erin\.tngl\.sh's comment/i }) ); await expect( await canvas.findByRole("menuitem", { name: /report comment/i }) ).toBeInTheDocument(); }}/>
<Story name="Editing with several accounts" args={{ thread: myThread }} parameters={{ auth: { accounts: 2 } }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click( canvas.getByRole("button", { name: /more actions for alice\.tngl\.sh's comment/i }) ); await userEvent.click(await canvas.findByRole("menuitem", { name: "Edit comment" })); await waitFor(() => canvas.getByPlaceholderText(/edit your comment/i)); await expect(canvas.getByRole("button", { name: "Save" })).toBeInTheDocument(); expect(canvas.queryByRole("button", { name: /comment as/i })).toBeNull(); }}/>
<Story name="Flagged author" args={{ thread: flaggedThread }} parameters={{ auth: true }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await expect(canvas.getByText("This account has been flagged.")).toBeInTheDocument(); expect(canvas.queryByText(/on the cusp/)).toBeNull(); expect(canvas.queryByPlaceholderText("Leave a reply...")).toBeNull(); }}/>
<Story name="Flagged author without replies" args={{ thread: flaggedSoloThread }} parameters={{ auth: true }}/>
<Story name="Flagged author revealed" args={{ thread: flaggedThread }} parameters={{ auth: true }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click(canvas.getByRole("button", { name: "Show anyway" })); await expect(canvas.getByText(/on the cusp/)).toBeInTheDocument(); await expect(canvas.getByPlaceholderText("Leave a reply...")).toBeInTheDocument(); }}/>
<Story name="Hidden author" args={{ thread: hiddenThread }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await expect( canvas.getByText("This account has been hidden by moderators.") ).toBeInTheDocument(); expect(canvas.queryByRole("button", { name: "Show anyway" })).toBeNull(); expect(canvas.queryByText(/to leave a reply/)).toBeNull(); }}/>
<Story name="Flagged reply" args={{ thread: flaggedReplyThread }} parameters={{ auth: true }} />
<Story name="Own hidden comment" args={{ thread: myFlaggedThread }} parameters={{ auth: true }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.queryByText("This account has been hidden by moderators.")).toBeNull(); await expect(canvas.getByPlaceholderText("Leave a reply...")).toBeInTheDocument(); }}/>
<Story name="Flagged comment" args={{ thread: flaggedCommentThread }} parameters={{ auth: true }} play={async ({ canvasElement }) => { const canvas = within(canvasElement); await expect(canvas.getByText("This comment has been flagged.")).toBeInTheDocument(); }}/>
<!-- the parent hands back what bobbin had before the write landed --><Story name="A stale re-read keeps a chip reaction" parameters={{ auth: { respond: reactionWrite } }} play={staleAfterChip} asChild> <CommentCard thread={read} subjectUri={sampleSubjectUri} subjectCid={sampleSubjectCid} markup={sampleMarkup} /> <button onclick={() => (read = { self: { ...thumbed }, replies: [] })}>Re-read</button></Story>
<Story name="A stale re-read keeps a picker reaction" parameters={{ auth: { respond: reactionWrite } }} play={staleAfterPicker} asChild> <CommentCard thread={readBare} subjectUri={sampleSubjectUri} subjectCid={sampleSubjectCid} markup={sampleMarkup} /> <button onclick={() => (readBare = { ...soloThread, self: { ...soloThread.self } })} >Re-read</button ></Story>