mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 16:17:10 +00:00
feat(ui): scrollable resource tabs and railpack default
Add a shared resource-heading-tabs scroller with overflow chevrons, wire it into resource/server navbars, keep links menus outside overflow, and default new git apps to Railpack instead of Nixpacks.
This commit is contained in:
1 parent
9721fcdf11
commit
1bb54c0ca1
21 files changed
+452
-101
No files matched your search
@@ -47,7 +47,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
?? StandaloneDocker::factory()->create(['server_id' => $this->server->id, 'network' => 'coolify-test']);
|
||||
});
|
||||
|
||||
test('existing application buildpack selector lists nixpacks before railpack', function () {
|
||||
test('existing application buildpack selector lists railpack before nixpacks', function () {
|
||||
$application = Application::factory()->create([
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
@@ -62,12 +62,12 @@ test('existing application buildpack selector lists nixpacks before railpack', f
|
||||
Livewire::test(General::class, ['application' => $application])
|
||||
->assertSuccessful()
|
||||
->assertSeeInOrder([
|
||||
'<option value="railpack">Railpack</option>',
|
||||
'<option value="nixpacks">Nixpacks</option>',
|
||||
'<option value="railpack">Railpack (Beta)</option>',
|
||||
], false);
|
||||
});
|
||||
|
||||
test('existing application shows railpack beta label in build pack selector', function () {
|
||||
test('existing application shows railpack without beta label in build pack selector', function () {
|
||||
$application = Application::factory()->create([
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
@@ -81,5 +81,7 @@ test('existing application shows railpack beta label in build pack selector', fu
|
||||
|
||||
Livewire::test(General::class, ['application' => $application])
|
||||
->assertSuccessful()
|
||||
->assertSee('Railpack (Beta)');
|
||||
->assertSee('Railpack')
|
||||
->assertDontSee('Railpack (Beta)')
|
||||
->assertDontSee('Railpack (beta)');
|
||||
});
|
||||
@@ -20,30 +20,32 @@ beforeEach(function () {
|
||||
});
|
||||
|
||||
describe('new application buildpack defaults', function () {
|
||||
test('github app repository flow defaults to nixpacks', function () {
|
||||
test('github app repository flow defaults to railpack', function () {
|
||||
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
|
||||
->assertSet('build_pack', 'nixpacks');
|
||||
->assertSet('build_pack', 'railpack');
|
||||
});
|
||||
|
||||
test('deploy key repository flow defaults to nixpacks', function () {
|
||||
test('deploy key repository flow defaults to railpack', function () {
|
||||
Livewire::test(GithubPrivateRepositoryDeployKey::class, ['type' => 'private-deploy-key'])
|
||||
->assertSet('build_pack', 'nixpacks');
|
||||
->assertSet('build_pack', 'railpack');
|
||||
});
|
||||
|
||||
test('public repository flow defaults to nixpacks and lists railpack second', function () {
|
||||
test('public repository flow defaults to railpack and lists railpack first', function () {
|
||||
Livewire::test(PublicGitRepository::class, ['type' => 'public'])
|
||||
->assertSet('build_pack', 'nixpacks');
|
||||
->assertSet('build_pack', 'railpack');
|
||||
});
|
||||
|
||||
test('public repository flow keeps railpack available after branch lookup', function () {
|
||||
Livewire::test(PublicGitRepository::class, ['type' => 'public'])
|
||||
->set('branchFound', true)
|
||||
->assertSeeInOrder(['Nixpacks', 'Railpack (Beta)']);
|
||||
->assertSeeInOrder(['Railpack', 'Nixpacks']);
|
||||
});
|
||||
|
||||
test('deploy key repository flow shows railpack beta label in build pack selector without beta badge', function () {
|
||||
test('deploy key repository flow shows railpack without beta label', function () {
|
||||
Livewire::test(GithubPrivateRepositoryDeployKey::class, ['type' => 'private-deploy-key'])
|
||||
->set('current_step', 'repository')
|
||||
->assertSee('Railpack (Beta)');
|
||||
->assertSee('Railpack')
|
||||
->assertDontSee('Railpack (Beta)')
|
||||
->assertDontSee('Railpack (beta)');
|
||||
});
|
||||
});
|
||||
@@ -44,34 +44,95 @@ it('keeps Links dropdowns outside the scrollable tabs strip', function () {
|
||||
$application = file_get_contents(resource_path('views/livewire/project/application/heading.blade.php'));
|
||||
$service = file_get_contents(resource_path('views/livewire/project/service/heading.blade.php'));
|
||||
$css = file_get_contents(resource_path('css/app.css'));
|
||||
$tabsComponent = file_get_contents(resource_path('views/components/resource-heading-tabs.blade.php'));
|
||||
|
||||
// Links must not sit inside the overflow-x-auto tabs scroller (causes scrollbar on open).
|
||||
expect($application)
|
||||
->toContain('resource-heading-tabs')
|
||||
->toContain('<x-resource-heading-tabs')
|
||||
->toContain('resource-heading-menus')
|
||||
->toContain('<x-applications.links');
|
||||
|
||||
$tabsBlockEnd = strpos($application, 'resource-heading-menus');
|
||||
$overflowInTabs = substr(
|
||||
$application,
|
||||
strpos($application, 'resource-heading-tabs'),
|
||||
$tabsBlockEnd - strpos($application, 'resource-heading-tabs'),
|
||||
);
|
||||
// Mobile and desktop each have a tabs + menus pair; verify every pair.
|
||||
preg_match_all('/<x-resource-heading-tabs/', $application, $applicationTabsMatches, PREG_OFFSET_CAPTURE);
|
||||
preg_match_all('/resource-heading-menus/', $application, $applicationMenusMatches, PREG_OFFSET_CAPTURE);
|
||||
expect($applicationTabsMatches[0])->toHaveCount(2);
|
||||
expect($applicationMenusMatches[0])->toHaveCount(2);
|
||||
|
||||
expect($overflowInTabs)->not->toContain('<x-applications.links');
|
||||
expect(strpos($application, '<x-applications.links'))->toBeGreaterThan($tabsBlockEnd);
|
||||
foreach ($applicationTabsMatches[0] as $index => [$match, $tabsOffset]) {
|
||||
$menusOffset = $applicationMenusMatches[0][$index][1];
|
||||
$overflowInTabs = substr($application, $tabsOffset, $menusOffset - $tabsOffset);
|
||||
|
||||
$serviceTabsEnd = strpos($service, 'resource-heading-menus');
|
||||
$serviceOverflow = substr(
|
||||
$service,
|
||||
strpos($service, 'resource-heading-tabs'),
|
||||
$serviceTabsEnd - strpos($service, 'resource-heading-tabs'),
|
||||
);
|
||||
expect($overflowInTabs)->not->toContain('<x-applications.links');
|
||||
expect(strpos($application, '<x-applications.links', $tabsOffset))
|
||||
->toBeGreaterThan($menusOffset);
|
||||
}
|
||||
|
||||
expect($serviceOverflow)->not->toContain('<x-services.links');
|
||||
expect(strpos($service, '<x-services.links'))->toBeGreaterThan($serviceTabsEnd);
|
||||
preg_match_all('/<x-resource-heading-tabs/', $service, $serviceTabsMatches, PREG_OFFSET_CAPTURE);
|
||||
preg_match_all('/resource-heading-menus/', $service, $serviceMenusMatches, PREG_OFFSET_CAPTURE);
|
||||
expect($serviceTabsMatches[0])->toHaveCount(2);
|
||||
expect($serviceMenusMatches[0])->toHaveCount(2);
|
||||
|
||||
foreach ($serviceTabsMatches[0] as $index => [$match, $tabsOffset]) {
|
||||
$menusOffset = $serviceMenusMatches[0][$index][1];
|
||||
$overflowInTabs = substr($service, $tabsOffset, $menusOffset - $tabsOffset);
|
||||
|
||||
expect($overflowInTabs)->not->toContain('<x-services.links');
|
||||
expect(strpos($service, '<x-services.links', $tabsOffset))
|
||||
->toBeGreaterThan($menusOffset);
|
||||
}
|
||||
|
||||
expect($css)
|
||||
->toContain('.resource-heading-tabs::-webkit-scrollbar')
|
||||
->toContain('scrollbar-width: none');
|
||||
->toContain('scrollbar-width: none')
|
||||
->toContain('.resource-heading-tabs-control')
|
||||
->toContain('.resource-heading-tabs-control-icon');
|
||||
|
||||
// Kumo-style overflow chevrons live on the shared tabs component.
|
||||
expect($tabsComponent)
|
||||
->toContain('Scroll tabs left')
|
||||
->toContain('Scroll tabs right')
|
||||
->toContain('scrollByDir')
|
||||
->toContain('canStart')
|
||||
->toContain('canEnd')
|
||||
->toContain('resource-heading-tabs-control is-start')
|
||||
->toContain('resource-heading-tabs-control is-end')
|
||||
->toContain('scrollActiveIntoView')
|
||||
->toContain('findActiveTab')
|
||||
->toContain('livewire:navigated');
|
||||
});
|
||||
|
||||
it('shows application and service Links on mobile headings', function () {
|
||||
$application = file_get_contents(resource_path('views/livewire/project/application/heading.blade.php'));
|
||||
$service = file_get_contents(resource_path('views/livewire/project/service/heading.blade.php'));
|
||||
|
||||
$mobileApplicationSection = str($application)->between('class="w-full md:hidden"', 'class="hidden w-full items-center md:flex')->toString();
|
||||
$mobileServiceSection = str($service)->between('class="w-full md:hidden"', 'class="hidden w-full items-center md:flex')->toString();
|
||||
|
||||
expect($mobileApplicationSection)
|
||||
->toContain('<x-applications.links')
|
||||
->toContain('resource-heading-menus')
|
||||
->toContain('<x-resource-heading-tabs');
|
||||
|
||||
expect($mobileServiceSection)
|
||||
->toContain('<x-services.links')
|
||||
->toContain('resource-heading-menus')
|
||||
->toContain('<x-resource-heading-tabs');
|
||||
|
||||
// One mobile + one desktop instance.
|
||||
expect(substr_count($application, '<x-applications.links'))->toBe(2);
|
||||
expect(substr_count($service, '<x-services.links'))->toBe(2);
|
||||
});
|
||||
|
||||
it('uses overflow scroll arrows on resource heading navbars', function () {
|
||||
$files = [
|
||||
resource_path('views/livewire/project/application/heading.blade.php'),
|
||||
resource_path('views/livewire/project/service/heading.blade.php'),
|
||||
resource_path('views/livewire/project/database/heading.blade.php'),
|
||||
resource_path('views/livewire/server/navbar.blade.php'),
|
||||
resource_path('views/components/project/navbar.blade.php'),
|
||||
];
|
||||
|
||||
foreach ($files as $path) {
|
||||
expect(file_get_contents($path))->toContain('<x-resource-heading-tabs');
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user