mattermost-mobile-upstream/app/actions/websocket/draft.test.ts
Guillermo Vayá 62c694adeb feat(drafts): Phase 5 — WebSocket + lifecycle triggers make sync live
Wires the DraftSyncManager's reconcile/WS entry points (previously defined
but never called in production) into the app's WebSocket and lifecycle flow.
Design choice: WS draft events are treated as CHANGE SIGNALS that trigger an
authoritative GET reconcile of the current team, not applied directly — this
reuses Phase 4's matrix/scope/absence/observation-fence, and the server's
silent empty-upsert delete (no event) forces GET-reconciliation regardless.
The current-team GET also returns the user's DM/GM drafts, so it converges
them too; other teams' channel drafts converge on team switch.

Triggers:
- draft_created / draft_deleted WS events -> enqueue -> reconcile current team
  (constants + app/actions/websocket/draft.ts handler + event dispatch cases).
- Reconnect/first-connect: requestReconcile in doReconnect after entry batches.
- Team switch: requestReconcile in handleTeamChange (before the tablet early
  return, so both paths fire).
- Foreground: WebsocketManager forwards app-active to DraftSyncManager.onForeground
  (covers a socket that stayed open, so no reconnect fired). No second AppState
  listener is added.
- Capability change: handleCapabilityChange after config (AllowSyncedDrafts) and
  after sync_drafts preference create/update/delete batches.

Also adds a central empty-team guard in requestReconcile (the GET is per-team).

Deferred (noted for a follow-up): Global Drafts stale-on-open reconcile (touches
a screen, needs on-device verification); direct-apply of buffered WS events (the
buffer is retained, reserved for that future optimization).

tsc + eslint clean; new/updated unit tests across manager, ws handler, event
dispatch, reconnect, team switch, config/preference capability. All affected
suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 16:50:19 +02:00

39 lines
1.3 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import DraftSyncManager from '@managers/draft_sync_manager';
import {handleDraftWebSocketEvent} from './draft';
jest.mock('@managers/draft_sync_manager', () => ({
__esModule: true,
default: {enqueueWebSocketEvent: jest.fn()},
}));
describe('handleDraftWebSocketEvent', () => {
const serverUrl = 'https://server.test';
const draftEvent = (event: string): WebSocketMessage => ({
event,
data: {draft: '{"channel_id":"c","root_id":"","message":"hi"}'},
broadcast: {} as WebsocketBroadcast,
seq: 1,
});
beforeEach(() => {
jest.clearAllMocks();
});
it('routes a draft_created event to DraftSyncManager.enqueueWebSocketEvent', () => {
const msg = draftEvent('draft_created');
handleDraftWebSocketEvent(serverUrl, msg);
expect(DraftSyncManager.enqueueWebSocketEvent).toHaveBeenCalledWith(serverUrl, msg);
});
it('routes a draft_deleted event to DraftSyncManager.enqueueWebSocketEvent', () => {
const msg = draftEvent('draft_deleted');
handleDraftWebSocketEvent(serverUrl, msg);
expect(DraftSyncManager.enqueueWebSocketEvent).toHaveBeenCalledWith(serverUrl, msg);
});
});