Files
admin-web/src/modules/inputPlaceholder.contract.test.ts
T

54 lines
1.9 KiB
TypeScript
Raw Normal View History

2026-09-03 19:29:38 +08:00
import { readFileSync, readdirSync } from 'node:fs'
import { extname, join, relative } from 'node:path'
import { describe, expect, it } from 'vitest'
const modulesRoot = join(process.cwd(), 'src/modules')
function vueFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name)
if (entry.isDirectory()) return vueFiles(path)
return extname(entry.name) === '.vue' ? [path] : []
})
}
function isPermanentlyReadOnly(tag: string): boolean {
return (
/\s(?:disabled|readonly)(?=\s|\/>|>)/.test(tag) ||
/\s(?:disabled|readonly)=(?:""|'')/.test(tag) ||
/\s(?::|v-bind:)(?:disabled|readonly)=(?:"true"|'true')/.test(tag)
)
}
function hasMeaningfulPlaceholder(tag: string): boolean {
const value = tag.match(/\s(?::|v-bind:)?placeholder\s*=\s*(["'])(.*?)\1/)?.[2]
return Boolean(value?.trim())
}
describe('正式业务模块输入提示合同', () => {
2026-10-20 14:52:06 +08:00
it('查询条件的可编辑输入框保留 placeholder', () => {
2026-09-03 19:29:38 +08:00
const violations: string[] = []
for (const file of vueFiles(modulesRoot)) {
const source = readFileSync(file, 'utf8').replace(/<!--[\s\S]*?-->/g, (comment) =>
comment.replace(/[^\n]/g, ' '),
)
const openingTag = /<el-input(?:-number)?\b(?:"[^"]*"|'[^']*'|[^'">])*>/g
for (const match of source.matchAll(openingTag)) {
const tag = match[0]
2026-10-20 14:52:06 +08:00
if (!/\s(?:v-model(?::[\w-]+)?(?:\.[\w-]+)*|:model-value)="[^"]*(?:filter|query|search|keyword)/i.test(tag)) continue
2026-09-03 19:29:38 +08:00
if (isPermanentlyReadOnly(tag)) continue
if (hasMeaningfulPlaceholder(tag)) continue
const line = source.slice(0, match.index).split('\n').length
violations.push(
`${relative(modulesRoot, file).replaceAll('\\', '/')}:${line} ${tag.replace(/\s+/g, ' ')}`,
)
}
}
expect(violations, `缺少 placeholder:\n${violations.join('\n')}`).toEqual([])
})
})