feat(SPA): users page and invite modal

This commit is contained in:
fsylvain committed 2026-09-29 08:35:14 +02:00
1 parent acdef18b37
commit 6afffc1984
20 files changed
+1880 -55

No files matched your search

+112 -1
View File
@@ -152,7 +152,7 @@ const sixArticlesFixture = [
const verifiedSession: ApiMock = {
status: 200,
body: { is_verified: true, username: 'marta' },
body: { id: 1, is_verified: true, username: 'marta' },
}
const editorNow = '2026-09-26T11:02:00Z'
@@ -565,6 +565,75 @@ function resetConfirmWith(response: ApiMock): Omit<VisualState, 'steps'> {
}
}
const usersFixture = [
{
kind: 'invite',
id: 2,
email: 'sam.oliveira@renew.example',
status: 'pending',
created_at: '2026-09-27T09:00:00Z',
},
{
kind: 'invite',
id: 1,
email: 'p.huber@gmail.com',
status: 'expired',
created_at: '2026-08-03T09:00:00Z',
},
{
kind: 'user',
id: 3,
username: 'ines',
display_name: 'Inès Duarte',
status: 'accepted',
created_at: '2026-06-18T09:00:00Z',
},
{
kind: 'user',
id: 2,
username: 'jonas',
display_name: '',
status: 'accepted',
created_at: '2026-04-02T09:00:00Z',
},
{
kind: 'user',
id: 1,
username: 'marta',
display_name: '',
status: 'accepted',
created_at: '2026-03-12T09:00:00Z',
},
]
function usersApi(overrides: Record<string, ApiMock> = {}): Record<string, ApiMock> {
return {
'GET /v1/auth/me': verifiedSession,
'GET /v1/user/': { status: 200, body: usersFixture },
...overrides,
}
}
async function openInviteModal(page: Page) {
await page.getByRole('button', { name: 'Invite user' }).click()
await page.getByRole('dialog').waitFor()
await page.mouse.move(0, 0)
}
function inviteWith(email: string, response?: ApiMock) {
return async (page: Page) => {
await openInviteModal(page)
await page.getByPlaceholder('name@example.com').fill(email)
if (response) {
await page.getByRole('button', { name: 'Send invite', exact: true }).click()
await page.mouse.move(0, 0)
} else {
await page.getByPlaceholder('name@example.com').blur()
await page.mouse.move(0, 0)
}
}
}
const integrationOrigin = 'https://cms.renew.example'
async function clickBaseUrlCopy(page: Page) {
@@ -572,6 +641,48 @@ async function clickBaseUrlCopy(page: Page) {
}
export const visualStates: Record<string, Record<string, VisualState>> = {
users: {
Default: { path: '/users', api: usersApi() },
Loading: {
path: '/users',
api: usersApi({ 'GET /v1/user/': { status: 200, body: usersFixture, hang: true } }),
steps: async (page) => {
await page.waitForTimeout(400)
},
},
'Only you': {
path: '/users',
api: usersApi({ 'GET /v1/user/': { status: 200, body: usersFixture.slice(4) } }),
},
Error: { path: '/users', api: usersApi({ 'GET /v1/user/': { status: 500 } }) },
'Invite: default': { path: '/users', api: usersApi(), steps: openInviteModal },
'Invite: validation': {
path: '/users',
api: usersApi(),
steps: inviteWith('sam.oliveira@renew'),
},
'Invite: sending': {
path: '/users',
api: usersApi({ 'POST /v1/user/invite': { status: 201, hang: true } }),
steps: inviteWith('lea.martin@renew.example', { status: 201, hang: true }),
},
'Invite: already a user (409)': {
path: '/users',
api: usersApi({
'POST /v1/user/invite': {
status: 409,
body: 'This address already has an account.',
},
}),
steps: inviteWith('jonas@renew.example', { status: 409 }),
},
'Invite: failed': {
path: '/users',
api: usersApi({ 'POST /v1/user/invite': { status: 500 } }),
steps: inviteWith('lea.martin@renew.example', { status: 500 }),
},
'Invite sent': { path: '/users', skip: 'clock-sensitive' },
},
integration: {
Default: {
path: '/integration',