feat: add minimal reader and shared account navigation

This commit is contained in:
2026-09-28 17:53:38 +09:00
parent 96e545401e
commit 4e8957c94a
15 changed files with 844 additions and 96 deletions
+34 -3
View File
@@ -1,12 +1,16 @@
import { expect, test } from '../fixtures'
import { showDeckManagement } from '../workspace'
test('opens account management after hydration without inheriting real OAuth configuration', async ({
page,
}) => {
await page.goto('/')
await showDeckManagement(page)
await expect(
page.getByRole('button', { name: '名前を変更', includeHidden: true }),
).toBeEnabled()
const manage = page.getByRole('button', { name: '接続アカウントを管理' })
if (!(await manage.isVisible())) {
await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
}
await expect(manage).toBeEnabled()
await manage.click()
const dialog = page.getByRole('dialog', { name: '接続アカウント' })
@@ -22,6 +26,27 @@ test('opens account management after hydration without inheriting real OAuth con
await expect(manage).toBeFocused()
})
test('opens account management from the inbox footer and restores focus on close', async ({
page,
a11y,
}) => {
await page.goto('/inbox')
await page.waitForLoadState('networkidle')
const manage = page.getByRole('button', { name: '接続アカウントを管理' })
await expect(manage).toBeVisible()
await manage.click()
const dialog = page.getByRole('dialog', { name: '接続アカウント' })
await expect(dialog).toBeVisible()
await expect(
dialog.getByText('利用するMastodonサーバーがまだ設定されていません。'),
).toBeVisible()
expect((await a11y().analyze()).violations).toEqual([])
await page.keyboard.press('Escape')
await expect(dialog).toHaveCount(0)
await expect(manage).toBeFocused()
await expect(page.getByRole('textbox', { name: '記事を検索' })).toBeVisible()
})
test('rejects an unbound OAuth callback without contacting an instance', async ({
page,
}) => {
@@ -30,7 +55,13 @@ test('rejects an unbound OAuth callback without contacting an instance', async (
await expect(page.getByRole('alert')).toContainText(
'Mastodonの認可を完了できませんでした。',
)
await showDeckManagement(page)
await expect(
page.getByRole('button', { name: '名前を変更', includeHidden: true }),
).toBeEnabled()
const manage = page.getByRole('button', { name: '接続アカウントを管理' })
if (!(await manage.isVisible())) {
await page.getByRole('button', { name: 'サイドバーを切り替え' }).click()
}
await expect(
page.getByRole('button', { name: '接続アカウントを管理' }),
).toBeEnabled()
+101
View File
@@ -0,0 +1,101 @@
import { expect, test } from '../fixtures'
test('selects articles and keeps the reader focused on their content', async ({
page,
}) => {
await page.goto('/inbox')
await page.waitForLoadState('networkidle')
const rows = page.locator('.inbox-article-row')
await expect(rows).toHaveCount(9)
const articleTitle = 'エージェントに渡す文脈は、短くてもよい'
await rows.filter({ hasText: articleTitle }).click()
const reader = page.getByRole('article', { name: articleTitle, exact: true })
await expect(reader).toBeVisible()
await expect(
reader.getByRole('heading', { name: articleTitle, exact: true }),
).toBeVisible()
await expect(
reader.getByText(
'まず目的を一文で書き、次に変えてはいけない条件を添える。',
{ exact: false },
),
).toBeVisible()
await expect(page.getByText('FOR YOU', { exact: true })).toHaveCount(0)
await expect(page.getByText('モック', { exact: true })).toHaveCount(0)
await expect(
page.getByText('この記事が届いた理由', { exact: true }),
).toHaveCount(0)
await expect(
page.getByRole('button', { name: /あなたへ|あとで読む|未読のみ/ }),
).toHaveCount(0)
const back = page.getByRole('button', { name: '記事一覧に戻る' })
if (await back.isVisible()) await back.click()
await rows
.filter({ hasText: '3カラムの余白が、情報を読むリズムをつくる' })
.click()
await expect(
page.getByRole('article', {
name: '3カラムの余白が、情報を読むリズムをつくる',
exact: true,
}),
).toBeVisible()
})
test('recovers from empty search and shares navigation with Twitter Lite', async ({
page,
a11y,
}) => {
await page.goto('/inbox')
await page.waitForLoadState('networkidle')
const rows = page.locator('.inbox-article-row')
await page
.getByRole('textbox', { name: '記事を検索' })
.fill('存在しない検索語xyz')
await expect(rows).toHaveCount(0)
await expect(
page.getByText('記事がありません', { exact: true }),
).toBeVisible()
await page.getByRole('button', { name: '検索をクリア' }).click()
await expect(rows).toHaveCount(9)
expect((await a11y().analyze()).violations).toEqual([])
await expect(
page.getByRole('button', { name: '興味を編集', exact: true }),
).toHaveCount(0)
await page.getByRole('link', { name: 'Twitter Lite', exact: true }).click()
await expect(page).toHaveURL(/\/deck$/)
await page.getByRole('link', { name: 'リーダー', exact: true }).click()
await expect(page).toHaveURL(/\/inbox$/)
await expect(rows).toHaveCount(9)
})
test('keeps article navigation readable without horizontal overflow', async ({
page,
a11y,
}, testInfo) => {
await page.goto('/inbox')
await page.waitForLoadState('networkidle')
for (const width of [320, 375, 414, 768]) {
await page.setViewportSize({ width, height: 850 })
await expect(
page.getByRole('textbox', { name: '記事を検索' }),
).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.locator('.inbox-article-row').first().click()
const reader = page.getByRole('article', {
name: 'ブラウザを見ながら考える。AIエージェントとの新しい開発の進め方',
exact: true,
})
await expect(reader).toBeVisible()
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBe(width)
await page.screenshot({
path: testInfo.outputPath(`inbox-reader-${width}.png`),
})
if (width === 320) expect((await a11y().analyze()).violations).toEqual([])
const back = page.getByRole('button', { name: '記事一覧に戻る' })
if (await back.isVisible()) await back.click()
}
})