import { describe, expect, it, vi } from 'vitest';

import {
  disableTrackRevisionsCommand,
  insertTypedControlCommand,
  listContentControlsCommand,
  readControlTextCommand,
  readTrackRevisionsStateCommand,
  setFormulaResultLockCommand,
  updateControlSpecCommand,
  writeTextDefaultCommand,
} from '../../src/onlyoffice/commands';
import { installOnlyOfficeGlobals, makeApiFixture, makeInsertSpec, type FixtureApi, type InsertSpec, type TextDefaultSpec } from '../fixtures/onlyoffice';

interface CommandResult {
  controlType?: string;
  controls?: Array<{
    Alias: string;
    ControlConfig: Record<string, unknown>;
    ControlRole: string;
    ControlType: string;
    InternalId: string;
    Lock: string;
    Tag: string;
  }>;
  error?: string;
  ok: boolean;
}

function parseResult(value: string): CommandResult {
  return JSON.parse(value) as CommandResult;
}

function setFixtureInsertScope(Api: FixtureApi, overrides: Partial<InsertSpec> & Pick<InsertSpec, 'controlType'>): void {
  installOnlyOfficeGlobals(Api, {
    elnTemplateInsert: makeInsertSpec(overrides),
  });
}

function setTextDefaultScope(Api: FixtureApi, spec: TextDefaultSpec): void {
  installOnlyOfficeGlobals(Api, { elnTemplateTextDefault: spec });
}

