Fix a test, avoid link nesting in article cards

This commit is contained in:
Jon Staab 2026-08-22 10:55:06 -07:00
parent 08821d27f4
commit 2edcfd9a79
7 changed files with 46 additions and 36 deletions

View file

@ -111,7 +111,7 @@ const openMessageMenu = (page: Page, text: string) =>
const openEventMenu = (card: Locator) => card.locator(".join").getByRole("button").last().click()
const articleCard = (page: Page, title: string) =>
page.getByRole("link", {name: /Written by/}).filter({hasText: title})
page.locator('[data-component="ArticleItem"]').filter({hasText: title})
// A FieldInline puts its control in the div immediately after its label, which is how one row of
// the hosting card is told apart from the others in the same grid.

View file

@ -101,10 +101,12 @@ const expectReactionRoundTrip = async (page: Page, scope: Locator, opener: Locat
const shortDate = (seconds: number) =>
new Intl.DateTimeFormat(undefined, {dateStyle: "short"}).format(new Date(seconds * 1000))
const articleCards = (page: Page) => page.getByRole("link", {name: /Written by/})
// The card is a div carrying an overlay link, so it is found by its component rather than by a
// role — its own contents include a profile button and the room and action links.
const articleCards = (page: Page) => page.locator('[data-component="ArticleItem"]')
const openArticle = (page: Page, title: string) =>
articleCards(page).filter({hasText: title}).getByText(title, {exact: true}).click()
articleCards(page).filter({hasText: title}).getByRole("link", {name: title, exact: true}).click()
test("US-037 write and publish an article", async ({seed, as}) => {
const scenario = await seed(({relay, user, at}) => {
@ -570,11 +572,11 @@ test("US-041 publish an article from a room", async ({seed, as}) => {
const card = articleCards(page).filter({hasText: "Repotting in Winter"})
await expect(card).toBeVisible()
await expect(card.getByRole("link", {name: "#Lounge"})).toBeVisible()
await expect(card.getByRole("link", {name: /#\s*Lounge/})).toBeVisible()
await openArticle(page, "Repotting in Winter")
const badge = page.getByRole("link", {name: "Posted in #Lounge"})
const badge = page.getByRole("link", {name: /Posted in #\s*Lounge/})
await expect(badge).toBeVisible()

View file

@ -254,7 +254,7 @@ test("US-056 autocomplete a mention or a room reference", async ({seed, as}) =>
await editor.press("Enter")
await expect(timeline(page).getByRole("link", {name: "#General"})).toHaveAttribute(
await expect(timeline(page).getByRole("link", {name: /#\s*General/})).toHaveAttribute(
"href",
roomPath(space.url, "general"),
)

View file

@ -387,7 +387,7 @@ test("US-071 content posts show delivery status in place", async ({seed, as}) =>
await writeArticle(alice, "Signals in the Noise", "Everything worth hearing is quiet.")
const article = alice.getByRole("link", {name: /Written by/}).filter({hasText: "Signals"})
const article = alice.locator('[data-component="ArticleItem"]').filter({hasText: "Signals"})
await expect(alice.getByRole("heading", {name: "Write an Article"})).toHaveCount(0)
@ -412,7 +412,7 @@ test("US-071 content posts show delivery status in place", async ({seed, as}) =>
await alice.goto(`${spacePath(quiet)}/articles`)
await writeArticle(alice, "Into the Void", "Nobody is listening.")
const stuck = alice.getByRole("link", {name: /Written by/}).filter({hasText: "Into the Void"})
const stuck = alice.locator('[data-component="ArticleItem"]').filter({hasText: "Into the Void"})
await expect(stuck.getByText("Sending...")).toBeVisible()
await expect(stuck.getByRole("button", {name: "Cancel"})).toBeVisible()
@ -497,7 +497,7 @@ test("US-072 a deleted post is marked deleted", async ({seed, as}) => {
// A post whose relay never answered, deleted while it is still marked failed.
await writeArticle(alice, "Into the Void", "Nobody is listening.")
const stuck = alice.getByRole("link", {name: /Written by/}).filter({hasText: "Into the Void"})
const stuck = alice.locator('[data-component="ArticleItem"]').filter({hasText: "Into the Void"})
await expect(stuck.getByText("Failed to send!")).toBeVisible()

View file

@ -24,29 +24,36 @@
const image = article.image()
</script>
<Link
class="cv flex flex-col gap-2 card card-interactive w-full"
href={makeArticlePath(url, getAddress(event))}>
{#if image}
<img src={image} alt="" class="h-40 w-full rounded-2xl object-cover" />
{/if}
<div class="flex w-full items-center justify-between gap-2">
<p class="text-xl">{title || "Untitled"}</p>
<p class="whitespace-nowrap text-sm opacity-75">
{formatTimestamp(article.publishedAt())}
</p>
<div data-component="ArticleItem" class="cv relative w-full card card-interactive">
<!-- An overlay rather than a wrapper: the card carries a profile button and the room and action
links, and none of those may sit inside an anchor. -->
<Link
class="absolute inset-0 rounded-2xl"
href={makeArticlePath(url, getAddress(event))}
aria-label={title || "Untitled"} />
<div class="pointer-events-none relative flex flex-col gap-2">
{#if image}
<img src={image} alt="" class="h-40 w-full rounded-2xl object-cover" />
{/if}
<div class="flex w-full items-center justify-between gap-2">
<p class="text-xl">{title || "Untitled"}</p>
<p class="whitespace-nowrap text-sm opacity-75">
{formatTimestamp(article.publishedAt())}
</p>
</div>
<Content
event={{content: summary || event.content, tags: event.tags}}
{url}
expandMode="inline"
minLength={100}
maxLength={300} />
<div
class="pointer-events-auto flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Written by
<ProfileLink pubkey={event.pubkey} {url} />
</span>
<ArticleActions showRoom showActivity {url} {event} />
</div>
</div>
<Content
event={{content: summary || event.content, tags: event.tags}}
{url}
expandMode="inline"
minLength={100}
maxLength={300} />
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Written by
<ProfileLink pubkey={event.pubkey} {url} />
</span>
<ArticleActions showRoom showActivity {url} {event} />
</div>
</Link>
</div>

View file

@ -204,7 +204,7 @@
{@html renderAsHtml(parsed)}
<button
type="button"
class="text-sm underline"
class="pointer-events-auto text-sm underline"
onclick={stopPropagation(preventDefault(expand))}>
Read more
</button>

View file

@ -11,7 +11,7 @@
replaceState = false,
...restProps
}: {
children: Snippet
children?: Snippet
href: string
external?: boolean
replaceState?: boolean
@ -19,6 +19,7 @@
class?: string
style?: string
"data-tip"?: string
"aria-label"?: string
} = $props()
const go = (e: Event) => {