From aa21606df1330b3a579db984a5fa65b690cd1d57 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 16 Feb 2023 11:55:28 +0000 Subject: [PATCH 1/4] add reblog to dummyData --- frontend/mock-db/init.ts | 33 +++++++------------ frontend/src/dummyData/generateDummyStatus.ts | 5 +-- frontend/src/dummyData/statuses.ts | 4 ++- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/frontend/mock-db/init.ts b/frontend/mock-db/init.ts index 5e20c16..7d8a151 100644 --- a/frontend/mock-db/init.ts +++ b/frontend/mock-db/init.ts @@ -1,5 +1,5 @@ import { createPerson, getPersonByEmail, type Person } from 'wildebeest/backend/src/activitypub/actors' -import { replies, statuses } from 'wildebeest/frontend/src/dummyData' +import { reblogs, replies, statuses } from 'wildebeest/frontend/src/dummyData' import type { Account, MastodonStatus } from 'wildebeest/frontend/src/types' import { Note } from 'wildebeest/backend/src/activitypub/objects/note' import { createReblog } from 'wildebeest/backend/src/mastodon/reblog' @@ -24,8 +24,17 @@ export async function init(domain: string, db: D1Database) { loadedStatuses.push({ status, note }) } - const { reblogger, noteToReblog } = await pickReblogDetails(loadedStatuses, domain, db) - await createReblog(db, reblogger, noteToReblog) + for (const reblog of reblogs) { + const rebloggerAccount = reblog.account + const reblogger = await getOrCreatePerson(domain, db, rebloggerAccount) + const reblogStatus = reblog.reblog + if (reblogStatus?.id) { + const noteToReblog = loadedStatuses.find(({ status: { id } }) => id === reblogStatus.id)?.note + if (noteToReblog) { + await createReblog(db, reblogger, noteToReblog) + } + } + } for (const reply of replies) { await createReply(domain, db, reply, loadedStatuses) @@ -74,21 +83,3 @@ async function getOrCreatePerson( } return newPerson } - -/** - * Picks the details to use to reblog an arbitrary note/status. - * - * Both the note/status and the reblogger are picked arbitrarily - * form a list of available notes/states (respectively from the first - * and second entries). - */ -async function pickReblogDetails( - loadedStatuses: { status: MastodonStatus; note: Note }[], - domain: string, - db: D1Database -) { - const rebloggerAccount = loadedStatuses[1].status.account - const reblogger = await getOrCreatePerson(domain, db, rebloggerAccount) - const noteToReblog = loadedStatuses[2].note - return { reblogger, noteToReblog } -} diff --git a/frontend/src/dummyData/generateDummyStatus.ts b/frontend/src/dummyData/generateDummyStatus.ts index 4bd01b9..e8891de 100644 --- a/frontend/src/dummyData/generateDummyStatus.ts +++ b/frontend/src/dummyData/generateDummyStatus.ts @@ -5,7 +5,8 @@ export function generateDummyStatus( content: string, account: Account, mediaAttachments: MediaAttachment[] = [], - inReplyTo: string | null = null + inReplyTo: string | null = null, + reblog: MastodonStatus | null = null ): MastodonStatus { return { id: `${Math.random() * 9999999}`.padStart(3, '7'), @@ -23,7 +24,7 @@ export function generateDummyStatus( favourites_count: Math.random() * 900, edited_at: null, content, - reblog: null, + reblog, application: { name: 'Wildebeest', website: null }, account, media_attachments: mediaAttachments, diff --git a/frontend/src/dummyData/statuses.ts b/frontend/src/dummyData/statuses.ts index bcac1bd..577935b 100644 --- a/frontend/src/dummyData/statuses.ts +++ b/frontend/src/dummyData/statuses.ts @@ -2,7 +2,7 @@ import type { MediaAttachment, MastodonStatus } from '~/types' import { generateDummyStatus } from './generateDummyStatus' import { ben, george, penny, rafael, zak } from './accounts' -// Raw statuses taken directly from mastodon +// Raw statuses which follow the precise structure found mastodon does const mastodonRawStatuses: MastodonStatus[] = [ generateDummyStatus("

Fine. I'll use Wildebeest!

", george), generateDummyStatus('We did it!', george, [ @@ -32,6 +32,8 @@ export const replies: MastodonStatus[] = [ generateDummyStatus('

Yes you guys did it!

', penny, [], statuses[1].id), ] +export const reblogs: MastodonStatus[] = [generateDummyStatus('', george, [], null, statuses[2])] + function getStandardMediaType(mediaAttachmentMastodonType: string): string { switch (mediaAttachmentMastodonType) { case 'image': From bed859f7374dfd572024e0ef52320553601973f5 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 16 Feb 2023 12:54:19 +0000 Subject: [PATCH 2/4] create reusable StatusAccountCard component --- frontend/src/components/Status/index.tsx | 14 ++------- .../StatusAccountCard/StatusAccountCard.tsx | 29 +++++++++++++++++ frontend/src/components/avatar/index.tsx | 31 ++++++++++--------- frontend/src/dummyData/accounts.ts | 4 +-- .../routes/(admin)/oauth/authorize/index.tsx | 1 + .../[accountId]/[statusId]/index.tsx | 26 ++-------------- 6 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 frontend/src/components/StatusAccountCard/StatusAccountCard.tsx diff --git a/frontend/src/components/Status/index.tsx b/frontend/src/components/Status/index.tsx index 1ef3698..1f15919 100644 --- a/frontend/src/components/Status/index.tsx +++ b/frontend/src/components/Status/index.tsx @@ -1,12 +1,12 @@ import { component$, $, useStyles$ } from '@builder.io/qwik' import { Link, useNavigate } from '@builder.io/qwik-city' import { formatTimeAgo } from '~/utils/dateTime' -import { Avatar } from '../avatar' import type { Account, MastodonStatus } from '~/types' import styles from '../../utils/innerHtmlContent.scss?inline' import { MediaGallery } from '../MediaGallery.tsx' import { useAccountUrl } from '~/utils/useAccountUrl' import { getDisplayNameElement } from '~/utils/getDisplayNameElement' +import { StatusAccountCard } from '../StatusAccountCard/StatusAccountCard' type Props = { status: MastodonStatus @@ -29,17 +29,7 @@ export default component$((props: Props) => {
-
- -
-
- - {getDisplayNameElement(status.account)} - -
-
@{status.account.username}
-
-
+
diff --git a/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx b/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx new file mode 100644 index 0000000..abeed9c --- /dev/null +++ b/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx @@ -0,0 +1,29 @@ +import { component$ } from '@builder.io/qwik' +import { Link } from '@builder.io/qwik-city' +import { type MastodonStatus } from '~/types' +import { getDisplayNameElement } from '~/utils/getDisplayNameElement' +import { useAccountUrl } from '~/utils/useAccountUrl' +import { Avatar, type AvatarDetails } from '../avatar' + +export const StatusAccountCard = component$<{ + status: MastodonStatus + subText: 'username' | 'acct' + secondaryAvatar?: AvatarDetails | null +}>(({ status, subText, secondaryAvatar }) => { + const accountUrl = useAccountUrl(status.account) + + return ( + +
+ +
+
{getDisplayNameElement(status.account)}
+
+ @{subText === 'username' ? status.account.username : status.account.acct} +
+ + ) +}) diff --git a/frontend/src/components/avatar/index.tsx b/frontend/src/components/avatar/index.tsx index 6611c0d..99febe7 100644 --- a/frontend/src/components/avatar/index.tsx +++ b/frontend/src/components/avatar/index.tsx @@ -1,32 +1,35 @@ import { component$ } from '@builder.io/qwik' +import { Link } from '@builder.io/qwik-city' import type { Account } from '~/types' import { useAccountUrl } from '~/utils/useAccountUrl' -type AvatarDetails = Partial> & Pick +export type AvatarDetails = Partial> & Pick type Props = { primary: AvatarDetails secondary: AvatarDetails | null + withLinks?: boolean } -export const Avatar = component$(({ primary, secondary }) => { +export const Avatar = component$(({ primary, secondary, withLinks }) => { const primaryUrl = useAccountUrl(primary) const secondaryUrl = useAccountUrl(secondary) + // eslint-disable-next-line qwik/single-jsx-root + const primaryImg = {`Avatar + + const secondaryImg = ( + {`Avatar + ) + return (
- - {`Avatar - - {secondary && ( - - {`Avatar - - )} + {withLinks ? {primaryImg} : primaryImg} + {secondary && (withLinks ? {secondaryImg} : secondaryImg)}
) }) diff --git a/frontend/src/dummyData/accounts.ts b/frontend/src/dummyData/accounts.ts index 3989770..cd929b7 100644 --- a/frontend/src/dummyData/accounts.ts +++ b/frontend/src/dummyData/accounts.ts @@ -5,8 +5,8 @@ export const george = generateDummyAccount({ username: 'george', acct: 'george_george@dummy.users.wildebeest.com', display_name: 'George :verified: 👍', - avatar: getAvatarUrl(805), - avatar_static: getAvatarUrl(805), + avatar: getAvatarUrl(837), + avatar_static: getAvatarUrl(837), }) export const zak = generateDummyAccount({ diff --git a/frontend/src/routes/(admin)/oauth/authorize/index.tsx b/frontend/src/routes/(admin)/oauth/authorize/index.tsx index 2f0b3f3..f209bb6 100644 --- a/frontend/src/routes/(admin)/oauth/authorize/index.tsx +++ b/frontend/src/routes/(admin)/oauth/authorize/index.tsx @@ -87,6 +87,7 @@ export default component$(() => { url: url.toString(), }} secondary={null} + withLinks={true} />

Signed in as:

diff --git a/frontend/src/routes/(frontend)/[accountId]/[statusId]/index.tsx b/frontend/src/routes/(frontend)/[accountId]/[statusId]/index.tsx index cb68843..bdad675 100644 --- a/frontend/src/routes/(frontend)/[accountId]/[statusId]/index.tsx +++ b/frontend/src/routes/(frontend)/[accountId]/[statusId]/index.tsx @@ -5,17 +5,15 @@ import { formatDateTime } from '~/utils/dateTime' import { formatRoundedNumber } from '~/utils/numbers' import * as statusAPI from 'wildebeest/functions/api/v1/statuses/[id]' import * as contextAPI from 'wildebeest/functions/api/v1/statuses/[id]/context' -import { DocumentHead, Link, loader$ } from '@builder.io/qwik-city' +import { DocumentHead, loader$ } from '@builder.io/qwik-city' import StickyHeader from '~/components/StickyHeader/StickyHeader' -import { Avatar } from '~/components/avatar' import { MediaGallery } from '~/components/MediaGallery.tsx' import { getNotFoundHtml } from '~/utils/getNotFoundHtml/getNotFoundHtml' import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml' import styles from '../../../../utils/innerHtmlContent.scss?inline' import { getTextContent } from 'wildebeest/backend/src/activitypub/objects' import { getDocumentHead } from '~/utils/getDocumentHead' -import { useAccountUrl } from '~/utils/useAccountUrl' -import { getDisplayNameElement } from '~/utils/getDisplayNameElement' +import { StatusAccountCard } from '~/components/StatusAccountCard/StatusAccountCard' export const statusLoader = loader$< { DATABASE: D1Database }, @@ -57,7 +55,7 @@ export default component$(() => { <>
- +
@@ -73,24 +71,6 @@ export default component$(() => { ) }) -export const AccountCard = component$<{ status: MastodonStatus }>(({ status }) => { - const accountUrl = useAccountUrl(status.account) - - return ( -
- -
-
- - {getDisplayNameElement(status.account)} - -
-
@{status.account.acct}
-
-
- ) -}) - export const InfoTray = component$<{ status: MastodonStatus }>(({ status }) => { return (
From 5b935a11e6676b20560c5037c72b41371f9f5e53 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 16 Feb 2023 13:00:26 +0000 Subject: [PATCH 3/4] update status click handler not to include account --- frontend/src/components/Status/index.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Status/index.tsx b/frontend/src/components/Status/index.tsx index 1f15919..1de3cf6 100644 --- a/frontend/src/components/Status/index.tsx +++ b/frontend/src/components/Status/index.tsx @@ -27,18 +27,20 @@ export default component$((props: Props) => { return (
-
-
- - -
- - {formatTimeAgo(new Date(status.created_at))} -
- -
-
+
+ + +
+ + {formatTimeAgo(new Date(status.created_at))} +
+
+
From d6daa7bfb3b6125864ca1b08c7e8fbcdcae01736 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 16 Feb 2023 13:31:52 +0000 Subject: [PATCH 4/4] update e2e tests after previous navigation tweaks --- .../StatusAccountCard/StatusAccountCard.tsx | 4 +- ui-e2e-tests/account-page.spec.ts | 49 +++++++------------ ui-e2e-tests/custom-emojis.spec.ts | 6 ++- ui-e2e-tests/infinite-scrolling.spec.ts | 4 +- ui-e2e-tests/invidivual-toot.spec.ts | 30 +++++++----- 5 files changed, 45 insertions(+), 48 deletions(-) diff --git a/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx b/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx index abeed9c..80dd826 100644 --- a/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx +++ b/frontend/src/components/StatusAccountCard/StatusAccountCard.tsx @@ -20,7 +20,9 @@ export const StatusAccountCard = component$<{
-
{getDisplayNameElement(status.account)}
+
+ {getDisplayNameElement(status.account)} +
@{subText === 'username' ? status.account.username : status.account.acct}
diff --git a/ui-e2e-tests/account-page.spec.ts b/ui-e2e-tests/account-page.spec.ts index ea5ea70..e0b9339 100644 --- a/ui-e2e-tests/account-page.spec.ts +++ b/ui-e2e-tests/account-page.spec.ts @@ -1,41 +1,26 @@ import { test, expect } from '@playwright/test' -const navigationsVia = ['name link', 'avatar'] as const +test(`Navigation via to and view of an account (with 1 post)`, async ({ page }) => { + await page.goto('http://127.0.0.1:8788/explore') + await page.getByRole('article').getByRole('link').filter({ hasText: 'Ben, just Ben' }).first().click() + await expect(page.getByTestId('account-info').getByRole('img', { name: 'Header of Ben, just Ben' })).toBeVisible() + await expect(page.getByTestId('account-info').getByRole('img', { name: 'Avatar of Ben, just Ben' })).toBeVisible() + await expect(page.getByTestId('account-info').getByRole('heading', { name: 'Ben, just Ben' })).toBeVisible() + await expect(page.getByTestId('account-info').getByText('Joined')).toBeVisible() + await expect(page.getByTestId('account-info').getByTestId('stats')).toHaveText('1Posts0Following0Followers') -navigationsVia.forEach((via) => - test(`Navigation via ${via} to and view of an account (with 1 post)`, async ({ page }) => { - await page.goto('http://127.0.0.1:8788/explore') - await page.getByRole('article').getByRole('link', { name: 'Ben, just Ben', exact: true }).click() - await page.getByRole('link', { name: 'Ben, just Ben', exact: true }).click() - await page.waitForLoadState('networkidle') - const linkOption = - via === 'name link' ? { name: 'Ben, just Ben', exact: true } : { name: 'Avatar of Ben, just Ben' } - await page.getByRole('link', linkOption).click() - await expect(page.getByTestId('account-info').getByRole('img', { name: 'Header of Ben, just Ben' })).toBeVisible() - await expect(page.getByTestId('account-info').getByRole('img', { name: 'Avatar of Ben, just Ben' })).toBeVisible() - await expect(page.getByTestId('account-info').getByRole('heading', { name: 'Ben, just Ben' })).toBeVisible() - await expect(page.getByTestId('account-info').getByText('Joined')).toBeVisible() - await expect(page.getByTestId('account-info').getByTestId('stats')).toHaveText('1Posts0Following0Followers') - - expect(await page.getByTestId('account-statuses').getByRole('article').count()).toBe(1) - await expect( - page.getByTestId('account-statuses').getByRole('article').getByRole('img', { name: 'Avatar of Ben, just Ben' }) - ).toBeVisible() - await expect(page.getByTestId('account-statuses').getByRole('article')).toContainText( - 'A very simple update: all good!' - ) - }) -) + expect(await page.getByTestId('account-statuses').getByRole('article').count()).toBe(1) + await expect( + page.getByTestId('account-statuses').getByRole('article').getByRole('img', { name: 'Avatar of Ben, just Ben' }) + ).toBeVisible() + await expect(page.getByTestId('account-statuses').getByRole('article')).toContainText( + 'A very simple update: all good!' + ) +}) test('Navigation to and view of an account (with 2 posts)', async ({ page }) => { await page.goto('http://127.0.0.1:8788/explore') - await page - .locator('article') - .filter({ hasText: "I'm Rafael and I am a web designer" }) - .locator('i.fa-globe + span') - .click() - await page.waitForLoadState('networkidle') - await page.getByRole('link', { name: 'Raffa123$', exact: true }).click() + await page.getByRole('article').getByRole('link').filter({ hasText: 'Raffa123$' }).first().click() await expect(page.getByTestId('account-info').getByRole('img', { name: 'Header of Raffa123$' })).toBeVisible() await expect(page.getByTestId('account-info').getByRole('img', { name: 'Avatar of Raffa123$' })).toBeVisible() diff --git a/ui-e2e-tests/custom-emojis.spec.ts b/ui-e2e-tests/custom-emojis.spec.ts index 6e89afa..24ca8dd 100644 --- a/ui-e2e-tests/custom-emojis.spec.ts +++ b/ui-e2e-tests/custom-emojis.spec.ts @@ -40,7 +40,11 @@ test('View of custom emojis in an toots author display name', async ({ page, bro .locator('i.fa-globe + span') .click() - const customEmojiLocator = page.getByRole('link', { name: 'George :verified: 👍', exact: true }).getByRole('img') + const customEmojiLocator = page + .getByRole('link') + .filter({ hasText: 'George' }) + .getByTestId('account-display-name') + .getByRole('img') await expect(customEmojiLocator).toBeVisible() await expect(customEmojiLocator).toHaveAttribute( 'src', diff --git a/ui-e2e-tests/infinite-scrolling.spec.ts b/ui-e2e-tests/infinite-scrolling.spec.ts index d023db2..7c29362 100644 --- a/ui-e2e-tests/infinite-scrolling.spec.ts +++ b/ui-e2e-tests/infinite-scrolling.spec.ts @@ -26,9 +26,7 @@ test.describe('Infinite (statuses) scrolling', () => { description: 'in account page', goToPageFn: async (page: Page) => { await page.goto('http://127.0.0.1:8788/explore') - await page.locator('article').filter({ hasText: "I'm Rafael" }).locator('i.fa-globe + span').click() - await page.waitForLoadState('networkidle') - await page.getByRole('link', { name: 'Raffa123$', exact: true }).click() + await page.getByRole('article').getByRole('link').filter({ hasText: 'Raffa123$' }).first().click() await expect(page.getByTestId('account-info').getByRole('img', { name: 'Header of Raffa123$' })).toBeVisible() }, fetchUrl: 'http://127.0.0.1:8788/api/v1/accounts/Rafael/statuses?*', diff --git a/ui-e2e-tests/invidivual-toot.spec.ts b/ui-e2e-tests/invidivual-toot.spec.ts index b8fe628..46e64e2 100644 --- a/ui-e2e-tests/invidivual-toot.spec.ts +++ b/ui-e2e-tests/invidivual-toot.spec.ts @@ -1,13 +1,21 @@ import { test, expect } from '@playwright/test' -test('Navigation to and view of an individual toot', async ({ page }) => { - await page.goto('http://127.0.0.1:8788/explore') - await page.locator('article').filter({ hasText: 'Ben, just Ben' }).locator('i.fa-globe + span').click() - await expect(page.getByRole('button', { name: 'Back' })).toBeVisible() - await expect(page.getByRole('link', { name: 'Avatar of Ben, just Ben' })).toBeVisible() - await expect(page.getByRole('link', { name: 'Ben, just Ben', exact: true })).toBeVisible() - await expect(page.locator('span', { hasText: 'A very simple update: all good!' })).toBeVisible() -}) +const navigationVias = ['time link', 'toot content'] as const + +navigationVias.forEach((via) => + test(`Navigation to and view of an individual toot via ${via}`, async ({ page }) => { + await page.goto('http://127.0.0.1:8788/explore') + if (via === 'time link') { + await page.locator('article').filter({ hasText: 'Ben, just Ben' }).locator('i.fa-globe + span').click() + } else { + await page.getByText('A very simple update: all good!').click() + } + await expect(page.getByRole('button', { name: 'Back' })).toBeVisible() + await expect(page.getByRole('link', { name: 'Avatar of Ben, just Ben' })).toBeVisible() + await expect(page.getByTestId('account-display-name').filter({ hasText: 'Ben, just Ben' })).toBeVisible() + await expect(page.locator('span', { hasText: 'A very simple update: all good!' })).toBeVisible() + }) +) test('Navigation to and view of an individual toot with images', async ({ page }) => { await page.goto('http://127.0.0.1:8788/explore') @@ -18,7 +26,7 @@ test('Navigation to and view of an individual toot with images', async ({ page } .click() await expect(page.getByRole('button', { name: 'Back' })).toBeVisible() await expect(page.getByRole('link', { name: 'Avatar of Raffa123$' })).toBeVisible() - await expect(page.getByRole('link', { name: 'Raffa123$', exact: true })).toBeVisible() + await expect(page.getByTestId('account-display-name').filter({ hasText: 'Raffa123$' })).toBeVisible() await expect(page.locator('p', { hasText: "I'm Rafael and I am a web designer!" })).toBeVisible() expect(await page.getByTestId('media-gallery').getByRole('img').count()).toBe(4) await expect(page.getByTestId('images-modal')).not.toBeVisible() @@ -57,7 +65,7 @@ test("Navigation to and view of a toot's replies", async ({ page }) => { .click() await expect(page.getByRole('link', { name: 'Avatar of Zak Smith' })).toBeVisible() - await expect(page.getByRole('link', { name: 'Zak Smith', exact: true })).toBeVisible() + await expect(page.getByTestId('account-display-name').filter({ hasText: 'Zak Smith' })).toBeVisible() await expect(page.getByText('Yes we did!')).toBeVisible() await page.getByRole('button', { name: 'Back' }).click() @@ -72,6 +80,6 @@ test("Navigation to and view of a toot's replies", async ({ page }) => { .click() await expect(page.getByRole('link', { name: 'Avatar of Penny' })).toBeVisible() - await expect(page.getByRole('link', { name: 'Penny', exact: true })).toBeVisible() + await expect(page.getByTestId('account-display-name').filter({ hasText: 'Penny' })).toBeVisible() await expect(page.getByText('Yes you guys did it!')).toBeVisible() })