fix(properties): harden recovery and window chrome

This commit is contained in:
NimBold
2026-08-05 21:41:59 +03:30
parent c1202229ac
commit b36f55e5b5
16 changed files with 527 additions and 132 deletions
+54 -10
View File
@@ -14,15 +14,17 @@ import {
applySecretPatch,
attachAsyncPropertiesListener,
beginExclusivePropertiesAction,
classifyPropertiesActionRequest,
createFrameCoalescer,
enqueuePropertiesAction,
formatPropertiesQueuePlacement,
getPropertiesLifecycleAction,
isExpectedPropertiesDiagnosticUnavailable,
propertiesDiagnosticPhase,
propertiesActionRequestKey,
propertiesDiagnosticRequestState,
propertiesLifecycleReachedPostcondition,
propertiesTorrentPeerLimit,
resetPropertiesActionState,
sanitizePropertiesSnapshot,
shouldAcceptPropertiesActionRequest,
} from './propertiesBridge';
@@ -65,6 +67,7 @@ describe('Properties window bridge', () => {
listRowDensity: 'compact',
locale: 'fa',
});
expect(snapshot.windowChrome).toEqual({ controlStyle: 'macos', side: 'left' });
});
it('adds resolver error metadata without exposing the queue-internal mode', () => {
@@ -202,6 +205,27 @@ describe('Properties window bridge', () => {
expect(snapshot.queueId).toBe('internal-queue-id');
});
it('preserves resolved Properties window chrome in the sanitized snapshot', () => {
const snapshot = sanitizePropertiesSnapshot({
id: 'chrome-1',
fileName: 'example.bin',
url: 'https://example.test/file',
status: 'paused',
category: 'Other',
dateAdded: '',
} as DownloadItem, {
theme: 'dark',
fontFamily: 'system',
appFontSize: 'standard',
listRowDensity: 'standard',
locale: 'en',
}, undefined, {
windowChrome: { controlStyle: 'windows', side: 'right' },
});
expect(snapshot.windowChrome).toEqual({ controlStyle: 'windows', side: 'right' });
});
it('keeps diagnostic refreshes quiet when cached data exists', () => {
expect(propertiesDiagnosticPhase(false, 'request-start')).toBe('initial');
expect(propertiesDiagnosticPhase(false, 'request-start', true)).toBe('refreshing');
@@ -264,15 +288,6 @@ describe('Properties window bridge', () => {
expect(getPropertiesLifecycleAction('completed')).toBeNull();
});
it('clears a lost lifecycle action from an authoritative postcondition', () => {
expect(propertiesLifecycleReachedPostcondition('resume', 'downloading')).toBe(true);
expect(propertiesLifecycleReachedPostcondition('resume', 'seeding')).toBe(true);
expect(propertiesLifecycleReachedPostcondition('resume', 'paused')).toBe(false);
expect(propertiesLifecycleReachedPostcondition('pause', 'paused')).toBe(true);
expect(propertiesLifecycleReachedPostcondition('pause', 'completed')).toBe(true);
expect(propertiesLifecycleReachedPostcondition('pause', 'downloading')).toBe(false);
});
it('keeps Torrent peer-cap telemetry distinct from generic connections', () => {
expect(propertiesTorrentPeerLimit(undefined)).toBe(55);
expect(propertiesTorrentPeerLimit(120)).toBe(120);
@@ -337,6 +352,35 @@ describe('Properties window bridge', () => {
})).toBe(false);
});
it('replays completed duplicate requests after a lost result and deduplicates retries', () => {
const registration = {
downloadId: 'download-1',
sessionId: 'session-1',
latestRequestId: 4,
};
const request = { downloadId: 'download-1', sessionId: 'session-1', requestId: 4 };
expect(classifyPropertiesActionRequest(registration, { ...request, requestId: 5 }, false, false)).toBe('accept');
expect(classifyPropertiesActionRequest(registration, request, true, false)).toBe('replay');
expect(classifyPropertiesActionRequest(registration, request, false, true)).toBe('pending');
expect(classifyPropertiesActionRequest(registration, { ...request, requestId: 3 }, false, false)).toBe('ignore');
expect(classifyPropertiesActionRequest(registration, { ...request, sessionId: 'session-old' }, false, false)).toBe('ignore');
const base = { windowLabel: 'properties-1', sessionId: 'session-1', requestId: 4 };
expect(propertiesActionRequestKey(base)).not.toBe(propertiesActionRequestKey({ ...base, requestId: 5 }));
expect(propertiesActionRequestKey(base)).not.toBe(propertiesActionRequestKey({ ...base, sessionId: 'session-2' }));
expect(propertiesActionRequestKey(base)).not.toBe(propertiesActionRequestKey({ ...base, windowLabel: 'properties-2' }));
});
it('resets pending action state while advancing the bridge-generation request cursor', () => {
expect(resetPropertiesActionState(9)).toEqual({
requestId: 10,
pendingAction: null,
request: null,
});
expect(resetPropertiesActionState(Number.MAX_SAFE_INTEGER).requestId).toBe(1);
});
it('serializes actions per window and continues after an earlier action fails', async () => {
const chains = new Map<string, Promise<void>>();
const events: string[] = [];