feat: recent activity card on Home and Retention card on Settings

This commit is contained in:
fsylvain committed 2026-10-07 16:01:28 +02:00
1 parent 35db442cd3
commit 43469ef08d
33 files changed
+2748 -204

No files matched your search

+107 -6
View File
@@ -165,6 +165,18 @@ const verifiedSession: ApiMock = {
}
const updateRunningVersion = 'v2.2.0-beta'
const newCardAbsentReason = 'new card absent from board frame (dashed placeholder)'
const homeActivityMock: ApiMock = {
status: 200,
body: { items: [], total: 0, page: 1, size: 5 },
}
const workspaceSettingsBody = {
version: updateRunningVersion,
activity_retention_days: 90,
activity_last_cleanup_at: '2026-10-06T03:00:00Z',
activity_last_cleanup_removed: 142,
activity_next_cleanup_at: '2026-10-07T03:00:00Z',
}
const updateLatestTag = 'v2.3.0'
const updateReleaseUrl = 'https://git.floriansylvain.fr/fsylvain/RenewCMS/releases/tag/v2.3.0'
const updateReleasedAt = '2026-09-24T08:00:00Z'
@@ -291,7 +303,7 @@ function updateApi(
): Record<string, ApiMock> {
return {
'GET /v1/auth/me': role === 'owner' ? verifiedSession : adminSession,
'GET /v1/settings/': { status: 200, body: { version: updateRunningVersion } },
'GET /v1/settings/': { status: 200, body: workspaceSettingsBody },
'GET /v1/system/update': { status: 200, body: status },
'POST /v1/system/update/check': check,
}
@@ -545,6 +557,7 @@ const newArticleBody = 'Small rooms show every grout line, so we start with the
function newArticleHomeApi(overrides: Record<string, ApiMock> = {}): Record<string, ApiMock> {
return {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
...overrides,
}
@@ -2378,7 +2391,7 @@ function cleanupApi(
): Record<string, ApiMock> {
return {
'GET /v1/auth/me': sessionWithRole(role),
'GET /v1/settings/': { status: 200, body: { version: updateRunningVersion } },
'GET /v1/settings/': { status: 200, body: workspaceSettingsBody },
'GET /v1/system/update': { status: 200, hang: true },
[purgeSettingsPath]: settings,
...overrides,
@@ -2392,8 +2405,9 @@ function settingsBody(body: unknown): ApiMock {
function cleanupState(
api: Record<string, ApiMock>,
steps?: (page: Page) => Promise<void>,
exception?: DiffException,
): VisualState {
return { path: '/settings', api, steps }
return { path: '/settings', api, steps, exception }
}
async function openCleanupDialog(page: Page) {
@@ -2486,7 +2500,11 @@ const cleanupStates: Record<string, VisualState> = {
await page.getByText('Enter a whole number of days between 1 and 365.').waitFor()
}),
'Read-only (non-admin)': cleanupState(cleanupApi(settingsBody(purgeOnBody), {}, 'member')),
'Loading skeleton': cleanupState(cleanupApi({ status: 200, hang: true })),
'Loading skeleton': cleanupState(cleanupApi({ status: 200, hang: true }), undefined, {
maxPercent: 17.5,
reason: newCardAbsentReason,
approvedBy: 'fallen',
}),
Offline: cleanupState(cleanupApi(settingsBody(purgeOnBody)), goOffline),
'Loading preview': cleanupState(
cleanupDialogApi({ [purgePreviewPath]: { status: 200, hang: true } }),
@@ -2960,6 +2978,7 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
now: boardNow,
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
'GET /v1/system/update': { status: 200, body: updateStatusBody() },
},
@@ -3102,13 +3121,34 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
'Up to date': { path: '/settings', skip: 'clock-sensitive' },
Checking: {
path: '/settings',
exception: {
maxPercent: 11.3,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: updateApi('owner', updateNeverChecked(), updateCheckHangs),
},
'Check failed': { path: '/settings', skip: 'clock-sensitive' },
'Update available': { path: '/settings', skip: 'clock-sensitive' },
'Update available, notes open': { path: '/settings', skip: 'clock-sensitive' },
Downloading: { path: '/settings', api: updateApi('owner', updateDownloading()) },
Verifying: { path: '/settings', api: updateApi('owner', updateJob('verifying')) },
Downloading: {
path: '/settings',
api: updateApi('owner', updateDownloading()),
exception: {
maxPercent: 10.6,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
},
Verifying: {
path: '/settings',
api: updateApi('owner', updateJob('verifying')),
exception: {
maxPercent: 10.8,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
},
'Ready to install': { path: '/settings', api: updateApi('owner', updateJob('ready')) },
'Restart: confirm': {
path: '/settings',
@@ -3148,14 +3188,29 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
},
'Download failed': {
path: '/settings',
exception: {
maxPercent: 10.9,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: updateApi('owner', updateJob('failed', { error: 'download' })),
},
'Verification failed': {
path: '/settings',
exception: {
maxPercent: 10.5,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: updateApi('owner', updateJob('failed', { error: 'verification' })),
},
'Too many requests (429)': {
path: '/settings',
exception: {
maxPercent: 11.3,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: updateApi('owner', updateNeverChecked(), {
status: 429,
headers: { 'Retry-After': '42' },
@@ -3163,10 +3218,20 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
},
'Not prepared': {
path: '/settings',
exception: {
maxPercent: 11.9,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: updateApi('owner', updateAvailable()),
},
'Not prepared: command copied': {
path: '/settings',
exception: {
maxPercent: 11.9,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
origin: integrationOrigin,
api: updateApi('owner', updateAvailable()),
steps: async (page) => {
@@ -3491,52 +3556,88 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
home: {
Default: {
path: '/home',
exception: {
maxPercent: 13.5,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
now: boardNow,
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/system/update': { status: 200, body: updateStatusBody() },
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
},
},
Loading: {
path: '/home',
exception: {
maxPercent: 18.6,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
now: boardNow,
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/system/update': { status: 200, body: updateStatusBody() },
'GET /v1/article/': { status: 200, body: sixArticlesFixture, hang: true },
},
},
'Empty, first run': {
path: '/home',
exception: {
maxPercent: 11.4,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
now: boardNow,
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/system/update': { status: 200, body: updateStatusBody() },
'GET /v1/article/': { status: 200, body: [] },
},
},
Error: {
path: '/home',
exception: {
maxPercent: 25.3,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
now: boardNow,
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/system/update': { status: 200, body: updateStatusBody() },
'GET /v1/article/': { status: 500 },
},
},
'Update available (owner)': {
path: '/home',
exception: {
maxPercent: 11.9,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
'GET /v1/system/update': { status: 200, body: updateAvailable() },
},
},
'Update ready to install (owner)': {
path: '/home',
exception: {
maxPercent: 11.9,
reason: newCardAbsentReason,
approvedBy: 'fallen',
},
api: {
'GET /v1/auth/me': verifiedSession,
'GET /v1/activity': homeActivityMock,
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
'GET /v1/system/update': { status: 200, body: updateJob('ready') },
},