'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";