Ctrl K
ComunidadGuías…

frontend/test/card.test.ts

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/* 共用卡面。
 *
 * 钉的是「卡片是一块填充面」这件事本身:Board 的卡从来不是描边空心壳,迁移时每页各拼一串
 * `rounded-2xl border border-separator-border`,十个文件二十处,整个管理区掉了一层底色。
 * 所以这里除了变体,还扫一遍 React 子树,拦住下一个自己拼卡面的人。 */
import { readdirSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';

import { expect, it } from 'vitest';

import { cardClass } from '../src/react/components/card';

const classes = (options?: Parameters<typeof cardClass>[0]) =>
  new Set(cardClass(options).split(' '));

it('默认那一档卡是填充的,没有描边', () => {
  const card = classes();
  expect(card).toContain('bg-background-secondary-default');
  expect(card).toContain('shadow-card');
  expect([...card].filter((name) => name.startsWith('border'))).toEqual([]);
});

it('圆角与内边距按变体给', () => {
  expect(classes()).toContain('rounded-2xl');
  expect(classes()).toContain('p-5');
  // 图表卡大一档,和旧 `.board-radial-card{border-radius:20px}` 一致。
  expect(classes({ radius: 'chart' })).toContain('rounded-2-5xl');
  // 头、体、脚各自排内边距的卡(读数卡、分区)自己不留。
  expect(classes({ padding: 'none' })).not.toContain('p-5');
});

it('描边卡是 primary 底加一条发丝线,不是透明空心壳', () => {
  const outlined = classes({ variant: 'outlined' });
  expect(outlined).toContain('bg-background-primary-default');
  expect(outlined).toContain('border-separator-border');
  expect(outlined).not.toContain('bg-background-secondary-default');
});

/* 旧 `.reviewitem` 收的是 `--surface-radius` 那一档,也没有接触阴影:阴影是给填充卡
   「浮在次级底上」用的,描边卡本来就靠线收边,再压一层影子等于两套收边办法叠着用。 */
it('描边卡自己带 14px 圆角,不跟填充卡那一档走,也不压阴影', () => {
  const outlined = classes({ variant: 'outlined' });
  expect(outlined).toContain('rounded-surface');
  expect(outlined).not.toContain('shadow-card');
  // 点名要别的圆角时仍然听调用方的。
  expect(classes({ variant: 'outlined', radius: 'chart' })).toContain('rounded-2-5xl');
});

it('卡里再浮一层是 primary 底、不画线', () => {
  const raised = classes({ variant: 'raised' });
  expect(raised).toContain('bg-background-primary-default');
  expect([...raised].filter((name) => name.startsWith('border'))).toEqual([]);
});

it('填充卡之上另加的那条描边不换底', () => {
  const fieldset = classes({ bordered: 'line' });
  expect(fieldset).toContain('bg-background-secondary-default');
  expect(fieldset).toContain('border-border-button-default');
  expect(classes({ bordered: 'soft' })).toContain('border-separator-border');
  // 失败卡的框线要有宽度才画得出来,且画成不占盒子的 outline,不把内容挤进 1px。
  const failed = [...classes({ bordered: 'error' })];
  expect(failed.filter((name) => name.startsWith('border'))).toEqual([]);
  expect(failed).toEqual(expect.arrayContaining(['outline-1', '-outline-offset-1', 'outline-border-error-default']));
});

/* 环画在 `::after` 覆盖层上:inset 的 box-shadow 画在子元素底下,脚注带一铺底色就盖掉环的下半截。 */
it('选中态在最上层压一圈 2px 描边,收掉接触阴影,悬停不盖选中', () => {
  const tab = cardClass({ interactive: true, selectable: true });
  expect(tab).toContain('relative');
  expect(tab).toContain('data-selected:shadow-none');
  expect(tab).toContain('data-selected:after:absolute');
  expect(tab).toContain('data-selected:after:inset-0');
  expect(tab).toContain('data-selected:after:z-1');
  expect(tab).toContain('data-selected:after:rounded-[inherit]');
  expect(tab).toContain('data-selected:after:border-2');
  expect(tab).toContain('data-selected:after:border-border-focus-ring');
  expect(tab).not.toContain('ring-inset');
  // 悬停那条写成 `not-data-selected:`,不指望工具类的先后顺序。
  expect(tab).toContain('not-data-selected:hover:bg-card-hover');
  expect(tab).not.toContain(' hover:bg-card-hover');
});

/** React 子树里除 BoardUI 源码外的 `.tsx`。 */
function sources(dir: string): string[] {
  return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
    const path = resolve(dir, entry.name);
    if (entry.isDirectory()) return entry.name === 'boardui' ? [] : sources(path);
    return entry.name.endsWith('.tsx') ? [path] : [];
  });
}

it('页面不自己拼卡面', () => {
  const offenders = sources(resolve(process.cwd(), 'src/react'))
    .filter((path) => {
      const code = readFileSync(path, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
      // 描边配上卡片那两档圆角(16px、20px)就是一张卡,卡面走 `components/card.tsx`。
      // 剩下的 `border-t` / `border-b` / `border-y` 是分隔线,`rounded-2lg` 是卡里的小块,
      // `shadow-dropdown` 是浮层——浮层本来就描边。
      return /className=\{?"[^"]*\bborder border-/.test(code)
        && /className=\{?"[^"]*\brounded-(?:2xl|2-5xl)\b/.test(code)
        && !code.includes('shadow-dropdown');
    })
    .map((path) => path.split(/[\\/]/).slice(-2).join('/'));
  expect(offenders).toEqual([]);
});