diff --git a/app/Liquid/Filters/Data.php b/app/Liquid/Filters/Data.php index 4437032..5b1f92f 100644 --- a/app/Liquid/Filters/Data.php +++ b/app/Liquid/Filters/Data.php @@ -19,48 +19,4 @@ class Data extends FiltersProvider { return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } - - /** - * Find an object in a collection by a specific key-value pair - * - * @param array $collection The collection to search in - * @param string $key The key to search for - * @param mixed $value The value to match - * @param mixed $fallback Optional fallback value if no match is found - * @return mixed The matching object or fallback value - */ - public function find_by(array $collection, string $key, mixed $value, mixed $fallback = null): mixed - { - foreach ($collection as $item) { - if (is_array($item) && isset($item[$key]) && $item[$key] === $value) { - return $item; - } - } - - return $fallback; - } - - /** - * Group a collection by a specific key - * - * @param array $collection The collection to group - * @param string $key The key to group by - * @return array The grouped collection - */ - public function group_by(array $collection, string $key): array - { - $grouped = []; - - foreach ($collection as $item) { - if (is_array($item) && array_key_exists($key, $item)) { - $groupKey = $item[$key]; - if (! isset($grouped[$groupKey])) { - $grouped[$groupKey] = []; - } - $grouped[$groupKey][] = $item; - } - } - - return $grouped; - } } diff --git a/app/Services/PluginExportService.php b/app/Services/PluginExportService.php deleted file mode 100644 index 3c2c3d0..0000000 --- a/app/Services/PluginExportService.php +++ /dev/null @@ -1,186 +0,0 @@ -generateSettingsYaml($plugin); - $settingsYaml = Yaml::dump($settings, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); - File::put($tempDir.'/settings.yml', $settingsYaml); - - // Generate full template content - $fullTemplate = $this->generateFullTemplate($plugin); - $extension = $plugin->markup_language === 'liquid' ? 'liquid' : 'blade.php'; - File::put($tempDir.'/full.'.$extension, $fullTemplate); - - // Generate shared.liquid if needed (for liquid templates) - if ($plugin->markup_language === 'liquid') { - $sharedTemplate = $this->generateSharedTemplate($plugin); - if ($sharedTemplate) { - File::put($tempDir.'/shared.liquid', $sharedTemplate); - } - } - - // Create ZIP file - $zipPath = $tempDir.'/plugin_'.$plugin->trmnlp_id.'.zip'; - $zip = new ZipArchive(); - - if ($zip->open($zipPath, ZipArchive::CREATE) !== true) { - throw new Exception('Could not create ZIP file.'); - } - - // Add files directly to ZIP root - $this->addDirectoryToZip($zip, $tempDir, ''); - $zip->close(); - - // Return the ZIP file as a download response - return response()->download($zipPath, 'plugin_'.$plugin->trmnlp_id.'.zip'); - - } catch (Exception $e) { - throw $e; - } - } - - /** - * Generate the settings.yml content for the plugin - */ - private function generateSettingsYaml(Plugin $plugin): array - { - $settings = []; - - // Add fields in the specific order requested - $settings['name'] = $plugin->name; - $settings['no_screen_padding'] = 'no'; // Default value - $settings['dark_mode'] = 'no'; // Default value - $settings['strategy'] = $plugin->data_strategy; - - // Add static data if available - if ($plugin->data_payload) { - $settings['static_data'] = json_encode($plugin->data_payload, JSON_PRETTY_PRINT); - } - - // Add polling configuration if applicable - if ($plugin->data_strategy === 'polling') { - if ($plugin->polling_verb) { - $settings['polling_verb'] = $plugin->polling_verb; - } - if ($plugin->polling_url) { - $settings['polling_url'] = $plugin->polling_url; - } - if ($plugin->polling_header) { - // Convert header format from "key: value" to "key=value" - $settings['polling_headers'] = str_replace(':', '=', $plugin->polling_header); - } - if ($plugin->polling_body) { - $settings['polling_body'] = $plugin->polling_body; - } - } - - $settings['refresh_interval'] = $plugin->data_stale_minutes; - $settings['id'] = $plugin->trmnlp_id; - - // Add custom fields from configuration template - if (isset($plugin->configuration_template['custom_fields'])) { - $settings['custom_fields'] = $plugin->configuration_template['custom_fields']; - } - - return $settings; - } - - /** - * Generate the full template content - */ - private function generateFullTemplate(Plugin $plugin): string - { - $markup = $plugin->render_markup; - - // Remove the wrapper div if it exists (it will be added during import) - $markup = preg_replace('/^
\s*/', '', $markup); - $markup = preg_replace('/\s*<\/div>\s*$/', '', $markup); - - return trim($markup); - } - - /** - * Generate the shared template content (for liquid templates) - */ - private function generateSharedTemplate(Plugin $plugin) - { - // For now, we don't have a way to store shared templates separately - // TODO - add support for shared templates - return null; - } - - /** - * Add a directory and its contents to a ZIP file - */ - private function addDirectoryToZip(ZipArchive $zip, string $dirPath, string $zipPath): void - { - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($dirPath), - RecursiveIteratorIterator::LEAVES_ONLY - ); - - foreach ($files as $file) { - if (! $file->isDir()) { - $filePath = $file->getRealPath(); - $fileName = basename($filePath); - - // For root directory, just use the filename - if ($zipPath === '') { - $relativePath = $fileName; - } else { - $relativePath = $zipPath.'/'.mb_substr($filePath, mb_strlen($dirPath) + 1); - } - - $zip->addFile($filePath, $relativePath); - } - } - } -} diff --git a/app/Services/PluginImportService.php b/app/Services/PluginImportService.php index dbd8ec8..9cf3d76 100644 --- a/app/Services/PluginImportService.php +++ b/app/Services/PluginImportService.php @@ -10,7 +10,6 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; -use Symfony\Component\Uid\Uuid; use Symfony\Component\Yaml\Yaml; use ZipArchive; @@ -59,6 +58,7 @@ class PluginImportService // Read full.liquid content $fullLiquid = File::get($filePaths['fullLiquidPath']); + $fullLiquid = '
'."\n".$fullLiquid."\n".'
'; // Prepend shared.liquid content if available if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) { @@ -66,8 +66,6 @@ class PluginImportService $fullLiquid = $sharedLiquid."\n".$fullLiquid; } - $fullLiquid = '
'."\n".$fullLiquid."\n".'
'; - // Check if the file ends with .liquid to set markup language $markupLanguage = 'blade'; if (pathinfo($filePaths['fullLiquidPath'], PATHINFO_EXTENSION) === 'liquid') { @@ -84,45 +82,34 @@ class PluginImportService 'custom_fields' => $settings['custom_fields'], ]; - $plugin_updated = isset($settings['id']) - && Plugin::where('user_id', $user->id)->where('trmnlp_id', $settings['id'])->exists(); - // Create a new plugin - $plugin = Plugin::updateOrCreate( - [ - 'user_id' => $user->id, 'trmnlp_id' => $settings['id'] ?? Uuid::v7(), - ], - [ - 'user_id' => $user->id, - 'name' => $settings['name'] ?? 'Imported Plugin', - 'trmnlp_id' => $settings['id'] ?? Uuid::v7(), - 'data_stale_minutes' => $settings['refresh_interval'] ?? 15, - 'data_strategy' => $settings['strategy'] ?? 'static', - 'polling_url' => $settings['polling_url'] ?? null, - 'polling_verb' => $settings['polling_verb'] ?? 'get', - 'polling_header' => isset($settings['polling_headers']) - ? str_replace('=', ':', $settings['polling_headers']) - : null, - 'polling_body' => $settings['polling_body'] ?? null, - 'markup_language' => $markupLanguage, - 'render_markup' => $fullLiquid, - 'configuration_template' => $configurationTemplate, - 'data_payload' => json_decode($settings['static_data'] ?? '{}', true), - ]); - - if (! $plugin_updated) { - // Extract default values from custom_fields and populate configuration - $configuration = []; + // Extract default values from custom_fields and populate configuration + $configuration = []; + if (isset($settings['custom_fields']) && is_array($settings['custom_fields'])) { foreach ($settings['custom_fields'] as $field) { if (isset($field['keyname']) && isset($field['default'])) { $configuration[$field['keyname']] = $field['default']; } } - // set only if plugin is new - $plugin->update([ - 'configuration' => $configuration, - ]); } - $plugin['trmnlp_yaml'] = $settingsYaml; + + // Create a new plugin + $plugin = Plugin::create([ + 'user_id' => $user->id, + 'name' => $settings['name'] ?? 'Imported Plugin', + 'data_stale_minutes' => $settings['refresh_interval'] ?? 15, + 'data_strategy' => $settings['strategy'] ?? 'static', + 'polling_url' => $settings['polling_url'] ?? null, + 'polling_verb' => $settings['polling_verb'] ?? 'get', + 'polling_header' => isset($settings['polling_headers']) + ? str_replace('=', ':', $settings['polling_headers']) + : null, + 'polling_body' => $settings['polling_body'] ?? null, + 'markup_language' => $markupLanguage, + 'render_markup' => $fullLiquid, + 'configuration_template' => $configurationTemplate, + 'configuration' => $configuration, + 'data_payload' => json_decode($settings['static_data'] ?? '{}', true), + ]); return $plugin; @@ -132,6 +119,12 @@ class PluginImportService } } + /** + * Find required files in the extracted ZIP directory + * + * @param string $tempDir The temporary directory path + * @return array Array containing paths to required files + */ private function findRequiredFiles(string $tempDir): array { $settingsYamlPath = null; diff --git a/composer.lock b/composer.lock index 58435b1..e53543f 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.356.3", + "version": "3.356.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "9c61b26408664c76d51101381bb64feda9bcfe2e" + "reference": "d4c61078d5f371c9a8a844946e96e12d6fa18080" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9c61b26408664c76d51101381bb64feda9bcfe2e", - "reference": "9c61b26408664c76d51101381bb64feda9bcfe2e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d4c61078d5f371c9a8a844946e96e12d6fa18080", + "reference": "d4c61078d5f371c9a8a844946e96e12d6fa18080", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.356.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.356.2" }, - "time": "2025-08-22T18:13:45+00:00" + "time": "2025-08-21T18:09:26+00:00" }, { "name": "bnussbau/laravel-trmnl-blade", @@ -942,22 +942,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1048,7 +1048,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" }, "funding": [ { @@ -1064,7 +1064,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", @@ -1151,16 +1151,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { @@ -1176,7 +1176,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1247,7 +1247,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.1" }, "funding": [ { @@ -1263,7 +1263,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2025-03-27T12:30:47+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2857,16 +2857,16 @@ }, { "name": "livewire/flux", - "version": "v2.2.4", + "version": "v2.2.5", "source": { "type": "git", "url": "https://github.com/livewire/flux.git", - "reference": "af81b5fd34c6490d5b5e05ed0f8140c0250e5069" + "reference": "2295f14766f86006d889f37ee208bcf347a0d013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/flux/zipball/af81b5fd34c6490d5b5e05ed0f8140c0250e5069", - "reference": "af81b5fd34c6490d5b5e05ed0f8140c0250e5069", + "url": "https://api.github.com/repos/livewire/flux/zipball/2295f14766f86006d889f37ee208bcf347a0d013", + "reference": "2295f14766f86006d889f37ee208bcf347a0d013", "shasum": "" }, "require": { @@ -2914,9 +2914,9 @@ ], "support": { "issues": "https://github.com/livewire/flux/issues", - "source": "https://github.com/livewire/flux/tree/v2.2.4" + "source": "https://github.com/livewire/flux/tree/v2.2.5" }, - "time": "2025-08-09T01:46:51+00:00" + "time": "2025-08-19T22:41:54+00:00" }, { "name": "livewire/livewire", @@ -8371,16 +8371,16 @@ }, { "name": "larastan/larastan", - "version": "v3.6.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "3c223047e374befd1b64959784685d6ecccf66aa" + "reference": "6431d010dd383a9279eb8874a76ddb571738564a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/3c223047e374befd1b64959784685d6ecccf66aa", - "reference": "3c223047e374befd1b64959784685d6ecccf66aa", + "url": "https://api.github.com/repos/larastan/larastan/zipball/6431d010dd383a9279eb8874a76ddb571738564a", + "reference": "6431d010dd383a9279eb8874a76ddb571738564a", "shasum": "" }, "require": { @@ -8448,7 +8448,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.6.1" + "source": "https://github.com/larastan/larastan/tree/v3.6.0" }, "funding": [ { @@ -8456,7 +8456,7 @@ "type": "github" } ], - "time": "2025-08-25T07:24:56+00:00" + "time": "2025-07-11T06:52:52+00:00" }, { "name": "laravel/boost", diff --git a/database/migrations/2025_08_22_231823_add_trmnlp_to_plugins_table.php b/database/migrations/2025_08_22_231823_add_trmnlp_to_plugins_table.php deleted file mode 100644 index 4c90d29..0000000 --- a/database/migrations/2025_08_22_231823_add_trmnlp_to_plugins_table.php +++ /dev/null @@ -1,28 +0,0 @@ -string('trmnlp_id')->nullable()->after('uuid'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('plugins', function (Blueprint $table) { - $table->dropColumn('trmnlp_id'); - }); - } -}; diff --git a/resources/views/livewire/plugins/recipe.blade.php b/resources/views/livewire/plugins/recipe.blade.php index 7a73358..0edee8a 100644 --- a/resources/views/livewire/plugins/recipe.blade.php +++ b/resources/views/livewire/plugins/recipe.blade.php @@ -6,7 +6,6 @@ use Keepsuit\Liquid\Exceptions\LiquidException; use Livewire\Volt\Component; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Http; new class extends Component { public Plugin $plugin; @@ -33,8 +32,6 @@ new class extends Component { public array $mashup_plugins = []; public array $configuration_template = []; public array $configuration = []; - public array $xhrSelectOptions = []; - public array $searchQueries = []; public function mount(): void { @@ -348,41 +345,6 @@ HTML; $this->plugin->delete(); $this->redirect(route('plugins.index')); } - - public function loadXhrSelectOptions(string $fieldKey, string $endpoint, ?string $query = null): void - { - abort_unless(auth()->user()->plugins->contains($this->plugin), 403); - - try { - $requestData = []; - if ($query !== null) { - $requestData = [ - 'function' => $fieldKey, - 'query' => $query - ]; - } - - $response = $query !== null - ? Http::post($endpoint, $requestData) - : Http::post($endpoint); - - if ($response->successful()) { - $this->xhrSelectOptions[$fieldKey] = $response->json(); - } else { - $this->xhrSelectOptions[$fieldKey] = []; - } - } catch (\Exception $e) { - $this->xhrSelectOptions[$fieldKey] = []; - } - } - - public function searchXhrSelect(string $fieldKey, string $endpoint): void - { - $query = $this->searchQueries[$fieldKey] ?? ''; - if (!empty($query)) { - $this->loadXhrSelectOptions($fieldKey, $endpoint, $query); - } - } } ?> @@ -682,76 +644,6 @@ HTML; @endif @endif - @elseif($field['field_type'] === 'xhrSelect') - - - @if(isset($xhrSelectOptions[$fieldKey]) && is_array($xhrSelectOptions[$fieldKey])) - @foreach($xhrSelectOptions[$fieldKey] as $option) - @if(is_array($option)) - @if(isset($option['id']) && isset($option['name'])) - {{-- xhrSelectSearch format: { 'id' => 'db-456', 'name' => 'Team Goals' } --}} - - @else - {{-- xhrSelect format: { 'Braves' => 123 } --}} - @foreach($option as $label => $value) - - @endforeach - @endif - @else - - @endif - @endforeach - @endif - - @elseif($field['field_type'] === 'xhrSelectSearch') -
- - {{ $field['name'] }} - {{ $field['description'] ?? '' }} - - - - - - @if((isset($xhrSelectOptions[$fieldKey]) && is_array($xhrSelectOptions[$fieldKey]) && count($xhrSelectOptions[$fieldKey]) > 0) || !empty($currentValue)) - - - @if(isset($xhrSelectOptions[$fieldKey]) && is_array($xhrSelectOptions[$fieldKey])) - @foreach($xhrSelectOptions[$fieldKey] as $option) - @if(is_array($option)) - @if(isset($option['id']) && isset($option['name'])) - {{-- xhrSelectSearch format: { 'id' => 'db-456', 'name' => 'Team Goals' } --}} - - @else - {{-- xhrSelect format: { 'Braves' => 123 } --}} - @foreach($option as $label => $value) - - @endforeach - @endif - @else - - @endif - @endforeach - @endif - @if(!empty($currentValue) && (!isset($xhrSelectOptions[$fieldKey]) || empty($xhrSelectOptions[$fieldKey]))) - {{-- Show current value even if no options are loaded --}} - - @endif - - @endif -
@else