describe('insertTypedControlCommand', () => {
  it('使用显示值、业务值和默认项创建下拉控件', () => {
    const fixture = makeApiFixture();
    const options = [
      { display: '合格', value: 'pass' },
      { display: '不合格', value: 'fail' },
    ];
    setFixtureInsertScope(fixture.Api, { controlType: 'dropdown', options, selectedIndex: 0 });

    const result = parseResult(insertTypedControlCommand());

    expect(result).toEqual({ ok: true, controlType: 'dropdown' });
    expect(fixture.calls.combo).toEqual([[options, 0]]);
    expect(fixture.calls.inserted[0]).toMatchObject({ block: false, replaceSelection: true });
    expect(fixture.calls.inserted[0]?.control.tag).toBe('eln.field.result');
    expect(fixture.calls.inserted[0]?.control.lock).toBe('unlocked');
  });

  it.each(['text', 'dropdown', 'checkbox', 'date', 'multiline'] as const)('带格式选区插入 %s 控件后保留完整文字格式', (controlType) => {
    const fixture = makeApiFixture();
    fixture.document.selectionTextPr = {
      bold: true,
      color: { r: 23, g: 109, b: 92 },
      fontFamily: 'SimSun',
      fontSize: 14,
      italic: true,
      strikeout: true,
      underline: true,
    };
    setFixtureInsertScope(fixture.Api, {
      controlType,
      options: [
        { display: '合格', value: 'pass' },
        { display: '不合格', value: 'fail' },
      ],
      selectedIndex: -1,
    });

    const result = parseResult(insertTypedControlCommand());

    expect(result).toEqual({ ok: true, controlType });
    expect(fixture.calls.inserted[0]?.control.GetRange?.().GetTextPr?.()).toEqual({
      bold: true,
      color: { r: 23, g: 109, b: 92 },
      fontFamily: 'SimSun',
      fontSize: 14,
      italic: true,
      strikeout: true,
      underline: true,
    });
  });

  it('光标位置插入复选框后继承当前字号和下划线', () => {
    const fixture = makeApiFixture();
    fixture.document.currentTextPr = { fontFamily: 'SimSun', fontSize: 18, underline: true };
    setFixtureInsertScope(fixture.Api, { controlType: 'checkbox', defaultChecked: false });

    const result = parseResult(insertTypedControlCommand());

    expect(result).toEqual({ ok: true, controlType: 'checkbox' });
    expect(fixture.calls.inserted[0]?.control.GetRange?.().GetTextPr?.()).toEqual({
      fontFamily: 'SimSun',
      fontSize: 18,
      underline: true,
    });
  });

  it('插入后按 Tag 重新定位持久化控件并应用选区格式', () => {
    const fixture = makeApiFixture();
    fixture.document.selectionTextPr = { fontFamily: 'SimSun', fontSize: 14, underline: true };
    let persistedControl: ReturnType<typeof fixture.makeControl> | undefined;
    fixture.document.InsertContent = (items) => {
      const transientControl = (items[0] as { inlineControl?: ReturnType<typeof fixture.makeControl> }).inlineControl;
      if (!transientControl) return false;
      persistedControl = fixture.makeControl('dropdown', transientControl.tag);
      fixture.document.controls.push(persistedControl);
      return true;
    };
    setFixtureInsertScope(fixture.Api, {
      controlType: 'dropdown',
      options: [
        { display: '合格', value: 'pass' },
        { display: '不合格', value: 'fail' },
      ],
      selectedIndex: -1,
    });

    expect(parseResult(insertTypedControlCommand())).toEqual({ ok: true, controlType: 'dropdown' });
    expect(persistedControl?.GetRange?.().GetTextPr?.()).toEqual({ fontFamily: 'SimSun', fontSize: 14, underline: true });
  });

  it('使用配置的默认状态创建复选框且不写占位文本', () => {
    const fixture = makeApiFixture();
    setFixtureInsertScope(fixture.Api, { controlType: 'checkbox', defaultChecked: true });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(true);
    expect(fixture.calls.checkbox).toEqual([{ checked: true, checkedSymbol: 0x2611, uncheckedSymbol: 0x2610 }]);
    expect(fixture.calls.inserted[0]?.control.placeholder).toBeUndefined();
  });

  it('使用内联内容控件写入单行文本默认值', () => {
    const fixture = makeApiFixture();
    setFixtureInsertScope(fixture.Api, {
      controlType: 'text',
      defaultValue: 'S-001',
      placeholder: '请输入样品编号',
    });

    const result = parseResult(insertTypedControlCommand());

    expect(result).toEqual({ ok: true, controlType: 'text' });
    expect(fixture.calls.inserted[0]).toMatchObject({ block: false, replaceSelection: true });
    expect(fixture.calls.inserted[0]?.control).toMatchObject({
      kind: 'text',
      elements: [{ text: 'S-001' }],
    });
    expect(fixture.calls.inserted[0]?.control.placeholder).toBeUndefined();
  });

  it('单行文本先挂入段落，再设置可回读的 Tag 属性', () => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl('text');
    const createParagraph = fixture.Api.CreateParagraph;
    let attached = false;
    fixture.Api.CreateInlineLvlSdt = () => control;
    fixture.Api.CreateParagraph = () => {
      const paragraph = createParagraph();
      const addInlineLvlSdt = paragraph.AddInlineLvlSdt;
      paragraph.AddInlineLvlSdt = (value) => {
        attached = true;
        addInlineLvlSdt(value);
      };
      return paragraph;
    };
    control.SetTag = (tag) => {
      if (!attached) return false;
      control.tag = tag;
      return true;
    };
    setFixtureInsertScope(fixture.Api, { controlType: 'text' });

    const result = parseResult(insertTypedControlCommand());

    expect(result).toEqual({ ok: true, controlType: 'text' });
    expect(fixture.document.controls).toEqual([control]);
  });

  it('单行文本没有默认值时设置占位文本', () => {
    const fixture = makeApiFixture();
    setFixtureInsertScope(fixture.Api, { controlType: 'text', defaultValue: '', placeholder: '请输入样品编号' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(true);
    expect(fixture.calls.inserted[0]?.control.placeholder).toBe('请输入样品编号');
  });

  it('使用本地年月日创建日期默认值', () => {
    const fixture = makeApiFixture();
    setFixtureInsertScope(fixture.Api, { controlType: 'date', defaultValue: '2026-08-01' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(true);
    expect(fixture.calls.dateProperties).toEqual({ format: 'yyyy-MM-dd', lang: 'zh-CN' });
    expect(fixture.calls.date[0]?.getFullYear()).toBe(2026);
    expect(fixture.calls.date[0]?.getMonth()).toBe(7);
    expect(fixture.calls.date[0]?.getDate()).toBe(1);
  });

  it('使用字段格式和本地完整时间创建日期时间默认值', () => {
    const fixture = makeApiFixture();
    setFixtureInsertScope(fixture.Api, {
      controlType: 'date',
      dateFormat: 'yyyy-MM-dd HH:mm:ss',
      defaultValue: '2026-08-01 14:35:27',
    });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(true);
    expect(fixture.calls.dateProperties).toEqual({ format: 'yyyy-MM-dd HH:mm:ss', lang: 'zh-CN' });
    expect(fixture.calls.date[0]?.getFullYear()).toBe(2026);
    expect(fixture.calls.date[0]?.getMonth()).toBe(7);
    expect(fixture.calls.date[0]?.getDate()).toBe(1);
    expect(fixture.calls.date[0]?.getHours()).toBe(14);
    expect(fixture.calls.date[0]?.getMinutes()).toBe(35);
    expect(fixture.calls.date[0]?.getSeconds()).toBe(27);
  });

  it('把多行默认值拆成独立段落并作为块级内容插入', () => {
    const fixture = makeApiFixture();
    setFixtureInsertScope(fixture.Api, {
      controlType: 'multiline',
      tag: 'eln.field.note',
      alias: '备注',
      defaultValue: '第一行\n第二行',
      placeholder: '请输入备注',
    });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(true);
    expect(fixture.calls.paragraphTexts).toEqual(['第一行', '第二行']);
    expect(fixture.calls.inserted[0]).toMatchObject({ block: true, replaceSelection: false });
    expect(fixture.calls.inserted[0]?.control.elements).toHaveLength(2);
  });

  it('日期无默认值时不写入当前日期', () => {
    const fixture = makeApiFixture();
    const createDatePicker = fixture.Api.CreateDatePickerContentControl!;
    fixture.Api.CreateDatePickerContentControl = (properties) => {
      const control = createDatePicker(properties);
      control.elements = [{ text: '2026-09-04' }];
      return control;
    };
    setFixtureInsertScope(fixture.Api, { controlType: 'date', defaultValue: '', placeholder: '' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(true);
    expect(fixture.calls.date).toEqual([]);
    expect(fixture.calls.inserted[0]?.control.elements).toEqual([]);
    expect(fixture.calls.inserted[0]?.control.placeholder).toBeFalsy();
  });

  it('构造器缺失时报告不支持且不插入', () => {
    const fixture = makeApiFixture();
    delete fixture.Api.CreateDatePickerContentControl;
    setFixtureInsertScope(fixture.Api, { controlType: 'date' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/不支持日期控件/);
    expect(fixture.calls.inserted).toHaveLength(0);
  });

  it('关键控件 setter 返回 false 时拒绝插入', () => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl('date');
    control.SetTag = () => false;
    fixture.Api.CreateDatePickerContentControl = () => control;
    setFixtureInsertScope(fixture.Api, { controlType: 'date' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/Tag/);
    expect(fixture.calls.inserted).toHaveLength(0);
  });

  it.each([
    {
      type: 'date' as const,
      defaultValue: '2026-08-01',
      patch: (Api: FixtureApi, control: ReturnType<ReturnType<typeof makeApiFixture>['makeControl']>) => {
        control.SetDate = () => false;
        Api.CreateDatePickerContentControl = () => control;
      },
    },
    {
      type: 'multiline' as const,
      defaultValue: '第一行\n第二行',
      patch: (Api: FixtureApi, control: ReturnType<ReturnType<typeof makeApiFixture>['makeControl']>) => {
        control.RemoveAllElements = () => false;
        Api.CreateBlockLvlSdt = () => control;
      },
    },
    {
      type: 'multiline' as const,
      defaultValue: '第一行\n第二行',
      patch: (Api: FixtureApi, control: ReturnType<ReturnType<typeof makeApiFixture>['makeControl']>) => {
        control.AddElement = () => false;
        Api.CreateBlockLvlSdt = () => control;
      },
    },
  ])('$type 默认值写入返回 false 时拒绝插入', ({ type, defaultValue, patch }) => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl(type);
    patch(fixture.Api, control);
    setFixtureInsertScope(fixture.Api, { controlType: type, defaultValue });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/失败/);
    expect(fixture.calls.inserted).toHaveLength(0);
  });

  it.each(['SetPlaceholderText', 'SetColor'] as const)('配置相应值时要求 %s API 存在', (missingMethod) => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl('date');
    delete control[missingMethod];
    fixture.Api.CreateDatePickerContentControl = () => control;
    setFixtureInsertScope(fixture.Api, { controlType: 'date', defaultValue: '' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/不支持/);
    expect(fixture.calls.inserted).toHaveLength(0);
  });

  it('文档 InsertContent 返回 false 时报告失败', () => {
    const fixture = makeApiFixture();
    fixture.document.InsertContent = () => false;
    setFixtureInsertScope(fixture.Api, { controlType: 'checkbox' });

    const result = parseResult(insertTypedControlCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/插入/);
  });
});

describe('disableTrackRevisionsCommand', () => {
  it('接受历史修订后关闭修订跟踪，并回读确认最终状态', () => {
    const fixture = makeApiFixture();
    const operations: string[] = [];
    let enabled = true;
    fixture.document.AcceptAllRevisionChanges = () => {
      operations.push('accept');
      return true;
    };
    fixture.document.Document = {
      HaveRevisionChanges: (includeCurrentUser) => {
        operations.push(`revisions:${includeCurrentUser}`);
        return false;
      },
      SetTrackRevisions: (value) => {
        operations.push(`local-track:${value}`);
        enabled = value;
      },
    };
    fixture.document.SetTrackRevisions = (value) => {
      operations.push(`track:${value}`);
      enabled = value;
      return true;
    };
    fixture.document.IsTrackRevisions = () => enabled;
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateReview: {} });

    expect(parseResult(disableTrackRevisionsCommand())).toEqual({ ok: true });
    expect(operations).toEqual(['accept', 'revisions:true', 'track:false', 'local-track:false']);
    expect(enabled).toBe(false);
  });

  it('修订跟踪回读仍开启时拒绝进入模板标注', () => {
    const fixture = makeApiFixture();
    fixture.document.AcceptAllRevisionChanges = () => true;
    fixture.document.Document = { HaveRevisionChanges: () => false, SetTrackRevisions: () => undefined };
    fixture.document.SetTrackRevisions = () => true;
    fixture.document.IsTrackRevisions = () => true;
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateReview: {} });

    expect(parseResult(disableTrackRevisionsCommand())).toEqual({ ok: false, error: '修订跟踪仍处于开启状态' });
  });

  it('接受后仍有历史修订时拒绝进入模板标注', () => {
    const fixture = makeApiFixture();
    fixture.document.AcceptAllRevisionChanges = () => true;
    fixture.document.Document = { HaveRevisionChanges: () => true, SetTrackRevisions: () => undefined };
    fixture.document.SetTrackRevisions = () => true;
    fixture.document.IsTrackRevisions = () => false;
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateReview: {} });

    expect(parseResult(disableTrackRevisionsCommand())).toEqual({ ok: false, error: '历史修订仍未全部接受' });
  });
});

