const test = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const root = path.resolve(__dirname, '..', '..') const read = (file) => fs.readFileSync(path.join(root, file), 'utf8') test('plate pages render the reference header card, tab bar and list', () => { const listing = read('src/Forum/ListingView.php') const cards = read('src/Forum/Cards.php') const view = read('src/Forum/View.php') assert.match(view, /public static function listing\(\): void \{\s*ListingView::plate\(\);/) for (const token of ['plate-tab', 'tab-nav-theme', 'affix-header-sm', 'data-forum-tab', 'data-forum-more', 'post_ajax_loader', 'content-wrap', 'content-layout', 'sidebar']) { assert.ok(listing.includes(token), `plate page missing ${token}`) } // Reference header: blurred cover + mask + stats + moderator avatars. for (const token of ['plate-header-wrap', 'forum-header blur-header', 'abs-blur-bg', 'forum-mask', 'moderator-btns', 'count-mates']) { assert.ok(cards.includes(token), `plate header missing ${token}`) } // Moderator-only tabs (pending / trash) appear on plate pages. assert.match(listing, /'pending'/) assert.match(listing, /'trash'/) // Topping posts are pulled out of the main loop on the first tab. assert.match(listing, /_jio_forum_pinned/) assert.match(listing, /post__not_in/) }) test('post detail pages use the reference article shell', () => { const post = read('src/Forum/PostView.php') const template = read('templates/pages/page-forum-post.php') assert.match(template, /PostView::render\(\)/) for (const token of ['fixed-wrap single-wrap', 'fixed-wrap-content', 'forum-single-cover', 'breadcrumb', 'id="article"', 'article jio-panel forum-article', 'article-header clearfix', 'forum-article-meta', 'wp-posts-content', 'article-tags', 'forum-article-footer', 'footer-actions', 'vote-box forum-vote', 'comments-card']) { assert.ok(post.includes(token), `post page missing ${token}`) } // Banners, permissions and actions stay server-side. assert.match(post, /Access::can_view\(/) assert.match(post, /Cards::title\(\$post, 'article-title'/) assert.match(post, /data-forum-action="score"/) assert.match(post, /jio_is_show_sidebar\(\)/) }) test('compose pages mirror the reference editor plus settings sidebar', () => { const edit = read('src/Forum/EditView.php') const template = read('templates/pages/page-forum-edit.php') assert.match(template, /EditView::render\(\)/) for (const token of ['editor-main-box', 'edit-posts-featured', 'edit-posts-title', 'line-form-input', 'jio_forum_content', 'bbs-posts-edit', 'but-average', 'bbs-posts-submit', 'data-forum-draft', 'drop-select', 'allow-view-drop', 'plate-drop', 'type-drop', 'forum-type-vote']) { assert.ok(edit.includes(token), `compose page missing ${token}`) } assert.match(edit, /class="sidebar show-sidebar"/) assert.match(edit, /wp_editor\(/) }) test('compose page saves draft state and the cover through the REST contract', () => { const rest = read('src/Forum/RestPosts.php') const runtime = read('assets/js/forum-runtime.js') assert.match(rest, /\$requested === 'draft'/) assert.match(rest, /featured_image_id/) assert.match(rest, /set_post_thumbnail\(/) for (const token of ['data-forum-type-set', 'data-forum-view', 'data-forum-plate', 'data-forum-status', 'data-forum-topics', 'data-forum-tags', 'data-forum-cover-file']) { assert.ok(runtime.includes(token), `compose runtime missing ${token}`) } }) test('quick publish mirrors the reference inline composer', () => { const quick = read('src/Forum/QuickPost.php') for (const token of ['quick-posts-box', 'quick-input-group', 'edit-posts-title', 'line-form-input', 'edit-posts-content', 'quick-upload', 'quick-more-btns', 'plate-drop', 'topic-drop', 'allow-view-drop', 'hide-type-drop', 'quick-posts-submit', 'data-forum-draft']) { assert.ok(quick.includes(token), `quick publish missing ${token}`) } // Guests get the login trigger instead of the editor actions. assert.match(quick, /signin-loader/) assert.match(quick, /登录后发布/) }) test('forum publishing is gated by the site captcha scene', () => { const rest = read('src/Forum/RestPosts.php') const captcha = read('inc/modules/captcha.php') const schema = read('src/Admin/Schema/SchemaPermissions.php') const vue = read('assets/admin/src/schema/sections/permissions/index.js') const quick = read('src/Forum/QuickPost.php') const edit = read('src/Forum/EditView.php') assert.match(captcha, /'forum_post'\s*=>\s*__\('论坛发帖'/) assert.match(schema, /\['label' => '论坛发帖', 'value' => 'forum_post'\]/) assert.match(vue, /\{ label: '论坛发帖', value: 'forum_post' \}/) assert.match(rest, /jio_require_captcha\('forum_post'/) assert.match(rest, /jio_captcha_proof/) assert.match(quick, /jio_render_captcha\('forum_post'\)/) assert.match(edit, /jio_render_captcha\('forum_post'\)/) }) test('hidden content becomes the site visibility shortcode on save', () => { const rest = read('src/Forum/Rest.php') const posts = read('src/Forum/RestPosts.php') const quick = read('src/Forum/QuickPost.php') const edit = read('src/Forum/EditView.php') assert.match(posts, /append_hidden_content\(\$content, \$input\)/) assert.match(rest, /'logged' => 'login', 'reply' => 'comment', 'vip1' => 'vip', 'payshow' => 'payshow'/) assert.match(rest, /hidecontent type="/) assert.match(rest, /payshow/) for (const token of ['hide_type', 'hide_content', '登录可见', '评论可见', '会员可见', '付费可见']) { assert.ok(quick.includes(token) && edit.includes(token), `hidden content missing ${token}`) } })