mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-03-14 12:23:33 +00:00
feat: added UI for configuration template in recipe settings
Some checks are pending
tests / ci (push) Waiting to run
Some checks are pending
tests / ci (push) Waiting to run
This commit is contained in:
parent
49222838c4
commit
4e345c493d
8 changed files with 262 additions and 9 deletions
|
|
@ -29,6 +29,7 @@ use InvalidArgumentException;
|
||||||
use Keepsuit\LaravelLiquid\LaravelLiquidExtension;
|
use Keepsuit\LaravelLiquid\LaravelLiquidExtension;
|
||||||
use Keepsuit\Liquid\Exceptions\LiquidException;
|
use Keepsuit\Liquid\Exceptions\LiquidException;
|
||||||
use Keepsuit\Liquid\Extensions\StandardExtension;
|
use Keepsuit\Liquid\Extensions\StandardExtension;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class Plugin extends Model
|
class Plugin extends Model
|
||||||
{
|
{
|
||||||
|
|
@ -79,11 +80,68 @@ class Plugin extends Model
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const CUSTOM_FIELDS_KEY = 'custom_fields';
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YAML for the custom_fields editor
|
||||||
|
*/
|
||||||
|
public function getCustomFieldsEditorYaml(): string
|
||||||
|
{
|
||||||
|
$template = $this->configuration_template;
|
||||||
|
$list = $template[self::CUSTOM_FIELDS_KEY] ?? null;
|
||||||
|
if ($list === null || $list === []) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Yaml::dump($list, 4, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse editor YAML and return configuration_template for DB (custom_fields key). Returns null when empty.
|
||||||
|
*/
|
||||||
|
public static function configurationTemplateFromCustomFieldsYaml(string $yaml, ?array $existingTemplate): ?array
|
||||||
|
{
|
||||||
|
$list = $yaml !== '' ? Yaml::parse($yaml) : [];
|
||||||
|
if ($list === null || (is_array($list) && $list === [])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = $existingTemplate ?? [];
|
||||||
|
$template[self::CUSTOM_FIELDS_KEY] = is_array($list) ? $list : [];
|
||||||
|
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that each custom field entry has field_type and name. For use with parsed editor YAML.
|
||||||
|
*
|
||||||
|
* @param array<int, array<string, mixed>> $list
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public static function validateCustomFieldsList(array $list): void
|
||||||
|
{
|
||||||
|
$validator = \Illuminate\Support\Facades\Validator::make(
|
||||||
|
['custom_fields' => $list],
|
||||||
|
[
|
||||||
|
'custom_fields' => ['required', 'array'],
|
||||||
|
'custom_fields.*.field_type' => ['required', 'string'],
|
||||||
|
'custom_fields.*.name' => ['required', 'string'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'custom_fields.*.field_type.required' => 'Each custom field must have a field_type.',
|
||||||
|
'custom_fields.*.name.required' => 'Each custom field must have a name.',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$validator->validate();
|
||||||
|
}
|
||||||
|
|
||||||
// sanitize configuration template descriptions and help texts (since they allow HTML rendering)
|
// sanitize configuration template descriptions and help texts (since they allow HTML rendering)
|
||||||
protected function sanitizeTemplate(): void
|
protected function sanitizeTemplate(): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
29
package-lock.json
generated
29
package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "laravel",
|
"name": "laravel-trmnl-server",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"@codemirror/lang-javascript": "^6.2.4",
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
"@codemirror/lang-json": "^6.0.2",
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
"@codemirror/lang-liquid": "^6.3.0",
|
"@codemirror/lang-liquid": "^6.3.0",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@codemirror/language": "^6.11.3",
|
"@codemirror/language": "^6.11.3",
|
||||||
"@codemirror/search": "^6.5.11",
|
"@codemirror/search": "^6.5.11",
|
||||||
"@codemirror/state": "^6.5.2",
|
"@codemirror/state": "^6.5.2",
|
||||||
|
|
@ -151,6 +152,21 @@
|
||||||
"@lezer/lr": "^1.3.1"
|
"@lezer/lr": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/lang-yaml": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.2.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"@lezer/yaml": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@codemirror/language": {
|
"node_modules/@codemirror/language": {
|
||||||
"version": "6.12.1",
|
"version": "6.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
|
||||||
|
|
@ -761,6 +777,17 @@
|
||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/yaml": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@marijn/find-cluster-break": {
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"@codemirror/lang-javascript": "^6.2.4",
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
"@codemirror/lang-json": "^6.0.2",
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
"@codemirror/lang-liquid": "^6.3.0",
|
"@codemirror/lang-liquid": "^6.3.0",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@codemirror/language": "^6.11.3",
|
"@codemirror/language": "^6.11.3",
|
||||||
"@codemirror/search": "^6.5.11",
|
"@codemirror/search": "^6.5.11",
|
||||||
"@codemirror/state": "^6.5.2",
|
"@codemirror/state": "^6.5.2",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { json } from '@codemirror/lang-json';
|
import { json } from '@codemirror/lang-json';
|
||||||
import { css } from '@codemirror/lang-css';
|
import { css } from '@codemirror/lang-css';
|
||||||
import { liquid } from '@codemirror/lang-liquid';
|
import { liquid } from '@codemirror/lang-liquid';
|
||||||
|
import { yaml } from '@codemirror/lang-yaml';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
import { githubLight } from '@fsegurai/codemirror-theme-github-light';
|
import { githubLight } from '@fsegurai/codemirror-theme-github-light';
|
||||||
|
|
||||||
|
|
@ -20,6 +21,8 @@ const LANGUAGE_MAP = {
|
||||||
'css': css,
|
'css': css,
|
||||||
'liquid': liquid,
|
'liquid': liquid,
|
||||||
'html': html,
|
'html': html,
|
||||||
|
'yaml': yaml,
|
||||||
|
'yml': yaml,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Theme support mapping
|
// Theme support mapping
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use App\Models\Plugin;
|
use App\Models\Plugin;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -54,12 +55,22 @@ new class extends Component
|
||||||
/**
|
/**
|
||||||
* Triggered by @close on the modal to discard any typed but unsaved changes
|
* Triggered by @close on the modal to discard any typed but unsaved changes
|
||||||
*/
|
*/
|
||||||
public int $resetIndex = 0; // Add this property
|
public int $resetIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recipe settings (or this modal) save, reload so Configuration Fields form stays in sync.
|
||||||
|
*/
|
||||||
|
#[On('config-updated')]
|
||||||
|
public function refreshFromParent(): void
|
||||||
|
{
|
||||||
|
$this->loadData();
|
||||||
|
$this->resetIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
public function resetForm(): void
|
public function resetForm(): void
|
||||||
{
|
{
|
||||||
$this->loadData();
|
$this->loadData();
|
||||||
++$this->resetIndex; // Increment to force DOM refresh
|
++$this->resetIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveConfiguration()
|
public function saveConfiguration()
|
||||||
|
|
|
||||||
|
|
@ -569,11 +569,10 @@ HTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[On('config-updated')]
|
#[On('config-updated')]
|
||||||
public function refreshPlugin()
|
public function refreshPlugin(): void
|
||||||
{
|
{
|
||||||
// This pulls the fresh 'configuration' from the DB
|
|
||||||
// and re-triggers the @if check in the Blade template
|
|
||||||
$this->plugin = $this->plugin->fresh();
|
$this->plugin = $this->plugin->fresh();
|
||||||
|
$this->configuration_template = $this->plugin->configuration_template ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Laravel Livewire computed property: access with $this->parsed_urls
|
// Laravel Livewire computed property: access with $this->parsed_urls
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
use App\Models\Plugin;
|
use App\Models\Plugin;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Symfony\Component\Yaml\Exception\ParseException;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This component contains the TRMNL Plugin Settings modal
|
* This component contains the TRMNL Plugin Settings modal.
|
||||||
*/
|
*/
|
||||||
new class extends Component
|
new class extends Component
|
||||||
{
|
{
|
||||||
|
|
@ -19,17 +21,19 @@ new class extends Component
|
||||||
|
|
||||||
public bool $use_trmnl_liquid_renderer = false;
|
public bool $use_trmnl_liquid_renderer = false;
|
||||||
|
|
||||||
|
public string $configurationTemplateYaml = '';
|
||||||
|
|
||||||
public int $resetIndex = 0;
|
public int $resetIndex = 0;
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
// Reload data
|
|
||||||
$this->plugin = $this->plugin->fresh();
|
$this->plugin = $this->plugin->fresh();
|
||||||
$this->trmnlp_id = $this->plugin->trmnlp_id;
|
$this->trmnlp_id = $this->plugin->trmnlp_id;
|
||||||
$this->uuid = $this->plugin->uuid;
|
$this->uuid = $this->plugin->uuid;
|
||||||
$this->alias = $this->plugin->alias ?? false;
|
$this->alias = $this->plugin->alias ?? false;
|
||||||
$this->use_trmnl_liquid_renderer = $this->plugin->preferred_renderer === 'trmnl-liquid';
|
$this->use_trmnl_liquid_renderer = $this->plugin->preferred_renderer === 'trmnl-liquid';
|
||||||
|
$this->configurationTemplateYaml = $this->plugin->getCustomFieldsEditorYaml();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveTrmnlpId(): void
|
public function saveTrmnlpId(): void
|
||||||
|
|
@ -47,14 +51,46 @@ new class extends Component
|
||||||
],
|
],
|
||||||
'alias' => 'boolean',
|
'alias' => 'boolean',
|
||||||
'use_trmnl_liquid_renderer' => 'boolean',
|
'use_trmnl_liquid_renderer' => 'boolean',
|
||||||
|
'configurationTemplateYaml' => [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||||
|
if ($value === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$parsed = Yaml::parse($value);
|
||||||
|
if (! is_array($parsed)) {
|
||||||
|
$fail('The configuration must be valid YAML and evaluate to an object/array.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Plugin::validateCustomFieldsList($parsed);
|
||||||
|
} catch (ParseException) {
|
||||||
|
$fail('The configuration must be valid YAML.');
|
||||||
|
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||||
|
foreach ($e->errors() as $messages) {
|
||||||
|
foreach ($messages as $message) {
|
||||||
|
$fail($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$configurationTemplate = Plugin::configurationTemplateFromCustomFieldsYaml(
|
||||||
|
$this->configurationTemplateYaml,
|
||||||
|
$this->plugin->configuration_template
|
||||||
|
);
|
||||||
|
|
||||||
$this->plugin->update([
|
$this->plugin->update([
|
||||||
'trmnlp_id' => empty($this->trmnlp_id) ? null : $this->trmnlp_id,
|
'trmnlp_id' => empty($this->trmnlp_id) ? null : $this->trmnlp_id,
|
||||||
'alias' => $this->alias,
|
'alias' => $this->alias,
|
||||||
'preferred_renderer' => $this->use_trmnl_liquid_renderer ? 'trmnl-liquid' : null,
|
'preferred_renderer' => $this->use_trmnl_liquid_renderer ? 'trmnl-liquid' : null,
|
||||||
|
'configuration_template' => $configurationTemplate,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->dispatch('config-updated');
|
||||||
Flux::modal('trmnlp-settings')->close();
|
Flux::modal('trmnlp-settings')->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +100,7 @@ new class extends Component
|
||||||
}
|
}
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
<flux:modal name="trmnlp-settings" class="min-w-[400px] space-y-6">
|
<flux:modal name="trmnlp-settings" class="min-w-[600px] max-w-2xl space-y-6">
|
||||||
<div wire:key="trmnlp-settings-form-{{ $resetIndex }}" class="space-y-6">
|
<div wire:key="trmnlp-settings-form-{{ $resetIndex }}" class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<flux:heading size="lg">Recipe Settings</flux:heading>
|
<flux:heading size="lg">Recipe Settings</flux:heading>
|
||||||
|
|
@ -98,6 +134,43 @@ new class extends Component
|
||||||
</flux:field>
|
</flux:field>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<flux:field>
|
||||||
|
<flux:label>Configuration template</flux:label>
|
||||||
|
<flux:description>
|
||||||
|
Build forms visually in the <a href="https://usetrmnl.github.io/trmnl-form-builder/" target="_blank" rel="noopener noreferrer">TRMNL YML Form Builder</a>.
|
||||||
|
Check the <a href="https://help.trmnl.com/en/articles/10513740-custom-plugin-form-builder" target="_blank" rel="noopener noreferrer">docs</a> for more information.
|
||||||
|
</flux:description>
|
||||||
|
@php
|
||||||
|
$configTemplateTextareaId = 'config-template-' . uniqid();
|
||||||
|
@endphp
|
||||||
|
<flux:textarea
|
||||||
|
wire:model="configurationTemplateYaml"
|
||||||
|
id="{{ $configTemplateTextareaId }}"
|
||||||
|
placeholder="[]"
|
||||||
|
rows="12"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
x-data="codeEditorFormComponent({
|
||||||
|
isDisabled: false,
|
||||||
|
language: 'yaml',
|
||||||
|
state: $wire.entangle('configurationTemplateYaml'),
|
||||||
|
textareaId: @js($configTemplateTextareaId)
|
||||||
|
})"
|
||||||
|
wire:ignore
|
||||||
|
wire:key="cm-{{ $configTemplateTextareaId }}"
|
||||||
|
class="min-h-[200px] h-[300px] overflow-hidden resize-y"
|
||||||
|
>
|
||||||
|
<div x-show="isLoading" class="flex items-center justify-center h-full">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<flux:icon.loading />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-show="!isLoading" x-ref="editor" class="h-full"></div>
|
||||||
|
</div>
|
||||||
|
<flux:error name="configurationTemplateYaml" />
|
||||||
|
</flux:field>
|
||||||
|
|
||||||
@if($alias)
|
@if($alias)
|
||||||
<flux:field>
|
<flux:field>
|
||||||
<flux:label>Alias URL</flux:label>
|
<flux:label>Alias URL</flux:label>
|
||||||
|
|
|
||||||
|
|
@ -149,3 +149,84 @@ test('recipe settings clears preferred_renderer when checkbox unchecked', functi
|
||||||
|
|
||||||
expect($plugin->fresh()->preferred_renderer)->toBeNull();
|
expect($plugin->fresh()->preferred_renderer)->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('recipe settings saves configuration_template from valid YAML', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$yaml = "- keyname: reading_days\n field_type: text\n name: Reading Days\n";
|
||||||
|
|
||||||
|
Livewire::test('plugins.recipes.settings', ['plugin' => $plugin])
|
||||||
|
->set('configurationTemplateYaml', $yaml)
|
||||||
|
->call('saveTrmnlpId')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'custom_fields' => [
|
||||||
|
[
|
||||||
|
'keyname' => 'reading_days',
|
||||||
|
'field_type' => 'text',
|
||||||
|
'name' => 'Reading Days',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
expect($plugin->fresh()->configuration_template)->toBe($expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('recipe settings validates invalid YAML', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test('plugins.recipes.settings', ['plugin' => $plugin])
|
||||||
|
->set('configurationTemplateYaml', "foo: bar: baz\n")
|
||||||
|
->call('saveTrmnlpId')
|
||||||
|
->assertHasErrors(['configurationTemplateYaml']);
|
||||||
|
|
||||||
|
expect($plugin->fresh()->configuration_template)->toBe([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('recipe settings validates YAML must evaluate to object or array', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => ['custom_fields' => []],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test('plugins.recipes.settings', ['plugin' => $plugin])
|
||||||
|
->set('configurationTemplateYaml', '123')
|
||||||
|
->call('saveTrmnlpId')
|
||||||
|
->assertHasErrors(['configurationTemplateYaml']);
|
||||||
|
|
||||||
|
expect($plugin->fresh()->configuration_template)->toBe(['custom_fields' => []]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('recipe settings validates each custom field has field_type and name', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$yaml = "- keyname: only_key\n field_type: text\n name: Has Name\n- keyname: missing_type\n name: No type\n";
|
||||||
|
|
||||||
|
Livewire::test('plugins.recipes.settings', ['plugin' => $plugin])
|
||||||
|
->set('configurationTemplateYaml', $yaml)
|
||||||
|
->call('saveTrmnlpId')
|
||||||
|
->assertHasErrors(['configurationTemplateYaml']);
|
||||||
|
|
||||||
|
expect($plugin->fresh()->configuration_template)->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue