Add DingTalk to unified IM plugin

This commit is contained in:
xmanrui 2026-08-15 06:09:19 +08:00
parent c7b573a7aa
commit af7e6a2296
14 changed files with 1809 additions and 578 deletions

View file

@ -6,19 +6,22 @@ import { renderToStaticMarkup } from 'react-dom/server';
import { IMSettingsTab } from '../plugin-src/client/index.js';
test('IM settings renders two logo channel tabs without enable switches', () => {
test('IM settings renders three compact logo channel tabs without enable switches', () => {
const markup = renderToStaticMarkup(React.createElement(IMSettingsTab, {
feishuRpcCall: async () => ({ ok: true, value: {} }),
weixinRpcCall: async () => ({ ok: true, value: {} }),
dingtalkRpcCall: async () => ({ ok: true, value: {} }),
}));
assert.match(markup, /IM机器人/);
assert.match(markup, /通过扫码把机器人接入 DeepSeek Harness/);
assert.match(markup, />微信</);
assert.match(markup, />飞书</);
assert.match(markup, />钉钉</);
assert.match(markup, /dim-logoWeixin/);
assert.match(markup, /dim-logoFeishu/);
assert.equal((markup.match(/role="tab"/g) ?? []).length, 2);
assert.match(markup, /dim-logoDingtalk/);
assert.equal((markup.match(/role="tab"/g) ?? []).length, 3);
assert.equal((markup.match(/aria-selected="true"/g) ?? []).length, 1);
assert.doesNotMatch(markup, /role="switch"|type="checkbox"/);
assert.doesNotMatch(markup, /dim-chevron|扫码绑定<\/small>|扫码接入<\/small>/);

View file

@ -3,14 +3,19 @@ import test from 'node:test';
import { createImHostPlugin, inject, name } from '../plugin-src/host/index.mjs';
test('Host composes Feishu and Weixin inside one plugin context', async () => {
test('Host composes Feishu, Weixin, and DingTalk inside one plugin context', async () => {
const calls = [];
const plugin = createImHostPlugin({
applyFeishu: async (ctx, config) => calls.push(['feishu', ctx, config]),
applyWeixin: async (ctx, config) => calls.push(['weixin', ctx, config]),
applyDingtalk: async (ctx, config) => calls.push(['dingtalk', ctx, config]),
});
const ctx = { marker: 'shared-context' };
const config = { feishu: { domain: 'feishu' }, weixin: { timeout: 30 } };
const config = {
feishu: { domain: 'feishu' },
weixin: { timeout: 30 },
dingtalk: { replyTimeoutMs: 60_000 },
};
await plugin.apply(ctx, config);
@ -19,6 +24,7 @@ test('Host composes Feishu and Weixin inside one plugin context', async () => {
assert.deepEqual(calls, [
['feishu', ctx, config.feishu],
['weixin', ctx, config.weixin],
['dingtalk', ctx, config.dingtalk],
]);
});
@ -27,8 +33,21 @@ test('Host does not start Weixin when Feishu activation fails', async () => {
const plugin = createImHostPlugin({
applyFeishu: async () => { throw new Error('feishu unavailable'); },
applyWeixin: async () => { weixinStarted = true; },
applyDingtalk: async () => { throw new Error('DingTalk must not start'); },
});
await assert.rejects(() => plugin.apply({}, {}), /feishu unavailable/);
assert.equal(weixinStarted, false);
});
test('Host does not start DingTalk when Weixin activation fails', async () => {
let dingtalkStarted = false;
const plugin = createImHostPlugin({
applyFeishu: async () => {},
applyWeixin: async () => { throw new Error('weixin unavailable'); },
applyDingtalk: async () => { dingtalkStarted = true; },
});
await assert.rejects(() => plugin.apply({}, {}), /weixin unavailable/);
assert.equal(dingtalkStarted, false);
});