Files
WooNooW/tests/PlaceholderRendererTest.php
Dwindi Ramadhana 396ca25be4 feat: Page Editor v1.0 - canonical schema, SSR parity, and migration
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
2026-05-30 13:02:08 +07:00

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);
}
}