mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
fix(macos): drop stale focus refreshes in the FDA nudge
refreshFullDiskAccessStatus() applied whichever getStatus() round-trip resolved last. Rapid blur/focus puts several in flight, so an earlier pre-grant 'unknown' landing after a newer 'granted' un-hid the card and also wrote 'unknown' into the module-level session cache, re-nagging a user who already has Full Disk Access for the rest of the session. The adjacent FullDiskAccessSetupPrompt already guards this with a refresh sequence; mirror it here. Also unmount React roots in afterEach: clearing document.body left them mounted, leaking each test's window focus listener into later tests.
This commit is contained in:
@@ -17,6 +17,10 @@ import {
|
||||
shouldShowFullDiskAccessNudge
|
||||
} from './FullDiskAccessNudge'
|
||||
|
||||
// Why: without this React skips its "update not wrapped in act" warnings, so a
|
||||
// state update escaping act() in these async probe paths would pass unnoticed.
|
||||
globalThis.IS_REACT_ACT_ENVIRONMENT = true
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
@@ -56,10 +60,13 @@ function fdaStatus(status: DeveloperPermissionStatus): DeveloperPermissionState[
|
||||
return [{ id: 'full-disk-access', status }]
|
||||
}
|
||||
|
||||
const mountedRoots: Root[] = []
|
||||
|
||||
async function renderNudge(): Promise<{ container: HTMLDivElement; root: Root }> {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
const root = createRoot(container)
|
||||
mountedRoots.push(root)
|
||||
await act(async () => {
|
||||
root.render(
|
||||
React.createElement(TooltipProvider, null, React.createElement(FullDiskAccessNudge))
|
||||
@@ -73,7 +80,15 @@ async function renderNudge(): Promise<{ container: HTMLDivElement; root: Root }>
|
||||
return { container, root }
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
// Why: clearing document.body leaves the roots mounted, so their window focus
|
||||
// listener survives and re-probes during later tests. Unmount them instead.
|
||||
const roots = mountedRoots.splice(0)
|
||||
await act(async () => {
|
||||
for (const root of roots) {
|
||||
root.unmount()
|
||||
}
|
||||
})
|
||||
vi.restoreAllMocks()
|
||||
document.body.innerHTML = ''
|
||||
resetFullDiskAccessProbeForTests()
|
||||
@@ -268,6 +283,56 @@ describe('FullDiskAccessNudge', () => {
|
||||
expect(container.textContent).toBe('')
|
||||
})
|
||||
|
||||
it('ignores a slow focus refresh that resolves after a newer one saw the grant', async () => {
|
||||
setUserAgent(MAC_UA)
|
||||
const pendingResolvers: ((states: DeveloperPermissionState[]) => void)[] = []
|
||||
let deferGetStatus = false
|
||||
installDeveloperPermissionsApi({
|
||||
getStatus: () =>
|
||||
deferGetStatus
|
||||
? new Promise<DeveloperPermissionState[]>((resolve) => {
|
||||
pendingResolvers.push(resolve)
|
||||
})
|
||||
: Promise.resolve(fdaStatus('unknown')),
|
||||
request: async () => ({
|
||||
id: 'full-disk-access',
|
||||
status: 'unknown',
|
||||
openedSystemSettings: true
|
||||
})
|
||||
})
|
||||
const { container } = await renderNudge()
|
||||
const openButton = Array.from(container.querySelectorAll('button')).find((button) =>
|
||||
button.textContent?.includes('Open System Settings')
|
||||
)
|
||||
await act(async () => {
|
||||
openButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
// Rapid blur/focus while the first round-trip is still in flight.
|
||||
deferGetStatus = true
|
||||
await act(async () => {
|
||||
window.dispatchEvent(new Event('focus'))
|
||||
window.dispatchEvent(new Event('focus'))
|
||||
})
|
||||
expect(pendingResolvers.length).toBe(2)
|
||||
// The newer refresh observes the grant and hides the card.
|
||||
await act(async () => {
|
||||
pendingResolvers[1]?.(fdaStatus('granted'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
expect(container.textContent).toBe('')
|
||||
// The older, slower refresh still reports the pre-grant status; it must not win.
|
||||
await act(async () => {
|
||||
pendingResolvers[0]?.(fdaStatus('unknown'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
expect(container.textContent).toBe('')
|
||||
// The stale result must not poison the session cache a later mount reads either.
|
||||
const remount = await renderNudge()
|
||||
expect(remount.container.textContent).toBe('')
|
||||
})
|
||||
|
||||
it('stops watching for the grant once the card is dismissed', async () => {
|
||||
setUserAgent(MAC_UA)
|
||||
const { getStatus } = installDeveloperPermissionsApi({
|
||||
|
||||
@@ -27,6 +27,7 @@ const DISMISS_KEY = 'orca.fullDiskAccessNudgeDismissed.v1'
|
||||
let cachedFullDiskAccessStatus: DeveloperPermissionStatus | undefined
|
||||
let fullDiskAccessProbed = false
|
||||
let fullDiskAccessProbe: Promise<DeveloperPermissionStatus | undefined> | null = null
|
||||
let fullDiskAccessRefreshSequence = 0
|
||||
|
||||
function getFullDiskAccessStatus(
|
||||
states: readonly DeveloperPermissionState[]
|
||||
@@ -62,9 +63,16 @@ function probeFullDiskAccessStatusOnce(): Promise<DeveloperPermissionStatus | un
|
||||
// Why: a fresh, cache-bypassing probe used only on CTA return-focus so the card
|
||||
// can hide the moment a Full Disk Access grant takes effect for this process.
|
||||
function refreshFullDiskAccessStatus(): Promise<DeveloperPermissionStatus | undefined> {
|
||||
// Why: rapid focus/blur puts several getStatus() round-trips in flight, and an
|
||||
// earlier pre-grant result resolving last would un-hide the card and poison the
|
||||
// session cache — same refresh-sequence guard as FullDiskAccessSetupPrompt.
|
||||
const refreshId = ++fullDiskAccessRefreshSequence
|
||||
return window.api.developerPermissions
|
||||
.getStatus()
|
||||
.then((states) => {
|
||||
if (refreshId !== fullDiskAccessRefreshSequence) {
|
||||
return cachedFullDiskAccessStatus
|
||||
}
|
||||
const next = getFullDiskAccessStatus(states)
|
||||
rememberFullDiskAccessStatus(next)
|
||||
return next
|
||||
|
||||
Reference in New Issue
Block a user