自动化测试 ​

本页给自动化测试提供可视化用例:对照页面上的组件,检查相关属性是否正确。

覆盖范围 ​

组件aria-labelaria-required说明
w-form-item已覆盖下发给内部控件根节点会保留自身 aria-label
w-input已覆盖已覆盖
w-textarea已覆盖已覆盖与 Input 同一套逻辑
w-radio-group已覆盖已覆盖写在组的根节点
w-checkbox-group已覆盖已覆盖写在组的根节点
w-color-picker已覆盖已覆盖触发器 role=button
w-rate已覆盖已覆盖根节点 role=slider
w-segmented已覆盖已覆盖根节点 role=radiogroup
w-slider已覆盖已覆盖范围根 + 两个手柄显式转发
w-input-otp已覆盖已覆盖根节点 role=group
w-tabs已覆盖(页签)不适用tab-pane 的标记落在 role=tab 节点
w-popover已覆盖(回归锁定)不适用不改代码,用例锁住「用户值优先」
w-date-picker已覆盖已覆盖范围拆分 ::开始 / ::结束
w-time-picker已覆盖已覆盖范围拆分;aria-required 为 String
w-upload已覆盖不涉及控件落 .w3-upload;文件项落 li
w-message-box已覆盖不涉及面板根 <值>::二次确认框,dialog 不变
w-message已覆盖不涉及根节点 <值>::消息提示
w-notification已覆盖不涉及根节点 <值>::通知
w-tree已覆盖已覆盖根节点回退 form-item;节点标记固定为 树节点::<node-key 值>
w-cascader已覆盖已覆盖区域取控件值;节点标记固定为 级联节点::<value>
w-table已覆盖—列 → th;行固定 表格行::<row-key>;筛选/列设置浮层有标记
w-table-pro已覆盖—透传 w-table,列/行标记自动继承
w-table-v2已覆盖—列 → div[role=columnheader];行固定 表格行::<row-key>
w-select已覆盖已覆盖区域取控件值 + 选项 下拉选项::<value>;input 唯一命中
w-select-v2已覆盖已覆盖同 select
w-dropdown已覆盖—菜单 ul 经 tooltip 落值

通用规则 ​

控件自己写了就用自己的;没写才用外面 form-item 的;两边都没写,就不输出这个属性。

属性谁说了算出现在哪
aria-label控件自己的 aria-label > form-item 的 aria-label > 不输出控件根节点
aria-required控件自己的 aria-required(含 "false")> form-item 是否必填 > 不输出控件根节点

aria-label ​

患者姓名
控件自身优先
备注

没有可见标签,名称来自 form-item 的 aria-label「表单-性别」

爱好
未打标

不包在 form-item 里,单选组仍是默认值「radio-group」

组件场景控件上的结果
w-form-item写了 aria-label根节点保留 aria-label="表单-患者姓名"
w-input内部没写,外面 form-item 写了aria-label="表单-患者姓名"
w-input自己也写了用自己的:aria-label="控件自身"
w-input两边都不写没有 aria-label
w-textarea内部没写,外面 form-item 写了aria-label="表单-备注"
w-radio-group在 form-item 内aria-label="表单-性别"
w-radio-group不包在 form-item 里仍是默认值 aria-label="radio-group"
w-checkbox-group在 form-item 内aria-label="表单-爱好"
w-tree配置了 node-keydiv[role=treeitem] 为 树节点::<node-key 值>
w-tree根节点没写,外面 form-item 写了div[role=tree] 用 form-item 的值
w-cascader组件写了 aria-label内部 input 拿到该值
w-cascader选项配置了 valueli[role=menuitem] 为 级联节点::<value>

颜色选择器(color-picker) ​

触发器 [role=button]:控件自身 aria-label > form-item 的 aria-label > 默认「颜色选择器」;form-item 用 aria-label 标记时丢弃悬挂的 aria-labelledby(规则 B),用可见 label 文本时保留。

主题色
控件自身优先
品牌颜色

不包 form-item:保持默认文案「颜色选择器」

编号场景期望
L01form-item 仅 aria-label触发器 aria-label="表单-主题色",无 labelledby
L02控件自身 + form-item 都写触发器用自身值 "控件自身"
L03form-item 用可见 label 文本触发器 aria-labelledby 有值
L04不包 form-item保持默认 "颜色选择器"
控制台断言脚本:
js
const q = (sel) => document.querySelector(sel)
const t = (sel) => q(sel)?.querySelector('[role="button"]')
console.assert(
  t('.case-l-color-01')?.getAttribute('aria-label') === '表单-主题色',
  'L01'
)
console.assert(
  !t('.case-l-color-01')?.hasAttribute('aria-labelledby'),
  'L01 labelledby'
)
console.assert(
  t('.case-l-color-02')?.getAttribute('aria-label') === '控件自身',
  'L02'
)
console.assert(!!t('.case-l-color-03')?.getAttribute('aria-labelledby'), 'L03')
console.assert(
  t('.case-l-color-04')?.getAttribute('aria-label') === '颜色选择器',
  'L04'
)

评分(rate) ​

根节点 [role=slider]:自身 > form-item aria-label > 默认「rating」;规则 B 同上。

满意度
控件自身优先
可见标签

不包 form-item:保持默认文案「rating」

编号场景期望
L01form-item 仅 aria-label根 aria-label="表单-评分",无 labelledby
L02控件自身优先根 aria-label="控件自身"
L03可见 label 文本根 aria-labelledby 有值
L04不包 form-item根 aria-label="rating"
控制台断言脚本:
js
const root = (sel) => document.querySelector(sel)?.querySelector('.w3-rate')
console.assert(
  root('.case-l-rate-01')?.getAttribute('aria-label') === '表单-评分',
  'L01'
)
console.assert(
  !root('.case-l-rate-01')?.hasAttribute('aria-labelledby'),
  'L01 labelledby'
)
console.assert(
  root('.case-l-rate-02')?.getAttribute('aria-label') === '控件自身',
  'L02'
)
console.assert(
  !!root('.case-l-rate-03')?.getAttribute('aria-labelledby'),
  'L03'
)
console.assert(
  root('.case-l-rate-04')?.getAttribute('aria-label') === 'rating',
  'L04'
)

分段控制器(segmented) ​

根节点 [role=radiogroup]:自身 > form-item aria-label > 默认「segmented」;规则 B 同上。

视图
控件自身优先
可见标签

不包 form-item:保持默认文案「segmented」

编号场景期望
L01form-item 仅 aria-label根 aria-label="表单-分段选择",无 labelledby
L02控件自身优先根 aria-label="控件自身"
L03可见 label 文本根 aria-labelledby 有值
L04不包 form-item根 aria-label="segmented"
控制台断言脚本:
js
const root = (sel) =>
  document.querySelector(sel)?.querySelector('.w3-segmented')