describe('readTrackRevisionsStateCommand', () => {
  it('独立回读当前修订跟踪状态', () => {
    const fixture = makeApiFixture();
    fixture.document.Document = { HaveRevisionChanges: () => false };
    fixture.document.IsTrackRevisions = () => false;
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateReviewState: {} });

    expect(parseResult(readTrackRevisionsStateCommand())).toEqual({ hasRevisionChanges: false, ok: true, trackRevisions: false });
  });
});

describe('listContentControlsCommand', () => {
  it('回读内容控件属性并识别五种具体类型', () => {
    const fixture = makeApiFixture();
    const definitions = [
      { alias: '文本', id: 'text-1', tag: 'eln.field.text', type: 'text' },
      { alias: '下拉', id: 'dropdown-1', tag: 'eln.field.dropdown', type: 'dropdown' },
      { alias: '复选', id: 'checkbox-1', tag: 'eln.field.checkbox', type: 'checkbox' },
      { alias: '日期', id: 'date-1', tag: 'eln.field.date', type: 'date' },
      { alias: '多行', id: 'multiline-1', tag: 'eln.field.multiline', type: 'multiline' },
    ] as const;

    fixture.document.controls = definitions.map(({ alias, id, tag, type }) => {
      const control = Object.assign(fixture.makeControl(type, tag), {
        GetAlias: () => alias,
        GetInternalId: () => id,
        IsBlockLevel: () => type === 'multiline',
        IsCheckBox: () => type === 'checkbox',
        IsComboBox: () => type === 'dropdown',
        IsDatePicker: () => type === 'date',
        IsDropDownList: () => false,
      });
      if (type === 'date') Object.assign(control, { ToJSON: () => JSON.stringify({ sdtPr: { date: { dateFormat: 'dd/MM/yyyy' } } }) });
      return control;
    });
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateList: {} });

    expect(parseResult(listContentControlsCommand())).toEqual({
      ok: true,
      controls: [
        {
          Alias: '文本',
          ControlConfig: { placeholder: '' },
          ControlRole: 'PRIMARY',
          ControlType: 'text',
          InternalId: 'text-1',
          Lock: 'unlocked',
          Tag: 'eln.field.text',
        },
        {
          Alias: '下拉',
          ControlConfig: { defaultValue: '', options: [], placeholder: '' },
          ControlRole: 'PRIMARY',
          ControlType: 'dropdown',
          InternalId: 'dropdown-1',
          Lock: 'unlocked',
          Tag: 'eln.field.dropdown',
        },
        {
          Alias: '复选',
          ControlConfig: { defaultChecked: false },
          ControlRole: 'PRIMARY',
          ControlType: 'checkbox',
          InternalId: 'checkbox-1',
          Lock: 'unlocked',
          Tag: 'eln.field.checkbox',
        },
        {
          Alias: '日期',
          ControlConfig: { dateFormat: 'dd/MM/yyyy', placeholder: '' },
          ControlRole: 'PRIMARY',
          ControlType: 'date',
          InternalId: 'date-1',
          Lock: 'unlocked',
          Tag: 'eln.field.date',
        },
        {
          Alias: '多行',
          ControlConfig: { placeholder: '' },
          ControlRole: 'PRIMARY',
          ControlType: 'multiline',
          InternalId: 'multiline-1',
          Lock: 'unlocked',
          Tag: 'eln.field.multiline',
        },
      ],
    });
  });

  it('从文档实际控件枚举角色、锁和类型专用配置', () => {
    const fixture = makeApiFixture();
    const dropdown = fixture.makeControl('dropdown', 'eln.ref.result.abc');
    dropdown.alias = '检测结论';
    dropdown.lock = 'contentLocked';
    dropdown.placeholder = '请选择';
    dropdown.options = [
      { display: '合格', value: 'PASS' },
      { display: '不合格', value: 'FAIL' },
    ];
    fixture.document.controls = [dropdown];
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateList: {} });

    expect(parseResult(listContentControlsCommand()).controls).toEqual([
      {
        Alias: '检测结论',
        ControlConfig: { options: dropdown.options, placeholder: '请选择' },
        ControlRole: 'REFERENCE',
        ControlType: 'dropdown',
        InternalId: 'id-eln.ref.result.abc',
        Lock: 'contentLocked',
        Tag: 'eln.ref.result.abc',
      },
    ]);
  });

  it('下拉主控件把实际选中项规范化为业务值，引用不把同步正文伪造为默认值', () => {
    const fixture = makeApiFixture();
    const primary = fixture.makeControl('dropdown', 'eln.field.result');
    const reference = fixture.makeControl('dropdown', 'eln.ref.result.ref1');
    for (const control of [primary, reference]) {
      control.options = [
        { display: '合格', value: 'PASS' },
        { display: '不合格', value: 'FAIL' },
      ];
      control.elements = [{ text: '不合格' }];
    }
    fixture.document.controls = [primary, reference];
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateList: {} });

    expect(parseResult(listContentControlsCommand()).controls?.map((control) => control.ControlConfig)).toEqual([
      { defaultValue: 'FAIL', options: primary.options, placeholder: '' },
      { options: reference.options, placeholder: '' },
    ]);
  });

  it('文本、日期和多行控件不把当前正文伪造为字段默认值', () => {
    const fixture = makeApiFixture();
    const text = fixture.makeControl('text', 'eln.field.text');
    const date = fixture.makeControl('date', 'eln.field.date');
    const multiline = fixture.makeControl('multiline', 'eln.field.note');
    text.elements = [{ text: '当前文本' }];
    date.elements = [{ text: '2026-08-16' }];
    multiline.elements = [{ text: '当前多行正文' }];
    Object.assign(date, { ToJSON: () => JSON.stringify({ sdtPr: { date: { dateFormat: 'yyyy年M月d日' } } }) });
    fixture.document.controls = [text, date, multiline];
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateList: {} });

    expect(parseResult(listContentControlsCommand()).controls?.map((control) => control.ControlConfig)).toEqual([
      { placeholder: '' },
      { dateFormat: 'yyyy年M月d日', placeholder: '' },
      { placeholder: '' },
    ]);
  });

  it('多个类型特征同时存在时按复选、下拉、日期、多行的顺序判定', () => {
    const fixture = makeApiFixture();
    fixture.document.controls = [
      Object.assign(fixture.makeControl('ambiguous', 'eln.field.ambiguous'), {
        GetAlias: () => '歧义字段',
        GetInternalId: () => 'ambiguous-1',
        IsBlockLevel: () => true,
        IsCheckBox: () => true,
        IsComboBox: () => true,
        IsDatePicker: () => true,
        IsDropDownList: () => true,
      }),
    ];
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateList: {} });

    expect(parseResult(listContentControlsCommand()).controls?.[0]?.ControlType).toBe('checkbox');
  });
});

