mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-27 17:55:59 -04:00
Merge remote-tracking branch 'origin/next' into api-sensitive-data-scrubber
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
it('shows an empty state when there are no projects', function () {
|
||||
$this->view('livewire.project.index', [
|
||||
'projects' => collect(),
|
||||
])
|
||||
->assertSee('No projects found.')
|
||||
->assertSee('onboarding');
|
||||
});
|
||||
|
||||
it('does not show the empty state when projects exist', function () {
|
||||
$project = new class
|
||||
{
|
||||
public string $name = 'Test Project';
|
||||
|
||||
public string $description = 'A project description';
|
||||
|
||||
public string $uuid = 'test-project-uuid';
|
||||
|
||||
public $environments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->environments = collect();
|
||||
}
|
||||
|
||||
public function navigateTo(): string
|
||||
{
|
||||
return '#';
|
||||
}
|
||||
};
|
||||
|
||||
$this->view('livewire.project.index', [
|
||||
'projects' => collect([$project]),
|
||||
])
|
||||
->assertSee('Test Project')
|
||||
->assertDontSee('No projects found.');
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use App\Console\Commands\Generate\Services;
|
||||
use Illuminate\Console\OutputStyle;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
|
||||
it('adds the last git commit timestamp to generated service template payloads', function () {
|
||||
$command = new Services;
|
||||
$command->setOutput(new OutputStyle(new ArrayInput([]), new NullOutput));
|
||||
$method = new ReflectionMethod($command, 'processFile');
|
||||
|
||||
$payload = $method->invoke($command, 'activepieces.yaml');
|
||||
|
||||
$expectedTimestamp = Process::run([
|
||||
'git',
|
||||
'log',
|
||||
'-1',
|
||||
'--format=%cI',
|
||||
'--',
|
||||
'templates/compose/activepieces.yaml',
|
||||
])->throw()->output();
|
||||
|
||||
expect($payload)
|
||||
->toHaveKey('template_last_updated_at')
|
||||
->and($payload['template_last_updated_at'])
|
||||
->toBe(trim($expectedTimestamp));
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
use App\Livewire\Project\New\Select;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ViewErrorBag;
|
||||
|
||||
@@ -22,38 +23,56 @@ it('returns the service templates bundle last updated timestamp', function () {
|
||||
->toBe(CarbonImmutable::createFromTimestamp(filemtime($templatePath))->timezone(config('app.timezone'))->format('M j, Y H:i'));
|
||||
});
|
||||
|
||||
it('returns each service template last updated timestamp', function () {
|
||||
it('returns each service template last updated timestamp from the generated bundle', function () {
|
||||
$component = new Select;
|
||||
$templatePath = base_path('templates/compose/activepieces.yaml');
|
||||
$templates = json_decode(file_get_contents(base_path('templates/'.config('constants.services.file_name'))), true);
|
||||
$templateTimestamp = $templates['activepieces']['template_last_updated_at'];
|
||||
|
||||
$resources = $component->loadServices();
|
||||
|
||||
expect($resources['services']['activepieces'])
|
||||
->toHaveKey('templateLastUpdated')
|
||||
->and($resources['services']['activepieces']['templateLastUpdated'])
|
||||
->toBe(CarbonImmutable::createFromTimestamp(filemtime($templatePath))->timezone(config('app.timezone'))->format('M j, Y H:i'));
|
||||
->toBe(CarbonImmutable::parse($templateTimestamp)->timezone(config('app.timezone'))->format('M j, Y H:i'));
|
||||
});
|
||||
|
||||
it('uses a service template timestamp cache keyed by bundle mtime', function () {
|
||||
$bundleMtime = filemtime(base_path('templates/'.config('constants.services.file_name')));
|
||||
Cache::put("service-template-last-updated-map:{$bundleMtime}", [
|
||||
'activepieces' => 'Cached timestamp',
|
||||
], now()->addDay());
|
||||
it('prefers embedded service template git timestamps from the templates bundle', function () {
|
||||
File::shouldReceive('get')
|
||||
->with(base_path('templates/'.config('constants.services.file_name')))
|
||||
->andReturn(json_encode([
|
||||
'activepieces' => [
|
||||
'documentation' => 'https://coolify.io/docs',
|
||||
'slogan' => 'Open source no-code business automation.',
|
||||
'compose' => '',
|
||||
'tags' => null,
|
||||
'category' => 'automation',
|
||||
'logo' => 'images/default.webp',
|
||||
'minversion' => '0.0.0',
|
||||
'template_last_updated_at' => '2026-05-31T12:34:56+00:00',
|
||||
],
|
||||
]));
|
||||
|
||||
$resources = (new Select)->loadServices();
|
||||
|
||||
expect($resources['services']['activepieces']['templateLastUpdated'])->toBe('Cached timestamp');
|
||||
expect($resources['services']['activepieces']['templateLastUpdated'])->toBe('May 31, 2026 12:34');
|
||||
});
|
||||
|
||||
it('does not use stale service template timestamp cache entries from another bundle mtime', function () {
|
||||
$bundleMtime = filemtime(base_path('templates/'.config('constants.services.file_name')));
|
||||
Cache::put('service-template-last-updated-map:'.($bundleMtime - 1), [
|
||||
'activepieces' => 'Stale cached timestamp',
|
||||
], now()->addDay());
|
||||
it('caches parsed local service templates by bundle mtime', function () {
|
||||
Cache::flush();
|
||||
|
||||
$resources = (new Select)->loadServices();
|
||||
$path = base_path('templates/'.config('constants.services.file_name'));
|
||||
$json = file_get_contents($path);
|
||||
|
||||
expect($resources['services']['activepieces']['templateLastUpdated'])->not->toBe('Stale cached timestamp');
|
||||
File::partialMock()
|
||||
->shouldReceive('get')
|
||||
->once()
|
||||
->with($path)
|
||||
->andReturn($json);
|
||||
|
||||
$first = get_service_templates();
|
||||
$second = get_service_templates();
|
||||
|
||||
expect($first->keys()->all())->toBe($second->keys()->all());
|
||||
});
|
||||
|
||||
it('renders the service templates last updated hint placeholder', function () {
|
||||
|
||||
@@ -25,12 +25,12 @@ it('ensures GlobalSearch clears search query when starting resource creation', f
|
||||
->toContain('$this->searchQuery = \'\'');
|
||||
});
|
||||
|
||||
it('ensures GlobalSearch uses Livewire redirect method', function () {
|
||||
it('ensures GlobalSearch uses redirect route helper', function () {
|
||||
$globalSearchFile = file_get_contents(__DIR__.'/../../app/Livewire/GlobalSearch.php');
|
||||
|
||||
// Check that completeResourceCreation uses $this->redirect()
|
||||
// Check that completeResourceCreation uses the shared redirect route helper
|
||||
expect($globalSearchFile)
|
||||
->toContain('$this->redirect(route(\'project.resource.create\'');
|
||||
->toContain('redirectRoute($this, \'project.resource.create\'');
|
||||
});
|
||||
|
||||
it('ensures docker-image item has quickcommand with new image', function () {
|
||||
@@ -42,3 +42,103 @@ it('ensures docker-image item has quickcommand with new image', function () {
|
||||
->toContain("'quickcommand' => '(type: new image)'")
|
||||
->toContain("'type' => 'docker-image'");
|
||||
});
|
||||
|
||||
it('uses neutral hover styling for GlobalSearch quick action rows', function () {
|
||||
$bladeFile = file_get_contents(__DIR__.'/../../resources/views/livewire/global-search.blade.php');
|
||||
|
||||
preg_match_all('/<button[^>]+(?:wire:click="navigateToResource|@click="\\$wire\\.navigateToResource)[^>]+>/s', $bladeFile, $quickActionButtons);
|
||||
|
||||
expect($quickActionButtons[0])->not->toBeEmpty();
|
||||
|
||||
foreach ($quickActionButtons[0] as $quickActionButton) {
|
||||
expect($quickActionButton)
|
||||
->toContain('hover:bg-neutral-100 dark:hover:bg-coolgray-200')
|
||||
->toContain('focus:bg-neutral-100 dark:focus:bg-coolgray-200')
|
||||
->toContain('focus-visible:ring-coollabs dark:focus-visible:ring-warning')
|
||||
->not->toContain('hover:bg-warning-50')
|
||||
->not->toContain('hover:border-warning-500');
|
||||
}
|
||||
});
|
||||
|
||||
it('uses product logos for GlobalSearch database quick actions', function () {
|
||||
$globalSearchFile = file_get_contents(__DIR__.'/../../app/Livewire/GlobalSearch.php');
|
||||
$bladeFile = file_get_contents(__DIR__.'/../../resources/views/livewire/global-search.blade.php');
|
||||
|
||||
expect($bladeFile)
|
||||
->toContain('asset($item[\'logo\'])')
|
||||
->toContain(":src=\"'/' + item.logo\"");
|
||||
|
||||
foreach ([
|
||||
'postgresql' => 'svgs/postgresql.svg',
|
||||
'mysql' => 'svgs/mysql.svg',
|
||||
'mariadb' => 'svgs/mariadb.svg',
|
||||
'redis' => 'svgs/redis.svg',
|
||||
'keydb' => 'svgs/keydb.svg',
|
||||
'dragonfly' => 'svgs/dragonfly.svg',
|
||||
'mongodb' => 'svgs/mongodb.svg',
|
||||
'clickhouse' => 'svgs/clickhouse-icon.svg',
|
||||
] as $type => $logo) {
|
||||
expect($globalSearchFile)
|
||||
->toContain("'type' => '{$type}'")
|
||||
->toContain("'logo' => '{$logo}'");
|
||||
|
||||
expect(file_exists(__DIR__.'/../../public/'.$logo))->toBeTrue();
|
||||
}
|
||||
});
|
||||
|
||||
it('uses neutral hover styling for GlobalSearch existing resource rows', function () {
|
||||
$bladeFile = file_get_contents(__DIR__.'/../../resources/views/livewire/global-search.blade.php');
|
||||
|
||||
preg_match_all('/<a[^>]+(?:href="{{ \\$result\\[\'link\'\\]|:href="result\\.link)[^>]+>/s', $bladeFile, $existingResourceLinks);
|
||||
|
||||
expect($existingResourceLinks[0])->not->toBeEmpty();
|
||||
|
||||
foreach ($existingResourceLinks[0] as $existingResourceLink) {
|
||||
expect($existingResourceLink)
|
||||
->toContain('hover:bg-neutral-100 dark:hover:bg-coolgray-200')
|
||||
->toContain('focus:bg-neutral-100 dark:focus:bg-coolgray-200')
|
||||
->toContain('focus-visible:ring-coollabs dark:focus-visible:ring-warning')
|
||||
->not->toContain('hover:bg-neutral-50')
|
||||
->not->toContain('focus:bg-warning')
|
||||
->not->toContain('hover:border-coollabs')
|
||||
->not->toContain('focus:border-warning');
|
||||
}
|
||||
});
|
||||
|
||||
it('uses visible cropped SVG marks for wide database logos', function () {
|
||||
$keydbLogo = file_get_contents(__DIR__.'/../../public/svgs/keydb.svg');
|
||||
$dragonflyLogo = file_get_contents(__DIR__.'/../../public/svgs/dragonfly.svg');
|
||||
$clickhouseLogo = file_get_contents(__DIR__.'/../../public/svgs/clickhouse-icon.svg');
|
||||
|
||||
expect($keydbLogo)
|
||||
->toContain('viewBox="0 0 160 182"')
|
||||
->toContain('svg{color:#d4d4d4}')
|
||||
->not->toContain('prefers-color-scheme');
|
||||
|
||||
expect($dragonflyLogo)
|
||||
->toContain('viewBox="0 0 88 88"')
|
||||
->toContain('svg{color:#d4d4d4}')
|
||||
->not->toContain('prefers-color-scheme');
|
||||
|
||||
expect($clickhouseLogo)
|
||||
->toContain('viewBox="1.70837 1.875 22.25025 22.2493"')
|
||||
->toContain('svg{color:#d4d4d4}')
|
||||
->toContain('x="20.7087"')
|
||||
->toContain('width="2.24992"')
|
||||
->not->toContain('viewBox="0 0 100 43"')
|
||||
->not->toContain('width="215"')
|
||||
->not->toContain('height="90"');
|
||||
});
|
||||
|
||||
it('uses cropped image assets instead of inline wide logos for GlobalSearch database icons', function () {
|
||||
$globalSearchFile = file_get_contents(__DIR__.'/../../app/Livewire/GlobalSearch.php');
|
||||
$bladeFile = file_get_contents(__DIR__.'/../../resources/views/livewire/global-search.blade.php');
|
||||
|
||||
expect($bladeFile)
|
||||
->toContain('class="w-8 h-8 object-contain"')
|
||||
->toContain('class="w-8 h-8 object-contain"')
|
||||
->not->toContain('$item[\'logo_html\']')
|
||||
->not->toContain('x-html="item.logo_html"');
|
||||
|
||||
expect($globalSearchFile)->not->toContain("'logo_html' =>");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user