const test = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const { spawnSync } = require('node:child_process')
const root = path.resolve(__dirname, '../..')
const read = file => fs.readFileSync(path.join(root, file), 'utf8')
const schema = read('src/Admin/Schema/SchemaPageDisplay.php')
const paymentSchema = read('src/Admin/Schema/SchemaPayment.php')
const adminVipSchema = read('assets/admin/src/schema/sections/payment/vip.js')
const paymentRuntime = read('src/Settings/PaymentRuntime.php')
const routes = read('assets/admin/src/constants/sections/page-display.js')
const controller = read('src/Admin/PageDisplayController.php')
const controllerBase = read('src/Admin/ControllerBase.php')
const provider = read('src/Admin/SchemaProvider.php')
const settingsFacade = read('src/Settings/Settings.php')
const scriptsPart1c = read('src/Core/ScriptsPart1c.php')
const footerCss = read('assets/css/frontend/footer/shell.css')
const frontendBaseCss = read('assets/css/frontend/base.css')
const headerMenuButtons = read('inc/helpers/aksrt-header-menu-buttons.php')
const frontendRenderer = read('src/WidgetSystem/FrontendRenderer.php')
const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php')
const widgetCss = read('assets/widgets/frontend.css')
const headerNavbar = read('inc/helpers/header/aksrt-header-navbar.php')
const headerMobile = read('inc/helpers/header/aksrt-header-mobile.php')
const headerRender = read('inc/helpers/header/aksrt-header-render.php')
const headerUserbox = read('inc/helpers/aksrt-header-userbox.php')
const headerSearch = read('inc/helpers/header/aksrt-header-menu-search.php')
const headerCss = read('assets/css/frontend/header-userbox.css')
const coreResetCss = read('assets/css/core/reset.css')
const vipCardRenderer = read('src/Content/VipServiceModal.php')
const vipModalCss = read('assets/css/frontend/vip-modal.css')
const payVipJs = read('assets/js/pay-vip.js')
const modalRuntimeJs = read('assets/js/core/modal-runtime.js')
const legacyMainCss = read('assets/css/main.css')
function activeRepositorySource() {
const roots = ['assets/admin/src', 'assets/js', 'inc', 'src', 'templates']
const rootFiles = ['404.php', 'archive.php', 'category.php', 'footer.php', 'functions.php', 'header.php', 'index.php', 'page.php', 'search.php', 'single.php', 'tag.php']
const files = [...rootFiles.filter(file => fs.existsSync(path.join(root, file)))]
const visit = relative => {
const absolute = path.join(root, relative)
for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) {
const child = path.join(relative, entry.name)
if (entry.isDirectory()) visit(child)
else if (/\.(?:php|js|vue|css)$/.test(entry.name) && !entry.name.endsWith('.disabled')) files.push(child)
}
}
roots.forEach(visit)
return files.map(file => read(file)).join('\n')
}
function functionBody(name) {
const start = schema.indexOf(`private static function ${name}`)
assert.notEqual(start, -1, `missing schema builder ${name}`)
const next = schema.indexOf('\n\tprivate static function ', start + 1)
return schema.slice(start, next === -1 ? schema.length : next)
}
function topLevelFieldIds(name) {
return [...functionBody(name).matchAll(/self::field\('([^']+)'/g)].map(match => match[1])
}
test('page display exposes exactly eight ordered routes', () => {
const expected = ['header-nav', 'header-components', 'footer', 'homepage', 'category', 'tag', 'user-home', 'other']
const schemaOrder = [...schema.matchAll(/^\t\t\t\t'([^']+)'\s+=> self::/gm)].map(match => match[1])
assert.deepEqual(schemaOrder, expected)
const routeOrder = [...routes.matchAll(/section:\s*'([^']+)'/g)].map(match => match[1])
assert.deepEqual(routeOrder, expected)
})
test('top-level field IDs match the legacy V9.0 eight-page contract', () => {
assert.deepEqual(topLevelFieldIds('header_nav'), [
'header_theme_custom', 'header_theme_bg_custom', 'header_theme_bg', 'header_theme_color_custom',
'header_layout', 'mobile_header_layout', 'mobile_navbar_align', 'nav_fixed', 'nav_scroll_down_hide',
'nav_newposts', 'nav_newposts_radius', 'nav_newposts_class', 'nav_newposts_text',
])
assert.deepEqual(topLevelFieldIds('header_components'), [
'header_slider_show', 'header_slider_show_type', 'header_slider_nav_color', 'header_slider',
'header_slider_option', 'header_slider_search_s', 'header_slider_search_option',
'header_slider_card_s', 'header_slider_card_option', 'header_slider_filter_blur',
])
assert.deepEqual(topLevelFieldIds('footer'), [
'footer_theme_custom', 'footer_theme_bg_custom', 'footer_theme_bg', 'footer_theme_color_custom',
'fcode_template', 'footer_t1_m_s', 'footer_t1_img', 'footer_t1_img_dark', 'footer_t1_t',
'fcode_t1_code', 'fcode_t2_code_1', 'fcode_t2_code_2', 'footer_contact_m_s',
'footer_contact_wechat_img', 'footer_contact_qq', 'footer_contact_weibo', 'footer_contact_email',
'footer_contact_more', 'desc', 'icon', 'link', 'img', 'footer_mini_img_m_s', 'footer_mini_img',
'footer_mini_img_2', 'fcode_customize_code',
])
assert.deepEqual(topLevelFieldIds('homepage'), [
'home_posts_list_s', 'home_list1_orderby_s', 'home_list1_orderby_option', 'index_list_title',
'home_exclude_posts', 'home_exclude_cats', 'home_lists', 'title', 'term_id', 'orderby',
])
assert.deepEqual(topLevelFieldIds('category'), [
'page_cover_cat_s', 'cat_post_count_s', 'cat_default_cover', 'cat_orderby_s', 'cat_orderby_option',
'ajax_list_cat_cat', 'ajax_list_option_cat_cat', 'ajax_list_cat_tag', 'ajax_list_option_cat_tag',
])
assert.deepEqual(topLevelFieldIds('tag'), [
'tag_post_count_s', 'page_cover_tag_s', 'tag_default_cover', 'tag_orderby_s', 'tag_orderby_option',
'ajax_list_tag_cat', 'ajax_list_option_tag_cat', 'ajax_list_tag_tag', 'ajax_list_option_tag_tag',
])
assert.deepEqual(topLevelFieldIds('user_home'), ['author_main_tab_opt', 'author_main_tab_swiper'])
assert.deepEqual(topLevelFieldIds('other'), ['page_header_style', 'page_header_cover_img'])
})
test('defaults, nested structures, dependencies, and excluded product families are fixed', () => {
assert.match(schema, /'author_main_tab_opt'.*?'post'.*?'favorite'.*?'comment'.*?'follow'/s)
assert.match(schema, /'home_posts_list_s', '首页文章', 'switcher', true/)
assert.match(schema, /'page_header_style', '页面标题', 'radio', ''/)
assert.match(schema, /'author' => '公开用户主页'/)
assert.match(schema, /'user_center' => '用户中心'/)
assert.match(schema, /'header_slider_search_s'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/)
assert.match(schema, /'header_slider_card_s'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/)
assert.match(schema, /'header_slider_filter_blur'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/)
assert.match(schema, /'all' => \[self::enabled\('home_posts_list_s'\), self::enabled\('home_list1_orderby_s'\)\]/)
for (const dead of ['nav_newposts_btns', 'forum_home', 'forum_plate', 'forum_post', 'ajax_list_cat_topics', 'ajax_list_tag_topics']) {
assert.equal(schema.includes(dead), false, `legacy/excluded field remains: ${dead}`)
}
assert.equal(headerMenuButtons.includes('nav_newposts_btns'), false)
assert.match(headerMenuButtons, /aksrt_render_new_add_btns\(\['post'\], \$class, \$label\)/)
// FA5 spelling of the reference '发布'
// default: fa-pencil does not exist in the bundled FA 5.15.4 free set.
assert.match(schema, /'nav_newposts_text', '按钮文案', 'textarea', '<\/i>发布'/)
assert.doesNotMatch(schema, /fa fas fa-fw fas /)
})
test('VIP navigation purchase switches are saved and consumed end to end', () => {
for (const key of ['nav_pay_vip', 'nav_user_pay_vip']) {
assert.match(paymentSchema, new RegExp(`'key' => '${key}'[^\\n]+?'type' => 'switch'[^\\n]+?'default' => true`))
assert.match(adminVipSchema, new RegExp(`${key}: true`))
assert.match(adminVipSchema, new RegExp(`key: '${key}'[^\\n]+type: 'switch'[^\\n]+default: true`))
assert.match(paymentRuntime, new RegExp(`'${key}'[^\\n]+!isset\\(\\$settings\\['${key}'\\]\\)[^\\n]+!empty\\(\\$settings\\['${key}'\\]\\)`))
}
assert.match(headerMenuButtons, /!empty\(\$vs\['enabled'\]\) && !empty\(\$vs\['nav_pay_vip'\]\)/)
assert.match(headerUserbox, /empty\(\$vip\['nav_user_pay_vip'\]\)[\s\S]*?return '';/)
assert.match(headerNavbar, /!empty\(\$vs\['enabled'\]\) && !empty\(\$vs\['nav_user_pay_vip'\]\)/)
})
test('header VIP hover cards match the legacy markup and style contract', () => {
assert.match(headerMenuButtons, //)
assert.match(headerMenuButtons, /