diff --git a/e2e/USER_STORIES.md b/e2e/USER_STORIES.md index 798b67eb..30b7dc29 100644 --- a/e2e/USER_STORIES.md +++ b/e2e/USER_STORIES.md @@ -1146,6 +1146,8 @@ Acceptance: - For a chat message, a "Sending..." toast appears and becomes "Message sent!" once the relay confirms; a failed retry leaves no success message and can be retried again. +- A successful retry clears the "Failed to send!" indicator on the message + itself, in a room and in a conversation. - For an article or comment, a successful retry clears the "Failed to send!" indicator and restores the normal reaction and menu actions, with no toast. diff --git a/e2e/specs/delivery.spec.ts b/e2e/specs/delivery.spec.ts index 17adb610..f5e4a8a8 100644 --- a/e2e/specs/delivery.spec.ts +++ b/e2e/specs/delivery.spec.ts @@ -247,12 +247,18 @@ test("US-069 see why a message failed to deliver", async ({seed, as}) => { test("US-070 retry a failed relay", async ({seed, as}) => { const scenario = await seed(({relay, user}) => { const space = relay("space") + const other = relay("other") space.room("general", {name: "General"}) space.join(user.alice, "general") space.join(user.bob, "general") + other.join(user.alice) seedPerson(space, user.alice, "Alice Anderson") seedPerson(space, user.bob, "Bob Barker") + + space.messagingRelayList(user.alice) + // Bob names both relays and belongs to one, so the other refuses a wrap addressed to him. + space.messagingRelayList(user.bob, [space.url, other.url]) }) const {url} = scenario.space("space") @@ -292,6 +298,9 @@ test("US-070 retry a failed relay", async ({seed, as}) => { await expect(toast(alice)).toContainText("Sending...") await expect(toast(alice)).toContainText("Message sent!") + // The message went out, so it no longer says it did not. + await expect(failure).toHaveCount(0) + const [sent] = getPublishedEvents(alice.context(), MESSAGE) // One attempt and two retries, every one of them to the relay that failed and to nothing else. @@ -300,6 +309,24 @@ test("US-070 retry a failed relay", async ({seed, as}) => { await bob.goto(roomPath(url, "archive")) await expect(message(bob, "is anyone here?")).toBeVisible() + + // The same retry in a conversation, where one of his two messaging relays turns her wrap away. + await alice.goto(chatPath(users.bob.pubkey)) + await send(alice, "did that one arrive?") + + const partial = alice.getByText("Failed to send!") + + await expect(partial).toBeVisible() + + await seed(({relay, user}) => { + relay("other").join(user.bob) + }) + + await partial.click() + await detail(alice).getByRole("button", {name: "Retry"}).click() + + await expect(toast(alice)).toContainText("Message sent!") + await expect(partial).toHaveCount(0) }) test("US-071 content posts show delivery status in place", async ({seed, as}) => { diff --git a/src/app/components/ChatMessage.svelte b/src/app/components/ChatMessage.svelte index ac2c5f53..30274b69 100644 --- a/src/app/components/ChatMessage.svelte +++ b/src/app/components/ChatMessage.svelte @@ -3,7 +3,6 @@ import {formatTimestampAsTime} from "@welshman/lib" import type {Maybe} from "@welshman/lib" import type {TrustedEvent, EventContent} from "@welshman/util" - import {Thunks} from "@welshman/app" import {isMobile} from "@lib/html" import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import Icon from "@lib/components/Icon.svelte" @@ -20,7 +19,8 @@ import ProfileDetail from "@app/components/ProfileDetail.svelte" import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte" import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte" - import {app, profiles, user} from "@app/core" + import {profiles, thunks, user} from "@app/core" + import {noThunks, thunksByEventId} from "@app/thunks" import {highlightedEvent} from "@app/routes" import {colorFor} from "@app/theme" import {pushModal} from "@app/modal" @@ -39,8 +39,7 @@ const isOwn = event.pubkey === $user.pubkey const profileDisplay = $profiles.display(event.pubkey).$ - const thunks = $app.use(Thunks).history - const thunk = $app.use(Thunks).merge($thunks.filter(t => t.options.event.id === event.id)) + const thunk = $derived($thunks.merge($thunksByEventId.get(event.id) ?? noThunks)) const colorValue = colorFor(event.pubkey) const reply = () => replyTo(event)