Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for and clean up MakePublicationCommand and related action #680

Merged
merged 79 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
4b8f027
Add todo
caendesilva Nov 21, 2022
9a1e8bc
Create MakePublicationCommandTest.php
caendesilva Nov 21, 2022
8be3d33
Update test link
caendesilva Nov 21, 2022
c2c992a
Update test method name
caendesilva Nov 21, 2022
0df0317
Copilot generate test
caendesilva Nov 21, 2022
6a7306d
Set up filesystem for test
caendesilva Nov 21, 2022
aee13a5
Fix PHPDoc link
caendesilva Nov 21, 2022
84392a1
Use explicit setup code
caendesilva Nov 21, 2022
f3da8c7
Update expectation question
caendesilva Nov 21, 2022
c7f0edd
Apply fixes from StyleCI
StyleCIBot Nov 21, 2022
3c05ef6
Merge branch 'publications-feature' into refactor-publications
caendesilva Nov 21, 2022
25b89a9
Use array as array
caendesilva Nov 21, 2022
fc84b9c
Use generic object
caendesilva Nov 21, 2022
1d81dac
Add logic to guard against infinite loop
caendesilva Nov 21, 2022
248b0dd
Display which validation failed
caendesilva Nov 21, 2022
102a445
Apply fixes from StyleCI
StyleCIBot Nov 21, 2022
1320baf
Begin refactoring test
caendesilva Nov 21, 2022
bf5352d
Create CreatesNewPublicationFileTest.php
caendesilva Nov 21, 2022
f361b7f
Type hint generic object
caendesilva Nov 21, 2022
9c34208
Update class PHPDoc
caendesilva Nov 21, 2022
327246b
Sketch out create test
caendesilva Nov 21, 2022
ec631be
Add placeholder assertions
caendesilva Nov 21, 2022
e803bf7
Type hint PublicationType
caendesilva Nov 21, 2022
7cf0e83
Get the directory from pubType class
caendesilva Nov 21, 2022
9740c2e
Add back Collection type hint
caendesilva Nov 21, 2022
b043af7
Use getFields method
caendesilva Nov 21, 2022
acfdcfc
Replace with traditional property declarations
caendesilva Nov 21, 2022
c214237
Allow integers as strings for JSON compatibility
caendesilva Nov 21, 2022
e304945
Extract helper method
caendesilva Nov 21, 2022
c5aa1d9
Apply fixes from StyleCI
StyleCIBot Nov 21, 2022
3ad6bb2
Expand abbreviation
caendesilva Nov 21, 2022
c7f4054
Throw RuntimeException on null
caendesilva Nov 21, 2022
4ae4797
Revert "Throw RuntimeException on null"
caendesilva Nov 21, 2022
5fcaceb
Use firstOrFail
caendesilva Nov 21, 2022
d57b54e
Revert "Use firstOrFail"
caendesilva Nov 21, 2022
b3146d9
Revert "Revert "Throw RuntimeException on null""
caendesilva Nov 21, 2022
d465f1d
Strongly type arrow function types
caendesilva Nov 21, 2022
3eb12a2
Use strict comparison
caendesilva Nov 21, 2022
9310ad4
Add missing closing tag in PHPDoc type annotation
caendesilva Nov 21, 2022
1c2da95
Change getFields method to use mapWithKeys
caendesilva Nov 21, 2022
5d921c9
Get the name
caendesilva Nov 21, 2022
0e96fc0
Add publication field
caendesilva Nov 21, 2022
fd2cdf8
Refactor action
caendesilva Nov 22, 2022
58ec109
Use Carbon for time so it can be frozen
caendesilva Nov 22, 2022
a2bba2f
Apply fixes from StyleCI
StyleCIBot Nov 22, 2022
0a17ee2
Clean up after test
caendesilva Nov 22, 2022
181d52e
Fix expectations
caendesilva Nov 22, 2022
d7f5fea
Test covers CreatesNewPublicationFile
caendesilva Nov 22, 2022
701bb87
Update and normalize console output
caendesilva Nov 22, 2022
6506e26
Normalize test data
caendesilva Nov 22, 2022
8a231f3
Create title for ask message
caendesilva Nov 22, 2022
79466d7
Revert "Create title for ask message"
caendesilva Nov 22, 2022
4173f89
Use ucfirst for message
caendesilva Nov 22, 2022
3fd42aa
Lowercase canonical field test data
caendesilva Nov 22, 2022
e147902
Use deleteDirectory function for test cleanup
caendesilva Nov 22, 2022
f2e21f2
Apply fixes from StyleCI
StyleCIBot Nov 22, 2022
7a8a9fa
Expect array with keys
caendesilva Nov 22, 2022
4a84da4
Apply fixes from StyleCI
StyleCIBot Nov 22, 2022
743db80
Import classes in global namespace
caendesilva Nov 22, 2022
7765b8c
Expand variable abbreviation
caendesilva Nov 22, 2022
230177c
Initialize possible undefined value
caendesilva Nov 22, 2022
a2f4839
Expand abbreviation
caendesilva Nov 22, 2022
e727ce8
Fix covers tag
caendesilva Nov 22, 2022
fa08ade
Inline static local variable
caendesilva Nov 22, 2022
44d4694
Format array
caendesilva Nov 22, 2022
36d311a
Make private methods protected
caendesilva Nov 22, 2022
7ba3ea9
Make private method protected
caendesilva Nov 22, 2022
d1eb825
Test covers CreatesNewPublicationType
caendesilva Nov 22, 2022
ab4b140
Update error message
caendesilva Nov 22, 2022
4c1ae7c
Fix assumed typo
caendesilva Nov 22, 2022
a9f7f49
Convert concatenation to string interpolation
caendesilva Nov 22, 2022
343a208
Outfile needs to be absolute
caendesilva Nov 22, 2022
dd6301c
Pass along output style and use it instead of echo
caendesilva Nov 22, 2022
b13381b
Remove extra newline
caendesilva Nov 22, 2022
3534db6
Expect output
caendesilva Nov 22, 2022
4134843
Apply fixes from StyleCI
StyleCIBot Nov 22, 2022
2a36639
Unwrap unnecessary curly braces syntax
caendesilva Nov 22, 2022
4dd2f72
Use firstOrFail
caendesilva Nov 22, 2022
85bd565
Replace qualifier with an import
caendesilva Nov 22, 2022
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
62 changes: 30 additions & 32 deletions packages/framework/src/Console/Commands/MakePublicationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@

