@actuate-media/cms-admin 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/views/user-detail-drawer.render.test.d.ts +2 -0
- package/dist/__tests__/views/user-detail-drawer.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/user-detail-drawer.render.test.js +113 -0
- package/dist/__tests__/views/user-detail-drawer.render.test.js.map +1 -0
- package/dist/__tests__/views/users.render.test.d.ts +2 -0
- package/dist/__tests__/views/users.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/users.render.test.js +146 -0
- package/dist/__tests__/views/users.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/views/Users.d.ts +1 -3
- package/dist/views/Users.d.ts.map +1 -1
- package/dist/views/Users.js +394 -73
- package/dist/views/Users.js.map +1 -1
- package/dist/views/users/UserDetailDrawer.d.ts +9 -0
- package/dist/views/users/UserDetailDrawer.d.ts.map +1 -0
- package/dist/views/users/UserDetailDrawer.js +236 -0
- package/dist/views/users/UserDetailDrawer.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/views/user-detail-drawer.render.test.tsx +119 -0
- package/src/__tests__/views/users.render.test.tsx +155 -0
- package/src/views/Users.tsx +1165 -331
- package/src/views/users/UserDetailDrawer.tsx +924 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Render tests for the rebuilt Users tab: dynamic metric cards, access-review
|
|
7
|
+
* banner, team table (members + pending invites), role filter pills, search,
|
|
8
|
+
* and the invite modal.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const MEMBERS = [
|
|
12
|
+
{
|
|
13
|
+
id: 'u1',
|
|
14
|
+
name: 'Dana Chen',
|
|
15
|
+
email: 'dana@actuatemedia.com',
|
|
16
|
+
initials: 'DC',
|
|
17
|
+
role: 'OWNER',
|
|
18
|
+
roleName: 'Owner',
|
|
19
|
+
status: 'active',
|
|
20
|
+
mfaEnabled: true,
|
|
21
|
+
externalDomain: false,
|
|
22
|
+
lastLoginAt: new Date().toISOString(),
|
|
23
|
+
lastActiveAt: new Date().toISOString(),
|
|
24
|
+
acceptedInviteAt: new Date().toISOString(),
|
|
25
|
+
createdAt: new Date().toISOString(),
|
|
26
|
+
isCurrentUser: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'u2',
|
|
30
|
+
name: 'Priya Nair',
|
|
31
|
+
email: 'priya@actuatemedia.com',
|
|
32
|
+
initials: 'PN',
|
|
33
|
+
role: 'EDITOR',
|
|
34
|
+
roleName: 'Editor',
|
|
35
|
+
status: 'active',
|
|
36
|
+
mfaEnabled: true,
|
|
37
|
+
externalDomain: false,
|
|
38
|
+
lastLoginAt: new Date(Date.now() - 86400000).toISOString(),
|
|
39
|
+
lastActiveAt: new Date(Date.now() - 86400000).toISOString(),
|
|
40
|
+
acceptedInviteAt: new Date().toISOString(),
|
|
41
|
+
createdAt: new Date().toISOString(),
|
|
42
|
+
isCurrentUser: false,
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const INVITES = [
|
|
47
|
+
{
|
|
48
|
+
id: 'i1',
|
|
49
|
+
email: 'jordan@actuatemedia.com',
|
|
50
|
+
role: 'EDITOR',
|
|
51
|
+
roleName: 'Editor',
|
|
52
|
+
status: 'PENDING',
|
|
53
|
+
expiresAt: new Date(Date.now() + 5 * 86400000).toISOString(),
|
|
54
|
+
createdAt: new Date(Date.now() - 2 * 86400000).toISOString(),
|
|
55
|
+
expired: false,
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
const STATS = {
|
|
60
|
+
members: 2,
|
|
61
|
+
pendingInvites: 1,
|
|
62
|
+
admins: 1,
|
|
63
|
+
seatsUsed: 3,
|
|
64
|
+
seatLimit: 10,
|
|
65
|
+
seatsAvailable: 7,
|
|
66
|
+
emailConfigured: true,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const REVIEW = [
|
|
70
|
+
{
|
|
71
|
+
id: 'inactive_user:u9',
|
|
72
|
+
userId: 'u9',
|
|
73
|
+
severity: 'high',
|
|
74
|
+
type: 'inactive_user',
|
|
75
|
+
title: 'Access review',
|
|
76
|
+
description: "Sara Lindqvist (sara@freelance.io) hasn't signed in for 94 days.",
|
|
77
|
+
recommendedAction: 'Revoke access',
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
const { cmsApi } = vi.hoisted(() => ({ cmsApi: vi.fn() }))
|
|
82
|
+
|
|
83
|
+
vi.mock('../../lib/api.js', () => ({ cmsApi }))
|
|
84
|
+
|
|
85
|
+
cmsApi.mockImplementation(async (endpoint?: string) => {
|
|
86
|
+
if (!endpoint) return { data: null }
|
|
87
|
+
if (endpoint.startsWith('/users/stats')) return { data: STATS }
|
|
88
|
+
if (endpoint.startsWith('/users')) return { data: MEMBERS }
|
|
89
|
+
if (endpoint.startsWith('/invites')) return { data: INVITES }
|
|
90
|
+
if (endpoint.startsWith('/access-review')) return { data: REVIEW }
|
|
91
|
+
return { data: null }
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const { Users } = await import('../../views/Users.js')
|
|
95
|
+
|
|
96
|
+
describe('Users tab', () => {
|
|
97
|
+
beforeEach(() => cmsApi.mockClear())
|
|
98
|
+
|
|
99
|
+
it('renders dynamic metric cards', async () => {
|
|
100
|
+
render(<Users embedded />)
|
|
101
|
+
expect(await screen.findByText('Members')).toBeTruthy()
|
|
102
|
+
expect(screen.getByText('Pending invites')).toBeTruthy()
|
|
103
|
+
expect(screen.getByText('Admins')).toBeTruthy()
|
|
104
|
+
expect(screen.getByText('Seats used')).toBeTruthy()
|
|
105
|
+
expect(screen.getByText('3 / 10')).toBeTruthy()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('renders the access review banner with the AI badge', async () => {
|
|
109
|
+
render(<Users embedded />)
|
|
110
|
+
expect(await screen.findByText('Access review')).toBeTruthy()
|
|
111
|
+
expect(screen.getByText('AI')).toBeTruthy()
|
|
112
|
+
expect(screen.getByText(/hasn't signed in for 94 days/)).toBeTruthy()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('renders members and pending invites in the team table', async () => {
|
|
116
|
+
render(<Users embedded />)
|
|
117
|
+
expect(await screen.findByText('Dana Chen')).toBeTruthy()
|
|
118
|
+
expect(screen.getByText('Priya Nair')).toBeTruthy()
|
|
119
|
+
// pending invite row
|
|
120
|
+
expect(screen.getByText('jordan@actuatemedia.com')).toBeTruthy()
|
|
121
|
+
expect(screen.getByText('Invited')).toBeTruthy()
|
|
122
|
+
expect(screen.getByText(/people have access/)).toBeTruthy()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('filters by role pill', async () => {
|
|
126
|
+
render(<Users embedded />)
|
|
127
|
+
await screen.findByText('Dana Chen')
|
|
128
|
+
fireEvent.click(screen.getByRole('button', { name: 'Owner', pressed: false }))
|
|
129
|
+
await waitFor(() => expect(screen.queryByText('Priya Nair')).toBeNull())
|
|
130
|
+
expect(screen.getByText('Dana Chen')).toBeTruthy()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('filters by search', async () => {
|
|
134
|
+
render(<Users embedded />)
|
|
135
|
+
await screen.findByText('Dana Chen')
|
|
136
|
+
fireEvent.change(screen.getByLabelText('Search team members'), { target: { value: 'priya' } })
|
|
137
|
+
await waitFor(() => expect(screen.queryByText('Dana Chen')).toBeNull())
|
|
138
|
+
expect(screen.getByText('Priya Nair')).toBeTruthy()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('opens the invite modal with role radio cards', async () => {
|
|
142
|
+
render(<Users embedded />)
|
|
143
|
+
await screen.findByText('Dana Chen')
|
|
144
|
+
fireEvent.click(screen.getByRole('button', { name: /Invite user/ }))
|
|
145
|
+
const dialog = await screen.findByRole('dialog')
|
|
146
|
+
expect(within(dialog).getByText('Invite a user')).toBeTruthy()
|
|
147
|
+
expect(within(dialog).getByPlaceholderText('name@company.com')).toBeTruthy()
|
|
148
|
+
// Owner sees the advanced Owner role card too (current user is Owner).
|
|
149
|
+
expect(within(dialog).getByText('Owner')).toBeTruthy()
|
|
150
|
+
expect(within(dialog).getByText('Admin')).toBeTruthy()
|
|
151
|
+
expect(within(dialog).getByText('Editor')).toBeTruthy()
|
|
152
|
+
expect(within(dialog).getByText('Viewer')).toBeTruthy()
|
|
153
|
+
expect(within(dialog).getByRole('button', { name: 'Send invite' })).toBeTruthy()
|
|
154
|
+
})
|
|
155
|
+
})
|