The navbar logo carried an inline `height:36px;width:auto;max-width:{logo_width}px`
while `.navbar-logo > img` already owns `height:36px;padding:0 30px`. Under
`box-sizing:border-box` that max-width capped the border box, so the 60px of
horizontal padding stayed inside it and wide logos were squeezed against a
fixed height, breaking the intrinsic aspect ratio.
- Render the nav logo with no inline sizing; CSS alone sizes it.
- Add a `$lazy` argument to jio_get_adaptive_theme_img() so the navbar can emit
the real src up front instead of a square lazyload placeholder; defaults to
'auto' so every other caller keeps the lazy_other behaviour.
- Remove the superseded logo_width plumbing: the setting (PHP + VUE schemas),
Settings::logo_width(), jio_get_site_logo_width(), the --jio-logo-width token
and the unused has_logo context key.
- Cover the contract in the header reference test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
153 lines
7.9 KiB
JavaScript
153 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
const test = require('node:test');
|
|
|
|
const root = path.resolve(__dirname, '..', '..');
|
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
|
|
|
function renderHeaderParts() {
|
|
const php = String.raw`
|
|
define('ABSPATH', __DIR__ . '/');
|
|
$GLOBALS['jio_test_user_id'] = 0;
|
|
function esc_attr($value) { return htmlspecialchars((string) $value, ENT_QUOTES); }
|
|
function esc_html($value) { return htmlspecialchars((string) $value, ENT_QUOTES); }
|
|
function esc_attr__($value, $domain = null) { return esc_attr($value); }
|
|
function esc_html__($value, $domain = null) { return esc_html($value); }
|
|
function esc_url($value) { return $value; }
|
|
function esc_url_raw($value) { return $value; }
|
|
function home_url($path = '/') { return 'https://example.test' . $path; }
|
|
function get_bloginfo($field = '') { return 'Jio'; }
|
|
function get_current_user_id() { return (int) $GLOBALS['jio_test_user_id']; }
|
|
function get_option($name, $default = false) { return $name === 'users_can_register'; }
|
|
function jio_render_header_menu_buttons() { return '<a class="newadd-btns">发布</a>'; }
|
|
function jio_get_translate_language_btn($class = '') { return '<a class="' . esc_attr($class) . '">语言</a>'; }
|
|
function jio_header_user_box_html($ctx) { return '<div class="sub-user-box">账户</div>'; }
|
|
function jio_get_avatar_html($userId, $size, $args = []) { return '<img class="avatar" width="' . (int) $size . '">'; }
|
|
function has_nav_menu($location) { return false; }
|
|
function is_super_admin() { return false; }
|
|
function is_singular($type = '') { return false; }
|
|
require __DIR__ . '/inc/helpers/header/jio-header-navbar.php';
|
|
require __DIR__ . '/inc/helpers/header/jio-header-mobile.php';
|
|
|
|
$results = [];
|
|
foreach (['1', '2', '3'] as $layout) {
|
|
foreach ([0, 7] as $userId) {
|
|
$GLOBALS['jio_test_user_id'] = $userId;
|
|
$ctx = [
|
|
'layout' => $layout,
|
|
'user_id' => $userId,
|
|
'theme_positions' => ['pc_nav', 'm_menu'],
|
|
'message_icon_positions' => [],
|
|
'messages_url' => '',
|
|
'unread_count' => 0,
|
|
'mobile_layout' => 'center',
|
|
'brand_name' => 'Jio',
|
|
'logo_html' => '',
|
|
];
|
|
ob_start(); jio_menu_button($ctx); $buttons = ob_get_clean();
|
|
$results['desktop'][$layout][$userId] = $buttons;
|
|
}
|
|
$ctx['message_icon_positions'] = ['nav_menu'];
|
|
$ctx['messages_url'] = 'https://example.test/messages';
|
|
$ctx['unread_count'] = 3;
|
|
ob_start(); jio_menu_button($ctx); $results['notifications'][$layout] = ob_get_clean();
|
|
}
|
|
foreach (['center', 'left'] as $mobileLayout) {
|
|
$ctx = [
|
|
'mobile_layout' => $mobileLayout,
|
|
'mobile_align' => 'left',
|
|
'theme_positions' => [],
|
|
'brand_name' => 'Jio',
|
|
'logo_html' => '',
|
|
'user_id' => 0,
|
|
];
|
|
ob_start(); jio_navbar_header($ctx); $results['mobile'][$mobileLayout]['header'] = ob_get_clean();
|
|
ob_start(); jio_nav_mobile_content($ctx); $results['mobile'][$mobileLayout]['drawer'] = ob_get_clean();
|
|
}
|
|
echo json_encode($results, JSON_UNESCAPED_SLASHES);
|
|
`;
|
|
const result = spawnSync('php', ['-r', php], { cwd: root, encoding: 'utf8' });
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
return JSON.parse(result.stdout);
|
|
}
|
|
|
|
test('rendered desktop layouts retain reference control order for guests and members', () => {
|
|
const output = renderHeaderParts();
|
|
for (const layout of ['1', '2', '3']) {
|
|
for (const userId of ['0', '7']) {
|
|
const html = output.desktop[layout][userId];
|
|
assert.match(html, /class="but" aria-label=/, `layout ${layout} menu fold`);
|
|
assert.match(html, /class="list-inline splitters relative"/, `layout ${layout} account list`);
|
|
assert.doesNotMatch(html, /jio-navbar-account|jio-nav-avatar/);
|
|
if (layout === '2' && userId === '0') assert.match(html, /class="signin-loader"/);
|
|
else assert.match(html, /class="jio-nav-icon[^"]*"|class="navbar-avatar"/);
|
|
const right = html.indexOf('list-inline splitters relative');
|
|
const actions = html.indexOf('navbar-but');
|
|
if (layout === '3' && actions !== -1) assert.ok(right < actions, 'layout 3 puts account group before actions');
|
|
if (layout !== '3' && actions !== -1) assert.ok(actions < right, `layout ${layout} puts actions before account group`);
|
|
}
|
|
}
|
|
assert.match(output.desktop['2']['0'], /signin-loader/);
|
|
assert.match(output.desktop['2']['7'], /class="navbar-avatar"/);
|
|
assert.match(output.desktop['1']['0'], /#icon-user/);
|
|
assert.match(output.desktop['3']['7'], /#icon-user/);
|
|
});
|
|
|
|
test('mobile search appears in the centered header or left drawer according to layout', () => {
|
|
const output = renderHeaderParts();
|
|
assert.match(output.mobile.center.header, /class="navbar-toggle main-search-btn"/);
|
|
assert.doesNotMatch(output.mobile.center.drawer, /main-search-btn/);
|
|
assert.doesNotMatch(output.mobile.left.header, /main-search-btn/);
|
|
assert.match(output.mobile.left.drawer, /class="toggle-radius main-search-btn"/);
|
|
});
|
|
|
|
test('account icons keep the reference spacing on both sides of each divider', () => {
|
|
const output = renderHeaderParts();
|
|
const lists = [
|
|
...Object.values(output.desktop).flatMap((states) => Object.values(states)),
|
|
...Object.values(output.notifications),
|
|
];
|
|
for (const html of lists) {
|
|
const account = html.match(/<ul class="list-inline splitters relative">([\s\S]*?)<\/ul><\/div>/);
|
|
assert.ok(account, 'missing the account action list');
|
|
// The reference menu button concatenates the items directly. Whitespace
|
|
// between inline-block li elements adds a font-dependent gap before the
|
|
// following item's divider, beyond the shared 5px li / 12px button padding.
|
|
assert.doesNotMatch(account[1], /<\/li>\s+<li\b/, 'extra text space shifts the divider');
|
|
assert.doesNotMatch(account[1], /<\/li>\s+$/, 'trailing text space widens the action group');
|
|
}
|
|
assert.match(output.notifications['1'], /jio-nav-notification/);
|
|
});
|
|
|
|
test('header source restores the reference typography and Bootstrap control dimensions', () => {
|
|
const css = read('assets/css/frontend/header-userbox.css');
|
|
const mainCss = read('assets/css/main.css');
|
|
assert.match(css, /\.header,\s*\.mobile-header,\s*\.navbar-search\s*\{[^}]*font-family:\s*"Helvetica Neue", Helvetica, Arial, sans-serif;[^}]*font-size:\s*14px;[^}]*font-weight:\s*400;[^}]*line-height:\s*1\.42857143;[^}]*-webkit-font-smoothing:\s*auto;[^}]*text-rendering:\s*auto;/s);
|
|
assert.match(css, /\.navbar-top \.jio-nav-icon\s*\{[^}]*display:\s*inline-block;[^}]*padding:\s*6px 12px;[^}]*font-size:\s*14px;[^}]*line-height:\s*1\.42857143;[^}]*border:\s*1px solid transparent;[^}]*border-radius:\s*4px;/s);
|
|
assert.match(mainCss, /\.navbar-avatar\s*\{[^}]*margin-top:\s*3px;[^}]*width:\s*29px;[^}]*height:\s*29px;/s);
|
|
assert.doesNotMatch(css, /\.jio-navbar-account|\.jio-nav-avatar|\.navbar-form \.toggle-radius:not|navbar-form\.flex\.ac\s*\{[^}]*min-height|\.navbar-search(?:\.show)?\s*\{[^}]*transform:/s);
|
|
});
|
|
|
|
test('nav logo is sized by CSS alone so the image keeps its aspect ratio', () => {
|
|
const mainCss = read('assets/css/main.css');
|
|
const render = read('inc/helpers/header/jio-header-render.php');
|
|
const helper = read('inc/helpers/template-tags/media-brand.php');
|
|
|
|
// The reference owns the logo box in CSS: fixed 36px height, auto width, no cap.
|
|
assert.match(mainCss, /\.navbar-logo > img \{\s*\n\s*height: 36px;\s*\n\s*padding: 0 30px;\s*\n\}/);
|
|
|
|
// A max-width on the <img> would cap the border box while the 60px horizontal
|
|
// padding stays inside it (box-sizing: border-box), squeezing wide logos.
|
|
assert.doesNotMatch(render, /max-width:\s*\d+px[^;]*"\s*\)/);
|
|
assert.doesNotMatch(render, /style="height:36px/);
|
|
assert.doesNotMatch(helper, /jio_get_site_logo_width|logo_width/);
|
|
|
|
// Nav logo loads its real src up front; only the shared helper still lazy-loads elsewhere.
|
|
assert.match(render, /jio_get_adaptive_theme_img\(\$logo_url, \$logo_dark, \$brand_name, '', false\)/);
|
|
assert.match(helper, /\$lazy = 'auto'/);
|
|
});
|