Files
wordpress-theme-aksrt/tools/generate-widget-golden-fixture.php
T

230 lines
10 KiB
PHP

<?php
declare(strict_types=1);
require dirname(__DIR__) . '/tests/widget-system/bootstrap.php';
use AKSRT\WidgetSystem\FieldSchema;
use AKSRT\WidgetSystem\WidgetRegistry;
$root = dirname(__DIR__);
$baseline_path = $root . '/.specs/baselines/widget-reference.json';
$baseline = json_decode((string) file_get_contents($baseline_path), true, 512, JSON_THROW_ON_ERROR);
if (($baseline['schema'] ?? '') !== 'aksrt.widget-reference.v1') {
throw new RuntimeException('Frozen widget reference baseline is invalid.');
}
$reference_root = rtrim((string) ($baseline['source_root'] ?? ''), '\\/');
$reference_hashes = [];
foreach ((array) ($baseline['files'] ?? []) as $file) {
$relative = (string) ($file['path'] ?? '');
$expected = (string) ($file['sha256'] ?? '');
$path = $reference_root . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relative);
if (!is_file($path) || hash_file('sha256', $path) !== $expected) {
throw new RuntimeException('Frozen reference changed or is missing: ' . $relative);
}
$reference_hashes[$relative] = $expected;
}
$group_sources = [
'general' => 'inc/widgets/widget-more.php',
'content' => 'inc/widgets/widget-posts.php',
'knowledge_product' => 'inc/widgets/widget-produck.php',
'slider' => 'inc/widgets/widget-slider.php',
'user' => 'inc/widgets/widget-user.php',
];
$viewports = [
['name' => 'desktop', 'width' => 1440, 'height' => 1100],
['name' => 'tablet', 'width' => 1024, 'height' => 1000],
['name' => 'mobile', 'width' => 390, 'height' => 844],
];
$modes = ['light', 'dark'];
$images = [
'https://fixtures.aksrt.test/media/course-cover-01.jpg',
'https://fixtures.aksrt.test/media/course-cover-02.jpg',
'https://fixtures.aksrt.test/media/course-cover-03.jpg',
];
function fixture_text(string $id, string $slug, int $index): string {
$labels = [
'question' => '购买后可以长期学习吗?',
'answer' => '可以,已购买内容会永久保存在你的学习中心。',
'name' => 'AKSRT 学员 ' . ($index + 1),
'author' => 'AKSRT 学员 ' . ($index + 1),
'role' => '知识产品体验官',
'price' => '¥99',
'value' => (string) (($index + 1) * 1280) . '+',
'label' => '累计学习人数',
'heading' => '从入门到精通的系统学习路径',
'description' => '固定内容用于组件 DOM、响应式和视觉差异验收。',
'content' => '结构清晰、案例完整,并持续更新配套资料。',
'title' => 'AKSRT 固定示例 ' . ($index + 1),
'text' => '保持好奇,持续创造。',
];
if (isset($labels[$id])) {
return $labels[$id];
}
if (str_contains($id, 'video')) {
return 'https://fixtures.aksrt.test/media/lesson.m3u8';
}
if (str_contains($id, 'url')) {
return 'https://fixtures.aksrt.test/' . $slug . '/' . ($index + 1);
}
if (str_contains($id, 'ids')) {
return str_contains($id, 'term') || str_contains($id, 'category') ? '101,102,103' : '';
}
return 'AKSRT ' . str_replace('_', ' ', $id) . ' ' . ($index + 1);
}
function fixture_value(array $field, string $slug, int $index, array $images) {
$type = (string) ($field['type'] ?? 'text');
$id = (string) ($field['id'] ?? '');
$default = $field['default'] ?? null;
if ($id === 'title') return 'AKSRT · ' . ucfirst(str_replace('_', ' ', $slug));
if ($id === 'subtitle') return '固定内容与交互状态视觉验收';
if ($id === 'title_highlight') return 'AKSRT';
if ($id === 'show_type') return 'all';
if ($id === 'show_id_type' || $id === 'show_ids') return '';
if ($id === 'layout_bg') {
return [
'light' => ['color' => '#ffffff', 'image' => '', 'position' => 'center center', 'repeat' => 'no-repeat', 'attachment' => 'scroll', 'size' => 'cover'],
'dark' => ['color' => '#1d1e24', 'image' => '', 'position' => 'center center', 'repeat' => 'no-repeat', 'attachment' => 'scroll', 'size' => 'cover'],
];
}
if ($type === 'group') {
$rows = [];
$count = min(3, max(1, (int) ($field['max_items'] ?? 3)));
for ($row = 0; $row < $count; $row++) {
$item = [];
foreach ((array) ($field['fields'] ?? []) as $child) {
$child_id = (string) ($child['id'] ?? '');
if ($child_id !== '') $item[$child_id] = fixture_value($child, $slug, $row, $images);
}
$rows[] = $item;
}
return $rows;
}
if (in_array($type, ['fieldset', 'accordion'], true)) {
$value = [];
foreach ((array) ($field['fields'] ?? []) as $child) {
$child_id = (string) ($child['id'] ?? '');
if ($child_id !== '') $value[$child_id] = fixture_value($child, $slug, $index, $images);
}
return $value;
}
if ($type === 'link') return ['url' => 'https://fixtures.aksrt.test/' . $slug, 'text' => '查看详情', 'target' => ''];
if ($type === 'upload') return $images[$index % count($images)];
if ($type === 'icon') return 'fa fas fa-graduation-cap';
if ($type === 'color') return '#667eea';
if ($type === 'spacing') return ['top' => 0, 'right' => 0, 'bottom' => 20, 'left' => 0];
if ($type === 'background') return ['color' => '#ffffff', 'image' => '', 'position' => 'center center', 'repeat' => 'no-repeat', 'attachment' => 'scroll', 'size' => 'cover'];
if ($type === 'switcher') return $default === null ? true : (bool) $default;
if (in_array($type, ['number', 'spinner', 'slider'], true)) return $default ?? ($field['min'] ?? 1);
if (in_array($type, ['radio', 'button_set', 'select', 'palette'], true)) return $default ?? (array_key_first((array) ($field['options'] ?? [])) ?? '');
if ($type === 'checkbox') return $default ?? [];
if ($id === 'title_link') return ['url' => 'https://fixtures.aksrt.test/' . $slug, 'text' => '查看更多', 'target' => ''];
if ($default !== null && $default !== '') return $default;
return fixture_text($id, $slug, $index);
}
$dynamic = ['posts', 'tab_posts', 'oneline_posts', 'hot_posts'];
$expandable = ['posts_toc', 'faq'];
$tabbed = ['tab_posts', 'feature_intro'];
$carousel = ['partners', 'notice', 'oneline_posts', 'reviews', 'slider', 'slider_media_combo'];
$components = [];
$matrix = [];
foreach (WidgetRegistry::all() as $slug => $definition) {
$settings = [];
foreach ($definition->fields() as $field) {
$id = (string) ($field['id'] ?? '');
$type = (string) ($field['type'] ?? 'text');
if ($id === '' || in_array($type, ['content', 'subheading', 'submessage'], true)) continue;
$settings[$id] = fixture_value($field, $slug, 0, $images);
}
$settings = $definition->normalize($settings);
$states = ['default', 'hover', 'focus'];
if (in_array($slug, $expandable, true)) $states[] = 'expanded';
if (in_array($slug, $tabbed, true)) $states[] = 'alternate-tab';
if (in_array($slug, $carousel, true)) $states[] = 'next-slide';
if (in_array($slug, $dynamic, true)) $states = array_merge($states, ['loading', 'empty', 'error']);
if ($slug === 'post_pay') $states = array_merge($states, ['guest-locked', 'member-locked', 'authorized', 'unconfigured']);
if ($slug === 'user_card') $states = array_merge($states, ['guest', 'signed-in']);
$source = $group_sources[$definition->group()] ?? 'inc/widgets/widget-index.php';
$components[$slug] = [
'id_base' => $definition->id_base(),
'name' => $definition->name(),
'group' => $definition->group(),
'size' => $definition->size(),
'reference' => ['file' => $source, 'sha256' => $reference_hashes[$source] ?? ''],
'field_ids' => array_values(array_filter(array_map(static fn(array $field): string => (string) ($field['id'] ?? ''), $definition->fields()))),
'settings' => $settings,
'root_selector' => '[data-aksrt-widget="' . $slug . '"]',
'states' => array_values(array_unique($states)),
];
foreach ($viewports as $viewport) {
foreach ($modes as $mode) {
$id = $slug . ':' . $viewport['name'] . ':' . $mode;
$matrix[$id] = [
'widget' => $slug,
'viewport' => $viewport,
'mode' => $mode,
'path' => 'tests/visual-results/' . $slug . '/' . $viewport['name'] . '-' . $mode . '.png',
];
}
}
}
$fixture = [
'schema' => 'aksrt.widget-golden.v1',
'captured_at' => (string) ($baseline['captured_at'] ?? ''),
'reference' => [
'source_root' => $reference_root,
'fingerprint' => hash('sha256', implode('', $reference_hashes)),
'files' => $reference_hashes,
'policy' => $baseline['source_policy'] ?? [],
],
'acceptance' => [
'dimension_delta_px_max' => 2,
'pixel_difference_percent_max' => 1.5,
'console_errors_max' => 0,
'unexpected_network_failures_max' => 0,
'a11y_critical_max' => 0,
'a11y_serious_max' => 0,
'reduced_motion_required' => true,
],
'dataset' => [
'site' => ['name' => 'AKSRT 知识库', 'url' => 'https://fixtures.aksrt.test'],
'terms' => [
['id' => 101, 'taxonomy' => 'category', 'name' => '产品设计', 'count' => 24],
['id' => 102, 'taxonomy' => 'category', 'name' => '前端开发', 'count' => 36],
['id' => 103, 'taxonomy' => 'post_tag', 'name' => '系统课程', 'count' => 18],
],
'users' => [
['id' => 201, 'name' => '林知夏', 'role' => '讲师', 'points' => 9680, 'followers' => 3280],
['id' => 202, 'name' => '周屿', 'role' => '作者', 'points' => 7420, 'followers' => 2160],
['id' => 203, 'name' => '陈墨', 'role' => '学员', 'points' => 5360, 'followers' => 980],
],
'posts' => [
['id' => 301, 'title' => '从零构建知识产品体系', 'pay_type' => 'reading', 'author' => 201, 'category' => 101],
['id' => 302, 'title' => '现代 WordPress 主题工程实践', 'pay_type' => 'video', 'author' => 202, 'category' => 102],
['id' => 303, 'title' => '高质量课程资料包', 'pay_type' => 'download', 'author' => 201, 'category' => 102],
['id' => 304, 'title' => '视觉案例图库', 'pay_type' => 'gallery', 'author' => 202, 'category' => 101],
['id' => 305, 'title' => '会员专属进阶课', 'pay_type' => 'vip', 'author' => 201, 'category' => 101],
['id' => 306, 'title' => '公开学习指南', 'pay_type' => 'no', 'author' => 203, 'category' => 102],
],
'comments' => [
['id' => 401, 'user' => 203, 'post' => 301, 'content' => '课程结构清晰,案例非常实用。'],
['id' => 402, 'user' => 202, 'post' => 302, 'content' => '对主题工程化讲解得很完整。'],
],
'payment_states' => ['guest-locked', 'member-locked', 'free', 'purchased', 'vip-authorized', 'unconfigured'],
'media' => ['images' => $images, 'video' => 'https://fixtures.aksrt.test/media/lesson.m3u8'],
],
'components' => $components,
'screenshot_matrix' => $matrix,
];
$output = $root . '/tests/fixtures/widget-golden-v1.json';
file_put_contents($output, json_encode($fixture, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n");
echo 'Generated widget golden fixture: ' . count($components) . ' components, ' . count($matrix) . " screenshots.\n";