|
|
|
@@ -11,6 +11,14 @@ function readSource(relativePath) {
|
|
|
|
return readFileSync(resolve(srcDir, relativePath), 'utf8')
|
|
|
|
return readFileSync(resolve(srcDir, relativePath), 'utf8')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function cssBlock(source, selector, offset = 0) {
|
|
|
|
|
|
|
|
const start = source.indexOf(`${selector} {`, offset)
|
|
|
|
|
|
|
|
assert.notEqual(start, -1)
|
|
|
|
|
|
|
|
const end = source.indexOf('\n}', start)
|
|
|
|
|
|
|
|
assert.notEqual(end, -1)
|
|
|
|
|
|
|
|
return source.slice(start, end)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test('project api exposes frontend page design endpoints', () => {
|
|
|
|
test('project api exposes frontend page design endpoints', () => {
|
|
|
|
const source = readSource('api/project.js')
|
|
|
|
const source = readSource('api/project.js')
|
|
|
|
|
|
|
|
|
|
|
|
@@ -53,7 +61,7 @@ test('frontend page designer reports local draft edits as dirty before commit',
|
|
|
|
assert.equal(source.includes("emit('dirty-change', true)"), true)
|
|
|
|
assert.equal(source.includes("emit('dirty-change', true)"), true)
|
|
|
|
assert.equal(source.includes('const suppressDirty = ref(false)'), true)
|
|
|
|
assert.equal(source.includes('const suppressDirty = ref(false)'), true)
|
|
|
|
assert.equal(source.includes('syncJson({ silent: true })'), true)
|
|
|
|
assert.equal(source.includes('syncJson({ silent: true })'), true)
|
|
|
|
assert.equal(source.includes('v-model="draft.pageName" @input="markDirty"'), true)
|
|
|
|
assert.equal(source.includes('v-model="draft.pageName" @input="syncPageName"'), true)
|
|
|
|
assert.equal(source.includes('v-model="draft.routePath" @input="markDirty"'), true)
|
|
|
|
assert.equal(source.includes('v-model="draft.routePath" @input="markDirty"'), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
@@ -61,7 +69,8 @@ test('frontend page designer renders page navigation as a normal menu inside the
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('<nav class="page-menu"'), false)
|
|
|
|
assert.equal(source.includes('<nav class="page-menu"'), false)
|
|
|
|
assert.equal(source.includes('class="canvas-page-menu"'), true)
|
|
|
|
assert.equal(source.includes(':class="[\'canvas\', canvasMenuPositionClass]"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes(':class="canvasPageMenuClass"'), true)
|
|
|
|
assert.equal(source.includes('class="canvas-page-menu-bar"'), true)
|
|
|
|
assert.equal(source.includes('class="canvas-page-menu-bar"'), true)
|
|
|
|
assert.equal(source.includes('v-for="group in pageMenuGroups"'), true)
|
|
|
|
assert.equal(source.includes('v-for="group in pageMenuGroups"'), true)
|
|
|
|
assert.equal(source.includes('@click.stop="selectMenuPage(child)"'), true)
|
|
|
|
assert.equal(source.includes('@click.stop="selectMenuPage(child)"'), true)
|
|
|
|
@@ -72,13 +81,42 @@ test('frontend page designer renders page navigation as a normal menu inside the
|
|
|
|
assert.equal(source.includes('const pageMenuGroups = computed'), true)
|
|
|
|
assert.equal(source.includes('const pageMenuGroups = computed'), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer applies configured menu position to the canvas', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const scriptStart = source.indexOf('const canvasMenuPosition = computed')
|
|
|
|
|
|
|
|
const scriptEnd = source.indexOf('const scopedPageMenuDesigns = computed', scriptStart)
|
|
|
|
|
|
|
|
const script = source.slice(scriptStart, scriptEnd)
|
|
|
|
|
|
|
|
const cssStart = source.indexOf('.canvas.menu-position-side')
|
|
|
|
|
|
|
|
const cssEnd = source.indexOf('.canvas-menu-group {', cssStart)
|
|
|
|
|
|
|
|
const css = source.slice(cssStart, cssEnd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.notEqual(scriptStart, -1)
|
|
|
|
|
|
|
|
assert.equal(script.includes('navigationConfig.value.position'), true)
|
|
|
|
|
|
|
|
assert.equal(script.includes('menuPositionOptions'), true)
|
|
|
|
|
|
|
|
assert.equal(script.includes('canvasMenuPositionClass'), true)
|
|
|
|
|
|
|
|
assert.equal(script.includes('canvasPageMenuClass'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('value="side"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('value="mobile"'), true)
|
|
|
|
|
|
|
|
assert.notEqual(cssStart, -1)
|
|
|
|
|
|
|
|
assert.equal(css.includes('grid-template-columns: 220px minmax(0, 1fr)'), true)
|
|
|
|
|
|
|
|
assert.equal(css.includes('.canvas.menu-position-side .canvas-page-menu'), true)
|
|
|
|
|
|
|
|
assert.equal(css.includes('.canvas.menu-position-side > :not(.canvas-page-menu)'), true)
|
|
|
|
|
|
|
|
assert.equal(css.includes('.canvas-page-menu.menu-position-side .canvas-page-menu-bar'), true)
|
|
|
|
|
|
|
|
assert.equal(css.includes('flex-direction: column'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer hides associated pages from the final menu while keeping them editable', () => {
|
|
|
|
test('frontend page designer hides associated pages from the final menu while keeping them editable', () => {
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('designMatchesScope'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const scopedPageMenuDesigns = computed'), true)
|
|
|
|
assert.equal(source.includes('const editableMenuPages = computed'), true)
|
|
|
|
assert.equal(source.includes('const editableMenuPages = computed'), true)
|
|
|
|
assert.equal(source.includes('const associatedPages = computed'), true)
|
|
|
|
assert.equal(source.includes('const selectedPageAssociatedPages = computed'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const unlinkedAssociatedPages = computed'), true)
|
|
|
|
assert.equal(source.includes('const visibleAssociatedPages = computed'), true)
|
|
|
|
assert.equal(source.includes('const visibleAssociatedPages = computed'), true)
|
|
|
|
assert.equal(source.includes('selectedMenuGroup.value?.children.length'), true)
|
|
|
|
assert.equal(source.includes('associatedPagesForDesign(pageMenuDesigns.value, currentDraftDesign.value)'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('uniqueDesigns(selectedPageAssociatedPages.value.concat(unlinkedAssociatedPages.value))'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('pageParentPageCode(design)'), true)
|
|
|
|
assert.equal(source.includes('shouldShowInMenu'), true)
|
|
|
|
assert.equal(source.includes('shouldShowInMenu'), true)
|
|
|
|
assert.equal(source.includes('isAssociatedPageType'), true)
|
|
|
|
assert.equal(source.includes('isAssociatedPageType'), true)
|
|
|
|
assert.equal(readSource('utils/pageDesignerMenu.js').includes('pageType === \'detail\''), true)
|
|
|
|
assert.equal(readSource('utils/pageDesignerMenu.js').includes('pageType === \'detail\''), true)
|
|
|
|
@@ -86,7 +124,9 @@ test('frontend page designer hides associated pages from the final menu while ke
|
|
|
|
assert.equal(source.includes('buildPageMenuGroups(editableMenuPages.value, { mode: activeMode() })'), true)
|
|
|
|
assert.equal(source.includes('buildPageMenuGroups(editableMenuPages.value, { mode: activeMode() })'), true)
|
|
|
|
assert.equal(source.includes('关联页面'), true)
|
|
|
|
assert.equal(source.includes('关联页面'), true)
|
|
|
|
assert.equal(source.includes('v-for="design in visibleAssociatedPages"'), true)
|
|
|
|
assert.equal(source.includes('v-for="design in visibleAssociatedPages"'), true)
|
|
|
|
assert.equal(source.includes('v-model="navigationConfig.showInMenu"'), true)
|
|
|
|
assert.equal(source.includes('v-model="navigationConfig.showInMenu"'), false)
|
|
|
|
|
|
|
|
assert.equal(source.includes(':model-value="currentShowInMenu"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@change="setNavigationVisibility"'), true)
|
|
|
|
assert.equal(source.includes('显示在菜单'), true)
|
|
|
|
assert.equal(source.includes('显示在菜单'), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
@@ -107,6 +147,9 @@ test('frontend page designer keeps menu display name separate from page name', (
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('菜单显示名称'), true)
|
|
|
|
assert.equal(source.includes('菜单显示名称'), true)
|
|
|
|
assert.equal(source.includes('v-model="navigationConfig.menuName"'), true)
|
|
|
|
assert.equal(source.includes('v-model="navigationConfig.menuName"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@input="syncMenuName"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@input="syncPageName"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function shouldSyncMenuNameWithPageName'), true)
|
|
|
|
assert.equal(source.includes("currentMenuLevel === 'primary'"), true)
|
|
|
|
assert.equal(source.includes("currentMenuLevel === 'primary'"), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
@@ -115,14 +158,211 @@ test('frontend page designer keeps left side focused on adding materials', () =>
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="add-content-materials"'), true)
|
|
|
|
assert.equal(source.includes('class="add-content-materials"'), true)
|
|
|
|
assert.equal(source.includes('@click="addPageDesign(\'primary\')"'), true)
|
|
|
|
assert.equal(source.includes('@click="addPageDesign(\'primary\')"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@click="addPageDesign(\'directory\')"'), true)
|
|
|
|
assert.equal(source.includes('@click="addPageDesign(\'secondary\')"'), true)
|
|
|
|
assert.equal(source.includes('@click="addPageDesign(\'secondary\')"'), true)
|
|
|
|
assert.equal(source.includes('class="material-pane"'), true)
|
|
|
|
assert.equal(source.includes('class="material-pane"'), true)
|
|
|
|
assert.equal(source.includes('class="material-section field-materials"'), true)
|
|
|
|
assert.equal(source.includes('class="material-section field-materials"'), true)
|
|
|
|
assert.equal(source.includes('class="material-section action-materials"'), true)
|
|
|
|
assert.equal(source.includes('basic-action-materials'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('business-action-materials'), true)
|
|
|
|
assert.equal(source.includes('v-if="isBusinessBlockLayout" class="material-section business-materials"'), true)
|
|
|
|
assert.equal(source.includes('v-if="isBusinessBlockLayout" class="material-section business-materials"'), true)
|
|
|
|
assert.equal(source.includes('<aside class="page-list">'), false)
|
|
|
|
assert.equal(source.includes('<aside class="page-list">'), false)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer optimizes add content shortcuts as a compact action stack', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="quick-actions optimized-page-actions"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="primary-page-action"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="secondary-page-actions"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="quick-action-icon"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="quick-action-meta"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('<Plus />'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('<FolderAdd />'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('<Tickets />'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('<Setting />'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('添加页面'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('主页面'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('菜单目录'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('子页面'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('菜单设置'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('legacy-page-actions'), false)
|
|
|
|
|
|
|
|
assert.equal(source.includes('新增一级菜单'), false)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer keeps material pools in a tabbed left toolbar near the canvas', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('const activeMaterialPanel = ref(\'page\')'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const materialPanelTabs = ['), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const visibleMaterialPanelTabs = computed'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="material-pane-tabs"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-for="tab in visibleMaterialPanelTabs"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes(':class="{ active: activeMaterialPanel === tab.value }"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@click="setActiveMaterialPanel(tab.value)"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="material-panel-scroll"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-show="activeMaterialPanel === \'field\'"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-show="activeMaterialPanel === \'action\'"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-show="activeMaterialPanel === \'business\'"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@click="setActiveMaterialPanel(\'business\')"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@click="setActiveMaterialPanel(\'action\')"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@click="setActiveMaterialPanel(\'field\')"'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer keeps page structure inside page materials', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const tabsStart = source.indexOf('const materialPanelTabs = [')
|
|
|
|
|
|
|
|
const tabsEnd = source.indexOf(']', tabsStart)
|
|
|
|
|
|
|
|
const tabs = source.slice(tabsStart, tabsEnd)
|
|
|
|
|
|
|
|
const pagePanelStart = source.indexOf('v-show="activeMaterialPanel === \'page\'"')
|
|
|
|
|
|
|
|
const fieldPanelStart = source.indexOf('v-show="activeMaterialPanel === \'field\'"', pagePanelStart)
|
|
|
|
|
|
|
|
const pagePanel = source.slice(pagePanelStart, fieldPanelStart)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(tabs.includes("value: 'structure'"), false)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-show="activeMaterialPanel === \'structure\'"'), false)
|
|
|
|
|
|
|
|
assert.equal(pagePanel.includes('class="material-section region-materials"'), true)
|
|
|
|
|
|
|
|
assert.equal(pagePanel.includes('hiddenRegions'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer pins the material toolbar and scrolls pools internally', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const materialPane = cssBlock(source, '.material-pane')
|
|
|
|
|
|
|
|
const materialScroll = cssBlock(source, '.material-panel-scroll')
|
|
|
|
|
|
|
|
const compactMediaStart = source.indexOf('@media (max-width: 760px)')
|
|
|
|
|
|
|
|
assert.notEqual(compactMediaStart, -1)
|
|
|
|
|
|
|
|
const compactMaterialPane = cssBlock(source, '.material-pane', compactMediaStart)
|
|
|
|
|
|
|
|
const compactMaterialScroll = cssBlock(source, '.material-panel-scroll', compactMediaStart)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(materialPane.includes('position: sticky'), true)
|
|
|
|
|
|
|
|
assert.equal(materialPane.includes('align-self: start'), true)
|
|
|
|
|
|
|
|
assert.equal(materialPane.includes('height: calc(100vh - 112px)'), true)
|
|
|
|
|
|
|
|
assert.equal(materialPane.includes('overflow: hidden'), true)
|
|
|
|
|
|
|
|
assert.equal(materialScroll.includes('flex: 1 1 auto'), true)
|
|
|
|
|
|
|
|
assert.equal(materialScroll.includes('overflow-y: auto'), true)
|
|
|
|
|
|
|
|
assert.equal(materialScroll.includes('overscroll-behavior: contain'), true)
|
|
|
|
|
|
|
|
assert.equal(compactMaterialPane.includes('position: sticky'), true)
|
|
|
|
|
|
|
|
assert.equal(compactMaterialPane.includes('height: min(58vh, 520px)'), true)
|
|
|
|
|
|
|
|
assert.equal(compactMaterialPane.includes('overflow: hidden'), true)
|
|
|
|
|
|
|
|
assert.equal(compactMaterialPane.includes('position: static'), false)
|
|
|
|
|
|
|
|
assert.equal(compactMaterialScroll.includes('overflow-y: auto'), true)
|
|
|
|
|
|
|
|
assert.equal(compactMaterialScroll.includes('overflow: visible'), false)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer supports editable directory menu nodes and top-level drag sorting', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('菜单目录'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('子页面'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('selectedConfigType === \'directory\''), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function addDirectoryChildMenu'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function startMenuGroupDrag'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function dropMenuGroup'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function applyMenuGroupOrder'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('applyPageMenuGroupOrder(pageMenuDesigns.value, groups, { mode: activeMode() })'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('draggable="true"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@dragstart.stop="startMenuGroupDrag(group)"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@drop.stop="dropMenuGroup(group)"'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer lets custom primary directories be deleted', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const directoryPaneStart = source.indexOf("selectedConfigType === 'directory'")
|
|
|
|
|
|
|
|
const directoryPaneEnd = source.indexOf('<div class="menu-config-list">', directoryPaneStart)
|
|
|
|
|
|
|
|
const directoryPane = source.slice(directoryPaneStart, directoryPaneEnd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(directoryPane.includes('删除当前目录'), true)
|
|
|
|
|
|
|
|
assert.equal(directoryPane.includes('@click="deletePageDesign(draft)"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes("pageCode.startsWith('menu_dir_')"), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer refreshes secondary menu configuration from current layout state', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const currentLevelStart = source.indexOf('const currentMenuLevel = computed')
|
|
|
|
|
|
|
|
const currentLevelEnd = source.indexOf('const currentParentMenuCode = computed', currentLevelStart)
|
|
|
|
|
|
|
|
const currentLevel = source.slice(currentLevelStart, currentLevelEnd)
|
|
|
|
|
|
|
|
const parentOptionsStart = source.indexOf('const parentMenuOptions = computed')
|
|
|
|
|
|
|
|
const parentOptionsEnd = source.indexOf('function inferCurrentParentMenuCode', parentOptionsStart)
|
|
|
|
|
|
|
|
const parentOptions = source.slice(parentOptionsStart, parentOptionsEnd)
|
|
|
|
|
|
|
|
const helperStart = source.indexOf('function inferCurrentParentMenuCode')
|
|
|
|
|
|
|
|
const helperEnd = source.indexOf('const currentMenuLevel = computed', helperStart)
|
|
|
|
|
|
|
|
const helper = source.slice(helperStart, helperEnd)
|
|
|
|
|
|
|
|
const panelStateStart = source.indexOf('function currentMenuPanelState')
|
|
|
|
|
|
|
|
const panelStateEnd = source.indexOf('const menuPanelState = computed', panelStateStart)
|
|
|
|
|
|
|
|
const panelState = source.slice(panelStateStart, panelStateEnd)
|
|
|
|
|
|
|
|
const setLevelStart = source.indexOf('function setMenuLevel')
|
|
|
|
|
|
|
|
const setLevelEnd = source.indexOf('function setParentMenuGroup', setLevelStart)
|
|
|
|
|
|
|
|
const setLevel = source.slice(setLevelStart, setLevelEnd)
|
|
|
|
|
|
|
|
const setParentStart = source.indexOf('function setParentMenuGroup')
|
|
|
|
|
|
|
|
const setParentEnd = source.indexOf('function layoutOptionsForPageType', setParentStart)
|
|
|
|
|
|
|
|
const setParent = source.slice(setParentStart, setParentEnd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(panelState.includes('currentNavigationConfig()'), true)
|
|
|
|
|
|
|
|
assert.equal(currentLevel.includes('pageMenuLevel(draft.value)'), false)
|
|
|
|
|
|
|
|
assert.equal(helper.includes('config.parentMenuCode'), true)
|
|
|
|
|
|
|
|
assert.equal(panelState.includes('inferCurrentParentMenuCode()'), true)
|
|
|
|
|
|
|
|
assert.equal(panelState.indexOf('inferCurrentParentMenuCode()') < panelState.indexOf('config.menuLevel'), true)
|
|
|
|
|
|
|
|
assert.equal(currentLevel.includes('menuPanelState.value.level'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('class="menu-level-switch"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes(":class=\"{ active: currentMenuLevel === 'secondary' }\""), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@click="setMenuLevel(\'secondary\')"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('<el-segmented'), false)
|
|
|
|
|
|
|
|
assert.equal(source.includes('label="所属一级菜单/目录"'), true)
|
|
|
|
|
|
|
|
assert.equal(parentOptions.includes('group.primary'), true)
|
|
|
|
|
|
|
|
assert.equal(parentOptions.includes('isMenuDirectoryDesign(group.primary)'), true)
|
|
|
|
|
|
|
|
assert.equal(parentOptions.includes('designKey(group.primary) !== selectedKey.value'), true)
|
|
|
|
|
|
|
|
assert.equal(setLevel.includes('parentMenuOptions.value'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('ref="parentMenuSelectRef"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const parentMenuSelectRef = ref(null)'), true)
|
|
|
|
|
|
|
|
assert.equal(setParent.includes('parentMenuSelectRef.value?.blur?.()'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer uses one inferred parent source for secondary menu level and parent selector', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const helperStart = source.indexOf('function inferCurrentParentMenuCode')
|
|
|
|
|
|
|
|
const helperEnd = source.indexOf('const currentMenuLevel = computed', helperStart)
|
|
|
|
|
|
|
|
const helper = source.slice(helperStart, helperEnd)
|
|
|
|
|
|
|
|
const currentLevelStart = source.indexOf('const currentMenuLevel = computed')
|
|
|
|
|
|
|
|
const currentLevelEnd = source.indexOf('const currentParentMenuCode = computed', currentLevelStart)
|
|
|
|
|
|
|
|
const currentLevel = source.slice(currentLevelStart, currentLevelEnd)
|
|
|
|
|
|
|
|
const currentParentStart = source.indexOf('const currentParentMenuCode = computed')
|
|
|
|
|
|
|
|
const currentParentEnd = source.indexOf('const directoryMenuCode = computed', currentParentStart)
|
|
|
|
|
|
|
|
const currentParent = source.slice(currentParentStart, currentParentEnd)
|
|
|
|
|
|
|
|
const panelStateStart = source.indexOf('function currentMenuPanelState')
|
|
|
|
|
|
|
|
const panelStateEnd = source.indexOf('const menuPanelState = computed', panelStateStart)
|
|
|
|
|
|
|
|
const panelState = source.slice(panelStateStart, panelStateEnd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.notEqual(helperStart, -1)
|
|
|
|
|
|
|
|
assert.equal(helper.includes('parentMenuOptions.value.find'), true)
|
|
|
|
|
|
|
|
assert.equal(helper.includes('config.menuCode'), true)
|
|
|
|
|
|
|
|
assert.equal(helper.includes('draft.value?.menuCode'), true)
|
|
|
|
|
|
|
|
assert.equal(panelState.includes('inferCurrentParentMenuCode()'), true)
|
|
|
|
|
|
|
|
assert.equal(panelState.indexOf('inferCurrentParentMenuCode()') < panelState.indexOf('config.menuLevel'), true)
|
|
|
|
|
|
|
|
assert.equal(currentLevel.includes('menuPanelState.value.level'), true)
|
|
|
|
|
|
|
|
assert.equal(currentParent.includes('menuPanelState.value.parentCode'), true)
|
|
|
|
|
|
|
|
assert.equal(currentParent.includes('pageGroupKey'), false)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer drives menu panel controls from normalized selected state', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const selectStart = source.indexOf('function selectDesign')
|
|
|
|
|
|
|
|
const selectEnd = source.indexOf('function addPageDesign', selectStart)
|
|
|
|
|
|
|
|
const selectDesign = source.slice(selectStart, selectEnd)
|
|
|
|
|
|
|
|
const visibilityStart = source.indexOf('function setNavigationVisibility')
|
|
|
|
|
|
|
|
const visibilityEnd = source.indexOf('function shouldSyncMenuNameWithPageName', visibilityStart)
|
|
|
|
|
|
|
|
const visibility = source.slice(visibilityStart, visibilityEnd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('const menuPanelState = computed(() => currentMenuPanelState())'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const currentShowInMenu = computed(() => menuPanelState.value.showInMenu)'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes(':model-value="currentShowInMenu"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-model="navigationConfig.showInMenu"'), false)
|
|
|
|
|
|
|
|
assert.equal(source.includes('@change="setNavigationVisibility"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('`menu-level-${selectedKey}-${currentMenuLevel}`'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function normalizeCurrentMenuConfig'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function applySecondaryParent'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('function applyPrimaryMenuState'), true)
|
|
|
|
|
|
|
|
assert.equal(selectDesign.includes('normalizeCurrentMenuConfig()'), true)
|
|
|
|
|
|
|
|
assert.equal(visibility.includes('normalizeCurrentMenuConfig()'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer removes redundant structure and canvas action blocks', () => {
|
|
|
|
test('frontend page designer removes redundant structure and canvas action blocks', () => {
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
@@ -155,6 +395,15 @@ test('frontend page designer keeps right pane focused on selected configuration'
|
|
|
|
assert.equal(source.includes('pendingSelectPageCode'), true)
|
|
|
|
assert.equal(source.includes('pendingSelectPageCode'), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer remounts property pane when selected menu changes', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes(':key="propertyPaneKey"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('const propertyPaneKey = computed'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('selectedConfigType.value'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('selectedKey.value'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer delegates business block pages without replacing normal layouts', () => {
|
|
|
|
test('frontend page designer delegates business block pages without replacing normal layouts', () => {
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
@@ -188,6 +437,16 @@ test('frontend page designer supports embedded business blocks on normal pages',
|
|
|
|
assert.equal(source.includes('v-if="selectedBusinessBlock || isBusinessBlockLayout"'), true)
|
|
|
|
assert.equal(source.includes('v-if="selectedBusinessBlock || isBusinessBlockLayout"'), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('embedded admin chart blocks retain span and render previews in grid wrappers', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes("import ChartBlockPreview from '@/components/business-blocks/ChartBlockPreview.vue'"), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('<ChartBlockPreview'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('embedded-block-grid'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('block.span || 12'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes("definition?.placements?.includes('embedded')"), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer can switch copy and defaults for admin mode', () => {
|
|
|
|
test('frontend page designer can switch copy and defaults for admin mode', () => {
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
@@ -199,6 +458,21 @@ test('frontend page designer can switch copy and defaults for admin mode', () =>
|
|
|
|
assert.equal(source.includes('designerCopy'), true)
|
|
|
|
assert.equal(source.includes('designerCopy'), true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer keeps admin business block home pages on business block canvas', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
const adminOptionsStart = source.indexOf('const adminLayoutOptionsByPageType = {')
|
|
|
|
|
|
|
|
const adminOptionsEnd = source.indexOf('const pageTypeLabels = {', adminOptionsStart)
|
|
|
|
|
|
|
|
const adminOptions = source.slice(adminOptionsStart, adminOptionsEnd)
|
|
|
|
|
|
|
|
const defaultLayoutStart = source.indexOf('function defaultLayout')
|
|
|
|
|
|
|
|
const defaultLayoutEnd = source.indexOf('function defaultNavigationConfig', defaultLayoutStart)
|
|
|
|
|
|
|
|
const defaultLayout = source.slice(defaultLayoutStart, defaultLayoutEnd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.notEqual(adminOptionsStart, -1)
|
|
|
|
|
|
|
|
assert.equal(adminOptions.includes('business_block'), true)
|
|
|
|
|
|
|
|
assert.equal(adminOptions.includes('business-blocks-v1'), true)
|
|
|
|
|
|
|
|
assert.equal(defaultLayout.includes("pageType === 'business_block' ? 'business-blocks-v1'"), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer exposes page-type scoped layout choices', () => {
|
|
|
|
test('frontend page designer exposes page-type scoped layout choices', () => {
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
@@ -265,3 +539,26 @@ test('frontend page designer exposes business actions in the draggable action pa
|
|
|
|
assert.equal(source.includes('props.database?.businessActions'), true)
|
|
|
|
assert.equal(source.includes('props.database?.businessActions'), true)
|
|
|
|
assert.equal(source.includes('action.ownerTable === draft.value?.tableName'), false)
|
|
|
|
assert.equal(source.includes('action.ownerTable === draft.value?.tableName'), false)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer separates basic and business action pools', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes('基础按钮池'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('业务按钮池'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('basic-action-materials'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('business-action-materials'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-for="action in builtinActionPalette"'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('v-for="action in businessActionPalette"'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test('frontend page designer exposes configurable business button settings', () => {
|
|
|
|
|
|
|
|
const source = readSource('components/FrontendPageDesigner.vue')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(source.includes("selectedConfigType === 'businessButton'"), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('业务按钮配置'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('businessButtonConfig'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('setBusinessButtonConfig'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('successMessage'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('confirmMessage'), true)
|
|
|
|
|
|
|
|
assert.equal(source.includes('refreshTarget'), true)
|
|
|
|
|
|
|
|
})
|
|
|
|
|