198 lines
9.3 KiB
PHP
198 lines
9.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\FindingResource;
|
|
use App\Filament\Resources\InventoryItemResource;
|
|
use App\Models\Finding;
|
|
use App\Models\InventoryItem;
|
|
use App\Models\OperationRun;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
pest()->browser()->timeout(20_000);
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
function operationActivityFeedbackSmokeLoginUrl(User $user, Tenant $tenant, string $redirect = ''): string
|
|
{
|
|
return route('admin.local.smoke-login', array_filter([
|
|
'email' => $user->email,
|
|
'tenant' => $tenant->external_id,
|
|
'workspace' => $tenant->workspace->slug,
|
|
'redirect' => $redirect,
|
|
], static fn (?string $value): bool => filled($value)));
|
|
}
|
|
|
|
it('keeps findings row actions reachable while the activity hint collapses and reopens within the browser session', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
InventoryItem::factory()->count(3)->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'display_name' => 'Browser Inventory Item',
|
|
'policy_type' => 'deviceConfiguration',
|
|
'platform' => 'windows',
|
|
'last_seen_at' => now()->subMinute(),
|
|
]);
|
|
|
|
Finding::factory()->count(5)->for($tenant)->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'status' => Finding::STATUS_NEW,
|
|
]);
|
|
|
|
OperationRun::factory()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'user_id' => (int) $user->getKey(),
|
|
'type' => 'inventory_sync',
|
|
'status' => 'running',
|
|
'outcome' => 'pending',
|
|
'started_at' => now()->subMinutes(2),
|
|
]);
|
|
|
|
visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant))
|
|
->waitForText('Dashboard')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$inventoryPage = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant))
|
|
->resize(1440, 1200)
|
|
->assertScript('window.innerWidth >= 1400', true)
|
|
->waitForText('Inventory Items')
|
|
->waitForText('Browser Inventory Item')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$shellGeometry = $inventoryPage->script(<<<'JS'
|
|
(() => {
|
|
const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]');
|
|
const topbar = document.querySelector('.fi-topbar');
|
|
const table = document.querySelector('.fi-ta');
|
|
const tableContainer = table?.closest('.overflow-x-auto') ?? table?.parentElement ?? null;
|
|
const contentShell = document.querySelector('.fi-page') ?? document.querySelector('.fi-main') ?? tableContainer?.parentElement ?? null;
|
|
const main = document.querySelector('.fi-main');
|
|
const header = banner?.querySelector('.tp-ops-activity-header') ?? null;
|
|
const actionGroup = document.querySelector('[data-testid="ops-ux-activity-feedback-actions"]');
|
|
const layout = banner?.querySelector('.tp-ops-activity-layout') ?? null;
|
|
const headerCopy = banner?.querySelector('.tp-ops-activity-header-copy') ?? null;
|
|
const title = banner?.querySelector('[data-testid="ops-ux-activity-feedback-title"]') ?? null;
|
|
const middleColumn = banner?.querySelector('.tp-ops-activity-summary') ?? null;
|
|
const track = banner?.querySelector('[data-testid="ops-ux-activity-feedback-track"]') ?? null;
|
|
|
|
if (!banner || !topbar || !tableContainer || !contentShell || !actionGroup || !layout || !header || !headerCopy || !title || !middleColumn || !track) {
|
|
return null;
|
|
}
|
|
|
|
const bannerRect = banner.getBoundingClientRect();
|
|
const topbarRect = topbar.getBoundingClientRect();
|
|
const tableRect = tableContainer.getBoundingClientRect();
|
|
const contentShellRect = contentShell.getBoundingClientRect();
|
|
const headerRect = header.getBoundingClientRect();
|
|
const actionGroupRect = actionGroup.getBoundingClientRect();
|
|
const headerCopyRect = headerCopy.getBoundingClientRect();
|
|
const titleRect = title.getBoundingClientRect();
|
|
const middleRect = middleColumn.getBoundingClientRect();
|
|
const trackRect = track.getBoundingClientRect();
|
|
const layoutStyle = window.getComputedStyle(layout);
|
|
const headerStyle = window.getComputedStyle(header);
|
|
const titleStyle = window.getComputedStyle(title);
|
|
const titleLineHeight = Number.parseFloat(titleStyle.lineHeight || '0');
|
|
|
|
return {
|
|
viewportWidth: window.innerWidth,
|
|
itemCount: banner.querySelectorAll('[data-testid="ops-ux-activity-feedback-item"]').length,
|
|
stylesheetCount: document.styleSheets.length,
|
|
hasThemeStylesheet: Array.from(document.styleSheets).some((sheet) => (sheet.href || '').includes('/build/assets/theme-')),
|
|
mainClassName: main?.className ?? '',
|
|
mainWidth: main?.getBoundingClientRect().width ?? 0,
|
|
layoutClassName: layout.className,
|
|
layoutDisplay: layoutStyle.display,
|
|
layoutFlexDirection: layoutStyle.flexDirection,
|
|
headerFlexDirection: headerStyle.flexDirection,
|
|
bannerTop: bannerRect.top,
|
|
topbarBottom: topbarRect.bottom,
|
|
bannerTopGap: bannerRect.top - topbarRect.bottom,
|
|
bannerHeight: bannerRect.height,
|
|
bannerWidth: bannerRect.width,
|
|
tableWidth: tableRect.width,
|
|
contentWidth: contentShellRect.width,
|
|
leftDelta: Math.abs(bannerRect.left - contentShellRect.left),
|
|
rightDelta: Math.abs(bannerRect.right - contentShellRect.right),
|
|
actionGroupRightDelta: Math.abs(bannerRect.right - actionGroupRect.right),
|
|
summaryTopDelta: middleRect.top - headerRect.bottom,
|
|
trackSpanRatio: middleRect.width > 0 ? trackRect.width / middleRect.width : 0,
|
|
titleOverflows: title.scrollWidth > (title.clientWidth + 1),
|
|
titleLineCount: titleLineHeight > 0 ? titleRect.height / titleLineHeight : 0,
|
|
headerCopyWidth: headerCopyRect.width,
|
|
middleHeight: middleRect.height,
|
|
actionHeight: actionGroupRect.height,
|
|
};
|
|
})()
|
|
JS);
|
|
|
|
expect($shellGeometry)->not->toBeNull()
|
|
->and($shellGeometry['viewportWidth'] ?? 0)->toBeGreaterThanOrEqual(1400)
|
|
->and($shellGeometry['itemCount'] ?? 0)->toBe(1)
|
|
->and($shellGeometry['stylesheetCount'] ?? 0)->toBeGreaterThan(0)
|
|
->and($shellGeometry['hasThemeStylesheet'] ?? false)->toBeTrue()
|
|
->and($shellGeometry['mainClassName'] ?? '')->toContain('fi-width-full')
|
|
->and($shellGeometry['mainWidth'] ?? 0)->toBeGreaterThanOrEqual(900)
|
|
->and($shellGeometry['layoutClassName'] ?? '')->toContain('tp-ops-activity-layout')
|
|
->and($shellGeometry['layoutClassName'] ?? '')->toContain('flex')
|
|
->and($shellGeometry['layoutDisplay'] ?? '')->toBe('flex')
|
|
->and($shellGeometry['layoutFlexDirection'] ?? '')->toBe('column')
|
|
->and($shellGeometry['headerFlexDirection'] ?? '')->toBe('row')
|
|
->and($shellGeometry['bannerTop'] ?? null)->toBeGreaterThanOrEqual($shellGeometry['topbarBottom'] ?? PHP_INT_MAX)
|
|
->and($shellGeometry['bannerTopGap'] ?? PHP_INT_MIN)->toBeGreaterThanOrEqual(12)
|
|
->and($shellGeometry['bannerWidth'] ?? 0)->toBeGreaterThan(0)
|
|
->and($shellGeometry['tableWidth'] ?? 0)->toBeGreaterThan(0)
|
|
->and($shellGeometry['contentWidth'] ?? 0)->toBeGreaterThan(0)
|
|
->and($shellGeometry['summaryTopDelta'] ?? PHP_INT_MIN)->toBeGreaterThanOrEqual(8)
|
|
->and($shellGeometry['trackSpanRatio'] ?? 0)->toBeGreaterThanOrEqual(0.72)
|
|
->and($shellGeometry['titleOverflows'] ?? true)->toBeFalse()
|
|
->and($shellGeometry['titleLineCount'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(1.2)
|
|
->and($shellGeometry['headerCopyWidth'] ?? 0)->toBeGreaterThanOrEqual(240)
|
|
->and($shellGeometry['leftDelta'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(32)
|
|
->and($shellGeometry['rightDelta'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(32)
|
|
->and($shellGeometry['actionGroupRightDelta'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(32)
|
|
->and($shellGeometry['bannerHeight'] ?? 0)->toBeGreaterThanOrEqual(96)
|
|
->and($shellGeometry['bannerHeight'] ?? PHP_INT_MAX)->toBeLessThanOrEqual(145);
|
|
|
|
$inventoryPage
|
|
->assertScript('document.documentElement.scrollWidth <= window.innerWidth', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page = visit(FindingResource::getUrl('index', panel: 'tenant', tenant: $tenant))
|
|
->resize(1440, 1200);
|
|
|
|
$page
|
|
->waitForText('Triage all matching')
|
|
->waitForText('View operation')
|
|
->waitForText('Show all operations')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->click('tbody tr.fi-ta-row:last-of-type [aria-label="More"]')
|
|
->waitForText('Triage')
|
|
->assertSee('Triage')
|
|
->click('[data-testid="ops-ux-activity-feedback-toggle"]')
|
|
->waitForText('Show activity')
|
|
->refresh()
|
|
->waitForText('Show activity');
|
|
|
|
$page->script(<<<'JS'
|
|
window.dispatchEvent(new CustomEvent('ops-ux:run-enqueued', {
|
|
detail: {
|
|
tenantId: Number(document.querySelector('[data-testid="ops-ux-activity-feedback-root"]')?.dataset.tenantId || 0),
|
|
},
|
|
}));
|
|
JS);
|
|
|
|
$page
|
|
->waitForText('View operation')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->assertDontSee('Show activity');
|
|
}); |