feat: roles SPA foundations

This commit is contained in:
Florian Sylvain committed 2026-10-27 22:21:12 +01:00
1 parent 80f7037d3a
commit 907949aa12
180 files changed
+4308 -835

No files matched your search

+17 -13
View File
@@ -7,6 +7,7 @@ import { ApiError, NetworkError, useApi } from '@/composables/useApi'
import type { CreatedInvite, UserListItem } from '@/composables/useUsers'
import { useSessionStore } from '@/stores/session'
import UsersView from '../UsersView.vue'
import { accessFor, roleRefFor } from '@/test/access'
vi.mock('@/composables/useApi', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/composables/useApi')>()
@@ -38,6 +39,7 @@ function defaultFixture(): UserListItem[] {
kind: 'invite',
id: 2,
email: 'sam.oliveira@renew.example',
role: roleRefFor('admin'),
status: 'pending',
created_at: '2026-09-27T09:00:00Z',
},
@@ -46,6 +48,7 @@ function defaultFixture(): UserListItem[] {
id: 1,
email: 'p.huber@gmail.com',
display_name: 'Paul Huber',
role: roleRefFor('admin'),
status: 'expired',
created_at: '2026-08-03T09:00:00Z',
},
@@ -54,7 +57,7 @@ function defaultFixture(): UserListItem[] {
id: 3,
username: 'ines',
email: 'ines.duarte@renew.example',
role: 'admin',
role: roleRefFor('admin'),
display_name: 'Inès Duarte',
status: 'accepted',
created_at: '2026-06-18T09:00:00Z',
@@ -64,7 +67,7 @@ function defaultFixture(): UserListItem[] {
id: 2,
username: 'jonas',
email: 'jonas@renew.example',
role: 'admin',
role: roleRefFor('admin'),
display_name: '',
status: 'accepted',
created_at: '2026-04-02T09:00:00Z',
@@ -74,7 +77,7 @@ function defaultFixture(): UserListItem[] {
id: 1,
username: 'marta',
email: 'marta@renew.example',
role: 'owner',
role: roleRefFor('owner'),
display_name: '',
status: 'accepted',
created_at: '2026-03-12T09:00:00Z',
@@ -91,6 +94,7 @@ function inviteFixture(email = VALID_EMAIL): CreatedInvite {
id: 3,
kind: 'invite',
email,
role: roleRefFor('admin'),
status: 'pending',
created_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(),
@@ -139,7 +143,7 @@ async function mountPage(
username: 'marta',
id: 1,
displayName: '',
role: role ?? undefined,
access: accessFor(role ?? undefined),
}
session.status = 'ready'
requestMock = vi.fn((path: string) => {
@@ -156,7 +160,7 @@ async function mountPage(
verified: true,
username: 'marta',
id: 1,
role: role ?? undefined,
access: accessFor(role ?? undefined),
}),
request: requestMock,
})
@@ -368,7 +372,7 @@ describe('Display names in the Users list', () => {
username: 'marta',
id: 1,
displayName: 'Marta Alves',
role: 'owner',
access: accessFor('owner'),
}
await flushPromises()
@@ -436,10 +440,10 @@ describe('Only you — nobody has been invited yet', () => {
})
})
describe('Non-owners cannot invite', () => {
describe('Roles without users.invite cannot invite', () => {
it.each([
{ name: 'admin on desktop', role: 'admin', desktop: true },
{ name: 'admin below md', role: 'admin', desktop: false },
{ name: 'a writer on desktop', role: 'member', desktop: true },
{ name: 'a writer below md', role: 'member', desktop: false },
{ name: 'a missing role on desktop', role: null, desktop: true },
{ name: 'an unknown role below md', role: 'guest', desktop: false },
])('renders the list but no Invite user for $name', async ({ role, desktop }) => {
@@ -449,8 +453,8 @@ describe('Non-owners cannot invite', () => {
expect(rows()).toHaveLength(5)
})
it('hides the Only-you hint for an admin', async () => {
await mountPage({ list: () => Promise.resolve(onlyYouFixture()) }, true, 'admin')
it('hides the Only-you hint for a writer', async () => {
await mountPage({ list: () => Promise.resolve(onlyYouFixture()) }, true, 'member')
expect(rows()).toHaveLength(1)
expect(document.body.textContent).not.toContain('Invite someone so they can sign in')
@@ -458,12 +462,12 @@ describe('Non-owners cannot invite', () => {
})
it.each([true, false])(
'shows no hint for an admin on marta and jonas alone (desktop %s)',
'shows no hint for a writer on marta and jonas alone (desktop %s)',
async (desktop) => {
await mountPage(
{ list: () => Promise.resolve(defaultFixture().slice(3)) },
desktop,
'admin',
'member',
)
expect(rows()).toHaveLength(2)