import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { createOnlyOfficeBridge } from '../../src/onlyoffice/bridge';
|
|
describe('filler bridge', () => {
|
afterEach(() => vi.unstubAllGlobals());
|
|
it('binds init and executes inspect command', async () => {
|
vi.stubGlobal('Api', { GetDocument: () => ({ GetAllContentControls: () => [] }) });
|
const asc: any = { scope: {}, plugin: { info: { options: { ticket: 'x' } }, callCommand: vi.fn((command, _a, _b, callback) => callback(command())), executeCommand: vi.fn(), executeMethod: vi.fn(), onThemeChangedBase: vi.fn() } };
|
const bridge = createOnlyOfficeBridge(asc);
|
const ready = vi.fn();
|
bridge.bindLifecycle({ onControlChanged: vi.fn(), onControlFocused: vi.fn(), onReady: ready, onThemeChanged: vi.fn() });
|
asc.plugin.init();
|
expect(ready).toHaveBeenCalled();
|
await expect(bridge.inspectFields()).resolves.toEqual([]);
|
});
|
|
it('reports an invalid editor command result when the SDK callback has no value', async () => {
|
const asc: any = {
|
scope: {},
|
plugin: {
|
callCommand: vi.fn((_command, _a, _b, callback) => callback(undefined)),
|
executeCommand: vi.fn(),
|
executeMethod: vi.fn(),
|
onThemeChangedBase: vi.fn(),
|
},
|
};
|
|
await expect(createOnlyOfficeBridge(asc).inspectFields()).rejects.toThrow('编辑器命令返回无效结果');
|
expect(asc.scope.elnRecordFillInspect).toBeUndefined();
|
});
|
|
it('does not serialize document commands outside record fill-only mode', async () => {
|
vi.stubGlobal('Api', { GetDocument: () => ({ GetAllContentControls: () => [] }) });
|
const callbacks: Array<() => void> = [];
|
const asc: any = {
|
scope: {},
|
plugin: {
|
callCommand: vi.fn((command, _a, _b, callback) => callbacks.push(() => callback(command()))),
|
executeCommand: vi.fn(),
|
executeMethod: vi.fn(),
|
onThemeChangedBase: vi.fn(),
|
},
|
};
|
const bridge = createOnlyOfficeBridge(asc);
|
|
const first = bridge.inspectFields();
|
const second = bridge.inspectFields();
|
expect(asc.plugin.callCommand).toHaveBeenCalledTimes(2);
|
callbacks.forEach((callback) => callback());
|
|
await expect(Promise.all([first, second])).resolves.toEqual([[], []]);
|
});
|
|
it('temporarily lifts record fill restrictions while running controlled document commands', async () => {
|
vi.stubGlobal('Api', { GetDocument: () => ({ GetAllContentControls: () => [] }) });
|
let restriction = 'none';
|
const transitions: string[] = [];
|
const asc: any = {
|
scope: {},
|
plugin: {
|
callCommand: vi.fn((command, _a, _b, callback) => {
|
expect(restriction).toBe('none');
|
callback(command());
|
}),
|
executeCommand: vi.fn(),
|
executeMethod: vi.fn((name, args, callback) => {
|
if (name === 'SetEditingRestrictions') {
|
restriction = args[0];
|
transitions.push(restriction);
|
}
|
callback(undefined);
|
}),
|
onThemeChangedBase: vi.fn(),
|
},
|
};
|
const bridge = createOnlyOfficeBridge(asc);
|
|
await bridge.setFillOnlyMode(true);
|
await expect(bridge.inspectFields()).resolves.toEqual([]);
|
|
expect(transitions).toEqual(['forms', 'none', 'forms']);
|
});
|
|
it('falls back to read-only when restoring fill-only restrictions fails', async () => {
|
vi.stubGlobal('Api', { GetDocument: () => ({ GetAllContentControls: () => [] }) });
|
let restriction = 'none';
|
let formsCalls = 0;
|
const asc: any = {
|
scope: {},
|
plugin: {
|
callCommand: vi.fn((command, _a, _b, callback) => callback(command())),
|
executeCommand: vi.fn(),
|
executeMethod: vi.fn((name, args, callback) => {
|
if (name === 'SetEditingRestrictions') {
|
if (args[0] === 'forms' && ++formsCalls === 2) throw new Error('restriction restore failed');
|
restriction = args[0];
|
}
|
callback(undefined);
|
}),
|
onThemeChangedBase: vi.fn(),
|
},
|
};
|
const bridge = createOnlyOfficeBridge(asc);
|
|
await bridge.setFillOnlyMode(true);
|
await expect(bridge.inspectFields()).rejects.toThrow('restriction restore failed');
|
|
expect(restriction).toBe('readOnly');
|
});
|
|
it('passes a repeat row identity through command scope and cleans it after callback', async () => {
|
const asc: any = {
|
scope: {},
|
plugin: {
|
callCommand: vi.fn((_command, _a, _b, callback) => {
|
expect(asc.scope.elnRecordFillRepeatRow).toEqual({ groupCode: 'results', rowId: 'row-1' });
|
callback(JSON.stringify({ ok: true, tags: ['eln.repeat.results.row-1.value'] }));
|
}),
|
executeCommand: vi.fn(),
|
executeMethod: vi.fn(),
|
onThemeChangedBase: vi.fn(),
|
},
|
};
|
|
await expect(createOnlyOfficeBridge(asc).appendRepeatRow('results', 'row-1')).resolves.toEqual([
|
'eln.repeat.results.row-1.value',
|
]);
|
expect(asc.scope.elnRecordFillRepeatRow).toBeUndefined();
|
|
await expect(createOnlyOfficeBridge(asc).removeRepeatRow('results', 'row-1')).resolves.toEqual([
|
'eln.repeat.results.row-1.value',
|
]);
|
expect(asc.scope.elnRecordFillRepeatRow).toBeUndefined();
|
});
|
|
it('binds and detaches controlled-document change and focus events', () => {
|
const handlers = new Map<string, (value: unknown) => void>();
|
const attachEditorEvent = vi.fn((name, handler) => handlers.set(name, handler));
|
const asc: any = {
|
scope: {},
|
plugin: {
|
attachEditorEvent,
|
callCommand: vi.fn(),
|
detachEditorEvent: vi.fn((name) => handlers.delete(name)),
|
executeCommand: vi.fn(),
|
executeMethod: vi.fn(),
|
onThemeChangedBase: vi.fn(),
|
},
|
};
|
const onControlChanged = vi.fn();
|
const onControlFocused = vi.fn();
|
const cleanup = createOnlyOfficeBridge(asc).bindLifecycle({
|
onControlChanged,
|
onControlFocused,
|
onReady: vi.fn(),
|
onThemeChanged: vi.fn(),
|
});
|
|
expect(attachEditorEvent).not.toHaveBeenCalled();
|
asc.plugin.init();
|
handlers.get('onChangeContentControl')?.({ InternalId: '1_2', Tag: 'eln.field.result' });
|
handlers.get('onFocusContentControl')?.({ InternalId: '1_2', Tag: 'eln.field.result' });
|
expect(onControlChanged).toHaveBeenCalledWith({ InternalId: '1_2', Tag: 'eln.field.result' });
|
expect(onControlFocused).toHaveBeenCalledWith({ InternalId: '1_2', Tag: 'eln.field.result' });
|
|
cleanup();
|
expect(asc.plugin.detachEditorEvent.mock.calls).toEqual([
|
['onChangeContentControl'],
|
['onFocusContentControl'],
|
]);
|
});
|
|
it('selects a controlled field by its internal id and rejects an editor failure', async () => {
|
const executeMethod = vi.fn((_name, _args, callback) => callback(false));
|
const asc: any = {
|
scope: {},
|
plugin: {
|
callCommand: vi.fn(), executeCommand: vi.fn(), executeMethod, onThemeChangedBase: vi.fn(),
|
},
|
};
|
const bridge = createOnlyOfficeBridge(asc);
|
|
await expect(bridge.selectControl('')).rejects.toThrow('字段控件缺少内部 ID');
|
await expect(bridge.selectControl('1_2')).rejects.toThrow('编辑器未能定位字段');
|
expect(executeMethod).toHaveBeenCalledWith('SelectContentControl', ['1_2'], expect.any(Function));
|
});
|
});
|