console.assert(
  root('.case-l-seg-01')?.getAttribute('aria-label') === '表单-分段选择',
  'L01'
)
console.assert(
  !root('.case-l-seg-01')?.hasAttribute('aria-labelledby'),
  'L01 labelledby'
)
console.assert(
  root('.case-l-seg-02')?.getAttribute('aria-label') === '控件自身',
  'L02'
)
console.assert(!!root('.case-l-seg-03')?.getAttribute('aria-labelledby'), 'L03')
console.assert(
  root('.case-l-seg-04')?.getAttribute('aria-label') === 'segmented',
  'L04'
)

滑块(slider) ​

范围模式的根 [role=group] 与两个 [role=slider] 手柄是独立节点:根取控件自身 > form-item aria-label > i18n 默认;手柄在有可拆分的基础值时输出 <值>::开始 / <值>::结束,rangeStartLabel / rangeEndLabel 优先;form-item 用可见 label 文本时根走 aria-labelledby,手柄保留自身 i18n 默认名。

单值
0
100
控件自身优先
0
100
范围模式
0
100

范围模式两个手柄分别带「{aria-label}::开始 / ::结束」

价格区间
0
100

不包 form-item:保持原有 i18n 默认

0
100
编号场景期望
L01单值 + form-item aria-label手柄 aria-label="表单-音量"
L02范围 + 控件自身 aria-label手柄 控件自身::开始 / 控件自身::结束
L03范围 + form-item 可见 label 文本根 aria-labelledby 有值;手柄保持 选择起始值/选择结束值
L04范围 + form-item 仅 aria-label根 "价格";手柄 价格::开始 / 价格::结束
L05裸用根 滑块介于 0 至 100;手柄 选择起始值/选择结束值
控制台断言脚本:
js
const handles = (sel) => [
  ...(document.querySelector(sel)?.querySelectorAll('[role="slider"]') || []),
]
const group = (sel) =>
  document.querySelector(sel)?.querySelector('[role="group"]')
console.assert(
  handles('.case-l-slider-01')[0]?.getAttribute('aria-label') === '表单-音量',
  'L01'
)
const h2 = handles('.case-l-slider-02')
console.assert(
  h2[0]?.getAttribute('aria-label') === '控件自身::开始',
  'L02 start'
)
console.assert(
  h2[1]?.getAttribute('aria-label') === '控件自身::结束',
  'L02 end'
)
console.assert(
  !!group('.case-l-slider-03')?.getAttribute('aria-labelledby'),
  'L03 root'
)
const h3 = handles('.case-l-slider-03')
console.assert(
  h3[0]?.getAttribute('aria-label') === '选择起始值',
  'L03 start default'
)
console.assert(
  h3[1]?.getAttribute('aria-label') === '选择结束值',
  'L03 end default'
)
console.assert(
  group('.case-l-slider-04')?.getAttribute('aria-label') === '价格',
  'L04 root'
)
const h4 = handles('.case-l-slider-04')
console.assert(h4[0]?.getAttribute('aria-label') === '价格::开始', 'L04 start')
console.assert(h4[1]?.getAttribute('aria-label') === '价格::结束', 'L04 end')
const h5 = handles('.case-l-slider-05')
console.assert(h5[0]?.getAttribute('aria-label') === '选择起始值', 'L05 start')
console.assert(h5[1]?.getAttribute('aria-label') === '选择结束值', 'L05 end')

一次性密码框(input-otp) ​

根节点 [role=group]:自身 > form-item aria-label > 默认「一次性密码输入框」;规则 B 同上。

短信验证码
控件自身优先
可见标签

不包 form-item:保持默认文案「一次性密码输入框」

编号场景期望
L01form-item 仅 aria-label根 aria-label="表单-验证码",无 labelledby
L02控件自身优先根 aria-label="控件自身"
L03可见 label 文本根 aria-labelledby 有值
L04不包 form-item根 aria-label="一次性密码输入框"
控制台断言脚本:
js
const root = (sel) =>
  document.querySelector(sel)?.querySelector('.w3-input-otp')
console.assert(
  root('.case-l-otp-01')?.getAttribute('aria-label') === '表单-验证码',
  'L01'
)
console.assert(
  !root('.case-l-otp-01')?.hasAttribute('aria-labelledby'),
  'L01 labelledby'
)
console.assert(
  root('.case-l-otp-02')?.getAttribute('aria-label') === '控件自身',
  'L02'
)
console.assert(!!root('.case-l-otp-03')?.getAttribute('aria-labelledby'), 'L03')
console.assert(
  root('.case-l-otp-04')?.getAttribute('aria-label') === '一次性密码输入框',
  'L04'
)

页签(tabs) ​

w-tab-pane 支持 aria-label:配置后渲染到对应 div[role=tab] 节点;多页签各自独立、互不覆盖;未配置时不输出该属性(与改造前一致)。取值规则为外部传值优先,组件不推导、不校验标记值。

页签打标:`w-tab-pane` 的 `aria-label` 渲染到对应 `div[role=tab]`, 多页签各自独立、互不覆盖;未配置则不输出该属性(与改造前一致)。

门诊患者内容

以上示例:前两个页签打了标记,第三个未打标。控制台断言脚本见本页「tabs」小节。

编号场景期望判定
T-01单个页签写 aria-labelrole=tab 节点 aria-label 等于配置值attributes("aria-label") === "页签-门诊患者"
T-02多页签各自写 aria-label各页签独立取值、互不覆盖两个 role=tab 节点分别等于各自配置值
T-03页签未写 aria-label不输出该属性(与改造前一致)aria-label 为 undefined(属性不存在)
T-04打标后既有属性aria-controls / aria-selected 不受影响值与改造前一致
控制台断言脚本:
js
const tabs = [...document.querySelectorAll('.case-t01 [role="tab"]')]
console.assert(
  tabs[0]?.getAttribute('aria-label') === '页签-门诊患者',
  'FAIL T-01'
)
console.assert(
  tabs[1]?.getAttribute('aria-label') === '页签-住院患者',
  'FAIL T-02'
)
console.assert(tabs[2] && !tabs[2].hasAttribute('aria-label'), 'FAIL T-03')
console.assert(
  tabs[0]?.getAttribute('aria-controls') === 'pane-outpatient',
  'FAIL T-04'
)
console.log(
  'tabs 断言完成:',
  tabs.map((t) => t.getAttribute('aria-label'))
)

气泡卡(popover) ​