describe('writeTextDefaultCommand', () => {
  it('按唯一 Tag 成功写入文本默认值', () => {
    const fixture = makeApiFixture();
    const target = fixture.makeControl('text', 'eln.field.sample_id');
    fixture.document.controls.push(target);
    setTextDefaultScope(fixture.Api, { tag: 'eln.field.sample_id', value: 'S-001' });

    const result = parseResult(writeTextDefaultCommand());

    expect(result.ok).toBe(true);
    expect(target.elements).toHaveLength(1);
    expect(target.elements[0]?.text).toBe('S-001');
  });

  it('写入文本默认值后保留原控件字符格式', () => {
    const fixture = makeApiFixture();
    const target = fixture.makeControl('text', 'eln.field.sample_id');
    target.textPr = {
      bold: true,
      color: { r: 23, g: 109, b: 92 },
      fontFamily: 'SimSun',
      fontSize: 14,
      italic: true,
      strikeout: true,
      underline: true,
    };
    fixture.document.controls.push(target);
    setTextDefaultScope(fixture.Api, { tag: 'eln.field.sample_id', value: 'S-001' });

    const result = parseResult(writeTextDefaultCommand());

    expect(result).toEqual({ ok: true });
    expect(target.GetRange?.().GetTextPr?.()).toEqual({
      bold: true,
      color: { r: 23, g: 109, b: 92 },
      fontFamily: 'SimSun',
      fontSize: 14,
      italic: true,
      strikeout: true,
      underline: true,
    });
  });

  it.each(['AddText', 'AddElement'] as const)('%s 返回 false 时拒绝文本默认值写入', (failedStep) => {
    const fixture = makeApiFixture();
    const target = fixture.makeControl('text', 'eln.field.sample_id');
    fixture.document.controls.push(target);

    if (failedStep === 'AddText') {
      fixture.Api.CreateRun = () => ({ text: '', AddText: () => false });
    } else {
      target.AddElement = () => false;
    }
    setTextDefaultScope(fixture.Api, { tag: 'eln.field.sample_id', value: 'S-001' });

    const result = parseResult(writeTextDefaultCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/失败/);
  });

  it('清空返回 false 但最终回读一致时仍接受写入', () => {
    const fixture = makeApiFixture();
    const target = fixture.makeControl('text', 'eln.field.sample_id');
    target.RemoveAllElements = () => false;
    fixture.document.controls.push(target);
    setTextDefaultScope(fixture.Api, { tag: 'eln.field.sample_id', value: 'S-001' });

    const result = parseResult(writeTextDefaultCommand());

    expect(result.ok).toBe(true);
    expect(target.GetRange?.().GetText?.()).toBe('S-001');
  });

  it('文本默认值回读不一致时拒绝写入', () => {
    const fixture = makeApiFixture();
    const target = fixture.makeControl('text', 'eln.field.sample_id');
    target.GetRange = () => ({ GetText: () => '旧值' });
    fixture.document.controls.push(target);
    setTextDefaultScope(fixture.Api, { tag: 'eln.field.sample_id', value: 'S-001' });

    const result = parseResult(writeTextDefaultCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/回读/);
  });

  it.each([0, 2])('目标 Tag 匹配数量为 %i 时拒绝写入', (targetCount) => {
    const fixture = makeApiFixture();
    for (let index = 0; index < targetCount; index += 1) {
      fixture.document.controls.push(fixture.makeControl('text', 'eln.field.sample_id'));
    }
    setTextDefaultScope(fixture.Api, { tag: 'eln.field.sample_id', value: 'S-001' });

    const result = parseResult(writeTextDefaultCommand());

    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/唯一控件/);
  });
});

