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

186 lines
7.0 KiB
JavaScript

'use strict';
// 文章详情页顶部封面高度契约:单图 / 多图(轮播) / 视频三种模式
// 必须共用同一高度公式 padding-bottom: calc(比例% + 50px)。
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 aligns with the image-mode height formula', () => {
assert.match(
singleRender,
/single-cover--video imgbox-container" style="padding-bottom:calc\(<\?php echo esc_attr\(\(string\) \$video_scale\); \?>% \+ 50px\);"/,
'视频封面必须输出 calc(video_scale% + 50px) 内联高度'
);
// 不得回退到裸比例高度。
assert.doesNotMatch(
singleRender,
/padding-bottom:<\?php echo esc_attr\(\(string\) \$video_scale\); \?>%;"/,
'视频封面禁止输出不带 +50px 的裸比例高度'
);
});
test('single cover: slide mode reserves the same +50px via the slider helper', () => {
assert.match(
singleRender,
/'scale_extra'\s*=>\s*'50px'/,
'多图封面必须向 aksrt_render_slider 传入 scale_extra=50px'
);
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 截断'
);
});
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('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',
'assets/css/frontend/enhancements/components/base.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(无标记生产方)已删除'
);
});