浮层根节点([role=tooltip])的 aria-label 取值规则:外部传值优先,未传保持现状。title 与用户传入的 aria-label 并存时用户值优先(运行时实测,方案原判断「title 覆盖用户值」已被证伪)。popover 不改模板,本节用例仅锁住既有行为、防止回归。

气泡卡取值规则:外部传值优先,未传保持现状。popover 未声明 `aria-label` prop,用户传值经 attrs 落到浮层根节点,优先于 `title`; 只有 `title` 时用 `title`;都不传则不输出。

本示例统一带 `teleported="false"`,浮层渲染在各 case 容器内,便于按容器定位断言(打标行为与默认 teleport 一致)。

编号场景期望判定
P-01仅 title浮层 aria-label 等于 titleclick 后 querySelector("[role=tooltip]") 读取
P-02仅用户传 aria-label浮层 aria-label 等于用户值同上
P-03title + 用户值并存用户值优先浮层 aria-label === 用户值(非 title)
P-04都不传浮层不输出 aria-label(与改造前一致)hasAttribute("aria-label") === false
控制台断言脚本(示例将浮层置于容器内 teleported=false,persistent 下节点常驻 DOM,点击前后均可读取):
js
const q = (sel) => document.querySelector(sel)
console.assert(
  q('.case-p01 [role="tooltip"]')?.getAttribute('aria-label') === '提示-标题',
  'FAIL P-01'
)
console.assert(
  q('.case-p02 [role="tooltip"]')?.getAttribute('aria-label') ===
    '提示-用户标记',
  'FAIL P-02'
)
console.assert(
  q('.case-p03 [role="tooltip"]')?.getAttribute('aria-label') ===
    '提示-用户标记',
  'FAIL P-03'
)
console.assert(
  q('.case-p04 [role="tooltip"]') &&
    !q('.case-p04 [role="tooltip"]').hasAttribute('aria-label'),
  'FAIL P-04'
)
console.log('popover 断言完成')

DatePicker 日期范围 ​

范围形态有两个原生 input,标签按 <值>::开始 / <值>::结束 拆分,保证同一标签全页唯一;单日期不拆分。取值:控件自身 aria-label > form-item > 不输出。

就诊日期(自身标签)
-
表单下发
-
自身优先
-
单日期不拆分
未配置省略
-
编号场景期望判定
L-DP-01范围,业务写 aria-label开始框 <值>::开始,结束框 <值>::结束两个框后缀各一,且无同名重复命中
L-DP-02范围,仅 form-item 写 aria-label两个框继承 form-item 值并拆分[0] 以 ::开始 结尾,[1] 以 ::结束 结尾
L-DP-03范围,form-item 与组件都写取组件自身的值两个框均以组件值开头
L-DP-04单日期(非 range)写 aria-label不拆分,原样绑定(w-input 既有行为)input 的 aria-label 不含 ::
L-DP-05范围,两边都不写属性省略,页面不出现字符串 "undefined"无 aria-label 属性
控制台断言脚本:
js
const q = (sel) => document.querySelectorAll(sel)
const label = (inp) => inp.getAttribute('aria-label')
const a = q('.case-l-dp-01 input')
console.assert(
  a.length === 2 &&
    label(a[0]) === '就诊日期::开始' &&
    label(a[1]) === '就诊日期::结束',
  'L-DP-01'
)
console.assert(q('[aria-label="就诊日期"]').length === 0, 'L-DP-01 唯一命中')
const b = q('.case-l-dp-02 input')
console.assert(
  label(b[0]) === '表单-就诊日期::开始' &&
    label(b[1]) === '表单-就诊日期::结束',
  'L-DP-02'
)
const c = q('.case-l-dp-03 input')
console.assert(
  label(c[0]) === '控件自身::开始' && label(c[1]) === '控件自身::结束',
  'L-DP-03'
)
const d = q('.case-l-dp-04 input')
console.assert(d.length === 1 && label(d[0]) === '单日期', 'L-DP-04')
const e = q('.case-l-dp-05 input')
console.assert(e.length === 2 && !label(e[0]) && !label(e[1]), 'L-DP-05')

TimePicker 时间范围 ​

与 DatePicker 共用范围触发器(timerange),拆分与回退规则一致。

就诊时间(自身标签)
-
表单下发
-
自身优先
-
编号场景期望判定
L-TP-01范围,业务写 aria-label::开始 / ::结束同 L-DP-01
L-TP-02范围,仅 form-item 写继承并拆分同 L-DP-02
L-TP-03范围,自身值优先用组件值拆分同 L-DP-03
控制台断言脚本:
js
const q = (sel) => document.querySelectorAll(sel)
const label = (inp) => inp.getAttribute('aria-label')
const a = q('.case-l-tp-01 input')
console.assert(
  label(a[0]) === '就诊时间::开始' && label(a[1]) === '就诊时间::结束',
  'L-TP-01'
)
const b = q('.case-l-tp-02 input')
console.assert(
  label(b[0]) === '表单-就诊时间::开始' &&
    label(b[1]) === '表单-就诊时间::结束',
  'L-TP-02'
)
const c = q('.case-l-tp-03 input')
console.assert(
  label(c[0]) === '控件自身::开始' && label(c[1]) === '控件自身::结束',
  'L-TP-03'
)

Select ​

w-select 的标记链路:区域标记前缀取业务传入值 —— 选择区根节点为 <值>::选择区,下拉选项区 ul[role=listbox] 为 <值>::下拉选项区(未传 aria-label 时不输出区域标记);选项节点按 value 固定渲染为 下拉选项::<value>,数据里的 'aria-label' 不生效;控件内部 input 的 aria-label 支持 form-item 回退并收敛到唯一命中。

① 数据选项:按 value 固定渲染 下拉选项::<value>,数据里的 aria-label 不生效

药物过敏史
选择过敏史

② form-item 回退:控件未写,取外层 form-item 的值

城市
选择城市

③ 控件自身优先:两边都写时用自己的值(唯一命中)

科室
选择科室

④ 模板式选项:同样按 value 固定,外部 aria-label 不生效

选择方案

⑤ filterable + 分组数据:同样透传与回退

