49 lines
4.0 KiB
JavaScript
49 lines
4.0 KiB
JavaScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
const root=process.cwd(); const failures=[]
|
|
const read=p=>fs.readFileSync(path.join(root,p),'utf8')
|
|
const must=(text,needles,scope)=>needles.forEach(n=>{if(!text.includes(n))failures.push(`${scope} missing ${n}`)})
|
|
|
|
const screenFrame=read('packages/kbx-ui/src/components/KbxScreenFrame.vue')
|
|
must(screenFrame,['expectedType?: KbxScreenType','templateMismatch','화면 템플릿 구성이 올바르지 않습니다.','data-template="templateCode"'],'ScreenFrame template contract')
|
|
const templateMap={
|
|
KbxListPage:['list','T01'],KbxMasterPage:['master','T02'],KbxTransactionPage:['transaction','T03'],KbxFastEntryPage:['fast-entry','T04'],
|
|
KbxMasterDetailPage:['master-detail','T05'],KbxQueuePage:['queue','T06'],KbxReconcilePage:['reconcile','T07'],KbxImportPage:['import','T08'],
|
|
}
|
|
for(const [name,[type,code]] of Object.entries(templateMap)){
|
|
const text=read(`packages/kbx-ui/src/components/${name}.vue`)
|
|
must(text,[`expected-type="${type}"`,`template-code="${code}"`],name)
|
|
}
|
|
const mobile=read('packages/kbx-ui/src/wms/KbxWmsMobilePage.vue')
|
|
must(mobile,["props.screen.type!=='wms-mobile'",'현장 화면 구성이 올바르지 않습니다.'],'T09 template contract')
|
|
|
|
const searchPanel=read('packages/kbx-ui/src/components/KbxSearchPanel.vue')
|
|
const searchField=read('packages/kbx-ui/src/components/KbxSearchFieldControl.vue')
|
|
must(searchPanel,['KbxSearchFieldControl','v-for="field in primary"','v-for="field in secondary"','role="search"'],'SearchPanel composition')
|
|
must(searchField,["field.type==='date-range'","field.type==='lookup'",'`${field.label} 시작일`','`${field.label} 종료일`'],'SearchFieldControl')
|
|
if(searchPanel.includes("field.type==='text'")||searchPanel.includes("field.type==='select'")||searchPanel.includes("field.type==='lookup'"))failures.push('SearchPanel must delegate field-type rendering to KbxSearchFieldControl')
|
|
|
|
const homeLogic=read('packages/kbx-ui/src/shell/homeNavigation.ts')
|
|
must(homeLogic,['launchKey:tab.key','seenLaunchKeys','coveredScreenIds','addTab(tab','instanceLabel:routeInstanceLabel','Boolean(b.pinned)'],'Home instance workbench')
|
|
const home=read('packages/kbx-ui/src/shell/KbxHomePage.vue')
|
|
must(home,[':key="item.launchKey"','item.instanceLabel','firstDirty','class="resume"'],'Home workbench UI')
|
|
if(home.includes('<code>{{item.screenId}}</code>')||home.includes('<small>{{entry.screenId}}</small>'))failures.push('Home must not expose ScreenId in normal browse surfaces')
|
|
|
|
const tabs=read('packages/kbx-ui/src/shell/KbxWorkspaceTabs.vue')
|
|
must(tabs,['tabButtons','selectAndFocus','ArrowLeft','ArrowRight','togglePin','aria-pressed="Boolean(tab.pinned)"'],'Workspace tabs')
|
|
if(tabs.includes("event.key==='Delete'"))failures.push('Workspace tabs must not overload Delete as close')
|
|
const store=read('apps/web/src/shell/workspaceStore.ts')
|
|
must(store,['togglePinned(tabKey:string)','!tab.dirty&&!tab.pinned'],'workspace pin policy')
|
|
const shell=read('packages/kbx-ui/src/shell/KbxApplicationShell.vue')
|
|
must(shell,['본문 바로가기','id="kbx-main-workspace"','toggleTabPin','@toggle-pin'],'ApplicationShell accessibility/pin contract')
|
|
const app=read('apps/web/src/shell/KbxAppFrame.vue')
|
|
must(app,["@toggle-tab-pin=\"tab=>store.togglePinned(tab.key)\"","resolveKbxSafeRecentPath(navEntry,safe)"],'AppFrame pin/recent persistence')
|
|
|
|
const menu=read('packages/kbx-ui/src/shell/KbxMenuSearch.vue')
|
|
must(menu,['restoreFocus','document.activeElement','검색 결과 {{results.length}}건'],'MenuSearch focus restoration')
|
|
const security=read('packages/kbx-ui/src/shell/navigationSecurity.ts')
|
|
must(security,['hasUnsafeEncodedPath','%2f','candidate.username','candidate.pathname:entry.path','Query/hash are deliberately stripped'],'Navigation security')
|
|
|
|
if(failures.length){console.error(failures.join('\n'));process.exit(1)}
|
|
console.log('v31 template/component/home navigation validation passed: runtime template-type guards, SearchPanel de-duplication, multi-instance Home workbench, roving tab focus/pinning, focus recovery, skip navigation, and persisted-route hardening.')
|