feat(structure): add region/city structure model with validation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 15:19:49 +03:30
parent cfa23b6384
commit 614ea98825
4 changed files with 403 additions and 2 deletions

View File

@@ -0,0 +1,44 @@
<?php
/**
* Minimal test runner for the WordPress-free parts of the plugin.
*
* php tests/run.php
*/
define( 'ABSPATH', __DIR__ . '/' );
require_once dirname( __DIR__ ) . '/includes/class-vc-structure.php';
function vc_assert_same( $expected, $actual, $msg = '' ) {
if ( $expected !== $actual ) {
throw new Exception( $msg . "\n expected: " . var_export( $expected, true ) . "\n actual: " . var_export( $actual, true ) );
}
}
function vc_assert_contains( $needle, array $errors ) {
if ( ! in_array( $needle, $errors, true ) ) {
throw new Exception( 'Missing error: ' . $needle . "\n got: " . var_export( $errors, true ) );
}
}
foreach ( glob( __DIR__ . '/test-*.php' ) as $file ) {
require_once $file;
}
$passed = 0;
$failed = 0;
foreach ( get_defined_functions()['user'] as $fn ) {
if ( 0 !== strpos( $fn, 'test_' ) ) {
continue;
}
try {
$fn();
$passed++;
} catch ( Throwable $e ) {
$failed++;
echo 'FAIL ' . $fn . ': ' . $e->getMessage() . "\n";
}
}
echo $passed . ' passed, ' . $failed . " failed\n";
exit( $failed ? 1 : 0 );

View File