搜索药品
编号场景期望判定
L01选项数据有 valueli[role=option] 为 下拉选项::<value>展开后首项 aria-label === '下拉选项::1'
L02form-item 写 aria-label内部 input 输出 form-item 的值document.querySelector('.case-d02 input') 带值
L03form-item 与控件都写取控件自身值,且全页唯一命中[aria-label="控件-科室"] 命中数 === 1
L04模板式 <w-option aria-label>外部值不生效,仍按 value 固定展开后首项 aria-label === '下拉选项::1'
L05分组数据组内/散项均按 value 固定命中 aria-label="下拉选项::1"
L-R1区域标记(取控件值)选择区根节点 <值>::选择区.case-d01 .w3-select === 药物过敏史::选择区
L-R2区域标记(取控件值)展开后 listbox 为 <值>::下拉选项区listbox === 药物过敏史::下拉选项区
L-NEG数据里写 'aria-label'不生效(节点只按 value 生成)[aria-label="过敏史-青霉素"] 不存在
L-UNI控件传 aria-label 并展开命中唯一为内部 input(修复前为 input+listbox 2 个)findAll('[aria-label=…]').length === 1
控制台断言脚本(手动展开对应下拉后再执行):
js
const assert = (name, cond) => console.log(`${cond ? 'PASS' : 'FAIL'} ${name}`)
// 展开「药物过敏史」后执行:
assert(
  'L01 选项按 value 固定打标',
  document
    .querySelector('.w3-select-dropdown__item')
    ?.getAttribute('aria-label') === '下拉选项::1'
)
assert(
  'L02 form-item 回退',
  document.querySelector('.case-d02 input')?.getAttribute('aria-label') ===
    '表单-城市'
)
assert(
  'L03 控件自身优先',
  document.querySelector('.case-d03 input')?.getAttribute('aria-label') ===
    '控件-科室'
)
assert(
  'L-R1 选择区标记取控件值',
  document.querySelector('.case-d01 .w3-select')?.getAttribute('aria-label') ===
    '药物过敏史::选择区'
)
assert(
  'L-R2 下拉选项区标记取控件值',
  document.querySelector('[role=listbox]')?.getAttribute('aria-label') ===
    '药物过敏史::下拉选项区'
)
assert(
  'L-UNI 唯一命中(含展开后)',
  document.querySelectorAll('[aria-label="控件-科室"]').length === 1
)
// 展开「选择方案」「搜索药品」后执行:
assert(
  'L04 模板式选项按 value 固定',
  [...document.querySelectorAll('.w3-select-dropdown__item')].some(
    (li) =>
      li.textContent.trim() === '门诊' &&
      li.getAttribute('aria-label') === '下拉选项::1'
  )
)
assert(
  'L05 分组数据按 value 固定',
  [...document.querySelectorAll('.w3-select-dropdown__item')].some(
    (li) =>
      li.textContent.trim() === '阿司匹林' &&
      li.getAttribute('aria-label') === '下拉选项::1'
  )
)
assert(
  'L-NEG 数据 aria-label 不生效',
  !document.querySelector('[aria-label="过敏史-青霉素"]')
)

SelectV2 ​

w-select-v2 与 select 同构:区域标记前缀取业务传入值 —— 选择区根节点为 <值>::选择区,下拉选项区 listbox 为 <值>::下拉选项区(未传 aria-label 时不输出);选项按 value 固定为 下拉选项::<value>,数据里的 'aria-label' 不生效;内部 input 的 aria-label 支持 form-item 回退且唯一命中。

① 数据选项:按 value 固定渲染 下拉选项::<value>,数据里的 aria-label 不生效

药物过敏史
选择过敏史

② form-item 回退:控件未写,取外层 form-item 的值

城市
选择城市

③ 控件自身优先:两边都写时用自己的值(唯一命中)

科室
选择科室

④ 数据项未写 aria-label:不输出属性

病区
选择病区

⑤ 分组数据:组内/散项同样透传

选择药品
编号场景期望判定
V01选项数据有 valueli[role=option] 为 下拉选项::<value>展开后首项 aria-label === '下拉选项::1'
V02form-item 写 aria-label,控件未写内部 input 输出 form-item 的值.case-v02 input 带 表单-V2城市
V03form-item 与控件都写取控件自身值,且展开后全页唯一命中[aria-label="控件-V2科室"] 命中数 === 1
V04数据里写 'aria-label'不生效(节点只按 value 生成)[aria-label="过敏史V2-青霉素"] 不存在
V05分组数据组内/散项均按 value 固定命中 aria-label="下拉选项::1"
V-R1区域标记(取控件值)选择区根节点 <值>::选择区.case-v01 .w3-select === 药物过敏史V2::选择区
V-R2区域标记(取控件值)展开后 listbox 为 <值>::下拉选项区listbox === 药物过敏史V2::下拉选项区
V-UNI控件传 aria-label 并展开唯一命中为内部 input(修复前为 input + listbox 2 个)findAll('[aria-label=…]').length === 1
控制台断言脚本(手动展开对应下拉后再执行):
js
const assert = (name, cond) => console.log(`${cond ? 'PASS' : 'FAIL'} ${name}`)
// 展开「药物过敏史」后执行:
assert(
  'V01 选项按 value 固定打标',
  document
    .querySelector('.w3-select-dropdown__item')
    ?.getAttribute('aria-label') === '下拉选项::1'
)
assert(
  'V02 form-item 回退',
  document.querySelector('.case-v02 input')?.getAttribute('aria-label') ===
    '表单-V2城市'
)
assert(
  'V03 控件自身优先',
  document.querySelector('.case-v03 input')?.getAttribute('aria-label') ===
    '控件-V2科室'
)
assert(
  'V-R1 选择区标记取控件值',
  document.querySelector('.case-v01 .w3-select')?.getAttribute('aria-label') ===
    '药物过敏史V2::选择区'
)
assert(
  'V-R2 下拉选项区标记取控件值',
  document.querySelector('[role=listbox]')?.getAttribute('aria-label') ===
    '药物过敏史V2::下拉选项区'
)
assert(
  'V-UNI 唯一命中(含展开后)',
  document.querySelectorAll('[aria-label="控件-V2科室"]').length === 1
)
// 展开「选择药品」「选择病区」后执行:
assert(
  'V05 分组数据按 value 固定',
  [...document.querySelectorAll('.w3-select-dropdown__item')].some(
    (li) =>
      li.textContent.trim() === '阿司匹林' &&
      li.getAttribute('aria-label') === '下拉选项::1'
  )
)
assert(
  'V04 数据 aria-label 不生效',
  !document.querySelector('[aria-label="过敏史V2-青霉素"]')
)

w-dropdown 的标记链路:区域标记前缀取业务传入值 —— 触发区根节点为 <dropdown 值>::触发区,菜单区 ul[role=menu] 为 <值>::下拉菜单区(菜单自身值 > dropdown 值;无值不输出);菜单项按 command 固定为 下拉菜单项::<command>,业务写的 aria-label 不生效。菜单 ul 的 aria 属性由 popper 的 triggerTargetEl watcher 接管,因此统一经 w-tooltip 的 ariaLabel 落值。

① 只给 w-dropdown 传 aria-label:展开后 ul[role=menu] 带该值

② dropdown 与 dropdown-menu 同时传:菜单自身值优先

