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())} -

+
+ + +
+ {#if image} + + {/if} +
+

{title || "Untitled"}

+

+ {formatTimestamp(article.publishedAt())} +

+
+ +
+ + Written by + + + +
- -
- - Written by - - - -
- +
diff --git a/src/app/components/Content.svelte b/src/app/components/Content.svelte index 3902b74f..154919d4 100644 --- a/src/app/components/Content.svelte +++ b/src/app/components/Content.svelte @@ -204,7 +204,7 @@ {@html renderAsHtml(parsed)} diff --git a/src/lib/components/Link.svelte b/src/lib/components/Link.svelte index feee991c..a6b7d0cd 100644 --- a/src/lib/components/Link.svelte +++ b/src/lib/components/Link.svelte @@ -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) => {