describe('command 自包含契约', () => {
  it.each([
    ['disableTrackRevisionsCommand', disableTrackRevisionsCommand],
    ['insertTypedControlCommand', insertTypedControlCommand],
    ['listContentControlsCommand', listContentControlsCommand],
    ['readTrackRevisionsStateCommand', readTrackRevisionsStateCommand],
    ['writeTextDefaultCommand', writeTextDefaultCommand],
    ['readControlTextCommand', readControlTextCommand],
    ['setFormulaResultLockCommand', setFormulaResultLockCommand],
    ['updateControlSpecCommand', updateControlSpecCommand],
  ] as const)('%s 源码只依赖 Asc、Api 和标准全局', (_name, command) => {
    const source = command.toString();

    expect(source).not.toMatch(/\b(?:import|require|window|document)\b/);
    expect(source).toContain('Asc.scope');
    expect(source).toContain('Api');
  });

  it('insertTypedControlCommand 的源码可在隔离函数中执行', () => {
    const fixture = makeApiFixture();
    const execute = new Function('Asc', 'Api', `return (${insertTypedControlCommand.toString()})()`) as (
      Asc: { scope: { elnTemplateInsert: InsertSpec } },
      Api: FixtureApi,
    ) => string;

    const result = parseResult(execute({ scope: { elnTemplateInsert: makeInsertSpec({ controlType: 'checkbox', defaultChecked: true }) } }, fixture.Api));

    expect(result).toEqual({ ok: true, controlType: 'checkbox' });
    expect(fixture.calls.inserted).toHaveLength(1);
  });

  it('listContentControlsCommand 的源码可在隔离函数中执行', () => {
    const fixture = makeApiFixture();
    const dateControl = fixture.makeControl('date', 'eln.field.date');
    dateControl.dateFormat = 'yyyy-MM-dd';
    fixture.document.controls = [
      Object.assign(dateControl, {
        GetAlias: () => '日期',
        GetInternalId: () => 'date-1',
        IsDatePicker: () => true,
      }),
    ];
    const execute = new Function('Asc', 'Api', `return (${listContentControlsCommand.toString()})()`) as (
      Asc: { scope: { elnTemplateList: Record<string, never> } },
      Api: FixtureApi,
    ) => string;

    expect(parseResult(execute({ scope: { elnTemplateList: {} } }, fixture.Api))).toEqual({
      ok: true,
      controls: [
        {
          Alias: '日期',
          ControlConfig: { dateFormat: 'yyyy-MM-dd', placeholder: '' },
          ControlRole: 'PRIMARY',
          ControlType: 'date',
          InternalId: 'date-1',
          Lock: 'unlocked',
          Tag: 'eln.field.date',
        },
      ],
    });
  });

  it('writeTextDefaultCommand 的源码可在隔离函数中执行', () => {
    const fixture = makeApiFixture();
    fixture.document.controls.push(fixture.makeControl('text', 'eln.field.sample_id'));
    const execute = new Function('Asc', 'Api', `return (${writeTextDefaultCommand.toString()})()`) as (
      Asc: { scope: { elnTemplateTextDefault: TextDefaultSpec } },
      Api: FixtureApi,
    ) => string;

    const result = parseResult(execute({ scope: { elnTemplateTextDefault: { tag: 'eln.field.sample_id', value: 'S-001' } } }, fixture.Api));

    expect(result).toEqual({ ok: true });
  });
});

