feat(activity): update summary cards to toggles and introduce export menu
This commit is contained in:
1 parent
73f4b539fe
commit
03b39e64f2
30 files changed
+2376
-238
No files matched your search
@@ -41,8 +41,17 @@ export const EXPORT_PATH = '/v1/activity/export'
|
||||
export const USERS_PATH = '/v1/user/'
|
||||
export const KEYS_PATH = '/v1/api-key/'
|
||||
|
||||
export const CSV_HEADER = 'when,actor,action,target,outcome,source,ip'
|
||||
export const CSV_WITH_ROWS = `${CSV_HEADER}\n2026-10-06,Marta Alves,Published,Spring release,success,web,203.0.113.24\n2026-10-05,Ines Duarte,Deleted,old-banner.png,success,web,203.0.113.12\n`
|
||||
export const CSV_HEADER_ONLY = `${CSV_HEADER}\n`
|
||||
|
||||
export function csvBlob(text: string) {
|
||||
return new Blob([text], { type: 'text/csv' })
|
||||
}
|
||||
|
||||
export const harness = {
|
||||
calls: [] as RecordedCall[],
|
||||
signals: [] as (AbortSignal | undefined)[],
|
||||
wrapper: null as VueWrapper | null,
|
||||
}
|
||||
|
||||
@@ -65,12 +74,13 @@ export async function mountActivity(options: MountOptions = {}) {
|
||||
const api: ActivityApiMock = {
|
||||
list: () => pageOf(activityItems),
|
||||
summary: () => summaryDefault,
|
||||
exportFile: () => new Blob(['when,actor'], { type: 'text/csv' }),
|
||||
exportFile: () => csvBlob(CSV_WITH_ROWS),
|
||||
users: () => [],
|
||||
keys: () => [],
|
||||
...options,
|
||||
}
|
||||
harness.calls = []
|
||||
harness.signals = []
|
||||
counters.clear()
|
||||
setViewportWidth(desktop ? DESKTOP_WIDTH : MOBILE_WIDTH)
|
||||
setActivePinia(createPinia())
|
||||
@@ -105,7 +115,8 @@ export async function mountActivity(options: MountOptions = {}) {
|
||||
throw new Error(`Unexpected request: ${path}`)
|
||||
}
|
||||
})
|
||||
const requestBlob = vi.fn(async (path: string) => {
|
||||
const requestBlob = vi.fn(async (path: string, options?: RequestInit) => {
|
||||
harness.signals.push(options?.signal ?? undefined)
|
||||
const url = record(path)
|
||||
return respond(api.exportFile, url.searchParams, url.pathname) as Blob
|
||||
})
|
||||
@@ -204,3 +215,38 @@ export async function expectIcon(root: Element | null | undefined, name: string,
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
expect(hasIcon(root, name)).toBe(false)
|
||||
}
|
||||
|
||||
export function exportButton() {
|
||||
return findButton('Export') ?? findButton('Exporter')
|
||||
}
|
||||
|
||||
export function menuItems() {
|
||||
return Array.from(document.body.querySelectorAll<HTMLElement>('[role="menuitem"]'))
|
||||
}
|
||||
|
||||
export function menuItem(label: string) {
|
||||
return menuItems().find((item) => textOf(item).startsWith(label))
|
||||
}
|
||||
|
||||
export async function openExportMenu() {
|
||||
await click(exportButton())
|
||||
await vi.waitFor(() => expect(menuItems().length).toBe(2))
|
||||
}
|
||||
|
||||
export function dialogElement() {
|
||||
return document.body.querySelector<HTMLElement>('[role="dialog"]')
|
||||
}
|
||||
|
||||
export function dialogButton(label: string) {
|
||||
return Array.from(dialogElement()?.querySelectorAll<HTMLButtonElement>('button') ?? []).find(
|
||||
(button) => textOf(button) === label || button.getAttribute('aria-label') === label,
|
||||
)
|
||||
}
|
||||
|
||||
export function dialogButtons() {
|
||||
return Array.from(
|
||||
dialogElement()?.querySelectorAll<HTMLButtonElement>(
|
||||
'footer button, [data-slot="footer"] button',
|
||||
) ?? [],
|
||||
).map(textOf)
|
||||
}
|
||||
Reference in new issue
Block a user