namespace Hyde\Console\Commands;

use Exception;
use Hyde\Console\Commands\Interfaces\CommandHandleInterface;
use Hyde\Framework\Actions\CreatesNewPublicationFile;
use Hyde\Framework\Features\Publications\PublicationHelper;
use Illuminate\Support\Str;
use InvalidArgumentException;
use LaravelZero\Framework\Commands\Command;
use Rgasch\Collection\Collection;

/**
* Hyde Command to create a new publication for a given publication type.
*
* @see \Hyde\Framework\Testing\Feature\Commands\MakePageCommandTest
* @see \Hyde\Framework\Testing\Feature\Commands\MakePublicationCommandTest
*/
class MakePublicationCommand extends Command implements CommandHandleInterface
{
Expand All @@ -27,11 +29,11 @@ class MakePublicationCommand extends Command implements CommandHandleInterface

public function handle(): int
{
$this->title('Creating a new Publication Item!');
$this->title('Creating a new Publication!');

$pubTypes = PublicationHelper::getPublicationTypes();
if ($pubTypes->isEmpty()) {
$this->output->error('Unable to locate any publication-types ... did you create any?');
$this->output->error('Unable to locate any publication types. Did you create any?');

return Command::FAILURE;
}
Expand All @@ -52,16 +54,16 @@ public function handle(): int
$fieldData = Collection::create();
$this->output->writeln('<bg=magenta;fg=white>Now please enter the field data:</>');
foreach ($pubType->fields as $field) {
$fieldData->{$field->name} = $this->captureFieldInput($field, $mediaFiles);
$fieldData->{$field['name']} = $this->captureFieldInput((object) $field, $mediaFiles);
}

try {
$creator = new CreatesNewPublicationFile($pubType, $fieldData);
$creator = new CreatesNewPublicationFile($pubType, $fieldData, output: $this->output);
$creator->create();
} catch (\InvalidArgumentException $e) { // FIXME: provide a properly typed exception
$msg = $e->getMessage();
} catch (InvalidArgumentException $exception) { // FIXME: provide a properly typed exception
$msg = $exception->getMessage();
// Useful for debugging
//$this->output->writeln("xxx " . $e->getTraceAsString());
//$this->output->writeln("xxx " . $exception->getTraceAsString());
$this->output->writeln("<bg=red;fg=white>$msg</>");
$overwrite = PublicationHelper::askWithValidation(
$this,
Expand All @@ -71,21 +73,23 @@ public function handle(): int
'n'
);
if (strtolower($overwrite) == 'y') {
$creator = new CreatesNewPublicationFile($pubType, $fieldData, true);
$creator = new CreatesNewPublicationFile($pubType, $fieldData, true, $this->output);
$creator->create();
} else {
$this->output->writeln('<bg=magenta;fg=white>Existing without overwriting existing publication file!</>');
$this->output->writeln('<bg=magenta;fg=white>Exiting without overwriting existing publication file!</>');
}
} catch (\Exception $e) {
$this->error('Error: '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine());
} catch (Exception $exception) {
$this->error("Error: {$exception->getMessage()} at {$exception->getFile()}:{$exception->getLine()}");

return Command::FAILURE;
}

$this->info('Publication created successfully!');

return Command::SUCCESS;
}

private function captureFieldInput(Collection $field, Collection $mediaFiles): string|array
protected function captureFieldInput(object $field, Collection $mediaFiles): string|array
{
$rulesPerType = $this->getValidationRulesPerType();

Expand Down Expand Up @@ -119,8 +123,9 @@ private function captureFieldInput(Collection $field, Collection $mediaFiles): s

if ($field->type === 'image') {
$this->output->writeln($field->name.' (end with an empty line)');
foreach ($mediaFiles as $k => $file) {
$offset = $k + 1;
$offset = 0;
foreach ($mediaFiles as $index => $file) {
$offset = $index + 1;
$this->output->writeln(" $offset: $file");
}
$selected = PublicationHelper::askWithValidation($this, $field->name, $field->name, ['required', 'integer', "between:1,$offset"]);
Expand Down Expand Up @@ -151,23 +156,16 @@ private function captureFieldInput(Collection $field, Collection $mediaFiles): s
return PublicationHelper::askWithValidation($this, $field->name, $field->name, $fieldRules);
}

private function getValidationRulesPerType(): Collection
protected function getValidationRulesPerType(): Collection
{
static $rulesPerType = null;
if (! $rulesPerType) {
$rulesPerType = Collection::create(
[
'string' => ['required', 'string', 'between'],
'boolean' => ['required', 'boolean'],
'integer' => ['required', 'integer', 'between'],
'float' => ['required', 'numeric', 'between'],
'datetime' => ['required', 'datetime', 'between'],
'url' => ['required', 'url'],
'text' => ['required', 'string', 'between'],
]
);
}

return $rulesPerType;
return Collection::create([
'string' => ['required', 'string', 'between'],
'boolean' => ['required', 'boolean'],
'integer' => ['required', 'integer', 'between'],
'float' => ['required', 'numeric', 'between'],
'datetime' => ['required', 'datetime', 'between'],
'url' => ['required', 'url'],
'text' => ['required', 'string', 'between'],
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function handle(): int
return Command::SUCCESS;
}

private function captureFieldsDefinitions(): Collection
protected function captureFieldsDefinitions(): Collection
{
$this->output->writeln('<bg=magenta;fg=white>You now need to define the fields in your publication type:</>');
$count = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@

use Hyde\Framework\Actions\Interfaces\CreateActionInterface;
use Hyde\Framework\Concerns\InteractsWithDirectories;
use Hyde\Framework\Features\Publications\Models\PublicationField;
use Hyde\Framework\Features\Publications\Models\PublicationType;
use Hyde\Framework\Features\Publications\PublicationHelper;
use Hyde\Hyde;
use Illuminate\Console\OutputStyle;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Rgasch\Collection\Collection;
use function Safe\date;
use RuntimeException;
use function Safe\file_put_contents;

/**
* Scaffold a new Markdown, Blade, or documentation page.
* Scaffold a publication file.
*
* @see \Hyde\Framework\Testing\Feature\Actions\CreatesNewPageSourceFileTest
* @see \Hyde\Framework\Testing\Feature\Actions\CreatesNewPublicationFileTest
*/
class CreatesNewPublicationFile implements CreateActionInterface
{
Expand All @@ -24,56 +30,59 @@ class CreatesNewPublicationFile implements CreateActionInterface
protected string $result;

public function __construct(
protected Collection $pubType,
protected PublicationType $pubType,
protected Collection $fieldData,
protected bool $force = false
protected bool $force = false,
protected ?OutputStyle $output = null,
) {
}

public function create(): void
{
$dir = dirname($this->pubType->schemaFile);
$dir = ($this->pubType->getDirectory());
$canonicalFieldName = $this->pubType->canonicalField;
$canonicalFieldDef = $this->pubType->fields->filter(fn ($f) => $f->name === $canonicalFieldName)->first();
$canonicalValue = $canonicalFieldDef->type != 'array' ? $this->fieldData->{$canonicalFieldName} : $this->fieldData->{$canonicalFieldName}[0];
$canonicalFieldDefinition = $this->pubType->getFields()->filter(fn (PublicationField $field): bool => $field->name === $canonicalFieldName)->first() ?? throw new RuntimeException("Could not find field definition for '$canonicalFieldName'");
$canonicalValue = $canonicalFieldDefinition->type !== 'array' ? $this->fieldData->{$canonicalFieldName} : $this->fieldData->{$canonicalFieldName}[0];
$canonicalStr = Str::of($canonicalValue)->substr(0, 64);
$slug = $canonicalStr->slug()->toString();
$fileName = PublicationHelper::formatNameForStorage($slug);
$outFile = "$dir/$fileName.md";
$outFile = Hyde::path("$dir/$fileName.md");
if (file_exists($outFile) && ! $this->force) {
throw new \InvalidArgumentException("File [$outFile] already exists");
throw new InvalidArgumentException("File [$outFile] already exists");
}

$now = date('Y-m-d H:i:s');
$now = Carbon::now()->format('Y-m-d H:i:s');
$output = "---\n";
$output .= "__createdAt: {$now}\n";
foreach ($this->fieldData as $k => $v) {
$field = $this->pubType->fields->where('name', $k)->first();
$output .= "__createdAt: $now\n";
foreach ($this->fieldData as $name => $value) {
/** @var PublicationField $fieldDefinition */
$fieldDefinition = $this->pubType->getFields()->where('name', $name)->firstOrFail();

if ($field->type == 'text') {
$output .= "{$k}: |\n";
foreach ($v as $line) {
if ($fieldDefinition->type == 'text') {
$output .= "$name: |\n";
foreach ($value as $line) {
$output .= " $line\n";
}
continue;
}

if ($field->type == 'array') {
$output .= "{$k}:\n";
foreach ($v as $item) {
if ($fieldDefinition->type == 'array') {
$output .= "$name:\n";
foreach ($value as $item) {
$output .= " - \"$item\"\n";
}
continue;
}

$output .= "{$k}: {$v}\n";
$output .= "$name: $value\n";
}
$output .= "---\n";
$output .= "Raw MD text ...\n";

$this->result = $output;
echo "Saving publication data to [$outFile]\n";
$this->output?->writeln(sprintf('Saving publication data to [%s]', Hyde::pathToRelative($outFile)));

$this->needsParentDirectory($outFile);
file_put_contents($outFile, $output);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* Scaffold a new publication type schema.
*
* @see \Hyde\Framework\Testing\Feature\Actions\CreatesNewPublicationTypeSchemaTest
* @see \Hyde\Framework\Testing\Feature\Actions\CreatesNewPublicationTypeTest
*/
class CreatesNewPublicationType implements CreateActionInterface
{
Expand Down Expand Up @@ -54,6 +54,8 @@ public function create(): void

$type->save($outFile);
$this->result = $type->toJson();

// TODO: Generate the detail and list templates?
}

public function getResult(): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ class PublicationField implements JsonSerializable, Arrayable
public final const TYPES = ['string', 'boolean', 'integer', 'float', 'datetime', 'url', 'array', 'text', 'image'];

public readonly string $type;
public readonly ?int $max;
public readonly ?int $min;
public readonly string $name;

public function __construct(string $type, public readonly string $name, public readonly ?int $min, public readonly ?int $max)
public function __construct(string $type, string $name, int|string|null $min, int|string|null $max)
{
$this->name = $name;
$this->min = $this->parseInt($min);
$this->max = $this->parseInt($max);
$this->type = strtolower($type);

if (! in_array(strtolower($type), self::TYPES)) {
Expand Down Expand Up @@ -50,4 +56,9 @@ public function validateInputAgainstRules(string $input): bool

return true;
}

protected function parseInt(int|string|null $min): ?int
{
return $min === null ? null : (int) $min;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ public function getDirectory(): string
return $this->directory;
}

/** @return \Illuminate\Support\Collection<\Hyde\Framework\Features\Publications\Models\PublicationField */
/** @return \Illuminate\Support\Collection<string, \Hyde\Framework\Features\Publications\Models\PublicationField> */
public function getFields(): Collection
{
return collect($this->fields)->map(function (array $data) {
return new PublicationField(...$data);
return collect($this->fields)->mapWithKeys(function (array $data) {
return [$data['name'] => new PublicationField(...$data)];
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
use Illuminate\Support\Str;
use LaravelZero\Framework\Commands\Command;
use Rgasch\Collection\Collection;
use RuntimeException;
use function Safe\file_get_contents;
use Spatie\YamlFrontMatter\YamlFrontMatter;
use function ucfirst;

class PublicationHelper
{
/** @var int How many times can the validation loop run? Guards against infinite loops. */
protected final const RETRY_COUNT = 10;

/**
* Ask for a CLI input value until we pass validation rules.
*
Expand All @@ -25,15 +30,23 @@ class PublicationHelper
* @param string $message
* @param \Rgasch\Collection\Collection|array $rules
* @param mixed|null $default
* @param bool $isBeingRetried
* @return mixed
*
* @throws RuntimeException
*/
public static function askWithValidation(Command $command, string $name, string $message, Collection|array $rules = [], mixed $default = null): mixed
public static function askWithValidation(Command $command, string $name, string $message, Collection|array $rules = [], mixed $default = null, bool $isBeingRetried = false): mixed
{
static $tries = 0;
if (! $isBeingRetried) {
$tries = 0;
}

if ($rules instanceof Collection) {
$rules = $rules->toArray();
}

$answer = $command->ask($message, $default);
$answer = $command->ask(ucfirst($message), $default);
$factory = app(ValidationFactory::class);
$validator = $factory->make([$name => $answer], [$name => $rules]);

Expand All @@ -45,7 +58,13 @@ public static function askWithValidation(Command $command, string $name, string
$command->error($error);
}

return self::askWithValidation($command, $name, $message, $rules);
$tries++;

if ($tries >= self::RETRY_COUNT) {
throw new RuntimeException(sprintf("Too many validation errors trying to validate '$name' with rules: [%s]", implode(', ', $rules)));
}

return self::askWithValidation($command, $name, $message, $rules, isBeingRetried: true);
}

/**
Expand Down
Loading