diff --git a/libs/tiptap-editor/src/__tests__/runnable-info.test.ts b/libs/tiptap-editor/src/__tests__/runnable-info.test.ts new file mode 100644 index 0000000..b49f704 --- /dev/null +++ b/libs/tiptap-editor/src/__tests__/runnable-info.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; +import { buildRunnableInfo } from '../slash-command'; + +/** + * buildRunnableInfo 测试:把弹框收集的配置转成 fence info string。 + * + * dirty=false(全默认) → 'python runnable'(省略 JSON) + * dirty=true → 'python runnable {"timeout_secs":N,"memory_mb":M,"allow_network":B}' + * JSON 字段顺序固定:timeout → memory → network + */ + +describe('buildRunnableInfo', () => { + const base = { + lang: 'python', + timeoutSecs: 5, + memoryMb: 256, + allowNetwork: false, + } as const; + + describe('dirty=false(全默认,省略 JSON)', () => { + it('python → "python runnable"', () => { + expect(buildRunnableInfo({ ...base, dirty: false })).toBe('python runnable'); + }); + it('node → "node runnable"', () => { + expect(buildRunnableInfo({ ...base, lang: 'node', dirty: false })).toBe('node runnable'); + }); + }); + + describe('dirty=true(写 JSON)', () => { + it('改了 timeout,JSON 含全部 3 项', () => { + expect(buildRunnableInfo({ ...base, timeoutSecs: 10, dirty: true })).toBe( + 'python runnable {"timeout_secs":10,"memory_mb":256,"allow_network":false}', + ); + }); + it('node + 改了 allow_network', () => { + expect(buildRunnableInfo({ ...base, lang: 'node', allowNetwork: true, dirty: true })).toBe( + 'node runnable {"timeout_secs":5,"memory_mb":256,"allow_network":true}', + ); + }); + it('JSON 字段顺序固定(timeout→memory→network)', () => { + const out = buildRunnableInfo({ ...base, dirty: true }); + const jsonStart = out.indexOf('{'); + const jsonEnd = out.lastIndexOf('}') + 1; + const keys = Object.keys(JSON.parse(out.slice(jsonStart, jsonEnd))); + expect(keys).toEqual(['timeout_secs', 'memory_mb', 'allow_network']); + }); + }); +}); diff --git a/libs/tiptap-editor/src/slash-command.ts b/libs/tiptap-editor/src/slash-command.ts index 0afee58..8e82018 100644 --- a/libs/tiptap-editor/src/slash-command.ts +++ b/libs/tiptap-editor/src/slash-command.ts @@ -363,3 +363,34 @@ function createPopup(props: SuggestionProps): SlashPopup { }, }; } + +/** buildRunnableInfo 的输入配置。 */ +export interface RunnableInfoOpts { + /** 语言名(python / node)。 */ + lang: string; + /** 超时秒数。 */ + timeoutSecs: number; + /** 内存上限(MB)。 */ + memoryMb: number; + /** 是否允许网络。 */ + allowNetwork: boolean; + /** 作者是否改动过任一 overrides 字段;false 则省略 JSON。 */ + dirty: boolean; +} + +/** + * 把弹框收集的配置转成 markdown fence 的 info string。 + * + * - dirty=false → `${lang} runnable`(省略 JSON,最小形态) + * - dirty=true → `${lang} runnable {"timeout_secs":N,"memory_mb":M,"allow_network":B}` + * + * JSON 字段顺序固定(timeout → memory → network),由显式构造保证(不依赖对象插入顺序)。 + * 到达此函数时值必然合法(弹框「插入」按钮在非法值时 disabled)。 + */ +export function buildRunnableInfo(opts: RunnableInfoOpts): string { + const prefix = `${opts.lang} runnable`; + if (!opts.dirty) return prefix; + // 显式拼字符串,保证字段顺序固定(timeout → memory → network),不依赖对象键序。 + const json = `{"timeout_secs":${opts.timeoutSecs},"memory_mb":${opts.memoryMb},"allow_network":${opts.allowNetwork}}`; + return `${prefix} ${json}`; +}