Verification system
Verification in lh-ui is not a formality — every check guards a class of regression that has actually happened before. This page explains item by item what is checked and how to read failure output.
verify:dts — output type consistency
Section titled “verify:dts — output type consistency”pnpm verify:dtsWhat it checks: the compiled .d.ts files in es/ / lib/ are consistent with the source type declarations, and the files pointed to by the exports keys really exist.
Typical failures:
- The source types were changed but
pnpm buildwas not re-run (the output is stale). sync:exportsgenerated an exports key, but the corresponding file was never built (a misassembled path).import typewas written asimport, leaving an extra copy of runtime code in the output.
Fix: re-run pnpm build in full, then pnpm verify:dts.
verify:styles — six checks
Section titled “verify:styles — six checks”pnpm verify:stylesScript: scripts/verify-styles.mjs. The six checks run in order; the first failure exits.
1. EP palette
Section titled “1. EP palette”A blacklist check: any of Element Plus’s 9 legacy color values (#409eff, #e6a23c, #f56c6c, #67c23a, #909399, #606266, #303133, #ebeef5, #c0c4cc) appearing in the scss files under styles/ fails the check. This is a regression guard from the D2 cleanup, keeping the old palette from creeping back — every lh-ui color must come from the token definitions in styles/theme/common/variables.scss.
2. var fallback
Section titled “2. var fallback”Checks that every var(--lh-*) in component scss carries a fallback value:
// ✅ passescolor: var(--lh-text-primary, rgba(0, 0, 0, 0.88));
// ❌ failscolor: var(--lh-text-primary);Why: when a token is missing (the user imports only part of the styles, or an older version lacks the token), a var() without a fallback resolves to an invalid value and the declaration is dropped — text becomes default black, borders vanish. A fallback guarantees “graceful degradation”.
3. @use tokens
Section titled “3. @use tokens”Checks that component scss imports the token layer through @use rather than scattering hex literals:
// ✅ passes@use 'theme/common/var' as *;.lh-card { background: var(--lh-bg-color, #fff); }
// ❌ fails.lh-card { background: #ffffff; }Exemption: hex is allowed inside a var() fallback (that is what the fallback is for). Failure output lists the file and line number.
4. Aggregation consistency
Section titled “4. Aggregation consistency”Checks that styles/index.scss (the full aggregation) matches “the component list obtained by scanning styles/*/index.scss”. styles/index.scss is generated by scripts/build-styles.mjs — do not hand-edit it; failing to re-run the generator after adding a component directory makes the two disagree.
5. Transition class names single-sourced
Section titled “5. Transition class names single-sourced”Checks component scss for bare -enter-from / -leave-from literals:
// ❌ fails: hand-written duplicate transition class names.lh-dialog-fade-enter-from { opacity: 0; }
// ✅ passes: using the mixin@include lh-enter-both('.lh-dialog-fade') { opacity: 0; }Why: Vue2 uses -enter, Vue3 / React use -enter-from; hand-writing both inevitably misses one port (see transition-mixins). 16 components once each wrote both sets, and fixing one place missed another.
6. Dark accessibility
Section titled “6. Dark accessibility”Checks that tokens in var.scss whose value looks like #{$lh-foo} (baked into a light value at compile time) have a matching override in var-dark.scss.
This is the most critical check. Gaps of this kind once left menu / table / timeline text in dark mode using the light values, rendering it invisible (black text on a black background). See the compile-time baking trap in Dark mode.
Note: there was once a false-positive fix — an off-by-one index in verify:styles falsely flagged a no-fallback exemption line (commit 791ff32). If a failure message looks unreasonable (an exempt line still being reported), first confirm whether it is this known issue.
verify:theme — the AA contrast gate
Section titled “verify:theme — the AA contrast gate”pnpm build # ← must run first; it reads the es/ outputpnpm verify:themeScript: scripts/verify-theme-contrast.mjs.
What it checks: across the five built-in themes (and their dark variants), every “text / background” pair has a WCAG contrast ratio ≥ 4.5:1 (level AA).
Why it must read the es/ output: a preset theme’s vars only reach their final form after the build (the derivation results of buildPresetDarkVars are also in the output).
Typical failures:
- A new preset’s light primary was not given
--lh-color-on-primary(white text on a light primary only reaches 3.x:1). - The selected-state text color (
--lh-selected-color) has insufficient contrast against the hover background.
Fix: darken / lighten the relevant colors until the contrast passes. getContrastRatio() and getRelativeLuminance() (exported from src/theme/themes/index.ts) can compute it locally, so you need not run the full gate every time.
CI does not run this check — it needs a build first and is not cheap. Guard it manually before a local release.
verify:versions — version consistency
Section titled “verify:versions — version consistency”pnpm verify:versionsScript: scripts/check-version-sync.mjs.
What it checks: the @lh-ui/share dependency range in lh-ui-plus’s / lh-ui-vue2’s package.json covers share’s current version.
Current status: passes while share is 0.3.0. It will fail after 0.4.0 is published — because both UI libraries’ dependency ranges are still ^0.3.0. This is expected behavior: per red line 5 (share is released first, the UI libraries follow), after a release you go to each sibling repo and run pnpm up @lh-ui/share@0.4.0 plus a regression pass.
lint and typecheck
Section titled “lint and typecheck”| Command | Scan scope |
|---|---|
pnpm lint |
src / tests / scripts / vite.config.ts / vitest.config.ts |
pnpm typecheck |
src/**/* (the include of tsconfig.json) |
docs/ is not in the scan scope — the docs site is a standalone Astro project whose quality is guarded by pnpm docs:build. Likewise styles/ is not in the typecheck scope (SCSS is handled by verify:styles).
lint’s framework-dependency gate: CI greps src/ for import 'vue' / import 'react' / @dcloudio/* — this is red line 1; core must be framework-agnostic.
pnpm testtests/unit/ is the single copy of core unit tests (red line 3): all three libraries share the results, and UI libraries must not copy them. Testing pure functions needs no component rendering — construct literal rects, assert the output, and all four ports share the same assertions.
Key test files:
| File | Coverage |
|---|---|
theme.spec.ts |
preset consistency (two-source sync between JS vars and SCSS variables) |
core/*.spec.ts |
each core component’s logic |
shared/*.spec.ts |
shared kernel algorithms (popper / keyboard / virtual, etc.) |
Running the whole verification suite
Section titled “Running the whole verification suite”The complete checklist before a release:
pnpm installpnpm lintpnpm typecheckpnpm testpnpm build # note that sync:exports modifies package.jsonpnpm verify:dtspnpm verify:styles # the six checkspnpm verify:theme # requires a build firstpnpm verify:versions # turns into downstream work after a releasepnpm docs:build # the docs siteprepublishOnly (triggered by pnpm release) includes only build + verify:dts + verify:styles. So test / lint / typecheck / verify:theme must be run explicitly by hand.
Related documentation
Section titled “Related documentation”Copyright © 2024-2026 FengMo WenXue