Files
wordpress-theme-aksrt/tests/widget-system/single-cover-height-contract.test.cjs
T

234 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// 文章详情页顶部封面高度契约(对齐参考主题):
// 单图/多图(轮播)封面标题叠加封面,高度 = calc(比例% + 50px)(zibll .single-cover /
// .single-cover-slide);视频封面标题在下方 footer、无叠加,高度 = 裸比例%
// (zibll .dplayer-scale-height)。
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const singleRender = read('inc/helpers/aksrt-single-render.php');
const sliderHelper = read('inc/helpers/aksrt-slider.php');
test('single cover: image mode keeps the calc(scale% + 50px) height', () => {
assert.match(
singleRender,
/single-cover imgbox-container" style="padding-bottom:calc\(<\?php echo esc_attr\(\(string\) \$img_scale\); \?>% \+ 50px\);"/,
'单图封面必须输出 calc(img_scale% + 50px) 内联高度'
);
});
test('single cover: video mode uses the bare ratio height like the reference dplayer box', () => {
// 参考主题 article_video_cover_option.scale:0-200,0 = 不固定长宽比 → 16:9 回退。
assert.match(
singleRender,
/\$video_padding = \$video_scale > 0 \? \(string\) \$video_scale : '56\.25';/,
'视频封面 scale=0 必须回退 16:9(56.25%),非 0 输出裸比例'
);
assert.match(
singleRender,
/single-cover--video imgbox-container" style="padding-bottom:<\?php echo esc_attr\(\$video_padding\); \?>%;"/,
'视频封面必须输出裸比例高度(标题在下方 footer,无叠加补偿)'
);
// +50px 是标题叠加封面的补偿量,仅单图/多图封面适用;视频封面
// 对齐参考主题 .dplayer-scale-height,不得继承该公式。
assert.doesNotMatch(
singleRender,
/single-cover--video imgbox-container" style="padding-bottom:calc\(/,
'视频封面禁止输出 +50px 叠加补偿高度'
);
});
test('single cover: slide mode reserves the same +50px via the slider helper', () => {
// 比例高度模式(scale_height 关闭分支)向 slider helper 传 scale_extra=50px;
// 固定高度模式(scale_height 开启分支)改用参考主题 pc/m_height 定高。
assert.match(
singleRender,
/\$slider_args\['scale_extra'\] = '50px';/,
'比例高度分支必须向 aksrt_render_slider 传 scale_extra=50px'
);
assert.match(
singleRender,
/\$slider_args\['pc_height'\] = \(\(int\) \(\$slide_option\['pc_height'\] \?\? 380\)\) \. 'px';/,
'固定高度分支必须使用参考主题 pc_height 定高'
);
assert.match(
sliderHelper,
/'scale_extra'\s*=>\s*''/,
'slider helper 需声明 scale_extra 默认参数'
);
// wrapper 的 padding-bottom 必须由比例 + 附加量组合而成。
assert.match(
sliderHelper,
/\$scale_padding = 'calc\(' \. \$scale_padding \. ' \+ ' \. \$scale_extra \. '\)';/,
'scale 模式必须把 scale_extra 合成 calc 表达式'
);
});
test('single cover: the dead static slide fallback is gone', () => {
assert.doesNotMatch(
singleRender,
/function_exists\('aksrt_render_slider'\)/,
'aksrt_render_slider 由 bootstrap 无条件加载,不得保留 function_exists 双路判断'
);
assert.doesNotMatch(
singleRender,
/single-cover--slide imgbox-container" style="padding-bottom/,
'多图封面只允许经由 slider helper 输出,禁止保留静态 fallback 分支'
);
});
test('slider scale mode: completes the definite height chain for cover cropping', () => {
assert.doesNotMatch(
sliderHelper,
/--scale-ratio/,
'--scale-ratio 无任何 CSS 消费方,禁止输出'
);
// wrapper 决定盒子高度;pc/m-height 100% 让 .swiper-wrapper/.swiper-slide
// 铺满比例盒,使 object-fit: cover 在确定的盒内居中裁切(对齐单图封面)。
assert.match(
sliderHelper,
/\$height_style = '--pc-height:100%;--m-height:100%;';/
);
assert.match(
sliderHelper,
/esc_attr\(\$height_style \. \(\$args\['scale_height'\] \? 'position:absolute;inset:0;width:100%;height:100%;' : ''\)\)/,
'内层 .new-swiper 必须绝对定位铺满 wrapper'
);
});
test('video cover: the player video fills and center-crops like the image cover', () => {
const player = read('assets/js/video-player.js');
assert.match(
player,
/\.aksrt-video video\{display:block;width:100%;height:100%;object-fit:cover;\}/,
'视频必须铺满封面比例盒并居中裁切'
);
assert.doesNotMatch(
player,
/max-height:70vh/,
'封面盒高度由比例盒决定,不得再套 70vh 截断'
);
assert.doesNotMatch(
player,
/video\.style\.cssText/,
'视频不得携带内联样式:内联 object-fit 会以更高优先级覆盖注入样式表的 cover 契约,产生信箱式黑边'
);
});
test('video cover: play never runs against a sourceless player', () => {
const player = read('assets/js/video-player.js');
// toggle 必须先守卫无源状态,且 play() 的 promise 必须被捕获。
assert.match(player, /hasSource\s*=\s*function\s*\(\)\s*\{[^}]*currentSrc[^}]*\}/);
assert.match(player, /toggle\s*=\s*function\s*\(\)\s*\{\s*if\s*\(!this\.hasSource\(\)\)\s*return;/);
assert.match(player, /this\.video\.play\(\)\.catch\(function/);
// hls/flv CDN 脚本加载失败必须收敛,不得产生未处理 rejection。
assert.match(player, /loadScript\(HLS_SRC\)\.then\([\s\S]*?\}\)\.catch\(function/);
assert.match(player, /loadScript\(FLV_SRC\)\.then\([\s\S]*?\}\)\.catch\(function/);
// iframe 剧集归 runtime main 的 .switch-iframe 链路,播放器不得接管其选择器。
assert.doesNotMatch(player, /closest\('\.switch-video, \.switch-iframe'\)/);
assert.doesNotMatch(player, /querySelectorAll\('\.switch-video, \.switch-iframe'\)/);
});
test('video cover: footer breadcrumb sits in the reference flex row', () => {
const breadcrumbCss = read('assets/css/frontend/breadcrumb.css');
const coverCss = read('assets/css/frontend/single-post/cover.css');
// 对齐 zibll:footer 里面包屑直接位于 flex jsb at 行,无自造包装层。
assert.match(
singleRender,
/single-video-footer dplayer-featured">\s*\n\s*<div class="flex jsb at">\s*\n\s*<\?php echo \$crumbs;/,
'视频 footer 面包屑必须在 flex jsb at 行内直出'
);
assert.doesNotMatch(
singleRender + breadcrumbCss + coverCss,
/post-breadcrumbs/,
'自造的 .post-breadcrumbs 包装层已死,禁止回归'
);
// 视频面包屑只归零 padding、保留基础弱化样式;-6px 左拉仅属封面叠加层。
assert.match(breadcrumbCss, /\.single-video \.breadcrumb \{\s*\n padding: 0;\s*\n\}/);
assert.doesNotMatch(
breadcrumbCss,
/\.single-video \.breadcrumb,[\s\S]{0,200}margin: 5px 0 0 -6px/,
'.single-video 不得并入 -6px 叠加组,否则面包屑贴出左边界'
);
// footer 内边距恢复 legacy 15px 契约,禁止 4px 侧边距覆盖。
assert.doesNotMatch(coverCss, /\.single-video-footer \{ padding:/);
});
test('resource singles get the cover leaf versioned, never via the aggregate @import', () => {
// 聚合文件的 @import 无版本号:浏览器会拿旧缓存(含已废弃的 .single-cover
// padding 规则)把封面高度链挤成 0,资源型文章封面直接空白。
assert.doesNotMatch(
read('assets/css/frontend/single-post.css'),
/@import[^;]*single-post\/cover\.css/,
'聚合样式不得再 @import cover leaf'
);
const enqueue = read('src/Core/ScriptsPart1b.php');
assert.match(
enqueue,
/if \(!empty\(\$singular_assets\['needs_cover'\]\)\) \{[\s\S]{0,400}?single-post\/cover\.css/,
'资源单页必须按 needs_cover 以 filemtime 版本号入队 cover leaf'
);
assert.match(
enqueue,
/'aksrt-single-post-cover'/,
'cover leaf 句柄与文章分支命名保持一致'
);
});
test('single cover css owns no fallback padding and the scale var is gone', () => {
for (const file of [
'assets/css/frontend/single-post/cover.css',
'assets/css/main.css',
'assets/css/main.min.css',
'assets/css/core/tokens.css',
'assets/css/build/public.css',
'assets/css/build/public.min.css',
]) {
const css = read(file);
assert.doesNotMatch(
css,
/\.single-cover\s*\{[^}]*padding-bottom/,
`${file} 的 .single-cover 不得声明 padding-bottom(高度归内联样式)`
);
assert.doesNotMatch(
css,
/--single-cover-scale/,
`${file} 不得再包含孤儿变量 --single-cover-scale`
);
}
assert.doesNotMatch(
read('src/Frontend/InlineStyleBuilder.php'),
/--single-cover-scale/,
'运行时 CSS 变量不得再输出孤儿 --single-cover-scale'
);
});
test('dead cover css is removed from every stylesheet surface', () => {
for (const file of [
'assets/css/frontend/single-post/cover.css',
'assets/css/main.css',
'assets/css/main.min.css',
'assets/css/build/public.css',
'assets/css/build/public.min.css',
]) {
const css = read(file);
assert.doesNotMatch(css, /\.single-cover-slide[,{ ]/, `${file} 不得再包含死的 .single-cover-slide 规则`);
assert.doesNotMatch(css, /\.single-cover-slider\b/, `${file} 不得再包含死的 .single-cover-slider 规则`);
}
assert.doesNotMatch(
read('assets/js/core/misc.js'),
/initSlideCovers/,
'misc.js 的 initSlideCovers(无标记生产方)已删除'
);
});