OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\ContentTemplates\Models;
|
||||
|
||||
use Leantime\Domain\ContentTemplates\Models\ContentTemplate;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the ContentTemplate value object.
|
||||
*
|
||||
* Two responsibilities:
|
||||
* - From a parsed YAML array, pull metadata fields and the appliesTo-keyed payload.
|
||||
* - Mark itself as "usable" only when key, appliesTo, and a non-empty payload are present.
|
||||
*/
|
||||
class ContentTemplateTest extends TestCase
|
||||
{
|
||||
public function test_from_array_extracts_canvas_payload_under_applies_to_key(): void
|
||||
{
|
||||
$tpl = ContentTemplate::fromArray([
|
||||
'key' => 'education-k12',
|
||||
'title' => 'K-12 Education Program',
|
||||
'description' => 'After-school tutoring.',
|
||||
'appliesTo' => 'logicmodel',
|
||||
'sector' => 'education',
|
||||
'icon' => 'fa-graduation-cap',
|
||||
'author' => 'Leantime',
|
||||
'version' => '1.0.0',
|
||||
'license' => 'CC0',
|
||||
'logicmodel' => [
|
||||
'items' => [
|
||||
['box' => 'lm_inputs', 'title' => 'Funding', 'description' => 'Annual grants.'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertSame('education-k12', $tpl->key);
|
||||
$this->assertSame('K-12 Education Program', $tpl->title);
|
||||
$this->assertSame('logicmodel', $tpl->appliesTo);
|
||||
$this->assertSame('education', $tpl->sector);
|
||||
$this->assertSame('fa-graduation-cap', $tpl->icon);
|
||||
$this->assertSame('Leantime', $tpl->author);
|
||||
$this->assertSame('1.0.0', $tpl->version);
|
||||
$this->assertSame('CC0', $tpl->license);
|
||||
$this->assertCount(1, $tpl->payload['items']);
|
||||
$this->assertTrue($tpl->isUsable());
|
||||
}
|
||||
|
||||
public function test_from_array_extracts_wiki_payload_under_applies_to_key(): void
|
||||
{
|
||||
$tpl = ContentTemplate::fromArray([
|
||||
'key' => 'meeting-notes',
|
||||
'title' => 'Meeting Notes',
|
||||
'description' => 'Standard meeting template.',
|
||||
'appliesTo' => 'wiki',
|
||||
'wiki' => [
|
||||
'articles' => [
|
||||
['title' => 'Notes', 'content' => '<h1>Hi</h1>'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertSame('wiki', $tpl->appliesTo);
|
||||
$this->assertCount(1, $tpl->payload['articles']);
|
||||
$this->assertTrue($tpl->isUsable());
|
||||
}
|
||||
|
||||
public function test_optional_fields_default_to_null_when_missing(): void
|
||||
{
|
||||
$tpl = ContentTemplate::fromArray([
|
||||
'key' => 'x',
|
||||
'title' => 'X',
|
||||
'description' => '',
|
||||
'appliesTo' => 'logicmodel',
|
||||
'logicmodel' => ['items' => [['box' => 'a']]],
|
||||
]);
|
||||
|
||||
$this->assertNull($tpl->sector);
|
||||
$this->assertNull($tpl->icon);
|
||||
$this->assertNull($tpl->author);
|
||||
$this->assertNull($tpl->version);
|
||||
$this->assertNull($tpl->license);
|
||||
}
|
||||
|
||||
public function test_is_usable_returns_false_for_missing_key_or_applies_to_or_empty_payload(): void
|
||||
{
|
||||
$missingKey = ContentTemplate::fromArray([
|
||||
'title' => 'X',
|
||||
'description' => '',
|
||||
'appliesTo' => 'logicmodel',
|
||||
'logicmodel' => ['items' => [['box' => 'a']]],
|
||||
]);
|
||||
$missingAppliesTo = ContentTemplate::fromArray([
|
||||
'key' => 'x',
|
||||
'title' => 'X',
|
||||
'description' => '',
|
||||
]);
|
||||
$emptyPayload = ContentTemplate::fromArray([
|
||||
'key' => 'x',
|
||||
'title' => 'X',
|
||||
'description' => '',
|
||||
'appliesTo' => 'logicmodel',
|
||||
]);
|
||||
|
||||
$this->assertFalse($missingKey->isUsable());
|
||||
$this->assertFalse($missingAppliesTo->isUsable());
|
||||
$this->assertFalse($emptyPayload->isUsable());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\ContentTemplates\Services\Appliers;
|
||||
|
||||
use Leantime\Core\Db\Db as DbCore;
|
||||
use Leantime\Domain\ContentTemplates\Models\ContentTemplate;
|
||||
use Leantime\Domain\ContentTemplates\Services\Appliers\CanvasItemsApplier;
|
||||
use Leantime\Domain\ContentTemplates\Services\Appliers\WikiApplier;
|
||||
use Leantime\Domain\ContentTemplates\Services\ContentTemplateRegistry;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the appliers' supports() routing logic and early-return
|
||||
* safety. Actual DB writes are exercised in integration tests once Phase 2
|
||||
* wires real templates; here we lock in the routing contract.
|
||||
*/
|
||||
class AppliersTest extends TestCase
|
||||
{
|
||||
use \Codeception\Test\Feature\Stub;
|
||||
|
||||
public function test_wiki_applier_supports_only_wiki(): void
|
||||
{
|
||||
$applier = new WikiApplier($this->makeDbCore());
|
||||
|
||||
$this->assertTrue($applier->supports('wiki'));
|
||||
$this->assertFalse($applier->supports('logicmodel'));
|
||||
$this->assertFalse($applier->supports('goal'));
|
||||
$this->assertFalse($applier->supports(''));
|
||||
}
|
||||
|
||||
public function test_canvas_applier_supports_any_non_wiki_non_empty_applies_to(): void
|
||||
{
|
||||
$applier = new CanvasItemsApplier($this->makeDbCore());
|
||||
|
||||
$this->assertTrue($applier->supports('logicmodel'));
|
||||
$this->assertTrue($applier->supports('goal'));
|
||||
$this->assertTrue($applier->supports('leancanvas'));
|
||||
$this->assertTrue($applier->supports('swot'));
|
||||
$this->assertTrue($applier->supports('any-future-canvas-type'));
|
||||
|
||||
$this->assertFalse($applier->supports('wiki'));
|
||||
$this->assertFalse($applier->supports(''));
|
||||
}
|
||||
|
||||
public function test_canvas_applier_returns_zero_for_invalid_target_id(): void
|
||||
{
|
||||
$applier = new CanvasItemsApplier($this->makeDbCore());
|
||||
|
||||
$this->assertSame(0, $applier->apply(0, $this->makeCanvasTemplate()));
|
||||
$this->assertSame(0, $applier->apply(-1, $this->makeCanvasTemplate()));
|
||||
}
|
||||
|
||||
public function test_canvas_applier_returns_zero_for_unusable_template(): void
|
||||
{
|
||||
$applier = new CanvasItemsApplier($this->makeDbCore());
|
||||
|
||||
$unusable = ContentTemplate::fromArray([
|
||||
'key' => '',
|
||||
'title' => 'X',
|
||||
'description' => '',
|
||||
'appliesTo' => 'logicmodel',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $applier->apply(42, $unusable));
|
||||
}
|
||||
|
||||
public function test_canvas_applier_returns_zero_for_empty_items_payload(): void
|
||||
{
|
||||
$applier = new CanvasItemsApplier($this->makeDbCore());
|
||||
|
||||
$emptyItems = ContentTemplate::fromArray([
|
||||
'key' => 'empty',
|
||||
'title' => 'Empty',
|
||||
'description' => '',
|
||||
'appliesTo' => 'logicmodel',
|
||||
'logicmodel' => ['items' => []],
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $applier->apply(42, $emptyItems));
|
||||
}
|
||||
|
||||
public function test_wiki_applier_returns_zero_for_invalid_target_id(): void
|
||||
{
|
||||
$applier = new WikiApplier($this->makeDbCore());
|
||||
|
||||
$this->assertSame(0, $applier->apply(0, $this->makeWikiTemplate()));
|
||||
}
|
||||
|
||||
public function test_wiki_applier_returns_zero_for_empty_articles_payload(): void
|
||||
{
|
||||
$applier = new WikiApplier($this->makeDbCore());
|
||||
|
||||
$emptyArticles = ContentTemplate::fromArray([
|
||||
'key' => 'empty',
|
||||
'title' => 'Empty',
|
||||
'description' => '',
|
||||
'appliesTo' => 'wiki',
|
||||
'wiki' => ['articles' => []],
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $applier->apply(42, $emptyArticles));
|
||||
}
|
||||
|
||||
public function test_registry_applier_for_falls_back_to_supports_when_no_explicit_binding(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$canvas = new CanvasItemsApplier($this->makeDbCore());
|
||||
$wiki = new WikiApplier($this->makeDbCore());
|
||||
|
||||
// Bind WikiApplier on 'wiki' and CanvasItemsApplier on 'logicmodel'.
|
||||
// Ask the registry for 'cp' (a canvas type that nobody explicitly
|
||||
// bound). The fallback should find CanvasItemsApplier via supports().
|
||||
$registry->registerApplier('wiki', $wiki);
|
||||
$registry->registerApplier('logicmodel', $canvas);
|
||||
|
||||
$this->assertSame($canvas, $registry->applierFor('cp'));
|
||||
$this->assertSame($canvas, $registry->applierFor('swot'));
|
||||
$this->assertSame($wiki, $registry->applierFor('wiki'));
|
||||
$this->assertSame($canvas, $registry->applierFor('logicmodel'));
|
||||
}
|
||||
|
||||
public function test_registry_applier_for_returns_null_when_no_applier_supports_type(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerApplier('wiki', new WikiApplier($this->makeDbCore()));
|
||||
|
||||
// 'logicmodel' isn't bound and WikiApplier doesn't support it.
|
||||
$this->assertNull($registry->applierFor('logicmodel'));
|
||||
}
|
||||
|
||||
private function makeDbCore(): DbCore
|
||||
{
|
||||
// The supports() / early-return paths never call the connection, so a
|
||||
// bare stub is enough. Methods that DO write are exercised in
|
||||
// integration tests (Phase 2+).
|
||||
return $this->make(DbCore::class);
|
||||
}
|
||||
|
||||
private function makeCanvasTemplate(): ContentTemplate
|
||||
{
|
||||
return ContentTemplate::fromArray([
|
||||
'key' => 'k',
|
||||
'title' => 'T',
|
||||
'description' => '',
|
||||
'appliesTo' => 'logicmodel',
|
||||
'logicmodel' => [
|
||||
'items' => [
|
||||
['box' => 'lm_inputs', 'title' => 'A', 'description' => 'aa'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function makeWikiTemplate(): ContentTemplate
|
||||
{
|
||||
return ContentTemplate::fromArray([
|
||||
'key' => 'k',
|
||||
'title' => 'T',
|
||||
'description' => '',
|
||||
'appliesTo' => 'wiki',
|
||||
'wiki' => [
|
||||
'articles' => [
|
||||
['title' => 'A', 'content' => '<p>aa</p>'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\ContentTemplates\Services;
|
||||
|
||||
use Leantime\Domain\ContentTemplates\Models\ContentTemplate;
|
||||
use Leantime\Domain\ContentTemplates\Services\ContentTemplateRegistry;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for ContentTemplateRegistry.
|
||||
*
|
||||
* Sets up a tmp library root containing one logicmodel template and one wiki
|
||||
* template, then exercises load / forAppliesTo / get / overrides.
|
||||
*/
|
||||
class ContentTemplateRegistryTest extends TestCase
|
||||
{
|
||||
private string $tmpRoot;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->tmpRoot = sys_get_temp_dir().'/ct-registry-test-'.uniqid();
|
||||
mkdir($this->tmpRoot.'/logicmodel', 0o777, true);
|
||||
mkdir($this->tmpRoot.'/wiki', 0o777, true);
|
||||
|
||||
file_put_contents($this->tmpRoot.'/logicmodel/sample.yaml', <<<'YAML'
|
||||
key: "sample"
|
||||
title: "Sample LM"
|
||||
description: "Test fixture."
|
||||
appliesTo: "logicmodel"
|
||||
sector: "test"
|
||||
logicmodel:
|
||||
items:
|
||||
- box: "lm_inputs"
|
||||
title: "Item"
|
||||
description: "Desc"
|
||||
YAML);
|
||||
|
||||
file_put_contents($this->tmpRoot.'/wiki/notes.yaml', <<<'YAML'
|
||||
key: "notes"
|
||||
title: "Notes"
|
||||
description: "Wiki test."
|
||||
appliesTo: "wiki"
|
||||
wiki:
|
||||
articles:
|
||||
- title: "Hello"
|
||||
content: "<p>Hi</p>"
|
||||
YAML);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->rmrf($this->tmpRoot);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_for_applies_to_returns_templates_under_that_bucket(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
|
||||
$lm = $registry->forAppliesTo('logicmodel');
|
||||
$wiki = $registry->forAppliesTo('wiki');
|
||||
|
||||
// The registry constructor auto-registers the core library root, so
|
||||
// built-in templates show up alongside the tmp ones. Pin contract on
|
||||
// "tmp fixtures are present and well-shaped" rather than exact count.
|
||||
$this->assertArrayHasKey('sample', $lm);
|
||||
$this->assertInstanceOf(ContentTemplate::class, $lm['sample']);
|
||||
$this->assertSame('logicmodel', $lm['sample']->appliesTo);
|
||||
|
||||
$this->assertArrayHasKey('notes', $wiki);
|
||||
$this->assertSame('wiki', $wiki['notes']->appliesTo);
|
||||
}
|
||||
|
||||
public function test_get_returns_single_template_by_applies_to_and_key(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
|
||||
$tpl = $registry->get('logicmodel', 'sample');
|
||||
|
||||
$this->assertNotNull($tpl);
|
||||
$this->assertSame('Sample LM', $tpl->title);
|
||||
$this->assertSame('test', $tpl->sector);
|
||||
}
|
||||
|
||||
public function test_get_returns_null_for_unknown_template(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
|
||||
$this->assertNull($registry->get('logicmodel', 'does-not-exist'));
|
||||
$this->assertNull($registry->get('unknown-applies-to', 'sample'));
|
||||
}
|
||||
|
||||
public function test_directory_name_overrides_applies_to_in_yaml(): void
|
||||
{
|
||||
// YAML claims appliesTo=wiki but the file is in the logicmodel
|
||||
// directory. The registry should rewrite the appliesTo to match the
|
||||
// directory, so a typo in the YAML can't pollute the wrong bucket.
|
||||
file_put_contents($this->tmpRoot.'/logicmodel/lies.yaml', <<<'YAML'
|
||||
key: "lies"
|
||||
title: "Liar"
|
||||
description: "Wrong appliesTo claim."
|
||||
appliesTo: "wiki"
|
||||
wiki:
|
||||
articles:
|
||||
- title: "Bait"
|
||||
content: ""
|
||||
logicmodel:
|
||||
items:
|
||||
- box: "lm_inputs"
|
||||
title: "Item"
|
||||
YAML);
|
||||
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
|
||||
$byLm = $registry->get('logicmodel', 'lies');
|
||||
$this->assertNotNull($byLm);
|
||||
$this->assertSame('logicmodel', $byLm->appliesTo);
|
||||
|
||||
$this->assertNull($registry->get('wiki', 'lies'));
|
||||
}
|
||||
|
||||
public function test_later_library_root_overrides_earlier_on_collision(): void
|
||||
{
|
||||
$secondRoot = sys_get_temp_dir().'/ct-registry-test-second-'.uniqid();
|
||||
mkdir($secondRoot.'/logicmodel', 0o777, true);
|
||||
file_put_contents($secondRoot.'/logicmodel/sample.yaml', <<<'YAML'
|
||||
key: "sample"
|
||||
title: "Override Title"
|
||||
description: "From second root."
|
||||
appliesTo: "logicmodel"
|
||||
logicmodel:
|
||||
items:
|
||||
- box: "lm_outputs"
|
||||
title: "Override Item"
|
||||
YAML);
|
||||
|
||||
try {
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
$registry->registerLibraryRoot($secondRoot);
|
||||
|
||||
$tpl = $registry->get('logicmodel', 'sample');
|
||||
|
||||
$this->assertNotNull($tpl);
|
||||
$this->assertSame('Override Title', $tpl->title);
|
||||
} finally {
|
||||
$this->rmrf($secondRoot);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_register_library_root_is_idempotent(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
$registry->registerLibraryRoot($this->tmpRoot.'/');
|
||||
|
||||
$lm = $registry->forAppliesTo('logicmodel');
|
||||
|
||||
$this->assertCount(1, $lm);
|
||||
}
|
||||
|
||||
public function test_invalid_yaml_is_skipped_without_crashing(): void
|
||||
{
|
||||
file_put_contents($this->tmpRoot.'/logicmodel/broken.yaml', "key: [unterminated\n");
|
||||
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$registry->registerLibraryRoot($this->tmpRoot);
|
||||
|
||||
$lm = $registry->forAppliesTo('logicmodel');
|
||||
|
||||
$this->assertCount(1, $lm);
|
||||
$this->assertArrayHasKey('sample', $lm);
|
||||
$this->assertArrayNotHasKey('broken', $lm);
|
||||
}
|
||||
|
||||
private function rmrf(string $dir): void
|
||||
{
|
||||
if (! is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
foreach (scandir($dir) as $f) {
|
||||
if ($f === '.' || $f === '..') {
|
||||
continue;
|
||||
}
|
||||
$path = $dir.'/'.$f;
|
||||
is_dir($path) ? $this->rmrf($path) : unlink($path);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\ContentTemplates\Support;
|
||||
|
||||
use Leantime\Core\Language;
|
||||
use Leantime\Domain\ContentTemplates\Support\TranslationResolver;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for TranslationResolver — the small helper that lets YAML
|
||||
* content templates carry t:KEY translation references through to
|
||||
* consumers without each consumer learning the convention.
|
||||
*/
|
||||
class TranslationResolverTest extends TestCase
|
||||
{
|
||||
use \Codeception\Test\Feature\Stub;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// __() routes through the bound Language. Stub it to a deterministic
|
||||
// prefix so the test doesn't depend on real locale files being present.
|
||||
$this->app->instance(Language::class, $this->make(Language::class, [
|
||||
'__' => fn (string $key): string => 'T:'.$key,
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_passes_through_strings_without_t_references_untouched(): void
|
||||
{
|
||||
$this->assertSame('plain string', TranslationResolver::resolve('plain string'));
|
||||
$this->assertSame('', TranslationResolver::resolve(''));
|
||||
$this->assertSame('<h1>Hello</h1>', TranslationResolver::resolve('<h1>Hello</h1>'));
|
||||
}
|
||||
|
||||
public function test_whole_string_t_prefix_resolves_via_translator(): void
|
||||
{
|
||||
$this->assertSame('T:templates.prd.title', TranslationResolver::resolve('t:templates.prd.title'));
|
||||
$this->assertSame('T:status.draft', TranslationResolver::resolve('t:status.draft'));
|
||||
}
|
||||
|
||||
public function test_substring_t_substitution_replaces_each_occurrence_in_place(): void
|
||||
{
|
||||
$resolved = TranslationResolver::resolve('<h1>{{ t:templates.prd.title }}</h1>');
|
||||
$this->assertSame('<h1>T:templates.prd.title</h1>', $resolved);
|
||||
|
||||
// Multiple substitutions in one string, with various whitespace inside braces.
|
||||
$resolved = TranslationResolver::resolve('{{t:templates.author}} Gloria — {{ t:templates.dates }} 2026');
|
||||
$this->assertSame('T:templates.author Gloria — T:templates.dates 2026', $resolved);
|
||||
}
|
||||
|
||||
public function test_resolve_array_walks_recursively_and_resolves_strings_only(): void
|
||||
{
|
||||
$resolved = TranslationResolver::resolveArray([
|
||||
'title' => 't:templates.prd.title',
|
||||
'description' => '{{ t:templates.prd.description }} (extra)',
|
||||
'nested' => [
|
||||
'content' => '<p>{{ t:templates.author }}</p>',
|
||||
'count' => 7, // non-strings pass through
|
||||
'flag' => true,
|
||||
],
|
||||
'plain' => 'no references here',
|
||||
]);
|
||||
|
||||
$this->assertSame([
|
||||
'title' => 'T:templates.prd.title',
|
||||
'description' => 'T:templates.prd.description (extra)',
|
||||
'nested' => [
|
||||
'content' => '<p>T:templates.author</p>',
|
||||
'count' => 7,
|
||||
'flag' => true,
|
||||
],
|
||||
'plain' => 'no references here',
|
||||
], $resolved);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Domain\ContentTemplates;
|
||||
|
||||
use Leantime\Domain\ContentTemplates\Services\ContentTemplateRegistry;
|
||||
use Unit\TestCase;
|
||||
|
||||
/**
|
||||
* Integration check for Phase 3 of the content-templates rollout: wiki
|
||||
* YAML templates dropped into Library/wiki/ are discoverable via the
|
||||
* registry and carry the article shape the legacy template list expects.
|
||||
*
|
||||
* Not a controller test — that lives in Acceptance. This pins the data
|
||||
* contract between the YAML on disk and the consumer.
|
||||
*/
|
||||
class WikiTemplatesDiscoveryTest extends TestCase
|
||||
{
|
||||
public function test_built_in_wiki_templates_are_discoverable_via_registry(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
|
||||
$wikiTemplates = $registry->forAppliesTo('wiki');
|
||||
|
||||
// Phase 3 ships at least the two demo templates (decision-record,
|
||||
// weekly-status). Asserting on count keeps this honest if either gets
|
||||
// removed.
|
||||
$this->assertGreaterThanOrEqual(2, count($wikiTemplates));
|
||||
$this->assertArrayHasKey('decision-record', $wikiTemplates);
|
||||
$this->assertArrayHasKey('weekly-status', $wikiTemplates);
|
||||
}
|
||||
|
||||
public function test_built_in_wiki_template_has_single_article_with_html_content(): void
|
||||
{
|
||||
$registry = new ContentTemplateRegistry;
|
||||
$tpl = $registry->get('wiki', 'decision-record');
|
||||
|
||||
$this->assertNotNull($tpl);
|
||||
$this->assertSame('wiki', $tpl->appliesTo);
|
||||
|
||||
$articles = $tpl->payload['articles'] ?? [];
|
||||
$this->assertNotEmpty($articles);
|
||||
$this->assertIsArray($articles[0]);
|
||||
|
||||
// The wiki Templates partial maps payload.articles[0].content into the
|
||||
// legacy Template->content field. HTML body, not markdown.
|
||||
$this->assertNotEmpty($articles[0]['content'] ?? '');
|
||||
$this->assertStringContainsString('<h1>', $articles[0]['content']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user