Files
pearcord-ui-flow/scenarios.js
T
Raven ScottandCursor cdd8eb8c98 feat(scenarios): audit modal filter double-refresh UI IPC scenario (v0.8.85)
Add runAuditModalFilterDoubleRefreshScenario to exercise set-audit-log-export-filter
followed by two refresh IPC cycles, asserting user prefs and guild schedule filter
remain stable — matching audit modal close/reopen without a live renderer.

Co-authored-by: Cursor <[email protected]>
2026-05-22 07:52:17 -04:00

230 lines
8.2 KiB
JavaScript

'use strict'
const { dispatchUiMessage } = require('./index')
const { AUDIT_FILTER } = require('pearcord-delivery-receipts')
async function ipc (platform, msg) {
const ok = await dispatchUiMessage(platform, msg, {})
if (!ok) throw new Error(`unhandled IPC type: ${msg?.type}`)
}
/**
* Full desktop UI journey: onboarding, settings, navigation, invite join, restore.
*/
async function runFullUiFlowScenario (platform) {
await ipc(platform, {
type: 'register',
username: 'ui_flow_user',
displayName: 'Pearcord UI'
})
let view = await platform.view()
if (!view.onboarded) throw new Error('onboarded after register')
if (!view.needsGuild) throw new Error('needsGuild after register')
await ipc(platform, { type: 'create-guild', name: 'Pearcord HQ' })
view = await platform.view()
if (view.guild?.name !== 'Pearcord HQ') throw new Error('guild name')
if (view.needsGuild) throw new Error('needsGuild cleared')
if (view.mode !== 'guild') throw new Error('guild mode')
if (!view.activeChannelId) throw new Error('active channel')
const guildId = view.guild.id
const generalId = view.activeChannelId
await ipc(platform, { type: 'update-profile', displayName: 'UI Flow Tester' })
await ipc(platform, {
type: 'update-user-prefs',
prefs: { theme: 'amoled', density: 'compact', showTimestamps: true }
})
view = await platform.view()
if (view.user?.displayName !== 'UI Flow Tester') throw new Error('profile display name')
if (view.userPrefs?.theme !== 'amoled') throw new Error('user prefs theme')
await ipc(platform, { type: 'update-guild-settings', name: 'Pearcord HQ Renamed' })
await ipc(platform, {
type: 'set-discovery-tags',
guildId,
tags: ['gaming', 'p2p']
})
await ipc(platform, {
type: 'set-automod-config',
config: { enabled: true, maxMentions: 4, blockInvites: true }
})
await ipc(platform, { type: 'add-guild-boost', amount: 1 })
await ipc(platform, { type: 'set-presence', status: 'idle', customStatus: 'smoke testing' })
view = await platform.view()
if (view.guild?.name !== 'Pearcord HQ Renamed') throw new Error('guild renamed')
if (!view.automodConfig?.enabled) throw new Error('automod enabled')
if ((view.guildBoost?.boostCount || 0) < 1) throw new Error('guild boost')
if (view.presence?.self?.customStatus !== 'smoke testing') throw new Error('custom status')
await ipc(platform, { type: 'send-message', content: 'UI flow smoke message' })
await ipc(platform, { type: 'select-home' })
view = await platform.view()
if (view.mode !== 'home') throw new Error('home mode after select-home')
const peerId = 'a'.repeat(32)
await ipc(platform, {
type: 'open-dm',
peerUserId: peerId,
peerDisplayName: 'Peer'
})
view = await platform.view()
if (view.mode !== 'dm') throw new Error('dm mode')
await ipc(platform, { type: 'send-message', content: 'DM from UI flow' })
await ipc(platform, { type: 'select-guild', guildId })
view = await platform.view()
if (view.mode !== 'guild') throw new Error('back to guild')
await ipc(platform, { type: 'select-channel', channelId: generalId })
if (view.activeChannelId !== generalId) {
view = await platform.view()
if (view.activeChannelId !== generalId) throw new Error('channel reselected')
}
await ipc(platform, {
type: 'update-notification-prefs',
prefs: { desktopEnabled: true, notifyOnAllMessages: true }
})
view = await platform.view()
if (!view.notificationPrefs?.desktopEnabled) throw new Error('notification prefs')
if (!view.notificationPrefs?.notifyOnAllMessages) throw new Error('notification prefs patch')
const invite = await platform.createInvite()
return {
guildId,
generalId,
inviteCode: invite.shareCode || invite.code,
view
}
}
/**
* Host onboarding: register → create guild → first message (UI IPC path).
*/
async function runOnboardingHostScenario (platform, opts = {}) {
const name = opts.guildName || 'Onboarding Mesh Guild'
const username = opts.username || 'onboard_host'
await ipc(platform, {
type: 'register',
username,
displayName: opts.displayName || 'Onboard Host'
})
let view = await platform.view()
if (!view.onboarded) throw new Error('host not onboarded after register')
if (!view.needsGuild) throw new Error('host should need guild after register')
await ipc(platform, { type: 'create-guild', name })
view = await platform.view()
if (!view.guild) throw new Error('host guild missing')
if (view.needsGuild) throw new Error('host still needsGuild after create')
if (view.mode !== 'guild') throw new Error('host not in guild mode')
const marker = opts.marker || `onboard-mesh-${Date.now()}`
await ipc(platform, {
type: 'send-message',
content: `onboarding pre-mesh ${marker}`
})
view = await platform.view()
if (!view.messages?.some((m) => String(m.content).includes(marker))) {
throw new Error('host pre-mesh message missing')
}
const invite = await platform.createInvite()
return {
guildId: view.guild.id,
channelId: view.activeChannelId,
marker,
inviteCode: invite.shareCode || invite.code,
view
}
}
/**
* Guest onboarding: register → join-invite (mirrors UI join modal after needsGuild).
*/
async function runOnboardingGuestJoinScenario (platform, inviteCode, opts = {}) {
await ipc(platform, {
type: 'register',
username: opts.username || 'onboard_guest',
displayName: opts.displayName || 'Onboard Guest'
})
let view = await platform.view()
if (!view.needsGuild) throw new Error('guest should need guild before join')
if (view.guild) throw new Error('guest should not have guild before join')
await ipc(platform, { type: 'join-invite', code: inviteCode })
view = await platform.view()
if (view.needsGuild) throw new Error('guest still needsGuild after join')
if (!view.guild) throw new Error('guest guild missing after join')
if (view.mode !== 'guild') throw new Error('guest not in guild mode')
if (!view.channels?.length) throw new Error('guest missing channels after join')
return { view }
}
/**
* Onboarding host with signed audit CSV export for badge/dashboard smokes (v0.8.83).
*/
async function runOnboardingHostAuditCsvScenario (platform, opts = {}) {
const base = await runOnboardingHostScenario(platform, opts)
const guildId = base.guildId
await platform._audit('automation.onboarding', { guildId, detail: 'ui ipc csv' })
await ipc(platform, {
type: 'export-audit-log',
format: 'csv',
filter: 'automation',
limit: 50,
gossip: false,
sign: true,
recordMesh: false
})
await ipc(platform, { type: 'sync-audit-export-csv', filter: 'automation', limit: 50 })
const view = await platform.view()
if (!view.auditExportCsvMeta?.signature) {
throw new Error('host auditExportCsvMeta missing after IPC export')
}
if (view.auditExportCsvMeta.signatureValid !== true) {
throw new Error('host audit csv signature invalid')
}
const dash = view.automationHealthDashboard || view.automationScheduleDashboard
if (!dash?.auditCsvMesh?.hasSignature) {
throw new Error('health dashboard missing auditCsvMesh')
}
return { ...base, auditExportCsvMeta: view.auditExportCsvMeta, dashboard: dash }
}
/**
* Audit modal filter survives two UI refresh cycles (simulates close/reopen) (v0.8.85).
*/
async function runAuditModalFilterDoubleRefreshScenario (platform, opts = {}) {
const base = await runOnboardingHostScenario(platform, opts)
await ipc(platform, {
type: 'set-audit-log-export-filter',
filter: AUDIT_FILTER.AUTOMATION
})
await ipc(platform, { type: 'refresh' })
let view = await platform.view()
if (view.auditLogExportFilter !== AUDIT_FILTER.AUTOMATION) {
throw new Error('filter lost after first refresh')
}
await ipc(platform, { type: 'refresh' })
view = await platform.view()
if (view.auditLogExportFilter !== AUDIT_FILTER.AUTOMATION) {
throw new Error('filter lost after second refresh')
}
if (view.auditExportScheduleFilter !== AUDIT_FILTER.AUTOMATION) {
throw new Error('schedule filter missing after double refresh')
}
return { ...base, filter: AUDIT_FILTER.AUTOMATION }
}
module.exports = {
runFullUiFlowScenario,
runOnboardingHostScenario,
runOnboardingGuestJoinScenario,
runOnboardingHostAuditCsvScenario,
runAuditModalFilterDoubleRefreshScenario,
ipc
}