③ 都不传:菜单 ul 无 aria-label(与改造前一致)

④ 回归:触发器自身 aria-label 不变,ul 的 aria-labelledby 保留

编号场景期望判定
D01只给 w-dropdown 传 aria-label菜单区为 操作菜单::下拉菜单区ul[aria-label="操作菜单::下拉菜单区"] 存在
D02dropdown 与 dropdown-menu 都传菜单区取菜单自身值命中 菜单自身标记::下拉菜单区
D03都不传两个区域都不输出根节点与 ul 均无 aria-label
D04回归:触发器自身打标触发器 aria-label 不变触发按钮属性保持业务原值
D05回归:ul 的 aria-labelledby保留并指向触发器 idaria-labelledby === 触发器 id
D-R1触发区(取 dropdown 值)根节点 <值>::触发区.case-d01 根 === 操作菜单::触发区
D06菜单项(取 command)li[role=menuitem] 为 下拉菜单项::<command>首项 === 下拉菜单项::a
控制台断言脚本(手动展开对应菜单后再执行):
js
const assert = (name, cond) => console.log(`${cond ? 'PASS' : 'FAIL'} ${name}`)
// 展开「更多操作」后执行:
assert(
  'D01 菜单区取 dropdown 值',
  !!document.querySelector('ul[role=menu][aria-label="操作菜单::下拉菜单区"]')
)
assert(
  'D-R1 触发区取 dropdown 值',
  document.querySelector('.case-d01')?.getAttribute('aria-label') ===
    '操作菜单::触发区'
)
assert(
  'D06 菜单项按 command 固定',
  document.querySelector('[role=menuitem]')?.getAttribute('aria-label') ===
    '下拉菜单项::a'
)
// 展开「批量处理」后执行:
assert(
  'D02 菜单区取菜单自身值',
  !!document.querySelector(
    'ul[role=menu][aria-label="菜单自身标记::下拉菜单区"]'
  )
)
// 展开「随访操作」后执行:
const trigger = document.querySelector('.case-d04 button')
const menu = document.querySelector('ul[role=menu]')
assert(
  'D04 触发器 aria-label 不变',
  trigger?.getAttribute('aria-label') === '触发器-展开随访菜单'
)
assert(
  'D05 aria-labelledby 保留',
  menu?.getAttribute('aria-labelledby') === trigger?.id
)

tree ​

节点标记固定为 树节点::<node-key 值>(未配置 node-key 时不输出);树根 div[role=tree] 依次回退组件自身与外层 form-item(控件自身值优先)。数据里 自带的 aria-label 不生效。

地区
浙江省
杭州市
另一个节点
自定义标签

未配置 node-key:根节点与节点都不输出 aria-label, 也不会出现 undefined 字符串

编号场景期望判定
T-L01根没写,外层 form-item 写 表单-地区根 div[role=tree] 输出 aria-label="表单-地区"控制台脚本 [T-L01] PASS
T-R01form-item 必填根节点输出 aria-required="true"控制台脚本 [T-R01] PASS
T-L02配置了 node-key节点标记为 树节点::<node-key 值>控制台脚本 [T-L02] PASS
T-L03未配置 node-key该节点不输出 aria-label 属性控制台脚本 [T-L03] PASS
T-L04form-item 与树都写取树自己的 aria-label="树自身"控制台脚本 [T-L04] PASS
T-L05两边都不写属性省略,页面上没有 "undefined" 字符串控制台脚本 [T-L05] PASS
T-L06全页唯一aria-label="树自身" 只命中 1 个节点控制台脚本 [T-L06] PASS
控制台断言脚本(浏览器打开本页,按 F12 粘贴运行):
js
;(() => {
  const log = (id, desc, pass) =>
    console[pass ? 'log' : 'error'](
      `[${id}] ${desc}: ${pass ? 'PASS' : 'FAIL'}`
    )
  const root1 = document.querySelector('.case-t01 [role=tree]')
  log(
    'T-L01',
    '根节点回退 form-item aria-label',
    root1?.getAttribute('aria-label') === '表单-地区'
  )
  log(
    'T-R01',
    '根节点回退 form-item 必填状态',
    root1?.getAttribute('aria-required') === 'true'
  )
  log(
    'T-L02',
    '节点标记为 树节点::<node-key 值>',
    [...document.querySelectorAll('.case-t01 [role=treeitem]')].some(
      (el) => el.getAttribute('aria-label') === '树节点::zj'
    )
  )
  log(
    'T-L03',
    '未配置 node-key 时节点不输出 aria-label',
    [...document.querySelectorAll('.case-t03 [role=treeitem]')].every(
      (el) => !el.hasAttribute('aria-label')
    )
  )
  const root2 = document.querySelector('.case-t02 [role=tree]')
  log(
    'T-L04',
    '控件自身值优先于 form-item 回退',
    root2?.getAttribute('aria-label') === '树自身'
  )
  const root3 = document.querySelector('.case-t03 [role=tree]')
  log(
    'T-L05',
    '两边不写时属性省略(无 undefined 字符串)',
    !root3?.hasAttribute('aria-label') &&
      !document.body.innerHTML.includes('aria-label="undefined"')
  )
  log(
    'T-L06',
    '全页唯一:值只落 1 个节点',
    document.querySelectorAll('[aria-label="树自身"]').length === 1
  )
})()

cascader ​

组件声明 ariaLabel prop 后业务值落到内部 input(form-item 回退由 ARIA-01 在 w-input 内部实现);区域标记前缀取传入值 —— 触发区根节点 <值>::选择区, 下拉浮层 <值>::下拉选项区(无值不输出);业务直写的 aria-required 经 $attrs 转发给 w-input;节点标记固定为 级联节点::<选项 value 值>,选项数据里的 aria-label 不生效。

地区
部门

裸用(不在 form-item 内)直接写 aria-required:转发到内部 input

组件没写 aria-label:内部 input 不输出,选项节点仍按 value 固定打标

