// tests/audit-demo-disabled-style.test.ts
// -----------------------------------------------------------------------------
// `scripts/audit-demo-disabled-style.mjs` 的**整脚本** fixture-root 回归面（[[INFRA-F138]]）。
//
// ⛔ 刻意不 import 闸导出的符号 —— 那样摘掉 `main()` 的接线照样全绿，正是 F138 要治的。
//    这里一律 spawn 整个脚本，判据逻辑 + 接线 + 出口码一起覆盖。
//
// 覆盖的六件事，每件都有**红/绿两臂或一个明确的 must-not-fire**：
//   ① 绿档非空过：自印读数必须是 **fixture 自己的**（文件数 / 命中数），且 stdout 非空
//      —— 后者是入口守卫的钉：守卫判假时闸 exit 0 且**零输出**，长得和「通过」一模一样。
//   ② S2 命中即红，且**逐字点名**文件:行 + 选择器。
//   ③ must-not-fire：`:hover` 不该红（本闸 2026-09-14 落地时 owner 拍「先只上禁用态那一半」）。
//   ④ **`@media` 穿透**：违例藏在 at-rule 块里照样要红。
//      🔴 这是回归钉，不是补充用例 —— 闸的第一版**真的漏了它**（只按花括号深度切分），
//      是 S0 已知向量自测当场抓到的；而真仓库里 `playground/docs/docs.css:1444` 那条
//      `.framework-switch--disabled` 恰恰就在 `@media` 里。
//   ⑤ S1 分母 fail-closed：扫描根不存在 ⇒ 红，⛔ 不是「扫到 0 个所以通过」。
//   ⑥ S3 豁免表 shrink-only：豁免行不再命中 ⇒ 红并要求删行。
import { describe, it, expect, afterEach } from 'vitest'
import {
  createGateFixture,
  writeFixtureFile,
  runGate,
  expectGateRed,
  expectGateGreen,
  cleanupGateFixtures,
} from './lib/gate-fixture-root'
import { rmSync } from 'node:fs'
import { join } from 'node:path'

const GATE = 'scripts/audit-demo-disabled-style.mjs'

/**
 * 闸的豁免表是脚本内常量，其唯一一行指向 `playground/docs/docs.css` 的
 * `framework-switch--disabled`。⇒ **每个 fixture 都必须复现它**，否则 S3（shrink-only）
 * 会因「豁免行不命中」而红，把别的用例的信号盖住。
 * ⚠️ 这不是测试的负担，是它顺带把「豁免表真的在生效」也钉住了 —— ⑥ 号用例正是拿掉它。
 */
const DOCS_CSS_WITH_EXEMPT_RULE = `
.framework-switch--disabled .locale-switch__button {
  color: var(--text-placeholder);
}
`

function makeFixture(files: Record<string, string> = {}) {
  return createGateFixture({
    gate: GATE,
    prefix: 'demo-disabled-style',
    dirs: ['playground/docs', 'react-pilot/src/demos'],
    files: {
      'playground/docs/docs.css': DOCS_CSS_WITH_EXEMPT_RULE,
      'react-pilot/src/demos/button-demo.css': '.btn-stage { display: flex; }\n',
      ...files,
    },
  })
}

afterEach(() => cleanupGateFixtures())

describe('audit:demo-disabled-style', () => {
  it('① 绿档：读数来自 fixture 自己，且 stdout 非空（入口守卫的钉）', () => {
    const root = makeFixture()
    const run = runGate(root, GATE)

    // 入口守卫判假 ⇒ exit 0 + 零输出。只断言退出码分不出这两种。
    expect(run.stdout.length).toBeGreaterThan(0)
    expectGateGreen(run, {
      contains: [
        'S0 量具自测 : 切出选择器 10/10 · 禁用态命中 7/7',
        '样式源文件 : 2（css 2 · vue 0）', // fixture 自己的数，⛔ 不是真仓库的 78
        '禁用态命中 : 1',
        '豁免       : 1 行（命中 1）',
      ],
    })
  })

  it('② S2：手写控件自定义禁用态样式 ⇒ 红并逐字点名', () => {
    const root = makeFixture({
      'react-pilot/src/demos/form-demo.css': '.form-demo-button:disabled {\n  color: grey;\n}\n',
    })
    const run = runGate(root, GATE)
    expectGateRed(run, {
      checks: [
        '[S2]',
        // 该 fixture 无前导空行 ⇒ 违例在第 1 行。行号是判据的一部分（要能指到实处），
        // 所以这里钉死具体数字而不是只匹配文件名。
        'react-pilot/src/demos/form-demo.css:1',
        '.form-demo-button:disabled',
      ],
    })
  })

  it('③ must-not-fire：只写 :hover 不该红（本闸只管禁用态那一半）', () => {
    const root = makeFixture({
      'react-pilot/src/demos/form-demo.css': '.form-demo-button:hover {\n  color: blue;\n}\n',
    })
    expectGateGreen(runGate(root, GATE), { contains: ['禁用态命中 : 1'] })
  })

  it('④ @media 穿透：违例藏在 at-rule 块里照样红（第一版真漏了这个）', () => {
    const root = makeFixture({
      'react-pilot/src/demos/form-demo.css':
        '@media (max-width: 600px) {\n  .form-demo-button:disabled {\n    color: grey;\n  }\n}\n',
    })
    expectGateRed(runGate(root, GATE), {
      checks: ['[S2]', '.form-demo-button:disabled'],
    })
  })

  it('⑤ S1 fail-closed：扫描根不存在 ⇒ 红，⛔ 不当「0 命中所以通过」', () => {
    const root = makeFixture()
    rmSync(join(root, 'react-pilot'), { recursive: true, force: true })
    expectGateRed(runGate(root, GATE), {
      checks: ['[S1]', 'react-pilot/src/demos', '不当「0 命中所以通过」'],
    })
  })

  it('⑥ S3 shrink-only：豁免行不再命中 ⇒ 红并要求删行', () => {
    const root = makeFixture()
    // 把豁免指向的那条规则拿掉 ⇒ 那一行豁免成了死行
    writeFixtureFile(root, 'playground/docs/docs.css', '.docs-shell { display: block; }\n')
    expectGateRed(runGate(root, GATE), {
      checks: ['[S3]', 'framework-switch--disabled', '请删掉该行'],
    })
  })
})