describe('setFormulaResultLockCommand', () => {
  it('锁定结果主控件与其全部引用，并返回原锁快照', () => {
    const fixture = makeApiFixture();
    const primary = fixture.makeControl('text', 'eln.field.result');
    const reference = fixture.makeControl('text', 'eln.ref.result.one');
    const unrelated = fixture.makeControl('text', 'eln.field.other');
    primary.lock = 'unlocked';
    reference.lock = 'contentLocked';
    unrelated.lock = 'unlocked';
    fixture.document.controls.push(primary, reference, unrelated);
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateFormulaLocks: { mode: 'lock', primaryTag: primary.tag } });

    expect(parseResult(setFormulaResultLockCommand())).toEqual({
      ok: true,
      snapshot: {
        primaryTag: 'eln.field.result',
        locks: [
          { tag: 'eln.field.result', lock: 'unlocked' },
          { tag: 'eln.ref.result.one', lock: 'contentLocked' },
        ],
      },
    });
    expect(primary.lock).toBe('sdtContentLocked');
    expect(reference.lock).toBe('sdtContentLocked');
    expect(unrelated.lock).toBe('unlocked');
  });

  it('恢复快照后回读每个锁状态，失败时不伪装成功', () => {
    const fixture = makeApiFixture();
    const primary = fixture.makeControl('text', 'eln.field.result');
    const reference = fixture.makeControl('text', 'eln.ref.result.one');
    primary.lock = 'contentLocked';
    reference.lock = 'contentLocked';
    fixture.document.controls.push(primary, reference);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateFormulaLocks: {
        mode: 'restore',
        snapshot: {
          primaryTag: primary.tag,
          locks: [
            { tag: primary.tag, lock: 'unlocked' },
            { tag: reference.tag, lock: 'contentLocked' },
          ],
        },
      },
    });

    expect(parseResult(setFormulaResultLockCommand())).toMatchObject({ ok: true });
    expect(primary.lock).toBe('unlocked');
    expect(reference.lock).toBe('contentLocked');
  });
});