编号场景期望判定
C-L01组件没写,外层 form-item 写 表单-地区内部 input 输出该值控制台脚本 [C-L01] PASS
C-R01form-item 必填内部 input 输出 aria-required="true"控制台脚本 [C-R01] PASS
C-L02form-item 与 cascader 都写取 cascader 自己的 aria-label="级联自身"控制台脚本 [C-L02] PASS
C-R02裸用直写 aria-required="true"内部 input 输出 aria-required="true"控制台脚本 [C-R02] PASS
C-L03裸用不写 aria-label内部 input 不输出 aria-label控制台脚本 [C-L03] PASS
C-L04选项配置了 valueli[role=menuitem] 为 级联节点::<value>控制台脚本 [C-L04] PASS
C-L05组件和 form-item 都没写 aria-label节点仍按 value 打标,仅 input 不输出控制台脚本 [C-L05] PASS
C-L06自身值落点aria-label="级联自身" 恰好 1 处(内部 input)控制台脚本 [C-L06] PASS
C-L07打开下拉浮层.w3-cascader__dropdown 为 <值>::下拉选项区控制台脚本 [C-L07] PASS
C-L08触发区根节点.w3-cascader 为 <值>::选择区控制台脚本 [C-L08] PASS
控制台断言脚本(打开本页,点开展开面板的用例前后各跑一次即可;脚本会自动展开面板):
js
;(async () => {
  const log = (id, desc, pass) =>
    console[pass ? 'log' : 'error'](
      `[${id}] ${desc}: ${pass ? 'PASS' : 'FAIL'}`
    )
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
  const input1 = document.querySelector('.case-c01 input')
  log(
    'C-L01',
    'input 回退 form-item aria-label',
    input1?.getAttribute('aria-label') === '表单-地区'
  )
  log(
    'C-R01',
    'input 回退 form-item 必填状态',
    input1?.getAttribute('aria-required') === 'true'
  )
  const input2 = document.querySelector('.case-c02 input')
  log(
    'C-L02',
    'cascader 自身值优先',
    input2?.getAttribute('aria-label') === '级联自身'
  )
  const input3 = document.querySelector('.case-c03 input')
  log(
    'C-R02',
    '裸用直写 aria-required 转发到内部 input',
    input3?.getAttribute('aria-required') === 'true'
  )
  log(
    'C-L03',
    '裸用不写时 input 无 aria-label',
    !input3?.hasAttribute('aria-label')
  )
  document.querySelector('.case-c01 .w3-cascader')?.click()
  await sleep(300)
  log(
    'C-L04',
    '节点标记等于 级联节点::<value>',
    [...document.querySelectorAll('.w3-cascader-node')].some(
      (el) => el.getAttribute('aria-label') === '级联节点::zj'
    )
  )
  log(
    'C-L07',
    '浮层为 <值>::下拉选项区',
    [...document.querySelectorAll('.w3-cascader__dropdown')].some(
      (el) => el.getAttribute('aria-label') === '表单-地区::下拉选项区'
    )
  )
  log(
    'C-L08',
    '触发区为 <值>::选择区',
    document
      .querySelector('.case-c01 .w3-cascader')
      ?.getAttribute('aria-label') === '表单-地区::选择区'
  )
  document.querySelector('.case-c04 .w3-cascader')?.click()
  await sleep(300)
  log(
    'C-L05',
    '未写 aria-label 时节点仍按 value 打标',
    [...document.querySelectorAll('.w3-cascader-node')].some(
      (el) =>
        el.textContent.includes('江苏省') &&
        el.getAttribute('aria-label') === '级联节点::js'
    )
  )
  log(
    'C-L06',
    '自身值只落内部 input(1 处)',
    document.querySelectorAll('[aria-label="级联自身"]').length === 1
  )
})()

表格族(table / table-pro / table-v2) ​

列配置写了 aria-label 就落到表头节点;行节点固定为 表格行::<row-key 值>(未配置 row-key 时不输出),不读取行数据的 aria-label;筛选浮层固定 筛选::<列标识>(列标识取列 aria-label,未配置回退列字段名),列设置浮层固定 列设置。组件不从 label 或单元格内容自动生成标记。

w-table:列 aria-label → th,行固定 表格行::<row-key> → tr

w-table-v2:列标记 → div[role=columnheader],行固定 表格行::<row-key> → div[role=row]

P-001
张三
P-002
李四
P-003
王五
病历号
姓名

w-table-pro:透传基础表格打标(不改代码)

暂无数据

浮层:筛选 → 筛选::<列标识>,列设置 → 列设置

编号组件场景期望判定
T-01w-table列写了 aria-labelth[aria-label] 等于配置值find('th').attributes('aria-label')
T-02w-table列未写th 没有 aria-label属性为 undefined
T-03w-table配置了 row-keytbody tr[aria-label] 为 表格行::<row-key 值>find('tbody tr').attributes('aria-label')
T-04w-table-v2列写了 ariaLabeldiv[role=columnheader][aria-label] 命中document.querySelector
T-05w-table-v2配置了 row-keydiv[role=row][aria-label] 为 表格行::<row-key 值>document.querySelector
T-06w-table-v2虚拟滚动后目标行滚动后仍可按 aria-label 定位先滚动再 querySelector
T-07w-table-pro列/行打标内层 w-table 自动继承(透传,不改代码)同 T-01/T-03
T-08w-table默认渲染表头与内容两个 table 固定带 aria-label="表头" / "表格内容"table[aria-label]
T-09w-table-v2默认渲染表头与内容两个 div[role=rowgroup] 固定带 aria-label="表头" / "表格内容"[role=rowgroup][aria-label]
T-10w-table筛选浮层[role=dialog] 带 aria-label="筛选::列-病历号"document.querySelector
T-11w-table列设置浮层[role=dialog] 带 aria-label="列设置"document.querySelector
控制台断言脚本(页面加载后执行,全部输出 PASS 即通过;vitest 侧已有对应用例且全绿):
js
const r = (name, ok) => console.log(ok ? 'PASS' : 'FAIL', name)
r(
  'T-01',
  document.querySelector('.case-t01 thead th')?.getAttribute('aria-label') ===
    '列-病历号'
)
r(
  'T-02',
  !document
    .querySelector('.case-t01 thead th:nth-child(2)')
    ?.hasAttribute('aria-label')
)
r(
  'T-03',
  document.querySelector('.case-t01 tbody tr')?.getAttribute('aria-label') ===
    '表格行::P-001'
)
r(
  'T-04',
  !!document.querySelector(
    '.case-t02 [role=columnheader][aria-label="列-V2病历号"]'
  )
)
r(
  'T-05',
  !!document.querySelector('.case-t02 [role=row][aria-label="表格行::P-001"]')
)
r(
  'T-07',
  !!document.querySelector('.case-t03 thead th[aria-label="列-病历号Pro"]')
)
r(
  'T-08',
  !!document.querySelector('.case-t01 table[aria-label="表头"]') &&
    !!document.querySelector('.case-t01 table[aria-label="表格内容"]')
)
r(
  'T-09',
  !!document.querySelector('.case-t02 [role=rowgroup][aria-label="表头"]') &&
    !!document.querySelector('.case-t02 [role=rowgroup][aria-label="表格内容"]')
)
// 浮层 teleport 到 body,需全局查询
r(
  'T-10',
  !!document.querySelector('[role=dialog][aria-label="筛选::列-病历号"]')
)
r('T-11', !!document.querySelector('[role=dialog][aria-label="列设置"]'))

