Major improvements to WooNooW Page Editor system: Schema & Architecture: - Canonical section schema with unified sectionSchema.ts - Normalized feature-grid to use items (not features) - Standardized default values across all section types - Schema versioning with automatic migration on read Backend (PHP): - Enhanced PlaceholderRenderer with typed output contracts - Added fallback behavior for empty/invalid dynamic sources - Added caching support for post data resolution - New SchemaMigration class for backward compatibility - New Features class for feature flags - Enhanced PageSSR with full style support - Removed controller-level special-casing for related_posts Frontend (Admin SPA): - Updated CanvasRenderer with schema-aware transformation - Enhanced InspectorPanel with canonical schema metadata - Added new section renderers Frontend (Customer SPA): - New section components: BentoCategoryGrid, MarqueeBanner, ProductCarousel, ShoppableImage - Updated FeatureGridSection for items prop contract Testing: - Add PHP tests: SchemaMigrationTest, PlaceholderRendererTest, PageSSRTest - Add TypeScript tests: schema-integration, feature-grid-regression - Add parity tests for React vs SSR content matching - Add CI script: check-schema-drift.mjs - Add VERIFICATION_CHECKLIST.md Documentation: - RELEASE_NOTES-v1.0.md with full release notes - docs/PAGE_EDITOR_SECTION_SCHEMA_V1.md - docs/PAGE_EDITOR_SSR_COVERAGE_AUDIT.md
224 lines
8.0 KiB
PHP
224 lines
8.0 KiB
PHP
<?php
|
|
/**
|
|
* PlaceholderRenderer Tests
|
|
* Tests for dynamic placeholder resolution with typed output contracts
|
|
*/
|
|
|
|
namespace WooNooW\Tests;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use WooNooW\Frontend\PlaceholderRenderer;
|
|
|
|
class PlaceholderRendererTest extends TestCase
|
|
{
|
|
/**
|
|
* Test get_source_type returns correct types
|
|
*/
|
|
public function test_get_source_type()
|
|
{
|
|
$this->assertEquals('scalar', PlaceholderRenderer::get_source_type('post_title'));
|
|
$this->assertEquals('html', PlaceholderRenderer::get_source_type('post_content'));
|
|
$this->assertEquals('url', PlaceholderRenderer::get_source_type('post_featured_image'));
|
|
$this->assertEquals('array', PlaceholderRenderer::get_source_type('post_categories'));
|
|
$this->assertEquals('array', PlaceholderRenderer::get_source_type('related_posts'));
|
|
$this->assertEquals('url', PlaceholderRenderer::get_source_type('post_url'));
|
|
}
|
|
|
|
/**
|
|
* Test validate_value_type for scalar values
|
|
*/
|
|
public function test_validate_value_type_scalar()
|
|
{
|
|
$this->assertTrue(PlaceholderRenderer::validate_value_type('Hello', 'scalar'));
|
|
$this->assertTrue(PlaceholderRenderer::validate_value_type(123, 'scalar'));
|
|
$this->assertTrue(PlaceholderRenderer::validate_value_type('', 'scalar'));
|
|
$this->assertFalse(PlaceholderRenderer::validate_value_type(['array'], 'scalar'));
|
|
}
|
|
|
|
/**
|
|
* Test validate_value_type for URL values
|
|
*/
|
|
public function test_validate_value_type_url()
|
|
{
|
|
$this->assertTrue(PlaceholderRenderer::validate_value_type('https://example.com/image.jpg', 'url'));
|
|
$this->assertFalse(PlaceholderRenderer::validate_value_type('', 'url'));
|
|
$this->assertFalse(PlaceholderRenderer::validate_value_type(['array'], 'url'));
|
|
}
|
|
|
|
/**
|
|
* Test validate_value_type for array values
|
|
*/
|
|
public function test_validate_value_type_array()
|
|
{
|
|
$this->assertTrue(PlaceholderRenderer::validate_value_type(['a', 'b', 'c'], 'array'));
|
|
$this->assertTrue(PlaceholderRenderer::validate_value_type([], 'array'));
|
|
$this->assertFalse(PlaceholderRenderer::validate_value_type('string', 'array'));
|
|
}
|
|
|
|
/**
|
|
* Test get_fallback returns correct defaults
|
|
*/
|
|
public function test_get_fallback()
|
|
{
|
|
$this->assertEquals('(Untitled)', PlaceholderRenderer::get_fallback('post_title'));
|
|
$this->assertEquals([], PlaceholderRenderer::get_fallback('post_categories', 'array'));
|
|
$this->assertEquals('', PlaceholderRenderer::get_fallback('unknown_source'));
|
|
$this->assertEquals([], PlaceholderRenderer::get_fallback('unknown_source', 'array'));
|
|
}
|
|
|
|
/**
|
|
* Test is_array_source identifies array-based sources
|
|
*/
|
|
public function test_is_array_source()
|
|
{
|
|
$this->assertTrue(PlaceholderRenderer::is_array_source('post_categories'));
|
|
$this->assertTrue(PlaceholderRenderer::is_array_source('post_tags'));
|
|
$this->assertTrue(PlaceholderRenderer::is_array_source('related_posts'));
|
|
$this->assertFalse(PlaceholderRenderer::is_array_source('post_title'));
|
|
$this->assertFalse(PlaceholderRenderer::is_array_source('post_featured_image'));
|
|
}
|
|
|
|
/**
|
|
* Test get_value resolves scalar values
|
|
*/
|
|
public function test_get_value_resolves_scalar()
|
|
{
|
|
$post_data = [
|
|
'title' => 'Test Post Title',
|
|
'content' => '<p>Post content here</p>',
|
|
'author' => 'John Doe',
|
|
];
|
|
|
|
$this->assertEquals('Test Post Title', PlaceholderRenderer::get_value('post_title', $post_data));
|
|
$this->assertEquals('<p>Post content here</p>', PlaceholderRenderer::get_value('post_content', $post_data));
|
|
$this->assertEquals('John Doe', PlaceholderRenderer::get_value('post_author', $post_data));
|
|
}
|
|
|
|
/**
|
|
* Test get_value resolves URL values
|
|
*/
|
|
public function test_get_value_resolves_url()
|
|
{
|
|
$post_data = [
|
|
'featured_image' => 'https://example.com/featured.jpg',
|
|
'url' => 'https://example.com/post',
|
|
];
|
|
|
|
$this->assertEquals('https://example.com/featured.jpg', PlaceholderRenderer::get_value('post_featured_image', $post_data));
|
|
$this->assertEquals('https://example.com/post', PlaceholderRenderer::get_value('post_url', $post_data));
|
|
}
|
|
|
|
/**
|
|
* Test get_value with fallback for empty values
|
|
*/
|
|
public function test_get_value_with_fallback()
|
|
{
|
|
$post_data = [
|
|
'title' => '',
|
|
'featured_image' => '',
|
|
];
|
|
|
|
$this->assertEquals('(Untitled)', PlaceholderRenderer::get_value('post_title', $post_data));
|
|
$this->assertEquals('', PlaceholderRenderer::get_value('post_featured_image', $post_data));
|
|
}
|
|
|
|
/**
|
|
* Test get_value with empty post_data
|
|
*/
|
|
public function test_get_value_with_empty_post_data()
|
|
{
|
|
$this->assertEquals('(Untitled)', PlaceholderRenderer::get_value('post_title', []));
|
|
$this->assertEquals('', PlaceholderRenderer::get_value('post_featured_image', []));
|
|
$this->assertEquals([], PlaceholderRenderer::get_value('post_categories', []));
|
|
}
|
|
|
|
/**
|
|
* Test get_value with options
|
|
*/
|
|
public function test_get_value_with_options()
|
|
{
|
|
$post_data = [
|
|
'title' => '',
|
|
];
|
|
|
|
// Without fallback
|
|
$result = PlaceholderRenderer::get_value('post_title', $post_data, ['use_fallback' => false]);
|
|
$this->assertEquals('', $result);
|
|
|
|
// Without type validation
|
|
$result = PlaceholderRenderer::get_value('post_title', $post_data, ['validate_type' => false]);
|
|
$this->assertEquals('(Untitled)', $result);
|
|
}
|
|
|
|
/**
|
|
* Test custom meta field resolution
|
|
*/
|
|
public function test_get_value_custom_meta_field()
|
|
{
|
|
$post_data = [
|
|
'meta' => [
|
|
'custom_field' => 'Custom Value',
|
|
],
|
|
];
|
|
|
|
$this->assertEquals('Custom Value', PlaceholderRenderer::get_value('post_field_custom_field', $post_data));
|
|
}
|
|
|
|
/**
|
|
* Test build_post_data returns required fields
|
|
*/
|
|
public function test_build_post_data_structure()
|
|
{
|
|
global $wpdb;
|
|
|
|
// Create a mock WP_Post
|
|
$post = new \stdClass();
|
|
$post->ID = 1;
|
|
$post->post_title = 'Test Title';
|
|
$post->post_content = 'Test Content';
|
|
$post->post_excerpt = 'Test Excerpt';
|
|
$post->post_author = 1;
|
|
$post->post_type = 'post';
|
|
$post->post_name = 'test-title';
|
|
$post->post_date = '2024-01-01 12:00:00';
|
|
|
|
// Mock WordPress functions
|
|
if (!function_exists('get_the_date')) {
|
|
function get_the_date() { return 'January 1, 2024'; }
|
|
}
|
|
if (!function_exists('get_permalink')) {
|
|
function get_permalink() { return 'https://example.com/test-title'; }
|
|
}
|
|
if (!function_exists('get_the_author_meta')) {
|
|
function get_the_author_meta() { return 'Admin'; }
|
|
}
|
|
if (!function_exists('get_post_thumbnail_id')) {
|
|
function get_post_thumbnail_id() { return 0; }
|
|
}
|
|
if (!function_exists('get_the_post_thumbnail_url')) {
|
|
function get_the_post_thumbnail_url() { return ''; }
|
|
}
|
|
if (!function_exists('get_object_taxonomies')) {
|
|
function get_object_taxonomies() { return []; }
|
|
}
|
|
if (!function_exists('get_the_terms')) {
|
|
function get_the_terms() { return []; }
|
|
}
|
|
if (!function_exists('get_post_meta')) {
|
|
function get_post_meta() { return []; }
|
|
}
|
|
if (!function_exists('apply_filters')) {
|
|
function apply_filters($tag, $value) { return $value; }
|
|
}
|
|
if (!function_exists('wp_trim_words')) {
|
|
function wp_trim_words($text, $num_words, $more) { return $text; }
|
|
}
|
|
|
|
$data = PlaceholderRenderer::build_post_data($post);
|
|
|
|
$this->assertArrayHasKey('title', $data);
|
|
$this->assertArrayHasKey('content', $data);
|
|
$this->assertArrayHasKey('url', $data);
|
|
$this->assertArrayHasKey('type', $data);
|
|
}
|
|
} |