| 1 |
<?php |
| 2 |
|
| 3 |
// Run inside a local WordPress installation: |
| 4 |
// docker exec -w /var/www/html/wp-content/plugins/sync-basalam \ |
| 5 |
// basalam-wordpress-local-wordpress-1 php tests/integration/product-attribute-suffix.php |
| 6 |
require getenv('WORDPRESS_BOOTSTRAP') ?: dirname(__DIR__, 5) . '/wp-load.php'; |
| 7 |
|
| 8 |
use SyncBasalam\Admin\Components\SettingPageComponents; |
| 9 |
use SyncBasalam\Admin\Product\ProductDataFactory; |
| 10 |
use SyncBasalam\Admin\Settings\SettingsConfig; |
| 11 |
use SyncBasalam\Admin\Settings\SettingsContainer; |
| 12 |
use SyncBasalam\Admin\Settings\SettingsPageHandler; |
| 13 |
|
| 14 |
if (wp_get_environment_type() !== 'local') { |
| 15 |
throw new RuntimeException('Run these integration tests only in a local WordPress environment.'); |
| 16 |
} |
| 17 |
|
| 18 |
$originalSettings = get_option('sync_basalam_settings'); |
| 19 |
$originalPost = $_POST; |
| 20 |
$products = []; |
| 21 |
$globalAttributes = []; |
| 22 |
$taxonomyAttributes = []; |
| 23 |
$assertions = 0; |
| 24 |
$scenarios = []; |
| 25 |
$assertSame = static function ($expected, $actual, string $scenario) use (&$assertions) { |
| 26 |
if ($expected !== $actual) { |
| 27 |
throw new RuntimeException($scenario . ': expected ' . var_export($expected, true) . ', got ' . var_export($actual, true)); |
| 28 |
} |
| 29 |
$assertions++; |
| 30 |
}; |
| 31 |
$refreshSettings = static function () { |
| 32 |
// Each scenario represents a new request; clear the request's settings cache. |
| 33 |
$property = new ReflectionProperty(SettingsContainer::class, 'settings'); |
| 34 |
$property->setAccessible(true); |
| 35 |
$property->setValue(syncBasalamSettings(), null); |
| 36 |
}; |
| 37 |
$configure = static function ($names, string $enabled = 'yes', string $prefix = '', string $suffix = '') use ($originalSettings, $refreshSettings) { |
| 38 |
update_option('sync_basalam_settings', array_merge(SettingsConfig::getDefaultSettings(), (array) $originalSettings, [ |
| 39 |
SettingsConfig::PRODUCT_ATTRIBUTE_SUFFIX_ENABLED => $enabled, |
| 40 |
SettingsConfig::PRODUCT_ATTRIBUTE_SUFFIX_PRIORITY => $names, |
| 41 |
SettingsConfig::PRODUCT_PREFIX_TITLE => $prefix, |
| 42 |
SettingsConfig::PRODUCT_SUFFIX_TITLE => $suffix, |
| 43 |
])); |
| 44 |
$refreshSettings(); |
| 45 |
}; |
| 46 |
$customAttributes = static function (array $values): array { |
| 47 |
$attributes = []; |
| 48 |
foreach ($values as $name => $options) { |
| 49 |
$attribute = new WC_Product_Attribute(); |
| 50 |
$attribute->set_name((string) $name); |
| 51 |
$attribute->set_options($options); |
| 52 |
$attributes[] = $attribute; |
| 53 |
} |
| 54 |
return $attributes; |
| 55 |
}; |
| 56 |
$nameFor = static function (WC_Product $product): string { |
| 57 |
return (new ProductDataFactory())->createHandler($product)->getName($product); |
| 58 |
}; |
| 59 |
|
| 60 |
try { |
| 61 |
// Fixture saves must not schedule remote product synchronization. |
| 62 |
remove_all_actions('save_post'); |
| 63 |
remove_all_actions('woocommerce_new_product'); |
| 64 |
remove_all_actions('woocommerce_update_product'); |
| 65 |
|
| 66 |
$book = ['ناشر' => ['نشر چش� |
| 67 |
ه'], 'نویسنده' => ['صادق هدایت']]; |
| 68 |
$cases = [ |
| 69 |
['English comma', 'ناشر, نویسنده', $book, 'کتاب (نشر چش� |
| 70 |
ه, صادق هدایت)'], |
| 71 |
['Persian comma', 'ناشر، نویسنده', $book, 'کتاب (نشر چش� |
| 72 |
ه, صادق هدایت)'], |
| 73 |
['configured order', 'نویسنده, ناشر', $book, 'کتاب (صادق هدایت, نشر چش� |
| 74 |
ه)'], |
| 75 |
['single-attribute compatibility', 'ناشر', $book, 'کتاب (نشر چش� |
| 76 |
ه)'], |
| 77 |
['whitespace', " \tناشر , نویسنده ", ['ناشر' => [' نشر چش� |
| 78 |
ه '], 'نویسنده' => [' صادق هدایت ']], 'کتاب (نشر چش� |
| 79 |
ه, صادق هدایت)'], |
| 80 |
['duplicates and empty entries', ', ناشر، ناشر, نویسنده,', $book, 'کتاب (نشر چش� |
| 81 |
ه, صادق هدایت)'], |
| 82 |
['missing first attribute', '� |
| 83 |
ترج� |
| 84 |
, ناشر', $book, 'کتاب (نشر چش� |
| 85 |
ه)'], |
| 86 |
['missing second attribute', 'ناشر, � |
| 87 |
ترج� |
| 88 |
', $book, 'کتاب (نشر چش� |
| 89 |
ه)'], |
| 90 |
['no matching attributes', '� |
| 91 |
ترج� |
| 92 |
, زبان', $book, 'کتاب'], |
| 93 |
['empty attribute', 'ناشر, نویسنده', ['ناشر' => [], 'نویسنده' => ['صادق هدایت']], 'کتاب (صادق هدایت)'], |
| 94 |
['blank value', 'ناشر, نویسنده', ['ناشر' => [' '], 'نویسنده' => ['صادق هدایت']], 'کتاب (صادق هدایت)'], |
| 95 |
['multiple values', 'ناشر, نویسنده', ['ناشر' => ['نشر چش� |
| 96 |
ه'], 'نویسنده' => ['صادق هدایت', 'بزرگ علوی']], 'کتاب (نشر چش� |
| 97 |
ه, صادق هدایت, بزرگ علوی)'], |
| 98 |
['zero value', 'ش� |
| 99 |
اره', ['ش� |
| 100 |
اره' => ['0']], 'کتاب (0)'], |
| 101 |
['empty setting', '', $book, 'کتاب'], |
| 102 |
['unset setting', null, $book, 'کتاب'], |
| 103 |
['only separators', ', ، ,', $book, 'کتاب'], |
| 104 |
['no product attributes', 'ناشر, نویسنده', [], 'کتاب'], |
| 105 |
]; |
| 106 |
|
| 107 |
foreach ($cases as [$scenario, $names, $attributes, $expected]) { |
| 108 |
$configure($names); |
| 109 |
foreach ([WC_Product_Simple::class, WC_Product_Variable::class] as $type) { |
| 110 |
$product = new $type(); |
| 111 |
$product->set_name('کتاب'); |
| 112 |
$product->set_attributes($customAttributes($attributes)); |
| 113 |
$assertSame($expected, $nameFor($product), $scenario . ' / ' . $type); |
| 114 |
} |
| 115 |
$scenarios[] = $scenario; |
| 116 |
} |
| 117 |
|
| 118 |
$configure('ناشر, نویسنده', 'yes', 'فروشگاه', 'اصل'); |
| 119 |
$product = new WC_Product_Simple(); |
| 120 |
$product->set_name('کتاب'); |
| 121 |
$product->set_attributes($customAttributes($book)); |
| 122 |
$assertSame('فروشگاه کتاب اصل (نشر چش� |
| 123 |
ه, صادق هدایت)', $nameFor($product), 'static prefix and suffix'); |
| 124 |
$configure('ناشر, نویسنده', 'no', 'فروشگاه', 'اصل'); |
| 125 |
$assertSame('فروشگاه کتاب اصل', $nameFor($product), 'disabled suffix'); |
| 126 |
$configure('ناشر, نویسنده'); |
| 127 |
$product->set_name(str_repeat('ک', 115)); |
| 128 |
$assertSame(str_repeat('ک', 115) . ' (نشر', $nameFor($product), 'Unicode title truncation'); |
| 129 |
$assertSame(120, mb_strlen($nameFor($product)), 'title length'); |
| 130 |
$scenarios[] = 'prefix, suffix, disabled feature and Unicode title limit'; |
| 131 |
|
| 132 |
// Exercise real WooCommerce taxonomy labels, terms and persisted products. |
| 133 |
foreach (['ناشر', 'نویسنده'] as $label) { |
| 134 |
$slug = 'suffix_e2e_' . strtolower(wp_generate_password(8, false)); |
| 135 |
$id = wc_create_attribute(['name' => $label, 'slug' => $slug, 'type' => 'select']); |
| 136 |
if (is_wp_error($id)) throw new RuntimeException($id->get_error_message()); |
| 137 |
$globalAttributes[] = $id; |
| 138 |
$taxonomy = wc_attribute_taxonomy_name($slug); |
| 139 |
register_taxonomy($taxonomy, ['product'], ['label' => $label, 'public' => false]); |
| 140 |
// WooCommerce normally fills this registry on the next request's init. |
| 141 |
$GLOBALS['wc_product_attributes'][$taxonomy] = wc_get_attribute_taxonomies()['id:' . $id]; |
| 142 |
$assertSame($label, wc_attribute_label($taxonomy), 'registered taxonomy label'); |
| 143 |
$term = wp_insert_term($label === 'ناشر' ? 'نشر چش� |
| 144 |
ه' : 'صادق هدایت', $taxonomy); |
| 145 |
if (is_wp_error($term)) throw new RuntimeException($term->get_error_message()); |
| 146 |
$attribute = new WC_Product_Attribute(); |
| 147 |
$attribute->set_id($id); |
| 148 |
$attribute->set_name($taxonomy); |
| 149 |
$attribute->set_options([$term['term_id']]); |
| 150 |
$taxonomyAttributes[$label] = $attribute; |
| 151 |
} |
| 152 |
|
| 153 |
foreach ([WC_Product_Simple::class, WC_Product_Variable::class] as $type) { |
| 154 |
$product = new $type(); |
| 155 |
$product->set_name('کتاب'); |
| 156 |
$product->set_status('draft'); |
| 157 |
$product->set_attributes([$taxonomyAttributes['نویسنده'], $taxonomyAttributes['ناشر']]); |
| 158 |
$products[] = $product->save(); |
| 159 |
$product = wc_get_product($product->get_id()); |
| 160 |
$assertSame('کتاب (نشر چش� |
| 161 |
ه, صادق هدایت)', $nameFor($product), 'two real taxonomy attributes / ' . $type); |
| 162 |
$product->set_attributes([$taxonomyAttributes['ناشر'], ...$customAttributes(['نویسنده' => ['بزرگ علوی']])]); |
| 163 |
$product->save(); |
| 164 |
$assertSame('کتاب (نشر چش� |
| 165 |
ه, بزرگ علوی)', $nameFor(wc_get_product($product->get_id())), 'mixed global and custom attributes / ' . $type); |
| 166 |
} |
| 167 |
$scenarios[] = 'persisted global and custom attributes on simple and variable products'; |
| 168 |
|
| 169 |
// Use the actual settings-save path with the submitted comma-separated value. |
| 170 |
$_POST = ['sync_basalam_settings' => wp_slash([ |
| 171 |
SettingsConfig::PRODUCT_ATTRIBUTE_SUFFIX_ENABLED => 'yes', |
| 172 |
SettingsConfig::PRODUCT_ATTRIBUTE_SUFFIX_PRIORITY => 'نویسنده، ناشر', |
| 173 |
])]; |
| 174 |
$assertSame(true, SettingsPageHandler::saveSettings(), 'settings save'); |
| 175 |
$refreshSettings(); |
| 176 |
$assertSame('نویسنده، ناشر', syncBasalamSettings()->getSettings(SettingsConfig::PRODUCT_ATTRIBUTE_SUFFIX_PRIORITY), 'saved names'); |
| 177 |
$product = new WC_Product_Simple(); |
| 178 |
$product->set_name('کتاب'); |
| 179 |
$product->set_attributes($customAttributes($book)); |
| 180 |
$assertSame('کتاب (صادق هدایت, نشر چش� |
| 181 |
ه)', $nameFor($product), 'title after saving settings'); |
| 182 |
ob_start(); |
| 183 |
SettingPageComponents::renderAttributeSuffixPriority(); |
| 184 |
$input = ob_get_clean(); |
| 185 |
$assertSame(true, strpos($input, 'value="نویسنده، ناشر"') !== false, 'settings input retains saved value'); |
| 186 |
$assertSame(true, strpos($input, 'placeholder="� |
| 187 |
ثال: ناشر, نویسنده"') !== false, 'two-attribute example'); |
| 188 |
$scenarios[] = 'settings save and rendered input'; |
| 189 |
|
| 190 |
echo wp_json_encode(['passed' => true, 'assertions' => $assertions, 'scenarios' => $scenarios], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . PHP_EOL; |
| 191 |
} finally { |
| 192 |
$_POST = $originalPost; |
| 193 |
foreach ($products as $id) { |
| 194 |
$product = wc_get_product($id); |
| 195 |
if ($product) $product->delete(true); |
| 196 |
} |
| 197 |
foreach ($globalAttributes as $id) wc_delete_attribute($id); |
| 198 |
if ($originalSettings === false) { |
| 199 |
delete_option('sync_basalam_settings'); |
| 200 |
} else { |
| 201 |
update_option('sync_basalam_settings', $originalSettings); |
| 202 |
} |
| 203 |
} |
| 204 |
|