mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 04:13:17 +08:00
feat(settings): move IM bot to top-level menu
Remove the legacy Plugins-tab registrations, keep the channel-logo navigation, and harden upgrade/package verification.
This commit is contained in:
parent
892e18db77
commit
1ce8126edb
30 changed files with 815 additions and 842 deletions
|
|
@ -5,15 +5,11 @@ import test from 'node:test';
|
|||
const CLIENT_URL = new URL('../../../plugin-src/client/channels/dingtalk/index.js', import.meta.url);
|
||||
const STYLES_URL = new URL('../../../plugin-src/client/channels/dingtalk/styles.js', import.meta.url);
|
||||
|
||||
test('standalone client exports a reusable settings component and registration', async () => {
|
||||
test('channel client exports a reusable settings component without a standalone settings registration', async () => {
|
||||
const source = await readFile(CLIENT_URL, 'utf8');
|
||||
assert.match(source, /export const name = 'dingtalk-settings'/);
|
||||
assert.match(source, /export const inject = \['slots', 'connection'\]/);
|
||||
assert.match(source, /export function DingtalkSettingsTab\(\{ rpcCall \}\)/);
|
||||
assert.match(source, /export function apply\(ctx\)/);
|
||||
assert.match(source, /ctx\.connection\.rpc\.call\(DINGTALK_RPC_CHANNEL/);
|
||||
assert.match(source, /id: 'dingtalk'/);
|
||||
assert.match(source, /label: '钉钉'/);
|
||||
assert.doesNotMatch(source, /export function apply\(ctx\)/);
|
||||
assert.doesNotMatch(source, /settings\.plugins\.tab/);
|
||||
});
|
||||
|
||||
test('QR guidance describes the complete official DingTalk authorization flow', async () => {
|
||||
|
|
|
|||
|
|
@ -993,7 +993,7 @@ test('bridge tells users to grant im:message:readonly when Feishu rejects image
|
|||
assert.match(sent[0], /im:message:readonly/);
|
||||
assert.match(sent[0], /\/repair/);
|
||||
assert.match(sent[0], /发布新版本/);
|
||||
assert.match(sent[0], /插件页面/);
|
||||
assert.match(sent[0], /「IM机器人」设置页/);
|
||||
assert.match(sent[0], /补全权限/);
|
||||
assert.doesNotMatch(sent[0], /99991672|HTTP 400|secret-shaped|private\/path/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ test('Feishu image loading maps the missing message scope to an actionable error
|
|||
assert.equal(error.code, 'feishu-image-permission-required');
|
||||
assert.match(error.userMessage, /im:message:readonly/);
|
||||
assert.match(error.userMessage, /\/repair/);
|
||||
assert.match(error.userMessage, /插件页面/);
|
||||
assert.match(error.userMessage, /「IM机器人」设置页/);
|
||||
assert.match(error.userMessage, /补全权限/);
|
||||
assert.match(error.userMessage, /发布新版本/);
|
||||
assert.equal(error.cause, providerError);
|
||||
|
|
|
|||
|
|
@ -791,7 +791,7 @@ test('connection test uses the selected bot runtime and shared message copy', as
|
|||
await fx.controller.initialize();
|
||||
assert.deepEqual(await fx.controller.sendConnectionTest(healthy.id), { sent: true });
|
||||
assert.deepEqual(fx.runtimes.get(healthy.id)[0].sentTests, [
|
||||
'✅ DeepSeek Harness 连接测试成功\n这条消息由插件页面中的“机器人 healthy(cli_heal••••7890)”机器人卡片发出。',
|
||||
'✅ DeepSeek Harness 连接测试成功\n这条消息由「IM机器人」设置页中的“机器人 healthy(cli_heal••••7890)”机器人卡片发出。',
|
||||
]);
|
||||
await fx.controller.close();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1378,7 +1378,7 @@ test('WhatsApp controller delegates connection test copy to the current runtime'
|
|||
await controller.initialize();
|
||||
assert.deepEqual(await controller.sendConnectionTest(config.botId), { sent: true });
|
||||
assert.deepEqual(sent, [
|
||||
'✅ DeepSeek Harness 连接测试成功\n这条消息由插件页面中的“Harness WhatsApp(1650••••0123)”机器人卡片发出。',
|
||||
'✅ DeepSeek Harness 连接测试成功\n这条消息由「IM机器人」设置页中的“Harness WhatsApp(1650••••0123)”机器人卡片发出。',
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -664,7 +664,21 @@ test('every shipped Chinese client string has an English projection', async () =
|
|||
}
|
||||
});
|
||||
|
||||
test('client registers a live bilingual locale seat and directory picker for the IM settings tab', async () => {
|
||||
test('client source contains no legacy Plugins-tab settings registrations', async () => {
|
||||
const paths = (await readdir(CLIENT_SOURCE_DIRECTORY_URL, { recursive: true }))
|
||||
.filter((path) => path.endsWith('.js'));
|
||||
const sources = await Promise.all(paths.map(async (path) => ({
|
||||
path,
|
||||
source: await readFile(new URL(path, CLIENT_SOURCE_DIRECTORY_URL), 'utf8'),
|
||||
})));
|
||||
const legacy = sources
|
||||
.filter(({ source }) => source.includes('settings.plugins.tab'))
|
||||
.map(({ path }) => path);
|
||||
|
||||
assert.deepEqual(legacy, []);
|
||||
});
|
||||
|
||||
test('client registers one top-level bilingual IM settings section with a directory picker', async () => {
|
||||
const effects = [];
|
||||
const registrations = [];
|
||||
const dictionaries = [];
|
||||
|
|
@ -697,7 +711,7 @@ test('client registers a live bilingual locale seat and directory picker for the
|
|||
},
|
||||
slots: {
|
||||
inject(name, install) {
|
||||
assert.equal(name, 'settings.plugins.tab');
|
||||
assert.equal(name, 'settings.section');
|
||||
install();
|
||||
},
|
||||
register(options, component) {
|
||||
|
|
@ -717,8 +731,12 @@ test('client registers a live bilingual locale seat and directory picker for the
|
|||
assert.equal(dictionaries[0].namespace, IM_LOCALE_NAMESPACE);
|
||||
assert.deepEqual(Object.keys(dictionaries[0].value.en).sort(), Object.keys(dictionaries[0].value.zh).sort());
|
||||
assert.equal(registrations.length, 1);
|
||||
assert.equal(registrations[0].options.name, 'settings.section');
|
||||
assert.equal(registrations[0].options.id, 'xmanrui-dsh-im');
|
||||
assert.equal(registrations[0].options.order, 21);
|
||||
assert.equal(registrations[0].options.locale, IM_LOCALE_NAMESPACE);
|
||||
assert.equal(registrations[0].options.label(), 'IM bots');
|
||||
assert.equal(registrations[0].component, IMSettingsTab);
|
||||
|
||||
const injected = registrations[0].options.inject();
|
||||
const signal = new AbortController().signal;
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ const WEIXIN_FORBIDDEN = new Error(
|
|||
|
||||
test('creates a localhost recovery target for the known loopback transport 403', () => {
|
||||
const recovery = createLoopbackRecovery(WEIXIN_FORBIDDEN, {
|
||||
href: 'http://127.0.0.1:3080/settings?tab=plugins#im',
|
||||
href: 'http://127.0.0.1:3080/settings',
|
||||
});
|
||||
|
||||
assert.deepEqual(recovery, {
|
||||
url: 'http://localhost:3080/settings?tab=plugins#im',
|
||||
url: 'http://localhost:3080/settings',
|
||||
origin: 'http://localhost:3080',
|
||||
});
|
||||
assert.deepEqual(createLoopbackRecovery(WEIXIN_FORBIDDEN, {
|
||||
|
|
@ -103,7 +103,7 @@ test('RPC wrappers preserve successful results, unrelated errors, and missing op
|
|||
|
||||
test('recovery notice exposes one localized navigation action', () => {
|
||||
const recovery = {
|
||||
url: 'http://localhost:3080/settings?tab=plugins#im',
|
||||
url: 'http://localhost:3080/settings',
|
||||
origin: 'http://localhost:3080',
|
||||
};
|
||||
const element = LoopbackRecoveryNotice({ recovery, onNavigate() {} });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue