fix(widgets): align mini paid badges with reference

This commit is contained in:
kate522 committed 2026-10-29 21:15:11 +08:00
1 parent fd034ff055
commit 985f18c750
8 files changed
+249 -31

No files matched your search

@@ -0,0 +1,95 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { execFileSync } = require('node:child_process');
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');
const cases = JSON.parse(execFileSync('php', [path.join(__dirname, 'tab-posts-render-harness.php')], { encoding: 'utf8' })).cases;
function row(html, id) {
const result = html.split('<div class="posts-mini ajax-item">').slice(1)
.find((item) => item.includes(`href="https://example.test/post/${id}"`));
assert.ok(result, `the actual mini renderer emitted post ${id}`);
return result;
}
function badge(html, id) {
const result = row(html, id).match(/<item class="meta-pay\b[\s\S]*?<\/item>/g) || [];
assert.equal(result.length, 1, 'each article has exactly one compact paid badge');
return result[0];
}
test('cash mini badges show type icons, a smaller currency mark and the published amount', () => {
const expected = [
[70, 'fa fas fa-book', '付费阅读', '12.5'],
[69, 'fa fas fa-download', '付费资源', '12'],
[68, 'fa far fa-file-image', '付费图片', '8.6'],
[67, 'fa fas fa-play-circle', '付费视频', '5'],
];
for (const [id, icon, title, amount] of expected) {
assert.equal(badge(cases.pay_meta_right, id),
`<item class="meta-pay badg badg-sm mr6 c-yellow" data-toggle="tooltip" title="${title}"><i class="${icon} mr3" aria-hidden="true"></i><span class="em09">¥</span>${amount}</item>`);
}
assert.doesNotMatch(cases.pay_meta_right, /旧版资源标签/);
assert.match(badge(cases.pay_meta_custom_currency, 70), /<span class="em09">€<\/span>12\.5<\/item>/);
assert.match(badge(cases.pay_meta_right, 55), /<span class="em09">¥<\/span>0\.5<\/item>/);
});
test('points mini badges use the existing SVG mark inside em09 without the long currency name', () => {
for (const [id, amount] of [[62, 125], [61, 60], [60, 500], [59, 12]]) {
const html = badge(cases.pay_meta_right, id);
assert.ok(html.includes(`<span class="em09"><svg class="points-mark" aria-hidden="true"><use xlink:href="#icon-points"></use></svg></span>${amount}</item>`));
assert.doesNotMatch(html, /积分|¥|€|class="icon"/);
}
});
test('zero prices render the short free badge and take priority over VIP purchase limits', () => {
for (const [id, title] of [[66, '免费阅读'], [65, '免费资源'], [64, '免费图片'], [63, '免费视频']]) {
const html = badge(cases.pay_meta_right, id);
assert.ok(html.includes(`title="${title}"`));
assert.match(html, /<\/i>免费<\/item>$/);
assert.doesNotMatch(html, /会员专属|jb-vip|em09|免费资源<\/item>/);
}
});
test('VIP badges use the selected level image inline and keep the reference membership-switch rule', () => {
for (const html of [cases.pay_meta_right, cases.pay_vip1_only, cases.pay_vip2_only]) {
for (const [id, level, name] of [[58, 1, '黄金会员'], [57, 2, '钻石会员']]) {
const current = badge(html, id);
assert.ok(current.includes(`mr6 jb-vip${level}"`));
assert.ok(current.includes(`<img class="img-icon mr3" src="https://example.test/vip-${level}.svg" data-toggle="tooltip" title="${name}" alt="${name}">会员专属`));
assert.doesNotMatch(current, /fa-certificate|class="em09"/);
}
}
for (const id of [58, 57]) {
const html = badge(cases.pay_vip_disabled, id);
assert.match(html, /mr6 c-yellow/);
assert.match(html, /<span class="em09">¥<\/span>12\.5/);
assert.doesNotMatch(html, /会员专属|img-icon|jb-vip/);
}
});
test('mini badges stay on the correct meta side, disappear with meta and ignore non-paid posts', () => {
const right = row(cases.pay_meta_right, 70);
const left = row(cases.pay_meta_left, 70);
assert.ok(right.indexOf('class="meta-right"') < right.indexOf('class="meta-pay '));
assert.ok(left.indexOf('class="meta-pay ') < left.indexOf('class="meta-right"'));
badge(cases.pay_meta_right, 70);
badge(cases.pay_meta_left, 70);
assert.doesNotMatch(cases.pay_meta_hidden, /meta-pay|item-meta/);
assert.doesNotMatch(row(cases.pay_meta_right, 51), /meta-pay/);
assert.equal(cases.pay_empty, '');
});
test('mini badge styles preserve reference padding and VIP aspect ratio across page bundles', () => {
const widgetCss = read('assets/widgets/frontend.css');
const rule = widgetCss.match(/\.widget-tab-post \.item-meta \.meta-pay\s*\{([^}]+)\}/)[1];
assert.match(rule, /padding:\s*0 4px/);
assert.match(rule, /cursor:\s*auto/);
for (const file of ['assets/css/frontend/post-card.css', 'assets/css/build/homepage.css']) {
assert.doesNotMatch(read(file), /\.item-meta \.meta-pay img\s*\{/);
}
assert.match(read('assets/css/main.css'), /\.img-icon\s*\{[^}]*height:\s*1\.1em;[^}]*display:\s*inline-block/s);
});
@@ -63,18 +63,30 @@ namespace AKSRT\Settings {
public static function get_post_thumb_settings(): array { return ['thumb_catimg_s' => false]; }
public static function get_post_page_settings(): array { return ['like_enabled' => true]; }
public static function get_comment_display_settings(): array { return ['close_comments' => false]; }
public static function get_paid_content_settings(): array { return $GLOBALS['tab_fixture_paid_settings'] ?? ['display' => ['currency_symbol' => '¥']]; }
public static function get_payment_vip_settings(): array { return $GLOBALS['tab_fixture_vip_settings'] ?? ['levels' => []]; }
}
}
namespace {
$GLOBALS['tab_fixture_posts'] = [];
foreach (range(1, 48) as $id) {
$GLOBALS['tab_fixture_posts'][] = ['id' => $id, 'cat' => $id <= 36 ? 7 : 8, 'date' => 1000 + $id, 'modified' => 2000 + $id, 'views' => 49 - $id];
foreach (range(1, 70) as $id) {
$category = $id <= 36 ? 7 : ($id <= 48 ? 8 : 9);
$GLOBALS['tab_fixture_posts'][] = ['id' => $id, 'cat' => $category, 'date' => 1000 + $id, 'modified' => 2000 + $id, 'views' => 71 - $id];
}
$GLOBALS['tab_fixture_current_post'] = null;
$GLOBALS['tab_fixture_global_style'] = [];
$GLOBALS['tab_fixture_mobile'] = false;
$GLOBALS['tab_fixture_avatar_calls'] = [];
$GLOBALS['tab_fixture_pay_meta'] = [];
$GLOBALS['tab_fixture_paid_settings'] = ['display' => ['currency_symbol' => '¥']];
$GLOBALS['tab_fixture_vip_settings'] = [
'enabled' => true,
'levels' => [
1 => ['enabled' => true, 'name' => '黄金会员', 'img_icon' => 'https://example.test/vip-1.svg'],
2 => ['enabled' => true, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
],
];
function __($text, $domain = null): string { return (string) $text; }
function esc_html__($text, $domain = null): string { return esc_html((string) $text); }
@@ -137,14 +149,21 @@ namespace {
function get_comments_link(int $id = 0): string { return 'https://example.test/post/' . $id . '#comments'; }
function get_post_type(int $id = 0): string { unset($id); return 'post'; }
function is_sticky(int $id = 0): bool { unset($id); return false; }
function aksrt_resource_card_badge_text(int $id): string { unset($id); return ''; }
function aksrt_get_pay_meta($post = null): array { unset($post); return ['pay_type' => 'no']; }
function aksrt_resource_card_badge_text(int $id): string { return isset($GLOBALS['tab_fixture_pay_meta'][$id]) ? '旧版资源标签' : ''; }
function aksrt_get_pay_meta($post = null): array {
$post_id = $post instanceof WP_Post ? $post->ID : (int) $post;
return $GLOBALS['tab_fixture_pay_meta'][$post_id] ?? ['pay_type' => 'no'];
}
function aksrt_get_pay_types(): array { return ['reading' => '付费阅读', 'download' => '付费下载', 'gallery' => '付费图片', 'video' => '付费视频']; }
function aksrt_format_price($price): string {
$value = number_format((float) $price, 2, '.', '');
return rtrim(rtrim($value, '0'), '.') ?: '0';
}
function aksrt_get_post_views(int $id): int { return $id * 10; }
function aksrt_get_post_likes(int $id): int { return $id * 2; }
function aksrt_format_number(int $number): string { return (string) $number; }
function aksrt_get_lazy_thumb(string $size = ''): string { unset($size); return 'https://example.test/placeholder.jpg'; }
function aksrt_get_lazy_attr(string $key, string $url, string $class = '', string $lazy = ''): string { unset($key, $lazy); return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '"'; }
function aksrt_icon_markup(string $key): string { return '<svg class="' . esc_attr($key) . '"></svg>'; }
function wp_reset_postdata(): void { $GLOBALS['tab_fixture_current_post'] = null; $GLOBALS['post'] = null; }
function aksrt_widget_ajax_null(string $message = ''): string { return '<div class="aksrt-widget__empty">' . esc_html($message) . '</div>'; }
function aksrt_render_placeholder(string $style, int $count): string { return '<div data-placeholder="' . esc_attr($style) . ':' . $count . '"></div>'; }
@@ -159,11 +178,34 @@ namespace {
function wp_json_encode($value): string { return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); }
require dirname(__DIR__, 2) . '/vendor/autoload.php';
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-icons.php';
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/config.php';
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/post-data.php';
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/markup.php';
$GLOBALS['tab_fixture_global_style'] = ['list_type' => 'card', 'list_show_type' => 'no_margin', 'list_list_option' => ['style' => 'style2'], 'list_card_option' => ['style' => 'style3'], 'list_orderby' => 'date', 'post_time_source' => 'modified', 'post_list_author' => true, 'post_list_time' => true];
$GLOBALS['tab_fixture_pay_meta'] = [
70 => ['pay_type' => 'reading', 'pay_modo' => '0', 'price' => '12.50', 'points_price' => 0, 'pay_limit' => 0],
69 => ['pay_type' => 'download', 'pay_modo' => '0', 'price' => '12.00', 'points_price' => 0, 'pay_limit' => 0],
68 => ['pay_type' => 'gallery', 'pay_modo' => '0', 'price' => '8.60', 'points_price' => 0, 'pay_limit' => 0],
67 => ['pay_type' => 'video', 'pay_modo' => '0', 'price' => '5.00', 'points_price' => 0, 'pay_limit' => 0],
66 => ['pay_type' => 'reading', 'pay_modo' => '0', 'price' => '0.00', 'points_price' => 0, 'pay_limit' => 0],
65 => ['pay_type' => 'download', 'pay_modo' => '0', 'price' => '0.00', 'points_price' => 0, 'pay_limit' => 0],
64 => ['pay_type' => 'gallery', 'pay_modo' => 'points', 'price' => '0.00', 'points_price' => 0, 'pay_limit' => 0],
63 => ['pay_type' => 'video', 'pay_modo' => '0', 'price' => '0.00', 'points_price' => 0, 'pay_limit' => 1],
62 => ['pay_type' => 'reading', 'pay_modo' => 'points', 'price' => '0.00', 'points_price' => 125, 'pay_limit' => 0],
61 => ['pay_type' => 'download', 'pay_modo' => 'points', 'price' => '0.00', 'points_price' => 60, 'pay_limit' => 0],
60 => ['pay_type' => 'gallery', 'pay_modo' => 'points', 'price' => '0.00', 'points_price' => 500, 'pay_limit' => 0],
59 => ['pay_type' => 'video', 'pay_modo' => 'points', 'price' => '0.00', 'points_price' => 12, 'pay_limit' => 0],
58 => ['pay_type' => 'reading', 'pay_modo' => '0', 'price' => '12.50', 'points_price' => 0, 'pay_limit' => 1],
57 => ['pay_type' => 'download', 'pay_modo' => '0', 'price' => '12.50', 'points_price' => 0, 'pay_limit' => 2],
56 => ['pay_type' => 'gallery', 'pay_modo' => '0', 'price' => '8.75', 'points_price' => 0, 'pay_limit' => 1],
55 => ['pay_type' => 'reading', 'pay_modo' => '0', 'price' => '0.50', 'points_price' => 0, 'pay_limit' => 0],
54 => ['pay_type' => 'download', 'pay_modo' => '0', 'price' => '0.50', 'points_price' => 0, 'pay_limit' => 0],
53 => ['pay_type' => 'gallery', 'pay_modo' => '0', 'price' => '0.50', 'points_price' => 0, 'pay_limit' => 0],
52 => ['pay_type' => 'video', 'pay_modo' => '0', 'price' => '0.50', 'points_price' => 0, 'pay_limit' => 0],
51 => ['pay_type' => 'no', 'pay_modo' => '0', 'price' => '0.00', 'points_price' => 0, 'pay_limit' => 0],
];
$tabs = [
['title' => '热门', 'cat' => '7', 'orderby' => 'date'],
['title' => '', 'cat' => '7'],
@@ -246,5 +288,49 @@ namespace {
ob_start();
\AKSRT\WidgetSystem\FrontendRenderer::render($definition, ['before_widget' => '<aside>', 'after_widget' => '</aside>'], $wrapper_instance, $widget);
$cases['frontend_wrapper'] = (string) ob_get_clean();
$paid_instance = \AKSRT\WidgetSystem\FieldSchema::normalize(\AKSRT\WidgetSystem\WidgetSchemas::for('tab_posts'), [
'style' => 'mini', 'mini_opt' => ['show_meta'], 'count' => 20, 'paginate' => '',
'tabs' => [['title' => '付费徽标用例', 'cat' => '9']],
]);
$cases['pay_meta_right'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$paid_instance['mini_opt'] = [];
$cases['pay_meta_hidden'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$paid_instance['mini_opt'] = ['show_meta'];
$GLOBALS['tab_fixture_global_style']['post_list_author'] = false;
$GLOBALS['tab_fixture_global_style']['post_list_time'] = false;
$cases['pay_meta_left'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$GLOBALS['tab_fixture_global_style']['post_list_author'] = true;
$GLOBALS['tab_fixture_global_style']['post_list_time'] = true;
$GLOBALS['tab_fixture_paid_settings']['display']['currency_symbol'] = '€';
$cases['pay_meta_custom_currency'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$GLOBALS['tab_fixture_paid_settings']['display']['currency_symbol'] = '¥';
$GLOBALS['tab_fixture_vip_settings'] = [
'enabled' => true,
'levels' => [
1 => ['enabled' => true, 'name' => '黄金会员', 'img_icon' => 'https://example.test/vip-1.svg'],
2 => ['enabled' => false, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
],
];
$cases['pay_vip1_only'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$GLOBALS['tab_fixture_vip_settings'] = [
'enabled' => true,
'levels' => [
1 => ['enabled' => false, 'name' => '黄金会员', 'img_icon' => 'https://example.test/vip-1.svg'],
2 => ['enabled' => true, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
],
];
$cases['pay_vip2_only'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$GLOBALS['tab_fixture_vip_settings'] = [
'enabled' => false,
'levels' => [
1 => ['enabled' => false, 'name' => '黄金会员', 'img_icon' => 'https://example.test/vip-1.svg'],
2 => ['enabled' => false, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
],
];
$cases['pay_vip_disabled'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
$cases['pay_empty'] = aksrt_renderer_compact_pay_badge([]);
echo json_encode(['cases' => $cases], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
}