Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
],
"require": {
"php": ">=8.2",
"ext-mbstring": "*"
"ext-mbstring": "*",
"nimbuscms/nimbus": "dev-main"
},
"require-dev": {
"phpstan/phpstan": "^2.2",
"phpunit/phpunit": "^11.0",
"nimbuscms/nimbus": "dev-main"
"phpunit/phpunit": "^11.0"
},
"repositories": [
{
Expand Down
2 changes: 1 addition & 1 deletion src/MarkdownPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ final class MarkdownPlugin implements Plugin

public function register(PluginContext $context): void
{
$context->fieldTypes()->register(new MarkdownFieldType(), self::ID);
$context->fieldTypes()->register(new MarkdownFieldType());
}
}
12 changes: 6 additions & 6 deletions tests/MarkdownFieldTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private function field(array $options = [], bool $required = false): Field
public function test_the_plugin_registers_its_field_type(): void
{
$registry = new FieldTypeRegistry();
(new MarkdownPlugin())->register(new PluginContext($registry));
(new MarkdownPlugin())->register(new PluginContext($registry, MarkdownPlugin::ID));

self::assertTrue($registry->has('markdown'));
self::assertSame('markdown', $registry->get('markdown')->type());
Expand All @@ -44,7 +44,7 @@ public function test_the_plugin_registers_its_field_type(): void
public function test_the_type_appears_in_the_field_picker(): void
{
$registry = new FieldTypeRegistry();
(new MarkdownPlugin())->register(new PluginContext($registry));
(new MarkdownPlugin())->register(new PluginContext($registry, MarkdownPlugin::ID));

self::assertArrayHasKey('markdown', $registry->choices());
self::assertSame('Markdown', $registry->choices()['markdown']);
Expand All @@ -62,10 +62,10 @@ public function test_the_plugin_id_matches_the_composer_manifest(): void
public function test_registering_twice_is_rejected_by_core(): void
{
$registry = new FieldTypeRegistry();
(new MarkdownPlugin())->register(new PluginContext($registry));
(new MarkdownPlugin())->register(new PluginContext($registry, MarkdownPlugin::ID));

$this->expectException(\Nimbus\Content\DuplicateFieldType::class);
(new MarkdownPlugin())->register(new PluginContext($registry));
(new MarkdownPlugin())->register(new PluginContext($registry, MarkdownPlugin::ID));
}

// -------------------------------------------------------- normalization
Expand Down Expand Up @@ -125,7 +125,7 @@ public function test_max_length_counts_characters_not_bytes(): void
public function test_required_empty_is_handled_by_core_not_here(): void
{
$registry = new FieldTypeRegistry();
(new MarkdownPlugin())->register(new PluginContext($registry));
(new MarkdownPlugin())->register(new PluginContext($registry, MarkdownPlugin::ID));

$collection = new Collection(1, 'posts', 'Posts', '#', '', [$this->field(required: true)], ['kind' => 'collection']);
$errors = (new Validator($registry))->validate($collection, ['body' => $this->type->normalize('')]);
Expand All @@ -137,7 +137,7 @@ public function test_required_empty_is_handled_by_core_not_here(): void
public function test_a_valid_required_value_passes_through_core_validation(): void
{
$registry = new FieldTypeRegistry();
(new MarkdownPlugin())->register(new PluginContext($registry));
(new MarkdownPlugin())->register(new PluginContext($registry, MarkdownPlugin::ID));

$collection = new Collection(1, 'posts', 'Posts', '#', '', [$this->field(required: true)], ['kind' => 'collection']);
$errors = (new Validator($registry))->validate($collection, ['body' => $this->type->normalize('# Hello')]);
Expand Down
192 changes: 192 additions & 0 deletions tests/PackageIntegrationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?php

declare(strict_types=1);

namespace NimbusCMS\Markdown\Tests;

use Nimbus\Content\Field;
use Nimbus\Content\FieldTypeRegistry;
use Nimbus\Content\UnknownFieldType;
use Nimbus\Plugin\PluginDiagnostic;
use Nimbus\Plugin\PluginLoader;
use NimbusCMS\Markdown\MarkdownPlugin;
use PHPUnit\Framework\TestCase;

/**
* Proves the *package boundary*, not the field implementation.
*
* MarkdownFieldTypeTest checks that the field behaves correctly when you hand
* it a value. This checks something different and easy to get wrong: that a
* real Composer installation of this package is discovered by Nimbus's own
* loader, using this package's real manifest, and registers without anyone
* editing core.
*
* Everything here is the genuine article — the installed `composer.json`, the
* real `PluginLoader`, the real `FieldTypeRegistry`. Only the path to
* `installed.json` is synthesised, because Composer writes that file about the
* *root* project and this package is the root when its own tests run.
*/
final class PackageIntegrationTest extends TestCase
{
private string $installedJson;

protected function setUp(): void
{
$this->installedJson = tempnam(sys_get_temp_dir(), 'nb-installed-') ?: '';
}

protected function tearDown(): void
{
@unlink($this->installedJson);
}

/** @return array<string,mixed> this package's actual composer manifest */
private function manifest(): array
{
$manifest = json_decode((string) file_get_contents(__DIR__ . '/../composer.json'), true);
self::assertIsArray($manifest);

return $manifest;
}

/**
* An installed.json describing this package exactly as Composer would,
* straight from the real manifest.
*/
private function installedAs(): string
{
$manifest = $this->manifest();
file_put_contents($this->installedJson, json_encode([
'packages' => [[
'name' => $manifest['name'],
'type' => $manifest['type'],
'extra' => $manifest['extra'],
]],
], JSON_THROW_ON_ERROR));

return $this->installedJson;
}

// ------------------------------------------------------- the manifest

public function test_the_package_declares_nimbus_as_a_runtime_dependency(): void
{
$manifest = $this->manifest();

// The plugin's production classes implement Nimbus interfaces, so core
// is a runtime requirement. In require-dev, Composer would happily
// install this package without a compatible Nimbus present.
self::assertArrayHasKey('nimbuscms/nimbus', $manifest['require']);
self::assertArrayNotHasKey('nimbuscms/nimbus', $manifest['require-dev'] ?? []);
}

public function test_the_package_is_typed_as_a_nimbus_plugin(): void
{
self::assertSame('nimbuscms-plugin', $this->manifest()['type']);
}

// -------------------------------------------------- discovery to registry

public function test_composer_discovery_registers_the_field_type(): void
{
$registry = new FieldTypeRegistry();
$loader = new PluginLoader($this->installedAs());
$diagnostics = $loader->load($registry);

self::assertSame([], $diagnostics, 'a correctly installed package must load cleanly');
self::assertSame(
[MarkdownPlugin::ID => $this->manifest()['name']],
$loader->registered(),
);

// Registered into the *shared* registry, under this plugin's id.
self::assertTrue($registry->has('markdown'));
self::assertSame(MarkdownPlugin::ID, $registry->providerOf('markdown'));
self::assertArrayHasKey('markdown', $registry->choices());
}

public function test_core_field_types_are_untouched_by_installation(): void
{
$registry = new FieldTypeRegistry();
(new PluginLoader($this->installedAs()))->load($registry);

foreach (['text', 'textarea', 'number', 'boolean', 'relation'] as $core) {
self::assertSame('core', $registry->providerOf($core));
}
}

// ---------------------------------------------------------- disabling

public function test_disabling_the_package_leaves_the_type_unregistered(): void
{
$registry = new FieldTypeRegistry();
$loader = new PluginLoader($this->installedAs(), [MarkdownPlugin::ID => false]);
$diagnostics = $loader->load($registry);

self::assertSame([], $loader->registered());
self::assertFalse($registry->has('markdown'));
self::assertCount(1, $diagnostics);
self::assertSame(PluginDiagnostic::DISABLED, $diagnostics[0]->reason);
self::assertFalse($diagnostics[0]->isFailure(), 'disabled is a choice, not a fault');
}

public function test_with_the_package_disabled_writes_are_blocked_and_content_is_kept(): void
{
$registry = new FieldTypeRegistry();
(new PluginLoader($this->installedAs(), [MarkdownPlugin::ID => false]))->load($registry);

$field = new Field('body', 'Body', 'markdown');
$stored = "# Still here\n\nWith **bold** text.";

// Write paths refuse the type outright...
try {
$registry->get('markdown');
self::fail('write paths must not resolve an unavailable type');
} catch (UnknownFieldType $e) {
self::assertSame('markdown', $e->type);
}

// ...while the admin degrades: the source is shown, never rewritten,
// and saving is refused until the package is back.
$fallback = $registry->forDisplay('markdown');
self::assertSame($stored, $fallback->normalize($stored), 'stored source must survive byte for byte');
self::assertNotNull($fallback->validate($field, $stored), 'saving must be blocked');
self::assertStringContainsString('Still here', $fallback->renderInput($field, $stored));
self::assertStringContainsString('markdown', $fallback->renderInput($field, $stored));
}

public function test_re_enabling_restores_the_field_type(): void
{
$path = $this->installedAs();

$disabled = new FieldTypeRegistry();
(new PluginLoader($path, [MarkdownPlugin::ID => false]))->load($disabled);
self::assertFalse($disabled->has('markdown'));

$enabled = new FieldTypeRegistry();
(new PluginLoader($path, [MarkdownPlugin::ID => true]))->load($enabled);

self::assertTrue($enabled->has('markdown'), 'flipping the switch back is all it takes');
self::assertSame('markdown', $enabled->get('markdown')->type());
}

// ----------------------------------------------------------- conflicts

public function test_a_second_package_cannot_take_this_plugins_id(): void
{
$manifest = $this->manifest();
file_put_contents($this->installedJson, json_encode(['packages' => [
['name' => $manifest['name'], 'type' => $manifest['type'], 'extra' => $manifest['extra']],
['name' => 'squatter/markdown', 'type' => 'nimbuscms-plugin', 'extra' => $manifest['extra']],
]], JSON_THROW_ON_ERROR));

$registry = new FieldTypeRegistry();
$loader = new PluginLoader($this->installedJson);
$diagnostics = $loader->load($registry);

self::assertSame([MarkdownPlugin::ID => $manifest['name']], $loader->registered());
self::assertCount(1, $diagnostics);
self::assertSame(PluginDiagnostic::DUPLICATE_ID, $diagnostics[0]->reason);
self::assertSame('squatter/markdown', $diagnostics[0]->package);
}
}
Loading