上传(upload) ​

组件标签上的 aria-label 作为前缀:上传触发区 .w3-upload(role="button")标记为 <值>::上传操作,删除节点标记为 <值>::删除操作(文案/图片列表的关闭图标、picture-card 的删除按钮);未配置时触发区不输出,删除图标保留自身 i18n「删除」。UploadFile 的 'aria-label' 字段仍渲染到对应文件列表项 li.w3-upload-list__item,两者互不影响。

文件项配置了 'aria-label',列表项 li 会输出该属性

未配置 'aria-label':li 上没有该属性(与改造前一致)

picture-card 模式:li 带标记,删除按钮自身不受影响

  • 按 delete 键可删除

标签上写 aria-label:上传触发区为 <值>::上传操作,删除节点为 <值>::删除操作

编号场景期望判定
U-01文件配置了 'aria-label'li 上输出该值document.querySelector('li.w3-upload-list__item').getAttribute('aria-label')
U-02文件未配置li 上没有 aria-labelgetAttribute('aria-label') 返回 null
U-03listType text / picture / picture-card三种模式 li 均按配置输出切换示例中三种列表类型分别断言
U-04picture-card 删除节点(已配置标签)为 <值>::删除操作.case-u03 .w3-upload-list__item-delete 的 aria-label
U-05文案列表关闭图标(已配置标签)为 <值>::删除操作.case-u04 .w3-icon--close 的 aria-label
U-06标签上写 aria-label.w3-upload 为 <值>::上传操作,外层包裹 div 不输出document.querySelector('.case-u04 .w3-upload').getAttribute('aria-label')
U-07标签未写.w3-upload 上没有 aria-labelhasAttribute('aria-label') === false
U-08标签未写时的删除节点关闭图标保留 i18n「删除」.case-u01 .w3-icon--close 的 aria-label
控制台断言脚本:
js
const li = document.querySelector('li.w3-upload-list__item')
console.assert(li?.getAttribute('aria-label') === '上传文件标记', 'U-01 失败')
const items = document.querySelectorAll('li.w3-upload-list__item')
const plain = [...items].find((el) => el !== li)
console.assert(plain && !plain.hasAttribute('aria-label'), 'U-02 失败')
const labeled = document.querySelector('.case-u04 .w3-upload')
console.assert(
  labeled?.getAttribute('aria-label') === '上传附件::上传操作',
  'U-06 失败'
)
console.assert(
  labeled?.parentElement?.getAttribute('aria-label') === null,
  'U-06 落点失败(外层包裹 div 不应带标记)'
)
console.assert(
  !document.querySelector('.case-u01 .w3-upload')?.hasAttribute('aria-label'),
  'U-07 失败'
)
console.assert(
  document
    .querySelector('.case-u03 .w3-upload-list__item-delete')
    ?.getAttribute('aria-label') === '照片墙::删除操作',
  'U-04 失败'
)
console.assert(
  document
    .querySelector('.case-u04 .w3-icon--close')
    ?.getAttribute('aria-label') === '上传附件::删除操作',
  'U-05 失败'
)
console.assert(
  document
    .querySelector('.case-u01 .w3-icon--close')
    ?.getAttribute('aria-label') === '删除',
  'U-08 失败'
)
console.log('U-01/02/04/05/06/07/08 断言通过')

二次确认框(message-box) ​

MessageBoxOptions 支持 ariaLabel 与 'aria-label',面板根节点 .w3-message-box 输出 <值>::二次确认框;未传时不输出。外层 [role=dialog] 仍以 title 兜底,行为不变。

打开 alert 与「未传」用例后,控制台按值查找 [...document.querySelectorAll('.w3-message-box')] 断言;外层 .w3-overlay-message-box[role=dialog] 仍以 title 兜底。

编号场景期望判定
M-01alert 传 ariaLabel面板为 <值>::二次确认框[...querySelectorAll('.w3-message-box')].some(带该值)
M-02confirm 传 'aria-label'同上(kebab 键等价)同 M-01
M-03prompt 传 ariaLabel同上同 M-01
M-04未传面板无 aria-labelhasAttribute('aria-label') === false
M-05外层 dialogaria-label 仍等于 title.w3-overlay-message-box[role=dialog] 的 aria-label
控制台断言脚本(先点开 alert 与「未传」两个按钮;多个面板会同时留在页面上,按值查找而不是取第一个):
js
const panels = [...document.querySelectorAll('.w3-message-box')]
console.assert(
  panels.some((el) => el.getAttribute('aria-label') === '面板标记::二次确认框'),
  'M-01 失败'
)
console.assert(
  panels.some((el) => !el.hasAttribute('aria-label')),
  'M-04 失败'
)
const dialog = document.querySelector('.w3-overlay-message-box[role=dialog]')
console.assert(dialog?.getAttribute('aria-label') === '标题文本', 'M-05 失败')
console.log('M-01/M-04/M-05 断言通过')

消息提示(message) ​

组件声明 ariaLabel prop(useAriaProps(['ariaLabel'])),根节点 .w3-message 输出 <值>::消息提示;组件式 attrs 透传与方法式 options 均生效,「声明后仍可用」为回归重点。

控制台执行 document.querySelector('.w3-message').getAttribute('aria-label') 断言;未传时该属性不输出。组件式 attrs 透传(S-03)用下方脚本验证。

编号场景期望判定
S-01方法式 options 传 ariaLabel根节点输出 <值>::消息提示document.querySelector('.w3-message').getAttribute('aria-label')
S-02未传不输出该属性hasAttribute('aria-label') === false
S-03组件式 attrs 'aria-label'仍生效(透传)mount(WMessage, { attrs: { 'aria-label' } }) 单测(message-aria.test.tsx)
S-04role=alert不变getAttribute('role') === 'alert'
控制台断言脚本:
js
const msg = [...document.querySelectorAll('.w3-message')].find(
  (el) => el.getAttribute('aria-label') === '消息标记::消息提示'
)
console.assert(msg, 'S-01 失败')
console.assert(msg?.getAttribute('role') === 'alert', 'S-04 失败')
console.log('S-01/S-04 断言通过')

通知(notification) ​

组件声明 ariaLabel prop,根节点 .w3-notification 输出 <值>::通知;$notify options 与组件式均生效,未传不输出。

控制台执行 document.querySelector('.w3-notification').getAttribute('aria-label') 断言;未传时该属性不输出,role="alert" 行为不变。