{{ $field['name'] }}: Field type "{{ $field['field_type'] }}" not yet supported

@endif diff --git a/routes/api.php b/routes/api.php index 435ee7d..2881796 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,7 +7,6 @@ use App\Models\DeviceModel; use App\Models\Plugin; use App\Models\User; use App\Services\ImageGenerationService; -use App\Services\PluginImportService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Log; @@ -512,48 +511,3 @@ Route::post('custom_plugins/{plugin_uuid}', function (string $plugin_uuid) { return response()->json(['message' => 'Data updated successfully']); })->name('api.custom_plugins.webhook'); - -Route::get('plugin_settings/{trmnlp_id}/archive', function (Request $request, string $trmnlp_id) { - if (! $trmnlp_id || trim($trmnlp_id) === '') { - return response()->json([ - 'message' => 'trmnlp_id is required', - ], 400); - } - - // Find the plugin by trmnlp_id and ensure it belongs to the authenticated user - $plugin = Plugin::where('trmnlp_id', $trmnlp_id) - ->where('user_id', auth()->user()->id) - ->firstOrFail(); - - // Use the export service to create the ZIP file - /** @var App\Services\PluginExportService $exporter */ - $exporter = app(App\Services\PluginExportService::class); - - return $exporter->exportToZip($plugin, auth()->user()); -})->middleware('auth:sanctum'); - -Route::post('plugin_settings/{trmnlp_id}/archive', function (Request $request, string $trmnlp_id) { - if (! $trmnlp_id) { - return response()->json([ - 'message' => 'trmnl_id is required', - ]); - } - - $validated = $request->validate([ - 'file' => 'required|file|mimes:zip', - ]); - - /** @var Illuminate\Http\UploadedFile $file */ - $file = $request->file('file'); - // Apply archive to existing plugin using the import service - /** @var PluginImportService $importer */ - $importer = app(PluginImportService::class); - $plugin = $importer->importFromZip($file, auth()->user()); - - return response()->json([ - 'message' => 'Plugin settings archive processed successfully', - 'data' => [ - 'settings_yaml' => $plugin['trmnlp_yaml'], - ], - ]); -})->middleware('auth:sanctum'); diff --git a/routes/web.php b/routes/web.php index e6afc1a..3be1c66 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,7 +1,5 @@ group(function () { Volt::route('plugins/markup', 'plugins.markup')->name('plugins.markup'); Volt::route('plugins/api', 'plugins.api')->name('plugins.api'); Volt::route('playlists', 'playlists.index')->name('playlists.index'); - - Route::get('plugin_settings/{trmnlp_id}/edit', function (Request $request, string $trmnlp_id) { - $plugin = Plugin::query() - ->where('user_id', $request->user()->id) - ->where('trmnlp_id', $trmnlp_id)->firstOrFail(); - - return redirect()->route('plugins.recipe', ['plugin' => $plugin]); - }); }); require __DIR__.'/auth.php'; diff --git a/tests/Feature/Api/PluginSettingsArchiveTest.php b/tests/Feature/Api/PluginSettingsArchiveTest.php deleted file mode 100644 index 517f2f8..0000000 --- a/tests/Feature/Api/PluginSettingsArchiveTest.php +++ /dev/null @@ -1,73 +0,0 @@ -create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'uuid' => (string) Str::uuid(), - ]); - - // Authenticate via Sanctum (endpoint requires auth:sanctum) - Sanctum::actingAs($user); - - // Build a temporary ZIP with required structure: src/settings.yml and src/full.liquid - $tempDir = sys_get_temp_dir().'/trmnl_zip_'.uniqid(); - $srcDir = $tempDir.'/src'; - if (! is_dir($srcDir)) { - mkdir($srcDir, 0777, true); - } - - $settingsYaml = <<<'YAML' -name: Sample Imported -strategy: static -refresh_interval: 10 -custom_fields: - - keyname: title - default: "Hello" -static_data: '{"message":"world"}' -YAML; - - $fullLiquid = <<<'LIQUID' -

{{ config.title }}

-
{{ data.message }}
-LIQUID; - - file_put_contents($srcDir.'/settings.yml', $settingsYaml); - file_put_contents($srcDir.'/full.liquid', $fullLiquid); - - $zipPath = sys_get_temp_dir().'/plugin_'.uniqid().'.zip'; - $zip = new ZipArchive(); - $zip->open($zipPath, ZipArchive::CREATE); - $zip->addFile($srcDir.'/settings.yml', 'src/settings.yml'); - $zip->addFile($srcDir.'/full.liquid', 'src/full.liquid'); - $zip->close(); - - // Prepare UploadedFile - $uploaded = new UploadedFile($zipPath, 'plugin.zip', 'application/zip', null, true); - - // Make request (multipart form-data) - $response = $this->post('/api/plugin_settings/'.$plugin->uuid.'/archive', [ - 'file' => $uploaded, - ], ['Accept' => 'application/json']); - - $response->assertSuccessful(); - - $imported = Plugin::query() - ->where('user_id', $user->id) - ->where('name', 'Sample Imported') - ->first(); - - expect($imported)->not->toBeNull(); - expect($imported->markup_language)->toBe('liquid'); - expect($imported->render_markup)->toContain('

{{ config.title }}

'); - // Configuration should have default for title (set on create) - expect($imported->configuration['title'] ?? null)->toBe('Hello'); -}); diff --git a/tests/Feature/PluginArchiveTest.php b/tests/Feature/PluginArchiveTest.php deleted file mode 100644 index 34014ec..0000000 --- a/tests/Feature/PluginArchiveTest.php +++ /dev/null @@ -1,308 +0,0 @@ -create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Test Plugin', - 'trmnlp_id' => 'test-plugin-123', - 'data_stale_minutes' => 30, - 'data_strategy' => 'static', - 'markup_language' => 'liquid', - 'render_markup' => '
Hello {{ config.name }}
', - 'configuration_template' => [ - 'custom_fields' => [ - [ - 'keyname' => 'name', - 'field_type' => 'text', - 'default' => 'World', - ], - ], - ], - 'data_payload' => ['message' => 'Hello World'], - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - expect($response)->toBeInstanceOf(Symfony\Component\HttpFoundation\BinaryFileResponse::class); - expect($response->getFile()->getFilename())->toContain('test-plugin-123.zip'); -}); - -it('exports plugin with polling configuration', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Polling Plugin', - 'trmnlp_id' => 'polling-plugin-456', - 'data_strategy' => 'polling', - 'polling_url' => 'https://api.example.com/data', - 'polling_verb' => 'post', - 'polling_header' => 'Authorization: Bearer token', - 'polling_body' => '{"key": "value"}', - 'markup_language' => 'blade', - 'render_markup' => '
Hello {{ $config["name"] }}
', - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - expect($response)->toBeInstanceOf(Symfony\Component\HttpFoundation\BinaryFileResponse::class); -}); - -it('exports and imports plugin maintaining all data', function () { - $user = User::factory()->create(); - $originalPlugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Round Trip Plugin', - 'trmnlp_id' => 'round-trip-789', - 'data_stale_minutes' => 45, - 'data_strategy' => 'static', - 'markup_language' => 'liquid', - 'render_markup' => '
Hello {{ config.name }}!
', - 'configuration_template' => [ - 'custom_fields' => [ - [ - 'keyname' => 'name', - 'field_type' => 'text', - 'default' => 'Test User', - ], - [ - 'keyname' => 'color', - 'field_type' => 'select', - 'default' => 'blue', - 'options' => ['red', 'green', 'blue'], - ], - ], - ], - 'data_payload' => ['items' => [1, 2, 3]], - ]); - - // Export the plugin - $exporter = app(PluginExportService::class); - $exportResponse = $exporter->exportToZip($originalPlugin, $user); - - // Get the exported file path - $exportedFilePath = $exportResponse->getFile()->getPathname(); - - // Create an UploadedFile from the exported ZIP - $uploadedFile = new UploadedFile( - $exportedFilePath, - 'plugin_round-trip-789.zip', - 'application/zip', - null, - true - ); - - // Import the plugin back - $importer = app(PluginImportService::class); - $importedPlugin = $importer->importFromZip($uploadedFile, $user); - - // Verify the imported plugin has the same data - expect($importedPlugin->name)->toBe('Round Trip Plugin'); - expect($importedPlugin->trmnlp_id)->toBe('round-trip-789'); - expect($importedPlugin->data_stale_minutes)->toBe(45); - expect($importedPlugin->data_strategy)->toBe('static'); - expect($importedPlugin->markup_language)->toBe('liquid'); - expect($importedPlugin->render_markup)->toContain('Hello {{ config.name }}!'); - expect($importedPlugin->configuration_template['custom_fields'])->toHaveCount(2); - expect($importedPlugin->data_payload)->toBe(['items' => [1, 2, 3]]); -}); - -it('handles blade templates correctly', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Blade Plugin', - 'trmnlp_id' => 'blade-plugin-101', - 'markup_language' => 'blade', - 'render_markup' => '
Hello {{ $config["name"] }}!
', - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - expect($response)->toBeInstanceOf(Symfony\Component\HttpFoundation\BinaryFileResponse::class); -}); - -it('removes wrapper div from exported markup', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Wrapped Plugin', - 'trmnlp_id' => 'wrapped-plugin-202', - 'markup_language' => 'liquid', - 'render_markup' => '
Hello World
', - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - expect($response)->toBeInstanceOf(Symfony\Component\HttpFoundation\BinaryFileResponse::class); -}); - -it('converts polling headers correctly', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Header Plugin', - 'trmnlp_id' => 'header-plugin-303', - 'data_strategy' => 'polling', - 'polling_header' => 'Authorization: Bearer token', - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - expect($response)->toBeInstanceOf(Symfony\Component\HttpFoundation\BinaryFileResponse::class); -}); - -it('api route returns zip file for authenticated user', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'API Test Plugin', - 'trmnlp_id' => 'api-test-404', - 'markup_language' => 'liquid', - 'render_markup' => '
API Test
', - ]); - - $response = $this->actingAs($user) - ->getJson("/api/plugin_settings/{$plugin->trmnlp_id}/archive"); - - $response->assertStatus(200); - $response->assertHeader('Content-Type', 'application/zip'); - $response->assertHeader('Content-Disposition', 'attachment; filename=plugin_api-test-404.zip'); -}); - -it('api route returns 404 for non-existent plugin', function () { - $user = User::factory()->create(); - - $response = $this->actingAs($user) - ->getJson('/api/plugin_settings/non-existent-id/archive'); - - $response->assertStatus(404); -}); - -it('api route returns 401 for unauthenticated user', function () { - $response = $this->getJson('/api/plugin_settings/test-id/archive'); - - $response->assertStatus(401); -}); - -it('api route returns 404 for plugin belonging to different user', function () { - $user1 = User::factory()->create(); - $user2 = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user1->id, - 'trmnlp_id' => 'other-user-plugin', - ]); - - $response = $this->actingAs($user2) - ->getJson("/api/plugin_settings/{$plugin->trmnlp_id}/archive"); - - $response->assertStatus(404); -}); - -it('exports zip with files in root directory', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'Root Directory Test', - 'trmnlp_id' => 'root-test-123', - 'markup_language' => 'liquid', - 'render_markup' => '
Test content
', - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - $zipPath = $response->getFile()->getPathname(); - $zip = new ZipArchive(); - $zip->open($zipPath); - - // Check that files are in the root, not in src/ - expect($zip->locateName('settings.yml'))->not->toBeFalse(); - expect($zip->locateName('full.liquid'))->not->toBeFalse(); - expect($zip->locateName('src/settings.yml'))->toBeFalse(); - expect($zip->locateName('src/full.liquid'))->toBeFalse(); - - $zip->close(); -}); - -it('maintains correct yaml field order', function () { - $user = User::factory()->create(); - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'name' => 'YAML Order Test', - 'trmnlp_id' => 'yaml-order-test', - 'data_strategy' => 'polling', - 'polling_url' => 'https://api.example.com/data', - 'polling_verb' => 'post', - 'data_stale_minutes' => 30, - 'markup_language' => 'liquid', - 'render_markup' => '
Test
', - ]); - - $exporter = app(PluginExportService::class); - $response = $exporter->exportToZip($plugin, $user); - - $zipPath = $response->getFile()->getPathname(); - $zip = new ZipArchive(); - $zip->open($zipPath); - - // Extract and read the settings.yml file - $zip->extractTo(sys_get_temp_dir(), 'settings.yml'); - $yamlContent = file_get_contents(sys_get_temp_dir() . '/settings.yml'); - $zip->close(); - - // Check that the YAML content has the expected field order - $expectedOrder = [ - 'name:', - 'no_screen_padding:', - 'dark_mode:', - 'strategy:', - 'static_data:', - 'polling_verb:', - 'polling_url:', - 'refresh_interval:', - 'id:', - 'custom_fields:', - ]; - - $lines = explode("\n", $yamlContent); - $fieldLines = []; - - foreach ($lines as $line) { - $line = trim($line); - if (preg_match('/^([a-zA-Z_]+):/', $line, $matches)) { - $fieldLines[] = $matches[1] . ':'; - } - } - - // Verify that the fields appear in the expected order (allowing for missing optional fields) - $currentIndex = 0; - foreach ($expectedOrder as $expectedField) { - $foundIndex = array_search($expectedField, $fieldLines); - if ($foundIndex !== false) { - expect($foundIndex)->toBeGreaterThanOrEqual($currentIndex); - $currentIndex = $foundIndex; - } - } - - // Clean up - unlink(sys_get_temp_dir() . '/settings.yml'); -}); diff --git a/tests/Feature/PluginInlineTemplatesTest.php b/tests/Feature/PluginInlineTemplatesTest.php index ce83d9d..ef2b96d 100644 --- a/tests/Feature/PluginInlineTemplatesTest.php +++ b/tests/Feature/PluginInlineTemplatesTest.php @@ -172,84 +172,4 @@ LIQUID $this->assertStringContainsString('This is a test', $result); $this->assertStringContainsString('class="simple"', $result); } - - public function test_plugin_with_find_by_filter(): void - { - $plugin = Plugin::factory()->create([ - 'markup_language' => 'liquid', - 'render_markup' => <<<'LIQUID' -{% template user_info %} -
-

{{ user.name }}

-

Age: {{ user.age }}

-
-{% endtemplate %} - -{% assign found_user = collection | find_by: 'name', 'Ryan' %} -{% render "user_info", user: found_user %} -LIQUID - , - 'data_payload' => [ - 'collection' => [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ], - ], - ]); - - $result = $plugin->render('full'); - - // Should render the user info for Ryan - $this->assertStringContainsString('Ryan', $result); - $this->assertStringContainsString('Age: 35', $result); - $this->assertStringContainsString('class="user"', $result); - } - - public function test_plugin_with_find_by_filter_and_fallback(): void - { - $plugin = Plugin::factory()->create([ - 'markup_language' => 'liquid', - 'render_markup' => <<<'LIQUID' -{{ collection | find_by: 'name', 'ronak', 'Not Found' }} -LIQUID - , - 'data_payload' => [ - 'collection' => [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ], - ], - ]); - - $result = $plugin->render('full'); - - // Should return the fallback value - $this->assertStringContainsString('Not Found', $result); - } - - public function test_plugin_with_group_by_filter(): void - { - $plugin = Plugin::factory()->create([ - 'markup_language' => 'liquid', - 'render_markup' => <<<'LIQUID' -{{ collection | group_by: 'age' | json }} -LIQUID - , - 'data_payload' => [ - 'collection' => [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ], - ], - ]); - - $result = $plugin->render('full'); - - // Should output JSON representation of grouped data - $this->assertStringContainsString('"35":[{"name":"Ryan","age":35}]', $result); - $this->assertStringContainsString('"29":[{"name":"Sara","age":29},{"name":"Jimbob","age":29}]', $result); - } } diff --git a/tests/Feature/PluginRequiredConfigurationTest.php b/tests/Feature/PluginRequiredConfigurationTest.php index 552b996..9d378ba 100644 --- a/tests/Feature/PluginRequiredConfigurationTest.php +++ b/tests/Feature/PluginRequiredConfigurationTest.php @@ -23,17 +23,17 @@ test('hasMissingRequiredConfigurationFields returns true when required field is 'field_type' => 'time_zone', 'name' => 'Timezone', 'description' => 'Select your timezone', - 'optional' => true, // Marked as optional - ], - ], + 'optional' => true // Marked as optional + ] + ] ]; $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => $configurationTemplate, 'configuration' => [ - 'timezone' => 'UTC', // Only timezone is set, api_key is missing - ], + 'timezone' => 'UTC' // Only timezone is set, api_key is missing + ] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue(); @@ -56,9 +56,9 @@ test('hasMissingRequiredConfigurationFields returns false when all required fiel 'field_type' => 'time_zone', 'name' => 'Timezone', 'description' => 'Select your timezone', - 'optional' => true, // Marked as optional - ], - ], + 'optional' => true // Marked as optional + ] + ] ]; $plugin = Plugin::factory()->create([ @@ -66,8 +66,8 @@ test('hasMissingRequiredConfigurationFields returns false when all required fiel 'configuration_template' => $configurationTemplate, 'configuration' => [ 'api_key' => 'test-api-key', // Required field is set - 'timezone' => 'UTC', - ], + 'timezone' => 'UTC' + ] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse(); @@ -79,7 +79,7 @@ test('hasMissingRequiredConfigurationFields returns false when no custom fields $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => [], - 'configuration' => [], + 'configuration' => [] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse(); @@ -96,16 +96,16 @@ test('hasMissingRequiredConfigurationFields returns true when explicitly require 'name' => 'API Key', 'description' => 'Your API key', // Not marked as optional, so it's required - ], - ], + ] + ] ]; $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => $configurationTemplate, 'configuration' => [ - 'api_key' => null, // Explicitly set to null - ], + 'api_key' => null // Explicitly set to null + ] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue(); @@ -122,16 +122,16 @@ test('hasMissingRequiredConfigurationFields returns true when required field is 'name' => 'API Key', 'description' => 'Your API key', // Not marked as optional, so it's required - ], - ], + ] + ] ]; $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => $configurationTemplate, 'configuration' => [ - 'api_key' => '', // Empty string - ], + 'api_key' => '' // Empty string + ] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue(); @@ -149,16 +149,16 @@ test('hasMissingRequiredConfigurationFields returns true when required array fie 'description' => 'Select items', 'multiple' => true, // Not marked as optional, so it's required - ], - ], + ] + ] ]; $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => $configurationTemplate, 'configuration' => [ - 'selected_items' => [], // Empty array - ], + 'selected_items' => [] // Empty array + ] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue(); @@ -178,16 +178,16 @@ test('hasMissingRequiredConfigurationFields returns false when author_bio field 'keyname' => 'plugin_field', 'name' => 'Field Name', 'field_type' => 'string', - ], - ], + ] + ] ]; $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => $configurationTemplate, 'configuration' => [ - 'plugin_field' => 'set', // Required field is set - ], + 'plugin_field' => 'set' // Required field is set + ] ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse(); @@ -203,67 +203,15 @@ test('hasMissingRequiredConfigurationFields returns false when field has default 'field_type' => 'string', 'name' => 'API Key', 'description' => 'Your API key', - 'default' => 'default-api-key', // Has default value - ], - ], + 'default' => 'default-api-key' // Has default value + ] + ] ]; $plugin = Plugin::factory()->create([ 'user_id' => $user->id, 'configuration_template' => $configurationTemplate, - 'configuration' => [], // Empty configuration, but field has default - ]); - - expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse(); -}); - -test('hasMissingRequiredConfigurationFields returns true when required xhrSelect field is missing', function () { - $user = User::factory()->create(); - - $configurationTemplate = [ - 'custom_fields' => [ - [ - 'keyname' => 'team', - 'field_type' => 'xhrSelect', - 'name' => 'Baseball Team', - 'description' => 'Select your team', - 'endpoint' => 'https://usetrmnl.com/custom_plugin_example_xhr_select.json', - // Not marked as optional, so it's required - ], - ], - ]; - - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'configuration_template' => $configurationTemplate, - 'configuration' => [], // Empty configuration - ]); - - expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue(); -}); - -test('hasMissingRequiredConfigurationFields returns false when required xhrSelect field is set', function () { - $user = User::factory()->create(); - - $configurationTemplate = [ - 'custom_fields' => [ - [ - 'keyname' => 'team', - 'field_type' => 'xhrSelect', - 'name' => 'Baseball Team', - 'description' => 'Select your team', - 'endpoint' => 'https://usetrmnl.com/custom_plugin_example_xhr_select.json', - // Not marked as optional, so it's required - ], - ], - ]; - - $plugin = Plugin::factory()->create([ - 'user_id' => $user->id, - 'configuration_template' => $configurationTemplate, - 'configuration' => [ - 'team' => '123', // Required field is set - ], + 'configuration' => [] // Empty configuration, but field has default ]); expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse(); diff --git a/tests/Unit/Liquid/Filters/DataTest.php b/tests/Unit/Liquid/Filters/DataTest.php index 8145088..ffb4088 100644 --- a/tests/Unit/Liquid/Filters/DataTest.php +++ b/tests/Unit/Liquid/Filters/DataTest.php @@ -53,187 +53,3 @@ test('json filter does not escape slashes', function () { expect($filter->json($data))->toBe('{"url":"https://example.com/path"}'); }); - -test('find_by filter finds object by key-value pair', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ]; - - $result = $filter->find_by($collection, 'name', 'Ryan'); - expect($result)->toBe(['name' => 'Ryan', 'age' => 35]); -}); - -test('find_by filter returns null when no match found', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ]; - - $result = $filter->find_by($collection, 'name', 'ronak'); - expect($result)->toBeNull(); -}); - -test('find_by filter returns fallback when no match found', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ]; - - $result = $filter->find_by($collection, 'name', 'ronak', 'Not Found'); - expect($result)->toBe('Not Found'); -}); - -test('find_by filter finds by age', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ]; - - $result = $filter->find_by($collection, 'age', 29); - expect($result)->toBe(['name' => 'Sara', 'age' => 29]); -}); - -test('find_by filter handles empty collection', function () { - $filter = new Data(); - $collection = []; - - $result = $filter->find_by($collection, 'name', 'Ryan'); - expect($result)->toBeNull(); -}); - -test('find_by filter handles collection with non-array items', function () { - $filter = new Data(); - $collection = [ - 'not an array', - ['name' => 'Ryan', 'age' => 35], - null, - ]; - - $result = $filter->find_by($collection, 'name', 'Ryan'); - expect($result)->toBe(['name' => 'Ryan', 'age' => 35]); -}); - -test('find_by filter handles items without the specified key', function () { - $filter = new Data(); - $collection = [ - ['age' => 35], - ['name' => 'Ryan', 'age' => 35], - ['title' => 'Developer'], - ]; - - $result = $filter->find_by($collection, 'name', 'Ryan'); - expect($result)->toBe(['name' => 'Ryan', 'age' => 35]); -}); - -test('group_by filter groups collection by age', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ]; - - $result = $filter->group_by($collection, 'age'); - - expect($result)->toBe([ - 35 => [['name' => 'Ryan', 'age' => 35]], - 29 => [ - ['name' => 'Sara', 'age' => 29], - ['name' => 'Jimbob', 'age' => 29], - ], - ]); -}); - -test('group_by filter groups collection by name', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Sara', 'age' => 29], - ['name' => 'Ryan', 'age' => 30], - ]; - - $result = $filter->group_by($collection, 'name'); - - expect($result)->toBe([ - 'Ryan' => [ - ['name' => 'Ryan', 'age' => 35], - ['name' => 'Ryan', 'age' => 30], - ], - 'Sara' => [['name' => 'Sara', 'age' => 29]], - ]); -}); - -test('group_by filter handles empty collection', function () { - $filter = new Data(); - $collection = []; - - $result = $filter->group_by($collection, 'age'); - expect($result)->toBe([]); -}); - -test('group_by filter handles collection with non-array items', function () { - $filter = new Data(); - $collection = [ - 'not an array', - ['name' => 'Ryan', 'age' => 35], - null, - ['name' => 'Sara', 'age' => 29], - ]; - - $result = $filter->group_by($collection, 'age'); - - expect($result)->toBe([ - 35 => [['name' => 'Ryan', 'age' => 35]], - 29 => [['name' => 'Sara', 'age' => 29]], - ]); -}); - -test('group_by filter handles items without the specified key', function () { - $filter = new Data(); - $collection = [ - ['age' => 35], - ['name' => 'Ryan', 'age' => 35], - ['title' => 'Developer'], - ['name' => 'Sara', 'age' => 29], - ]; - - $result = $filter->group_by($collection, 'age'); - - expect($result)->toBe([ - 35 => [ - ['age' => 35], - ['name' => 'Ryan', 'age' => 35], - ], - 29 => [['name' => 'Sara', 'age' => 29]], - ]); -}); - -test('group_by filter handles mixed data types as keys', function () { - $filter = new Data(); - $collection = [ - ['name' => 'Ryan', 'active' => true], - ['name' => 'Sara', 'active' => false], - ['name' => 'Jimbob', 'active' => true], - ['name' => 'Alice', 'active' => null], - ]; - - $result = $filter->group_by($collection, 'active'); - - expect($result)->toBe([ - 1 => [ // PHP converts true to 1 - ['name' => 'Ryan', 'active' => true], - ['name' => 'Jimbob', 'active' => true], - ], - 0 => [['name' => 'Sara', 'active' => false]], // PHP converts false to 0 - '' => [['name' => 'Alice', 'active' => null]], // PHP converts null keys to empty string - ]); -}); diff --git a/tests/Unit/Liquid/InlineTemplatesTest.php b/tests/Unit/Liquid/InlineTemplatesTest.php index bf68ddf..4535740 100644 --- a/tests/Unit/Liquid/InlineTemplatesTest.php +++ b/tests/Unit/Liquid/InlineTemplatesTest.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Tests\Unit\Liquid; use App\Liquid\FileSystems\InlineTemplatesFileSystem; -use App\Liquid\Filters\Data; use App\Liquid\Tags\TemplateTag; use Keepsuit\Liquid\Environment; use Keepsuit\Liquid\Exceptions\LiquidException; @@ -28,7 +27,6 @@ class InlineTemplatesTest extends TestCase ); $this->environment->tagRegistry->register(TemplateTag::class); $this->environment->tagRegistry->register(RenderTag::class); - $this->environment->filterRegistry->register(Data::class); } public function test_template_tag_registers_template(): void