diff --git a/e2e/specs/admin.spec.ts b/e2e/specs/admin.spec.ts
index 25a8f578..235a254f 100644
--- a/e2e/specs/admin.spec.ts
+++ b/e2e/specs/admin.spec.ts
@@ -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.
diff --git a/e2e/specs/articles-threads.spec.ts b/e2e/specs/articles-threads.spec.ts
index 2314fb40..61bc4540 100644
--- a/e2e/specs/articles-threads.spec.ts
+++ b/e2e/specs/articles-threads.spec.ts
@@ -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()
diff --git a/e2e/specs/composer.spec.ts b/e2e/specs/composer.spec.ts
index 3ec2b9c1..30c00669 100644
--- a/e2e/specs/composer.spec.ts
+++ b/e2e/specs/composer.spec.ts
@@ -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"),
)
diff --git a/e2e/specs/delivery.spec.ts b/e2e/specs/delivery.spec.ts
index 4bfdc7ff..236be149 100644
--- a/e2e/specs/delivery.spec.ts
+++ b/e2e/specs/delivery.spec.ts
@@ -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()
diff --git a/src/app/components/ArticleItem.svelte b/src/app/components/ArticleItem.svelte
index af506e7d..b88f4f34 100644
--- a/src/app/components/ArticleItem.svelte
+++ b/src/app/components/ArticleItem.svelte
@@ -24,29 +24,36 @@
const image = article.image()
-
- {#if image}
-
- {/if}
-
{title || "Untitled"}
-- {formatTimestamp(article.publishedAt())} -
+{title || "Untitled"}
++ {formatTimestamp(article.publishedAt())} +
+