@@ -0,0 +1,149 @@
<?php
/**
* VC_Structure unit tests.
*/
function vc_fixture_structure() {
return array(
array(
'name' => 'Europe',
'slug' => 'europe',
'term_id' => 0,
'cities' => array(
array( 'id' => '737', 'name' => 'Amsterdam', 'slug' => 'amsterdam' ),
array( 'id' => '479', 'name' => 'Paris', 'slug' => 'paris' ),
),
),
array(
'name' => 'Americas',
'slug' => 'americas',
'term_id' => 0,
'cities' => array(
array( 'id' => '678', 'name' => 'Boston', 'slug' => 'boston' ),
),
),
);
}
function vc_fixture_map() {
return array( '737' => 'Amsterdam', '479' => 'Paris', '678' => 'Boston', '22' => 'Victoria' );
}
function test_default_regions() {
$regions = VC_Structure::default_regions();
vc_assert_same(
array( 'uk', 'europe', 'asia', 'middle-east-and-africa', 'americas', 'australasia' ),
array_column( $regions, 'slug' )
);
vc_assert_same( 'Middle East and Africa', $regions[3]['name'] );
vc_assert_same( array(), $regions[0]['cities'] );
vc_assert_same( 0, $regions[0]['term_id'] );
}
function test_normalize_fills_slugs() {
$s = VC_Structure::normalize(
array(
array(
'name' => ' Middle East and Africa ',
'slug' => '',
'term_id' => '7',
'cities' => array(
array( 'id' => '', 'name' => 'Ghost', 'slug' => 'ghost' ),
array( 'id' => '828', 'name' => 'Dubai', 'slug' => '' ),
),
),
)
);
vc_assert_same( 'Middle East and Africa', $s[0]['name'] );
vc_assert_same( 'middle-east-and-africa', $s[0]['slug'] );
vc_assert_same( 7, $s[0]['term_id'] );
vc_assert_same( array( array( 'id' => '828', 'name' => 'Dubai', 'slug' => 'dubai' ) ), $s[0]['cities'] );
}
function test_validate_ok() {
vc_assert_same( array(), VC_Structure::validate( vc_fixture_structure(), vc_fixture_map(), array() ) );
}
function test_validate_unknown_city() {
$s = vc_fixture_structure();
$s[1]['cities'][0]['id'] = '999';
vc_assert_contains( 'City ID 999 is not a Viator destination.', VC_Structure::validate( $s, vc_fixture_map(), array() ) );
}
function test_validate_duplicate_city() {
$s = vc_fixture_structure();
$s[1]['cities'][] = array( 'id' => '737', 'name' => 'Amsterdam', 'slug' => 'amsterdam-2' );
vc_assert_contains( 'Amsterdam is listed more than once.', VC_Structure::validate( $s, vc_fixture_map(), array() ) );
}
function test_validate_duplicate_slug() {
$s = vc_fixture_structure();
$s[1]['slug'] = 'paris';
vc_assert_contains( 'Slug "paris" is used more than once.', VC_Structure::validate( $s, vc_fixture_map(), array() ) );
}
function test_validate_bad_slug() {
$s = vc_fixture_structure();
$s[0]['cities'][0]['slug'] = 'amsterdam!';
$s[1]['cities'][0]['slug'] = 'New York';
$errors = VC_Structure::validate( $s, vc_fixture_map(), array() );
vc_assert_contains( 'Slug "amsterdam!" is invalid: use only a-z, 0-9 and -.', $errors );
vc_assert_contains( 'Slug "New York" is invalid: use only a-z, 0-9 and -.', $errors );
}
function test_validate_reserved() {
$s = vc_fixture_structure();
$s[0]['cities'][0]['slug'] = 'contact';
vc_assert_contains( 'Slug "contact" is reserved.', VC_Structure::validate( $s, vc_fixture_map(), array() ) );
}
function test_validate_taken_slug() {
$s = vc_fixture_structure();
$s[1]['cities'][] = array( 'id' => '22', 'name' => 'Victoria', 'slug' => 'victoria' );
$errors = VC_Structure::validate( $s, vc_fixture_map(), array( 'victoria' => 'destination "Victoria"' ) );
vc_assert_contains( 'Slug "victoria" is already used by destination "Victoria".', $errors );
}
function test_validate_empty_region() {
$s = vc_fixture_structure();
$s[0]['name'] = '';
vc_assert_contains( 'Region 1 has no name.', VC_Structure::validate( $s, vc_fixture_map(), array() ) );
}
function test_canonical_city_primary() {
vc_assert_same( 'paris', VC_Structure::canonical_city( vc_fixture_structure(), array( '479', '737' ), '479' ) );
}
function test_canonical_city_fallback_order() {
vc_assert_same( 'amsterdam', VC_Structure::canonical_city( vc_fixture_structure(), array( '678', '479', '737' ), '5555' ) );
}
function test_canonical_city_none() {
vc_assert_same( '', VC_Structure::canonical_city( vc_fixture_structure(), array( '5555' ), '' ) );
}
function test_primary_destination_id() {
vc_assert_same( '2', VC_Structure::primary_destination_id( array( array( 'ref' => '1' ), array( 'ref' => '2', 'primary' => true ) ) ) );
vc_assert_same( '', VC_Structure::primary_destination_id( array() ) );
}
function test_rewrite_rules() {
vc_assert_same(
array(
'^europe/?$' => 'index.php?viator_destination=europe',
'^americas/?$' => 'index.php?viator_destination=americas',
'^amsterdam/?$' => 'index.php?viator_destination=amsterdam',
'^amsterdam/([^/]+)/?$' => 'index.php?viator_tour=$matches[1]',
'^paris/?$' => 'index.php?viator_destination=paris',
'^paris/([^/]+)/?$' => 'index.php?viator_tour=$matches[1]',
'^boston/?$' => 'index.php?viator_destination=boston',
'^boston/([^/]+)/?$' => 'index.php?viator_tour=$matches[1]',
),
VC_Structure::rewrite_rules( vc_fixture_structure() )
);
}
function test_rewrite_rules_empty_region() {
$rules = VC_Structure::rewrite_rules( array( array( 'name' => 'Europe', 'slug' => 'europe', 'term_id' => 0, 'cities' => array() ) ) );
vc_assert_same( array( '^europe/?$' => 'index.php?viator_destination=europe' ), $rules );
}