选择器组件压力测试

覆盖选择器组件边界情况、TypeScript 类型推断与 API 交互的综合性测试。 用于验证 strictNullChecks 缺陷修复,以及 renderValue/placeholder 的行为。

1. strictNullChecks 缺陷

value={objectOrNull} 不应导致 T 被推断为 never。 onValueChange 回调应当收到 User | null。

选择用户(Object | Null)
已选择: 无
const [selected, setSelected] = useState<User | null>(null);

<Select<User>
  label="Select User"
  placeholder="Choose a user..."
  value={selected}
  onValueChange={(value) => setSelected(value)}  // value: User | null
  renderValue={(user) => <span>{user.name}</span>}
>
  {users.map((user) => (
    <Select.Option key={user.id} value={user}>
      {user.name}
    </Select.Option>
  ))}
</Select>

2. renderValue 与 placeholder

renderValue 只会在非空值时被调用。 未选中任何值时显示 placeholder。

选择国家
状态: 未选择(上方应显示占位符)
<Select<Country>
  label="Select Country"
  placeholder="Pick a country..."      // Shows when value is null
  value={country}
  onValueChange={setCountry}
  renderValue={(c) => (                // Only called when c is non-null
    <span>{c.flag} {c.name}</span>
  )}
>
  {countries.map((c) => (
    <Select.Option key={c.code} value={c}>
      {c.flag} {c.name}
    </Select.Option>
  ))}
</Select>

3. items 数组属性

使用 items 属性传入 { label, value } 对象数组。 会自动生成 Select.Option 元素。

优先级(items 数组)
已选择: 无
const priorities: Priority[] = [
  { level: 1, name: "Critical", color: "text-lark-danger" },
  { level: 2, name: "High", color: "text-lark-warning" },
  // ...
];

<Select<Priority>
  label="Priority"
  placeholder="Select priority..."
  value={priority}
  onValueChange={setPriority}
  items={priorities.map((p) => ({
    label: <span className={p.color}>[{p.level}] {p.name}</span>,
    value: p,
  }))}
  renderValue={(p) => (
    <span className={p.color}>[{p.level}] {p.name}</span>
  )}
/>

4. items 对象映射

使用 items 属性传入对象映射。 支持通过 SelectItemDescriptor 表示禁用的选项。

状态(对象映射)
已选择: 无
<Select
  label="Status"
  placeholder="Select status..."
  value={status}
  onValueChange={setStatus}
  items={{
    active: "Active",                              // Simple string label
    pending: "Pending",
    inactive: "Inactive",
    archived: { label: "Archived", disabled: true }, // SelectItemDescriptor
  }}
/>

5. 多选

使用对象取值的 multiple 模式。renderValue 收到的是 T[] 而不是 T。

选择用户(多选)
已选择(0): 无
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);

<Select<User, true>                    // Note: second generic is true
  label="Select Users"
  placeholder="Choose users..."
  multiple                             // Enable multi-select
  value={selectedUsers}                // T[] not T
  onValueChange={setSelectedUsers}     // (users: User[]) => void
  renderValue={(users) => (            // users is User[]
    <span>{users.map((u) => u.name).join(", ")}</span>
  )}
>
  {users.map((user) => (
    <Select.Option key={user.id} value={user}>
      {user.name}
    </Select.Option>
  ))}
</Select>

6. 受控与非受控

在受控(value)与非受控(defaultValue)两种模式之间切换。

受控选择器
受控值: 无
// Controlled: you manage the state
const [value, setValue] = useState<string | null>(null);
<Select
  value={value}
  onValueChange={setValue}
  items={{ apple: "Apple", banana: "Banana" }}
/>

// Uncontrolled: component manages state internally
<Select
  defaultValue="banana"
  items={{ apple: "Apple", banana: "Banana" }}
/>

7. 分组与分隔线

使用复合组件构建结构化的选项列表。

分组选项
已选择: 无
<Select label="Category" placeholder="Select...">
  <Select.Group>
    <Select.GroupLabel>Fruits</Select.GroupLabel>
    <Select.Option value="apple">Apple</Select.Option>
    <Select.Option value="banana">Banana</Select.Option>
  </Select.Group>
  <Select.Separator />
  <Select.Group>
    <Select.GroupLabel>Vegetables</Select.GroupLabel>
    <Select.Option value="carrot">Carrot</Select.Option>
    <Select.Option value="broccoli">Broccoli</Select.Option>
  </Select.Group>
</Select>

8. Field 集成

当传入 label 时会自动包裹 Field 组件。

必填字段

请从列表中选择一个选项。

值: 无 | 有效: 否
<Select
  label="Required Field"
  placeholder="This field is required..."
  required                             // Shows required indicator
  value={value}
  onValueChange={setValue}
  description="Helper text below"      // Shows description
  error={!value ? "Required" : undefined}  // Shows error message
  labelTooltip="Tooltip on hover"      // Info icon with tooltip
  items={{ a: "Option A", b: "Option B" }}
/>

9. 尺寸变体

四种尺寸与 Input 组件的尺寸规范保持一致。

极小(xs)
小(sm)
常规(默认)
大(lg)
<Select size="xs" ... />  // Extra small: h-5 (20px)
<Select size="sm" ... />  // Small: h-6.5 (26px)
<Select size="base" ... /> // Default: h-9 (36px)
<Select size="lg" ... />  // Large: h-10 (40px)

10. 加载状态

加载期间显示骨架屏并禁用交互。

数据加载
const [loading, setLoading] = useState(true);

<Select
  label="Data Loading"
  placeholder="Select when loaded..."
  loading={loading}  // Shows SkeletonLine, disables trigger
  items={{ ... }}
/>

11. 禁用选项

可以通过 SelectItemDescriptor 或 Option 上的 disabled 属性禁用单个选项。

包含禁用选项
已选择: 无
// Via items prop with SelectItemDescriptor
<Select
  items={{
    available: "Available",
    disabled: { label: "Disabled", disabled: true },
  }}
/>

// Via Select.Option prop
<Select>
  <Select.Option value="available">Available</Select.Option>
  <Select.Option value="disabled" disabled>Disabled</Select.Option>
</Select>

12. 类型推断(不显式指定泛型)

在可能的情况下,TypeScript 会根据用法推断类型。

推断类型
推断值: 无
// No explicit <T> - inferred from items/value
const [value, setValue] = useState<string | null>(null);

<Select
  value={value}
  onValueChange={(v) => setValue(v)}  // v inferred as string | null
  items={{ foo: "Foo", bar: "Bar" }}
/>

// With explicit generic for complex types
<Select<User>
  value={user}
  onValueChange={setUser}
  renderValue={(u) => u.name}  // u is User, not unknown
>
  ...
</Select>

测试覆盖概览