- chat: left-aligned discord-style messages with grouped senders, hover actions, an auto-growing composer and file cards - send: render messages and files locally first and replace the placeholder when the worker publishes the real item; failures keep the text with a retry action - navigation: space rail plus a flatter channel list with the space name header and collapsible groups - members: right pane tabs (files / members) grouped by online, offline and pending users, with a prominent invite action and a top bar shortcut - notifications: toast host for invite results; invitees get a toast with an accept action, and the invitation payload now carries the space name and inviter inside the encrypted grant - users over devices: drop device names, the device list and device rename/release; senders show the account (guests show as guest) and the share panel only keeps links - follow contract 7.0.0 (max_members, members_used, members_full, /join) - docs: ui-design and frontend updated for the new layout and concepts
30 lines
1.9 KiB
TypeScript
30 lines
1.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { isOfflineFailure } from './resourceMetadata';
|
|
import { toPublishedFile } from './fileCatalog';
|
|
import { exportResource } from './accountVault';
|
|
import type { FileItem } from '../types';
|
|
import type { SavedResource } from '../database';
|
|
|
|
describe('resource and rendering boundaries', () => {
|
|
it('does not treat authorization, tampering or contract errors as offline cache hits', () => {
|
|
expect(isOfflineFailure(new Error('网络连接失败'))).toBe(true);
|
|
expect(isOfflineFailure(Object.assign(new Error('forbidden'), { status: 403 }))).toBe(false);
|
|
expect(isOfflineFailure(new Error('api_contract_mismatch'))).toBe(false);
|
|
expect(isOfflineFailure(new Error('bad_meta'))).toBe(false);
|
|
});
|
|
it('publishes only resource metadata, excluding UI state and download counters', () => {
|
|
const item: FileItem = { id: 'a', threadId: 'channel', kind: 'file', name: 'secret.txt', mime: 'text/plain', size: 10, ts: 1,
|
|
gated: true, remaining: 5, preview: 'text', state: { kind: 'receiving', progress: 0.2 }, unseen: true, versions: [] };
|
|
const wire = toPublishedFile(item);
|
|
expect(wire.name).toBe('secret.txt');
|
|
for (const field of ['state', 'preview', 'remaining', 'versions', 'unseen']) expect(wire).not.toHaveProperty(field);
|
|
});
|
|
it('never synchronizes draft content or server structure snapshots inside the key vault', () => {
|
|
const resource: SavedResource = { id: 'a', kind: 'share', mode: 'enc', role: 'owner', link: 'key', createdAt: 1, expiresAt: 2,
|
|
endpointSecret: new Uint8Array(32), maxMembers: 0, draftItems: [], manifestEntries: [],
|
|
workspaceMetadata: { structure: { revision: 1, channels: [], groups: [], folders: [], locations: [] }, people: [] } };
|
|
const exported = exportResource(resource);
|
|
for (const field of ['draftItems', 'manifestEntries', 'workspaceMetadata', 'endpointSecret']) expect(exported).not.toHaveProperty(field);
|
|
});
|
|
});
|