describe('字段引用与版本更新命令', () => {
  it('按主字段 Tag 读取唯一控件的当前文本', () => {
    const fixture = makeApiFixture();
    const primary = fixture.makeControl('text', 'eln.field.result');
    primary.elements = [{ text: '12.3' }];
    fixture.document.controls.push(primary);
    installOnlyOfficeGlobals(fixture.Api, { elnTemplateReadText: { tag: 'eln.field.result' } });

    expect(parseResult(readControlTextCommand())).toEqual({ ok: true, text: '12.3' });
  });

  it('批量更新主字段和全部引用的 Tag、Alias、配置和只读锁', () => {
    const fixture = makeApiFixture();
    const primary = fixture.makeControl('text', 'eln.field.result');
    const reference = fixture.makeControl('text', 'eln.ref.result.abc');
    primary.placeholder = '原提示';
    reference.placeholder = '原提示';
    fixture.document.controls.push(primary, reference);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: 'eln.field.result',
            tag: 'eln.field.result',
            alias: '新结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '请输入新结果' },
            lock: 'unlocked',
          },
          {
            currentTag: 'eln.ref.result.abc',
            tag: 'eln.ref.result.abc',
            alias: '新结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '请输入新结果' },
            lock: 'contentLocked',
          },
        ],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 2 });
    expect(primary.alias).toBe('新结果');
    expect(primary.placeholder).toBe('请输入新结果');
    expect(primary.lock).toBe('unlocked');
    expect(reference.alias).toBe('新结果');
    expect(reference.lock).toBe('contentLocked');
  });

  it('目标 placeholder 为空但旧值非空时在任何 setter 前拒绝清空', () => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl('text', 'eln.field.result');
    control.alias = '原结果';
    control.lock = 'contentLocked';
    control.placeholder = '原提示';
    fixture.document.controls.push(control);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: control.tag,
            tag: 'eln.field.result_new',
            alias: '新结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '' },
            lock: 'unlocked',
          },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: false, error: '当前编辑器不支持清空已有占位文本' });
    expect(control).toMatchObject({ alias: '原结果', lock: 'contentLocked', placeholder: '原提示', tag: 'eln.field.result' });
  });

  it('已有控件 placeholder 为空但目标非空时在任何 setter 前拒绝更新', () => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl('text', 'eln.field.result');
    const setters = {
      SetAlias: vi.fn(() => true),
      SetLock: vi.fn(() => true),
      SetPlaceholderText: vi.fn(() => true),
      SetTag: vi.fn(() => true),
    };
    Object.assign(control, setters);
    fixture.document.controls.push(control);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: control.tag,
            tag: 'eln.field.result_new',
            alias: '新结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '请输入结果' },
            lock: 'unlocked',
          },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: false, error: '当前编辑器不支持为已有控件设置占位文本' });
    expect(setters.SetTag).not.toHaveBeenCalled();
    expect(setters.SetAlias).not.toHaveBeenCalled();
    expect(setters.SetLock).not.toHaveBeenCalled();
    expect(setters.SetPlaceholderText).not.toHaveBeenCalled();
  });

  it('目标和旧 placeholder 均为空时允许更新', () => {
    const fixture = makeApiFixture();
    const control = fixture.makeControl('text', 'eln.field.result');
    fixture.document.controls.push(control);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: control.tag,
            tag: control.tag,
            alias: '结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '' },
            lock: 'unlocked',
          },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 1 });
  });

  it('下拉当前值不在新选项中时在任何 setter 前拒绝更新', () => {
    const fixture = makeApiFixture();
    const dropdown = fixture.makeControl('dropdown', 'eln.field.conclusion');
    dropdown.alias = '原结论';
    dropdown.lock = 'contentLocked';
    dropdown.placeholder = '请选择';
    dropdown.options = [
      { display: '合格', value: 'PASS' },
      { display: '不合格', value: 'FAIL' },
    ];
    dropdown.elements = [{ text: '合格' }];
    fixture.document.controls.push(dropdown);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: dropdown.tag,
            tag: 'eln.field.conclusion_new',
            alias: '新结论',
            controlType: 'DROPDOWN',
            controlConfig: {
              options: [
                { display: '待定', value: 'PENDING' },
                { display: '取消', value: 'CANCELLED' },
              ],
              placeholder: '请选择',
            },
            lock: 'unlocked',
          },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: false, error: '下拉控件当前值不在新选项中' });
    expect(dropdown).toMatchObject({
      alias: '原结论',
      lock: 'contentLocked',
      options: [
        { display: '合格', value: 'PASS' },
        { display: '不合格', value: 'FAIL' },
      ],
      tag: 'eln.field.conclusion',
    });
  });

  it.each([
    ['空文本', ''],
    ['占位文本', '请选择'],
  ])('下拉当前为%s时视为未选中并允许更新选项', (_label, currentText) => {
    const fixture = makeApiFixture();
    const dropdown = fixture.makeControl('dropdown', 'eln.field.conclusion');
    dropdown.placeholder = '请选择';
    dropdown.options = [
      { display: '合格', value: 'PASS' },
      { display: '不合格', value: 'FAIL' },
    ];
    dropdown.elements = currentText ? [{ text: currentText }] : [];
    fixture.document.controls.push(dropdown);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: dropdown.tag,
            tag: dropdown.tag,
            alias: '结论',
            controlType: 'DROPDOWN',
            controlConfig: {
              options: [
                { display: '待定', value: 'PENDING' },
                { display: '取消', value: 'CANCELLED' },
              ],
              placeholder: '请选择',
            },
            lock: 'unlocked',
          },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 1 });
    expect(dropdown.options).toEqual([
      { display: '待定', value: 'PENDING' },
      { display: '取消', value: 'CANCELLED' },
    ]);
  });

  it('原位更新下拉和日期配置且保留复选框当前状态', () => {
    const fixture = makeApiFixture();
    const dropdown = fixture.makeControl('dropdown', 'eln.field.conclusion');
    const checkbox = fixture.makeControl('checkbox', 'eln.field.reviewed');
    const date = fixture.makeControl('date', 'eln.field.test_date');
    const setCheckBoxChecked = vi.fn((value: boolean) => {
      checkbox.checked = value;
      return true;
    });
    checkbox.SetCheckBoxChecked = setCheckBoxChecked;
    dropdown.placeholder = '原提示';
    date.placeholder = '原提示';
    date.dateValue = new Date(2025, 5, 15);
    fixture.document.controls.push(dropdown, checkbox, date);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: dropdown.tag,
            tag: dropdown.tag,
            alias: '结论',
            controlType: 'DROPDOWN',
            controlConfig: {
              options: [
                { display: '合格', value: 'PASS' },
                { display: '不合格', value: 'FAIL' },
              ],
              placeholder: '请选择',
            },
            lock: 'unlocked',
          },
          {
            currentTag: checkbox.tag,
            tag: checkbox.tag,
            alias: '已复核',
            controlType: 'CHECKBOX',
            controlConfig: { defaultChecked: true },
            lock: 'contentLocked',
          },
          {
            currentTag: date.tag,
            tag: date.tag,
            alias: '检验日期',
            controlType: 'DATE',
            controlConfig: { defaultValue: '2026-08-16', dateFormat: 'yyyy-MM-dd', placeholder: '请选择日期' },
            lock: 'unlocked',
          },
        ],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 3 });
    expect(dropdown.options).toEqual([
      { display: '合格', value: 'PASS' },
      { display: '不合格', value: 'FAIL' },
    ]);
    expect(checkbox.checked).toBeFalsy();
    expect(setCheckBoxChecked).not.toHaveBeenCalled();
    expect(date.dateFormat).toBe('yyyy-MM-dd');
    expect(date.dateValue).toEqual(new Date(2025, 5, 15));
  });

  it('类型变化时在修改文档前明确拒绝', () => {
    const fixture = makeApiFixture();
    const before = fixture.makeControl('text', 'eln.field.conclusion');
    before.elements = [{ text: '合格' }];
    fixture.document.controls.push(before);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: before.tag,
            tag: before.tag,
            alias: '结论',
            controlType: 'DROPDOWN',
            controlConfig: {
              options: [
                { display: '合格', value: 'PASS' },
                { display: '不合格', value: 'FAIL' },
              ],
            },
            lock: 'unlocked',
          },
        ],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: false, error: '当前阶段不支持修改已有控件类型' });
    expect(fixture.document.controls).toHaveLength(1);
    expect(fixture.document.controls[0]).toBe(before);
    expect(fixture.calls.inserted).toHaveLength(0);
  });

  it('同类型日期升级成功时保留文档当前日期值', () => {
    const fixture = makeApiFixture();
    const date = fixture.makeControl('date', 'eln.field.test_date');
    date.placeholder = '原提示';
    date.dateValue = new Date(2025, 5, 15);
    fixture.document.controls.push(date);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: date.tag,
            tag: date.tag,
            alias: '检验日期',
            controlType: 'DATE',
            controlConfig: { defaultValue: '2026-08-16', dateFormat: 'yyyy-MM-dd', placeholder: '请选择日期' },
            lock: 'unlocked',
          },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 1 });
    expect(date.dateValue).toEqual(new Date(2025, 5, 15));
  });

  it('日期升级后后续控件更新失败时日期值保持不变', () => {
    const fixture = makeApiFixture();
    const date = fixture.makeControl('date', 'eln.field.test_date');
    const text = fixture.makeControl('text', 'eln.field.result');
    date.dateValue = new Date(2025, 5, 15);
    text.SetAlias = () => false;
    fixture.document.controls.push(date, text);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: date.tag,
            tag: date.tag,
            alias: '检验日期',
            controlType: 'DATE',
            controlConfig: { defaultValue: '2026-08-16', dateFormat: 'yyyy-MM-dd' },
            lock: 'unlocked',
          },
          { currentTag: text.tag, tag: text.tag, alias: '新结果', controlType: 'TEXT', controlConfig: {}, lock: 'unlocked' },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand()).ok).toBe(false);
    expect(date.dateValue).toEqual(new Date(2025, 5, 15));
  });

  it('主字段提升的第二个更新失败时回滚已更新控件且不删除旧主字段', () => {
    const fixture = makeApiFixture();
    const oldPrimary = fixture.makeControl('text', 'eln.field.result');
    const promoted = fixture.makeControl('text', 'eln.ref.result.promoted');
    const remainingReference = fixture.makeControl('text', 'eln.ref.result.remaining');
    promoted.alias = '原提升引用';
    promoted.lock = 'contentLocked';
    promoted.placeholder = '原提示';
    remainingReference.alias = '原保留引用';
    remainingReference.lock = 'contentLocked';
    remainingReference.placeholder = '原提示';
    remainingReference.SetTag = () => false;
    fixture.document.controls.push(oldPrimary, promoted, remainingReference);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          { currentTag: promoted.tag, tag: oldPrimary.tag, alias: '结果', controlType: 'TEXT', controlConfig: { placeholder: '新提示' }, lock: 'unlocked' },
          {
            currentTag: remainingReference.tag,
            tag: remainingReference.tag,
            alias: '结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '新提示' },
            lock: 'contentLocked',
          },
        ],
        removals: [oldPrimary.tag],
      },
    });

    expect(parseResult(updateControlSpecCommand()).ok).toBe(false);
    expect(promoted).toMatchObject({ alias: '原提升引用', lock: 'contentLocked', tag: 'eln.ref.result.promoted' });
    expect(remainingReference).toMatchObject({ alias: '原保留引用', lock: 'contentLocked', tag: 'eln.ref.result.remaining' });
    expect(fixture.document.controls).toContain(oldPrimary);
  });

  it('删除旧控件失败时回滚全部更新', () => {
    const fixture = makeApiFixture();
    const oldPrimary = fixture.makeControl('text', 'eln.field.result');
    const reference = fixture.makeControl('text', 'eln.ref.result.reference');
    reference.alias = '原引用';
    reference.lock = 'contentLocked';
    reference.placeholder = '原提示';
    oldPrimary.Delete = () => false;
    fixture.document.controls.push(oldPrimary, reference);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: reference.tag,
            tag: reference.tag,
            alias: '新结果',
            controlType: 'TEXT',
            controlConfig: { placeholder: '新提示' },
            lock: 'contentLocked',
          },
        ],
        removals: [oldPrimary.tag],
      },
    });

    expect(parseResult(updateControlSpecCommand()).ok).toBe(false);
    expect(reference).toMatchObject({ alias: '原引用', lock: 'contentLocked', placeholder: '原提示', tag: 'eln.ref.result.reference' });
    expect(fixture.document.controls).toContain(oldPrimary);
  });

  it('删除字段控件时同时删除占位内容', () => {
    const fixture = makeApiFixture();
    const reference = fixture.makeControl('text', 'eln.ref.result.reference');
    const deleteControl = vi.fn(() => true);
    reference.Delete = deleteControl;
    fixture.document.controls.push(reference);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [],
        removals: [reference.tag],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 0 });
    expect(deleteControl).toHaveBeenCalledWith(false);
  });

  it('引用提升时先释放主 Tag，不向编辑器写入暂态重复 Tag', () => {
    const fixture = makeApiFixture();
    const oldPrimary = fixture.makeControl('text', 'eln.field.result');
    const promoted = fixture.makeControl('text', 'eln.ref.result.promoted');
    oldPrimary.SetTag = (value) => {
      if (fixture.document.controls.some((control) => control !== oldPrimary && control.tag === value)) return false;
      oldPrimary.tag = value;
      return true;
    };
    promoted.SetTag = (value) => {
      if (fixture.document.controls.some((control) => control !== promoted && control.tag === value)) return false;
      promoted.tag = value;
      return true;
    };
    fixture.document.controls.push(oldPrimary, promoted);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          {
            currentTag: promoted.tag,
            tag: oldPrimary.tag,
            alias: '结果',
            controlType: 'TEXT',
            controlConfig: {},
            lock: 'unlocked',
          },
        ],
        removals: [oldPrimary.tag],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toEqual({ ok: true, updated: 1 });
    expect(fixture.document.controls).toHaveLength(1);
    expect(fixture.document.controls[0]).toBe(promoted);
    expect(promoted.tag).toBe('eln.field.result');
  });

  it('补偿 setter 失败时明确报告文档回滚失败', () => {
    const fixture = makeApiFixture();
    const first = fixture.makeControl('text', 'eln.field.first');
    const second = fixture.makeControl('text', 'eln.field.second');
    let firstSetTagCalls = 0;
    first.SetTag = (value) => {
      firstSetTagCalls += 1;
      if (firstSetTagCalls > 1) return false;
      first.tag = value;
      return true;
    };
    second.SetTag = () => false;
    fixture.document.controls.push(first, second);
    installOnlyOfficeGlobals(fixture.Api, {
      elnTemplateUpdateControls: {
        updates: [
          { currentTag: first.tag, tag: 'eln.field.first_new', alias: '新一', controlType: 'TEXT', controlConfig: {}, lock: 'unlocked' },
          { currentTag: second.tag, tag: 'eln.field.second_new', alias: '新二', controlType: 'TEXT', controlConfig: {}, lock: 'unlocked' },
        ],
        removals: [],
      },
    });

    expect(parseResult(updateControlSpecCommand())).toMatchObject({ ok: false, error: expect.stringContaining('回滚失败') });
  });
});
