|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace Tests\Unit;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Support\StudyTourDeclarationExporter;
|
|
|
|
|
|
use PhpOffice\PhpWord\IOFactory;
|
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
use ZipArchive;
|
|
|
|
|
|
|
|
|
|
|
|
class StudyTourDeclarationExporterTest extends TestCase
|
|
|
|
|
|
{
|
|
|
|
|
|
public function test_html_to_plain_text_keeps_paragraphs(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$html = '<p>第一段</p><p>第二段<br/>续行</p><ul><li>要点A</li><li>要点B</li></ul>';
|
|
|
|
|
|
$text = StudyTourDeclarationExporter::htmlToPlainText($html);
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertStringContainsString("第一段", $text);
|
|
|
|
|
|
$this->assertStringContainsString("第二段", $text);
|
|
|
|
|
|
$this->assertStringContainsString("续行", $text);
|
|
|
|
|
|
$this->assertStringContainsString("要点A", $text);
|
|
|
|
|
|
$this->assertStringContainsString("要点B", $text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function test_sanitize_filename_strips_illegal_chars(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->assertSame('线路A_B', StudyTourDeclarationExporter::sanitizeFilename('线路A/B'));
|
|
|
|
|
|
$this->assertSame('研学线路', StudyTourDeclarationExporter::sanitizeFilename(' '));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function test_docx_contains_declaration_sections_and_cover_image(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$png = sys_get_temp_dir().'/st-cover-test.png';
|
|
|
|
|
|
file_put_contents($png, base64_decode(
|
|
|
|
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
$path = StudyTourDeclarationExporter::writeDocxFile([
|
|
|
|
|
|
'name' => '太湖蚕桑研学',
|
|
|
|
|
|
'org_name' => '苏州市科协',
|
|
|
|
|
|
'seasons' => ['spring', 'autumn'],
|
|
|
|
|
|
'venue_names' => ['苏州丝绸博物馆', '自定义营地'],
|
|
|
|
|
|
'suitable_count' => '30人',
|
|
|
|
|
|
'suitable_audience' => 'primary,junior',
|
|
|
|
|
|
'duration' => '2天1晚',
|
|
|
|
|
|
'contact_person' => '张老师',
|
|
|
|
|
|
'contact_phones' => '13800000000',
|
|
|
|
|
|
'cover_image_path' => $png,
|
|
|
|
|
|
'intro_html' => '<p>走进蚕桑文化。</p>',
|
|
|
|
|
|
'route_plans' => [[
|
|
|
|
|
|
'date_label' => '第一天',
|
|
|
|
|
|
'items' => [[
|
|
|
|
|
|
'time' => '09:00',
|
|
|
|
|
|
'activity' => '开营仪式',
|
|
|
|
|
|
'location' => '活动中心',
|
|
|
|
|
|
]],
|
|
|
|
|
|
]],
|
|
|
|
|
|
'courses' => [[
|
|
|
|
|
|
'sort' => 1,
|
|
|
|
|
|
'name' => '缫丝体验',
|
|
|
|
|
|
'content' => '了解传统缫丝工艺',
|
|
|
|
|
|
]],
|
|
|
|
|
|
'fee_html' => '<p>680元/人</p>',
|
|
|
|
|
|
'implementation_html' => '<p>2026年7月1日第1场</p>',
|
|
|
|
|
|
'season_options' => [
|
|
|
|
|
|
['value' => 'spring', 'label' => '春季'],
|
|
|
|
|
|
['value' => 'summer', 'label' => '夏季'],
|
|
|
|
|
|
['value' => 'autumn', 'label' => '秋季'],
|
|
|
|
|
|
['value' => 'winter', 'label' => '冬季'],
|
|
|
|
|
|
],
|
|
|
|
|
|
'audience_options' => [
|
|
|
|
|
|
['value' => 'kindergarten', 'label' => '幼儿园'],
|
|
|
|
|
|
['value' => 'primary', 'label' => '小学'],
|
|
|
|
|
|
['value' => 'junior', 'label' => '初中'],
|
|
|
|
|
|
['value' => 'high', 'label' => '高中'],
|
|
|
|
|
|
['value' => 'all', 'label' => '全学段'],
|
|
|
|
|
|
],
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertFileExists($path);
|
|
|
|
|
|
|
|
|
|
|
|
$zip = new ZipArchive();
|
|
|
|
|
|
$this->assertTrue($zip->open($path) === true);
|
|
|
|
|
|
$xml = (string) $zip->getFromName('word/document.xml');
|
|
|
|
|
|
$mediaCount = 0;
|
|
|
|
|
|
for ($i = 0; $i < $zip->numFiles; $i++) {
|
|
|
|
|
|
$name = (string) $zip->getNameIndex($i);
|
|
|
|
|
|
if (str_starts_with($name, 'word/media/')) {
|
|
|
|
|
|
$mediaCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
$zip->close();
|
|
|
|
|
|
|
|
|
|
|
|
foreach ([
|
|
|
|
|
|
'一、线路基本情况',
|
|
|
|
|
|
'二、线路简介',
|
|
|
|
|
|
'三、线路规划',
|
|
|
|
|
|
'四、研学课程',
|
|
|
|
|
|
'五、线路收费标准',
|
|
|
|
|
|
'六、线路计划实施情况',
|
|
|
|
|
|
'太湖蚕桑研学',
|
|
|
|
|
|
'苏州市科协',
|
|
|
|
|
|
'苏州丝绸博物馆',
|
|
|
|
|
|
'走进蚕桑文化',
|
|
|
|
|
|
'开营仪式',
|
|
|
|
|
|
'缫丝体验',
|
|
|
|
|
|
'680元/人',
|
|
|
|
|
|
'封面图',
|
|
|
|
|
|
] as $needle) {
|
|
|
|
|
|
$this->assertStringContainsString($needle, $xml, "missing {$needle}");
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->assertGreaterThan(0, $mediaCount, 'cover image should be embedded');
|
|
|
|
|
|
|
|
|
|
|
|
$phpWord = IOFactory::load($path);
|
|
|
|
|
|
$this->assertNotEmpty($phpWord->getSections());
|
|
|
|
|
|
|
|
|
|
|
|
@unlink($path);
|
|
|
|
|
|
@unlink($png);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|