mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-08 04:01:21 +00:00
Client, mapping, and capability layer only — intentionally NOT wired into any
lifecycle/trigger path yet (no coordinator, no workers).
- types/api/drafts.d.ts: add DraftApi (server response) and DraftUpsertRequest
(POST body) DTOs alongside DraftProps.
- types/api/config.d.ts: add AllowSyncedDrafts to ClientConfig.
- client/rest/base.ts: add draft route helpers (drafts POST, user/team GET,
channel/thread DELETE).
- client/rest/drafts.ts (+test): ClientDrafts mixin — getDrafts, upsertDraft,
deleteDraft (channel vs thread by rootId), Connection-Id header on POST/DELETE.
Mixed into client/rest/index.ts (also drops a pre-existing duplicate
ClientScheduledPost entry in the .with() list).
- constants/preferences.ts: add ADVANCED_SYNC_DRAFTS ('sync_drafts').
- utils/draft/sync.ts (+test): pure bidirectional normalization. server->mobile
maps update_at->server_update_at (never local update_at), priority, props
passthrough, exact file_ids, files from metadata.files; mobile->server sends
only channel_id/root_id/message/type/props/file_ids/priority and returns null
for an empty message so it never POSTs. Fail-closed burn-on-read reconstruction
(durations come from server config, not props; missing/invalid -> no enabled
borConfig).
- queries/servers/drafts.ts: getIsDraftSyncEnabled / observeIsDraftSyncEnabled —
enabled when AllowSyncedDrafts==='true' AND sync_drafts pref not 'false'
(absent = enabled). No server-version gate.
Checks: npm run tsc clean; eslint --quiet clean; 69 focused Jest tests pass
across the new client/sync suites and the scheduled_post/post-handler/drafts
regression neighbors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import TestHelper from '@test/test_helper';
|
|
|
|
import type ClientBase from './base';
|
|
import type {ClientDraftsMix} from './drafts';
|
|
|
|
describe('ClientDrafts', () => {
|
|
let client: ClientDraftsMix & ClientBase;
|
|
const draft: DraftUpsertRequest = {
|
|
channel_id: 'channel_id',
|
|
root_id: '',
|
|
message: 'draft message',
|
|
type: '',
|
|
props: {},
|
|
file_ids: [],
|
|
};
|
|
|
|
beforeAll(() => {
|
|
client = TestHelper.createClient();
|
|
client.doFetch = jest.fn();
|
|
});
|
|
|
|
test('getDrafts', async () => {
|
|
const teamId = 'team_id';
|
|
const groupLabel = 'WebSocket Reconnect';
|
|
const expectedUrl = client.getUserTeamDraftsRoute('me', teamId);
|
|
const expectedOptions = {
|
|
method: 'get',
|
|
groupLabel,
|
|
};
|
|
|
|
await client.getDrafts(teamId, groupLabel);
|
|
|
|
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
|
});
|
|
|
|
test('upsertDraft', async () => {
|
|
await client.upsertDraft(draft, 'connection_id');
|
|
|
|
const expectedUrl = client.getDraftsRoute();
|
|
const expectedOptions = {
|
|
method: 'post',
|
|
body: draft,
|
|
headers: {'Connection-Id': 'connection_id'},
|
|
};
|
|
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
|
});
|
|
|
|
test('upsertDraft without connection ID', async () => {
|
|
await client.upsertDraft(draft);
|
|
|
|
const expectedUrl = client.getDraftsRoute();
|
|
const expectedOptions = {
|
|
method: 'post',
|
|
body: draft,
|
|
headers: {},
|
|
};
|
|
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
|
});
|
|
|
|
test('deleteDraft for a channel draft (empty rootId) hits the channel route', async () => {
|
|
const channelId = 'channel_id';
|
|
const connectionId = 'connection_id';
|
|
const expectedUrl = client.getUserChannelDraftsRoute('me', channelId);
|
|
const expectedOptions = {
|
|
method: 'delete',
|
|
headers: {'Connection-Id': connectionId},
|
|
};
|
|
|
|
await client.deleteDraft(channelId, '', connectionId);
|
|
|
|
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
|
});
|
|
|
|
test('deleteDraft for a thread draft (non-empty rootId) hits the thread route', async () => {
|
|
const channelId = 'channel_id';
|
|
const rootId = 'root_id';
|
|
const expectedUrl = client.getUserThreadDraftRoute('me', channelId, rootId);
|
|
const expectedOptions = {
|
|
method: 'delete',
|
|
headers: {},
|
|
};
|
|
|
|
await client.deleteDraft(channelId, rootId);
|
|
|
|
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
|
});
|
|
});
|