编号场景预期判定
N-01$notify options 传 ariaLabel根节点输出 <值>::通知document.querySelector('.w3-notification').getAttribute('aria-label')
N-02未传不输出该属性hasAttribute('aria-label') === false
N-03组件式 attrs 'aria-label'仍生效mount(WNotification, { attrs: { 'aria-label' } }) 单测(notification-aria.test.tsx)
N-04role=alert不变getAttribute('role') === 'alert'
控制台断言脚本:
js
const n = [...document.querySelectorAll('.w3-notification')].find(
  (el) => el.getAttribute('aria-label') === '通知标记::通知'
)
console.assert(n, 'N-01 失败')
console.assert(n?.getAttribute('role') === 'alert', 'N-04 失败')
console.log('N-01/N-04 断言通过')

aria-required ​

form-item 加了 required(或校验判定为必填),内部控件会带上 aria-required="true"。

必填输入
非必填输入
用户值优先
必填备注
必填单选
必填复选
动态必填
组件场景控件上的结果
w-inputform-item 标了 requiredaria-required="true"
w-input非必填没有 aria-required
w-input必填,但自己写了 aria-required="false"用自己的:false
w-input点击按钮切换 requiredaria-required 跟着变
w-input单独使用并自己写了保留用户写的值
w-textareaform-item 必填aria-required="true"
w-radio-groupform-item 必填组根节点 aria-required="true"
w-checkbox-groupform-item 必填组根节点 aria-required="true"
w-treeform-item 必填根节点 div[role=tree] 输出 aria-required="true"
w-cascaderform-item 必填内部 input 输出 aria-required="true"(ARIA-01 回退)
w-cascader裸用并直写 aria-required="true"转发给内部 input,输出 aria-required="true"
以上组件任意必填场景都没有原生 required

扩展输入(color-picker / rate / segmented / slider / input-otp) ​

用户直写 aria-required(含 "false")优先,组件不转真值、不新增 ariaRequired prop;未传时 form-item 必填才输出 "true",非必填不输出,且不使用原生 required。

组件承载节点必填时输出
w-color-picker触发器 [role=button]aria-required="true"
w-rate根 [role=slider]aria-required="true"
w-segmented根 [role=radiogroup]aria-required="true"
w-slider范围根 + 两个手柄(共 3 个)三处均 aria-required="true"
w-input-otp根 [role=group]aria-required="true"
编号场景期望
----------------------------------------------------------------------
R01form-item 必填上表承载节点输出 "true"
R02form-item 非必填不输出 aria-required
R03裸用 aria-required="false"用户值原样保留 "false";必填时也优先
以上四个单节点组件的取值由 packages/components/*/__tests__/*-aria.test.tsx 锁定;slider 另有可视化用例:

滑块(slider) ​

slider 的两个手柄是嵌套节点,根节点的透传属性到不了,必须显式转发;范围模式必填时 [aria-required] 命中根 + 两个手柄(共 3 个)。

价格区间
0
100

aria-required 命中根节点 + 两个手柄(共 3 个)

数量
0
100
非必填
0
100
裸用 aria-required="false":用户值原样保留
编号场景期望
R01form-item 必填 + 范围根与两个手柄共 3 个 aria-required="true"
R02form-item 必填 + 单值手柄 aria-required="true"(根非承载节点,不带 role)
R03form-item 非必填该用例内无 aria-required
R04裸用 aria-required="false"用户值原样保留 "false"
控制台断言脚本:
js
const d = document.querySelector('.case-r-slider-01')?.closest('.example')
console.assert(
  d?.querySelectorAll('.case-r-slider-01 [aria-required]').length === 3,
  'R01 count'
)
;[...(d?.querySelectorAll('.case-r-slider-01 [aria-required]') || [])].forEach(
  (n) => console.assert(n.getAttribute('aria-required') === 'true', 'R01 value')
)
console.assert(
  d?.querySelectorAll('.case-r-slider-03 [aria-required]').length === 0,
  'R03'
)
console.assert(
  d
    ?.querySelector('.case-r-slider-04 .w3-rate')
    ?.getAttribute('aria-required') === 'false',
  'R04'
)

DatePicker 日期范围 ​

aria-required 为 String 型:业务直写(含 "false")原样输出,未写时回退 form-item 必填状态;单日期 input 与范围两个 input 都会带上,未配置时属性省略且不使用原生 required。

表单必填
-
直写必填(单日期)
直写必填(范围)
-
直写 false 原样保留
自身 false 优先于必填
-
未配置省略
-
编号场景期望判定
R-DP-01form-item 标 required,范围形态两个框均 aria-required="true"[0]/[1] 均为 "true"
R-DP-02业务直写 aria-required="true"单日期 input 与范围两个框均 "true"命中数 1 / 2
R-DP-03业务直写 aria-required="false""false" 原样保留(String 型,不被布尔化)值为 "false"
R-DP-04form-item 必填但业务写 "false"自身值优先,仍为 "false"两个框均 "false"
R-DP-05未配置(非必填、未写)属性省略,无原生 required,不出现 "undefined"属性为空
控制台断言脚本:
js
const q = (sel) => document.querySelectorAll(sel)
const req = (inp) => inp.getAttribute('aria-required')
const a = q('.case-r-dp-01 input')
console.assert(req(a[0]) === 'true' && req(a[1]) === 'true', 'R-DP-01')
const b1 = q('.case-r-dp-02-single input')
const b2 = q('.case-r-dp-02-range input')
console.assert(req(b1[0]) === 'true', 'R-DP-02 单日期')
console.assert(req(b2[0]) === 'true' && req(b2[1]) === 'true', 'R-DP-02 范围')
const c = q('.case-r-dp-03 input')
console.assert(req(c[0]) === 'false', 'R-DP-03')
const d = q('.case-r-dp-04 input')
console.assert(req(d[0]) === 'false' && req(d[1]) === 'false', 'R-DP-04')
const e = q('.case-r-dp-05 input')
console.assert(
  e.every((i) => req(i) === null && i.getAttribute('required') === null),
  'R-DP-05'
)

TimePicker 时间范围 ​

与 DatePicker 共用同一套取值与下发逻辑(timerange)。

表单必填
-
直写必填
-
编号场景期望判定
R-TP-01form-item 标 required,范围两个框均 "true"同 R-DP-01
R-TP-02业务直写 aria-required="true"两个框均 "true"同 R-DP-02
控制台断言脚本:
js
const q = (sel) => document.querySelectorAll(sel)
const req = (inp) => inp.getAttribute('aria-required')
const a = q('.case-r-tp-01 input')
console.assert(req(a[0]) === 'true' && req(a[1]) === 'true', 'R-TP-01')
const b = q('.case-r-tp-02 input')
console.assert(req(b[0]) === 'true' && req(b[1]) === 'true', 'R-TP-02')

Copyright 2025 Winning Health 版权所有