<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Cherry Editor - Toolbar API 完整测试</title>
    <style>
      *, *::before, *::after { box-sizing: border-box; }
      html, body {
        margin: 0; padding: 0; height: 100%;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        background: #f5f5f5;
      }
      .control-panel {
        position: fixed;
        top: 0;
        left: 50%;
        transform: translateX(-50%);
        z-index: 9999;
        background: #fff;
        border: 2px solid #3582fb;
        border-radius: 8px;
        padding: 10px 16px;
        box-shadow: 0 4px 16px rgba(0,0,0,.12);
        display: flex;
        gap: 6px;
        flex-wrap: wrap;
        align-items: center;
        max-width: 95vw;
      }
      .control-panel h3 {
        margin: 0 8px 0 0;
        color: #333;
        font-size: 13px;
        white-space: nowrap;
      }
      button {
        padding: 5px 12px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 12px;
        transition: all .15s;
        white-space: nowrap;
        font-weight: 500;
      }
      button:hover { opacity: .9; transform: translateY(-1px); }
      button:active { transform: translateY(0); }
      .btn-run-all { background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; }
      .btn-reset { background: #6b7280; color: #fff; }
      .btn-group { display: inline-flex; align-items: center; gap: 4px; margin: 0 4px; padding-right: 8px; border-right: 1px solid #ddd; }
      .btn-group:last-child { border-right: none; }

      /* 测试报告面板 */
      .report-panel {
        position: fixed;
        bottom: 0; left: 0; right: 0;
        z-index: 9999;
        background: rgba(15,23,42,.94);
        color: #e2e8f0;
        border-top: 3px solid #3582fb;
        font-family: 'SF Mono', Monaco, 'Menlo', monospace;
        font-size: 12px;
        line-height: 1.5;
        max-height: 45vh;
        overflow-y: auto;
        transition: transform .25s ease;
        transform: translateY(calc(100% - 32px));
      }
      .report-panel.expanded { transform: translateY(0); }
      .report-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 6px 16px;
        cursor: pointer;
        user-select: none;
        background: rgba(255,255,255,.04);
      }
      .report-header:hover { background: rgba(255,255,255,.08); }
      .report-summary {
        display: flex;
        gap: 16px;
        font-weight: 600;
      }
      .stat-pass { color: #34d399; }
      .stat-fail { color: #f87171; }
      .stat-total { color: #93c5fd; }
      .report-toggle { font-size: 11px; color: #94a3b8; cursor: pointer; }
      .report-body { padding: 8px 16px 16px; }

      .test-case {
        padding: 6px 10px;
        margin: 4px 0;
        border-radius: 4px;
        background: rgba(255,255,255,.03);
        border-left: 3px solid transparent;
      }
      .test-case.pass { border-left-color: #34d399; }
      .test-case.fail { border-left-color: #f87171; background: rgba(248,113,113,.08); }
      .test-name { font-weight: 600; color: #e2e8f0; }
      .test-detail { color: #94a3b8; font-size: 11px; margin-top: 2px; }
      .test-assert { color: #a78bfa; font-size: 11px; margin-top: 2px; }
      .assert-ok::before { content: '✓ '; color: #34d399; }
      .assert-err::before { content: '✗ '; color: #f87171; }

      #markdown { min-height: 60vh; padding-top: 56px; }
    </style>
    <link rel="stylesheet" type="text/css" href="../packages/cherry-markdown/dist/cherry-markdown.css" />
    <link rel="Shortcut Icon" href="./logo/favicon.ico" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css" crossorigin="anonymous">
  </head>

  <body>
    <!-- 控制面板 -->
    <div class="control-panel">
      <h3>Toolbar API Test Suite</h3>
      <button class="btn-run-all" onclick="runAllTests()">▶ 运行全部测试</button>
      <button class="btn-reset" onclick="resetEditor()">↺ 重置编辑器</button>

      <span class="btn-group">
        <button class="primary" style="background:#3582fb;color:#fff" onclick="manual('resetAdd')">resetToolbar +按钮</button>
        <button class="danger" style="background:#ef4444;color:#fff" onclick="manual('resetClear')">resetToolbar 清空</button>
      </span>
      <span class="btn-group">
        <button onclick="manual('epShow')" style="background:#22c55e;color:#fff">e&p+显TB</button>
        <button onclick="manual('epHide')" style="background:#f59e0b;color:#fff">e&p+隐TB</button>
      </span>
      <span class="btn-group">
        <button onclick="manual('eoShow')" style="background:#06b6d4;color:#fff">eo+显TB</button>
        <button onclick="manual('eoHide')" style="background:#f97316;color:#fff">eo+隐TB</button>
      </span>
      <span class="btn-group">
        <button onclick="manual('po')" style="background:#8b5cf6;color:#fff">previewOnly</button>
        <button onclick="manual('recover')" style="background:#ec4899;color:#fff">恢复双栏</button>
      </span>
    </div>

    <div id="markdown"></div>

    <!-- 测试报告 -->
    <div id="reportPanel" class="report-panel">
      <div class="report-header" onclick="toggleReport()">
        <div class="report-summary">
          <span id="summaryText">点击展开/折叠报告</span>
          <span id="passStat" class="stat-pass"></span>
          <span id="failStat" class="stat-fail"></span>
          <span id="totalStat" class="stat-total"></span>
        </div>
        <span class="report-toggle" id="toggleLabel">▲ 展开详情</span>
      </div>
      <div id="reportBody" class="report-body"></div>
    </div>

    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
    <script src="../packages/cherry-markdown/dist/cherry-markdown.js"></script>
    <script type="module">
      // ============================================================
      //  测试框架
      // ============================================================
      let testResults = [];

      function assert(condition, message) {
        return { ok: condition, msg: message };
      }

      async function testCase(name, fn) {
        const result = { name, asserts: [], passed: true };
        try {
          const assertions = await fn();
          result.asserts = assertions;
          result.passed = assertions.every(a => a.ok);
        } catch (err) {
          result.asserts = [{ ok: false, msg: `异常: ${err.message}` }];
          result.passed = false;
        }
        testResults.push(result);
        renderResult(result);
        return result.passed;
      }

      function renderResult(r) {
        const body = document.getElementById('reportBody');
        const el = document.createElement('div');
        el.className = `test-case ${r.passed ? 'pass' : 'fail'}`;
        const assertHtml = r.asserts.map(a =>
          `<div class="test-assert ${a.ok ? 'assert-ok' : 'assert-err'}">${a.msg}</div>`
        ).join('');
        el.innerHTML = `
          <div class="test-name">${r.passed ? '✅' : '❌'} ${r.name}</div>
          ${assertHtml}
        `;
        body.appendChild(el);
        updateSummary();
      }

      function updateSummary() {
        const total = testResults.length;
        const pass = testResults.filter(r => r.passed).length;
        const fail = total - pass;
        document.getElementById('passStat').textContent = `PASS ${pass}`;
        document.getElementById('failStat').textContent = `FAIL ${fail}`;
        document.getElementById('totalStat').textContent = `TOTAL ${total}`;
        document.getElementById('summaryText').textContent =
          fail === 0
            ? `🎉 全部通过! (${total}/${total})`
            : `⚠️ ${fail} 个测试未通过 (${pass}/${total})`;
      }

      // ============================================================
      //  辅助:获取当前 DOM 状态快照
      // ============================================================
      function getSnapshot() {
        const c = window.cherry;
        if (!c) return null;
        const wrapper = c.wrapperDom;
        const toolbarEl = wrapper?.querySelector('.cherry-toolbar');
        return {
          shouldHide: c.shouldHideToolbar(),
          noToolbarClass: wrapper?.classList.contains('cherry--no-toolbar') ?? null,
          toolbarInDOM: toolbarEl ? wrapper.contains(toolbarEl) : false,
          toolbarExists: !!toolbarEl,
          toolbarDisplay: toolbarEl ? getComputedStyle(toolbarEl).display : null,
          toolbarConfig: c.options.toolbars.toolbar,
          toolbarRightConfig: c.options.toolbars.toolbarRight,
          previewerHidden: c.previewer?.isPreviewerHidden?.() ?? null,
          childrenOrder: Array.from(wrapper?.children || []).map(el =>
            el.tagName.toLowerCase() + (el.className ? '.' + Array.from(el.classList).slice(0,2).join('.') : '')
          ),
        };
      }

      function snapStr(s) {
        if (!s) return '(null)';
        return JSON.stringify({
          shouldHide: s.shouldHide,
          noTBCss: s.noToolbarClass,
          inDOM: s.toolbarInDOM,
          display: s.toolbarDisplay,
          tbLen: s.toolbarConfig?.length,
          tbrLen: s.toolbarRightConfig?.length,
          prevHidden: s.previewerHidden,
        });
      }

      // ============================================================
      //  Cherry 实例管理
      // ============================================================
      let cherryInstance = null;

      function createCherry(configOverrides = {}) {
        const base = {
          id: 'markdown',
          value: '# Toolbar API Test\n\n## 测试区域\n\n在上方面板操作或点击「运行全部测试」。\n',
          externals: { echarts: window.echarts },
          toolbars: {
            toolbar: [],
            toolbarRight: [],
            bubble: false,
            float: false,
          },
          editor: {
            defaultModel: 'edit&preview',
            codemirror: { placeholder: '输入内容测试...' },
          },
        };
        // 清理旧实例
        if (cherryInstance) {
          try { cherryInstance.destroy(); } catch(e) {}
          document.getElementById('markdown').innerHTML = '';
        }
        cherryInstance = new Cherry(Object.assign({}, base, configOverrides, {
          toolbars: Object.assign({}, base.toolbars, configOverrides.toolbars || {}),
          editor: Object.assign({}, base.editor, configOverrides.editor || {}),
        }));
        window.cherry = cherryInstance;
        // 等待渲染完成
        return new Promise(resolve => setTimeout(resolve, 300));
      }

      window.resetEditor = async function() {
        await createCherry();
        console.log('[RESET] 编辑器已重置');
      };

      // ============================================================
      //  手动操作(供按钮调用)
      // ============================================================
      window.manual = async function(action) {
        switch(action) {
          case 'resetAdd':
            cherryInstance.resetToolbar('toolbar', ['bold','italic','header','list']);
            break;
          case 'resetClear':
            cherryInstance.resetToolbar('toolbar', []);
            break;
          case 'epShow': cherryInstance.switchModel('edit&preview', true); break;
          case 'epHide': cherryInstance.switchModel('edit&preview', false); break;
          case 'eoShow': cherryInstance.switchModel('editOnly', true); break;
          case 'eoHide': cherryInstance.switchModel('editOnly', false); break;
          case 'po': cherryInstance.switchModel('previewOnly'); break;
          case 'recover': cherryInstance.switchModel('edit&preview', true); break;
        }
        logSnap(action);
      };

      function logSnap(label) {
        const s = getSnapshot();
        console.log(`[${label}]`, snapStr(s));
      }

      // ============================================================
      //  测试用例定义
      // ============================================================

      // --- 基础状态测试 ---

      async function T01_初始空工具栏() {
        await createCherry({ toolbars: { toolbar: [], toolbarRight: [] } });
        const s = getSnapshot();
        return [
          assert(s.toolbarConfig.length === 0, `toolbar 长度应为 0,实际 ${s.toolbarConfig?.length}`),
          assert(s.toolbarRightConfig.length === 0, `toolbarRight 长度应为 0`),
          assert(!s.toolbarInDOM || !s.toolbarDisplay || s.toolbarDisplay === 'none',
            `toolbar 不应在页面中可见 (inDOM=${s.toolbarInDOM}, display=${s.toolbarDisplay})`),
        ];
      }

      async function T02_resetToolbar从空添加按钮() {
        await createCherry({ toolbars: { toolbar: [], toolbarRight: [] } });
        cherryInstance.resetToolbar('toolbar', ['bold','italic']);
        await delay(200);
        const s = getSnapshot();
        return [
          assert(s.toolbarConfig.length > 0, `toolbar 应有按钮,实际长度 ${s.toolbarConfig?.length}`),
          assert(s.toolbarInDOM, `toolbar 应在 DOM 树中`),
          assert(s.toolbarDisplay !== 'none', `toolbar display 应不为 none,实际 ${s.toolbarDisplay}`),
        ];
      }

      async function T03_resetToolbar清空() {
        await createCherry({ toolbars: { toolbar: ['bold'], toolbarRight: ['fullScreen'] } });
        cherryInstance.resetToolbar('toolbar', []);
        await delay(200);
        const s = getSnapshot();
        return [
          assert(s.toolbarConfig.length === 0, `toolbar 应为空`),
          assert(s.noToolbarClass, `应有 cherry--no-toolbar 类`),
        ];
      }

      // --- switchModel 测试 ---

      async function T04_switchModel_editPreview_showToolbar() {
        // 初始有工具栏
        await createCherry({ toolbars: { toolbar: ['bold'] } });
        cherryInstance.switchModel('edit&preview', true);
        await delay(150);
        const s = getSnapshot();
        return [
          assert(!s.noToolbarClass, `不应有 cherry--no-toolbar 类`),
          assert(s.toolbarInDOM, `toolbar 应在 DOM 中`),
        ];
      }

      async function T05_switchModel_editPreview_hideToolbar() {
        await createCherry({ toolbars: { toolbar: ['bold'] } });
        cherryInstance.switchModel('edit&preview', false);
        await delay(150);
        const s = getSnapshot();
        return [
          assert(s.noToolbarClass, `应有 cherry--no-toolbar 类 (实际: ${s.noToolbarClass})`),
          // CSS 隐藏但 DOM 仍在树中
          assert(s.toolbarInDOM, `toolbar DOM 应仍在树中(CSS隐藏方式)`),
        ];
      }

      async function T06_switchModel_editOnly_showToolbar() {
        await createCherry({ toolbars: { toolbar: ['bold'] } });
        cherryInstance.switchModel('editOnly', true);
        await delay(150);
        const s = getSnapshot();
        return [
          assert(s.previewerHidden === true, `预览区应被隐藏 (实际: ${s.previewerHidden})`),
          assert(!s.noToolbarClass, `不应有 cherry--no-toolbar 类`),
          assert(s.toolbarInDOM, `toolbar 应在 DOM 中`),
        ];
      }

      async function T07_switchModel_editOnly_hideToolbar() {
        await createCherry({ toolbars: { toolbar: ['bold'] } });
        cherryInstance.switchModel('editOnly', false);
        await delay(150);
        const s = getSnapshot();
        return [
          assert(s.previewerHidden === true, `预览区应被隐藏`),
          assert(s.noToolbarClass, `应有 cherry--no-toolbar 类`),
        ];
      }

      async function T08_switchModel_previewOnly() {
        await createCherry({ toolbars: { toolbar: ['bold'] } });
        cherryInstance.switchModel('previewOnly');
        await delay(150);
        const s = getSnapshot();
        return [
          assert(s.noToolbarClass, `应有 cherry--no-toolbar 类`),
        ];
      }

      // --- 竞态 / 组合操作测试 ---

      async function T09_先resetToolbar后switchModel显示() {
        // 初始空工具栏 → resetToolbar 添加 → switchModel 显示
        await createCherry({ toolbars: { toolbar: [], toolbarRight: [] } });
        cherryInstance.resetToolbar('toolbar', ['bold','italic']);
        await delay(100);
        cherryInstance.switchModel('editOnly', true);
        await delay(150);
        const s = getSnapshot();
        return [
          assert(s.toolbarInDOM, `resetToolbar后 switchModel 显示: toolbar应在DOM中 (inDOM=${s.toolbarInDOM})`),
          assert(!s.noToolbarClass, `不应有 no-toolbar 类`),
          assert(s.previewerHidden === true, `预览区应隐藏`),
        ];
      }

      async function T10_先resetToolbar清空后switchModel隐藏() {
        await createCherry({ toolbars: { toolbar: ['bold'] } });
        cherryInstance.resetToolbar('toolbar', []);
        await delay(100);
        cherryInstance.switchModel('editOnly', false);
        await delay(150);
        const s = getSnapshot();
        return [
          assert(s.noToolbarClass,
            `清空后隐藏: 应有 cherry--no-toolbar 类`),
        ];
      }

      async function T11_多次连续resetToolbar() {
        await createCherry({ toolbars: { toolbar: [], toolbarRight: [] } });
        // 快速连续操作
        cherryInstance.resetToolbar('toolbar', ['bold']);
        await delay(50);
        cherryInstance.resetToolbar('toolbar', ['bold','italic','header']);
        await delay(50);
        cherryInstance.resetToolbar('toolbar', []);
        await delay(200);
        const s = getSnapshot();
        return [
          assert(s.toolbarConfig.length === 0, `最终 toolbar 应为空 (长度=${s.toolbarConfig?.length})`),
          assert(s.noToolbarClass, `最终应有 cherry--no-toolbar 类`),
        ];
      }

      async function T12_空工具栏时switchModel再resetToolbar() {
        // 空初始 → switchModel → resetToolbar 添加
        await createCherry({ toolbars: { toolbar: [], toolbarRight: [] } });
        cherryInstance.switchModel('editOnly', true);  // 尝试显示空工具栏
        await delay(100);
        const s1 = getSnapshot();
        cherryInstance.resetToolbar('toolbar', ['bold']);  // 再动态添加
        await delay(200);
        const s2 = getSnapshot();
        return [
          // 第一步:空配置下 switchModel 无法凭空创建内容(符合设计)
          assert(s2.toolbarInDOM,
            `switchModel后再resetToolbar: toolbar 最终应在 DOM 中 (inDOM=${s2.toolbarInDOM})`),
          assert(s2.toolbarConfig.length > 0,
            `toolbar 配置应有内容 (长度=${s2.toolbarConfig?.length})`),
        ];
      }

      async function T13_toolbarRight单独控制() {
        await createCherry({ toolbars: { toolbar: [], toolbarRight: ['fullScreen'] } });
        await delay(100);
        const s = getSnapshot();
        // toolbarRight 有内容时应触发渲染
        return [
          assert(s.toolbarRightConfig.length > 0, `toolbarRight 有内容`),
          assert(s.shouldHide === false, `shouldHideToolbar 应为 false (实际: ${s.shouldHide})`),
          assert(!s.noToolbarClass, `不应有 cherry--no-toolbar 类`),
        ];
      }

      async function T14_toolbarFalse回退defaultToolbar() {
        await createCherry({ toolbars: { toolbar: false, toolbarRight: false } });
        await delay(100);
        const s = getSnapshot();
        // toolbar:false 回退到 defaultToolbar,应有很多默认按钮
        return [
          assert(Array.isArray(s.toolbarConfig), `toolbar 应为数组`),
          assert(s.toolbarConfig.length > 5,
            `toolbar false 应回退 defaultToolbar (长度>=5, 实际:${s.toolbarConfig?.length})`),
          assert(s.shouldHide === true, `toolbar:false 应隐藏 (实际: ${s.shouldHide})`),
          assert(s.noToolbarClass, `应有 cherry--no-toolbar 类`),
        ];
      }

      async function T15_toolbar和toolbarRight都有内容() {
        await createCherry({ toolbars: { toolbar: ['bold'], toolbarRight: ['theme'] } });
        await delay(100);
        const s = getSnapshot();
        return [
          assert(!s.noToolbarClass, `不应有 cherry--no-toolbar 类`),
          assert(s.shouldHide === false, `shouldHide 应为 false`),
        ];
      }

      async function T16_toolbarFalse且toolbarRight为空() {
        await createCherry({ toolbars: { toolbar: false, toolbarRight: [] } });
        await delay(100);
        const s = getSnapshot();
        return [
          assert(s.noToolbarClass, `应有 cherry--no-toolbar 类`),
          assert(s.shouldHide === true, `shouldHide 应为 true`),
        ];
      }

      async function T17_toolbarFalse且toolbarRight有内容() {
        await createCherry({ toolbars: { toolbar: false, toolbarRight: ['theme'] } });
        await delay(100);
        const s = getSnapshot();
        return [
          assert(s.noToolbarClass, `toolbar:false 禁用整个顶部栏,应有 cherry--no-toolbar`),
          assert(s.shouldHide === true, `shouldHide 应为 true`),
        ];
      }

      async function T18_toolbar有内容且toolbarRightFalse() {
        await createCherry({ toolbars: { toolbar: ['bold'], toolbarRight: false } });
        await delay(100);
        const s = getSnapshot();
        return [
          assert(!s.noToolbarClass, `左侧有按钮,不应有 cherry--no-toolbar`),
          assert(s.shouldHide === false, `shouldHide 应为 false`),
          assert(Array.isArray(s.toolbarRightConfig), `toolbarRight:false 应归一化为数组`),
        ];
      }

      function delay(ms) { return new Promise(r => setTimeout(r, ms)); }

      // ============================================================
      //  运行全部测试
      // ============================================================
      window.runAllTests = async function() {
        const body = document.getElementById('reportBody');
        body.innerHTML = '<div class="test-case"><div class="test-name">⏳ 正在运行...</div></div>';
        testResults = [];
        updateSummary();

        // 展开
        document.getElementById('reportPanel').classList.add('expanded');
        document.getElementById('toggleLabel').textContent = '▼ 折叠';

        // 按顺序执行
        const tests = [
          ['T01 初始空工具栏', T01_初始空工具栏],
          ['T02 resetToolbar 从空→有按钮', T02_resetToolbar从空添加按钮],
          ['T03 resetToolbar 清空', T03_resetToolbar清空],
          ['T04 switchModel edit&preview + show', T04_switchModel_editPreview_showToolbar],
          ['T05 switchModel edit&preview + hide', T05_switchModel_editPreview_hideToolbar],
          ['T06 switchModel editOnly + show', T06_switchModel_editOnly_showToolbar],
          ['T07 switchModel editOnly + hide', T07_switchModel_editOnly_hideToolbar],
          ['T08 switchModel previewOnly', T08_switchModel_previewOnly],
          ['T09 竞态: reset→switchModel显示', T09_先resetToolbar后switchModel显示],
          ['T10 竞态: reset清空→switchModel隐藏', T10_先resetToolbar清空后switchModel隐藏],
          ['T11 连续多次 resetToolbar', T11_多次连续resetToolbar],
          ['T12 空→switchModel→resetToolbar', T12_空工具栏时switchModel再resetToolbar],
          ['T13 toolbarRight 单独控制', T13_toolbarRight单独控制],
          ['T14 toolbar:false 回退逻辑', T14_toolbarFalse回退defaultToolbar],
          ['T15 toolbar+toolbarRight都有内容', T15_toolbar和toolbarRight都有内容],
          ['T16 toolbar:false+toolbarRight:[]', T16_toolbarFalse且toolbarRight为空],
          ['T17 toolbar:false+toolbarRight有内容', T17_toolbarFalse且toolbarRight有内容],
          ['T18 toolbar有内容+toolbarRight:false', T18_toolbar有内容且toolbarRightFalse],
        ];

        for (const [name, fn] of tests) {
          body.innerHTML += `<div class="test-case"><div class="test-name">⏳ 运行: ${name}...</div></div>`;
          await testCase(name, fn);
          await delay(150);  // 给 UI 时间更新
        }

        const passCount = testResults.filter(r => r.passed).length;
        const failCount = tests.length - passCount;
        console.log(`\n===== 测试完成 =====`);
        console.log(`通过: ${passCount} / 失败: ${failCount} / 总计: ${tests.length}`);
      };

      // 折叠/展开报告
      window.toggleReport = function() {
        const panel = document.getElementById('reportPanel');
        const label = document.getElementById('toggleLabel');
        panel.classList.toggle('expanded');
        label.textContent = panel.classList.contains('expanded') ? '▼ 折叠' : '▲ 展开详情';
      };

      // 初始化
      await createCherry();
      console.log('[INIT] Cherry 实例已就绪,初始配置 toolbar:[], toolbarRight:[]');
      console.log('[INFO] 点击 "运行全部测试" 开始自动测试');
    </script>
  </body>
</html>