2026-07-17 23:00:13 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Enums\UserWorkspace\Role;
|
|
|
|
|
use App\Models\Post;
|
|
|
|
|
use App\Models\PostPlatform;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Models\Workspace;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Guards against horizontal overflow on mobile across the layout variants
|
Remove the automations module (#333)
* Remove the automations module
Drops the visual workflow builder end to end: actions, node runners,
commands, jobs, models, observers, policy, resources, requests, routes,
broadcast channel, scheduler entries, Horizon supervisor, Vue pages and
components, canvas undo/redo history, CodeEditor, translations, factories
and tests.
A new migration rewrites posts.created_via = 'automation' to 'web' and
drops the five automation tables. The CreatedVia::Automation enum case is
removed. The 2026-08-21 social-account identity migration now skips its
automation node repointing when the automations table no longer exists,
so it stays re-runnable after the drop.
Orphaned dependencies removed: simplepie/simplepie, @vue-flow/*,
codemirror and @codemirror/*.
* Drop the orphaned common.beta translation key
* Remove automation leftovers: ResolvableUrl rule, feed fixtures, useShortcut, nav badge
* Drop the unused chart wrapper and @unovis packages
* Address review: keep the shipped migration untouched, drop the dead previewOnly chain
- Restore 2026_08_21 migration to exactly what production ran; the
rehearsal test now recreates the automations table it expects instead.
- Mark the drop migration's down() irreversible like its siblings.
- Simplify DropAutomationTablesMigrationTest to the sibling shape.
- Remove previewOnly / aiGenerateVariants: the only caller that set the
prop was the deleted automation Generate node.
- Run pint over lang/*/common.php after the beta key removal.
* Exercise the drop migration against the real automation tables and their FKs
* Drop DuplicateIdentityRehearsalTest: it re-ran a frozen migration that reads the removed automations table
2026-09-05 14:03:23 +00:00
|
|
|
* (AuthLayout, default AppLayout, full-width editor).
|
2026-07-17 23:00:13 +00:00
|
|
|
* Reproduces the AuthSplitLayout long-name overflow that a fixed grid column
|
|
|
|
|
* caused. Returns the widest offending element on failure for quick triage.
|
|
|
|
|
*/
|
|
|
|
|
function assertNoHorizontalOverflow(mixed $page, string $label): void
|
|
|
|
|
{
|
|
|
|
|
$result = $page->script(<<<'JS'
|
|
|
|
|
(async () => {
|
|
|
|
|
for (let i = 0; i < 80; i++) {
|
|
|
|
|
if (document.querySelector('h1, h2, main, [data-sidebar="trigger"]')) break;
|
|
|
|
|
await new Promise((r) => setTimeout(r, 50));
|
|
|
|
|
}
|
|
|
|
|
await new Promise((r) => setTimeout(r, 400));
|
|
|
|
|
const vw = window.innerWidth;
|
|
|
|
|
let worst = null;
|
|
|
|
|
document.querySelectorAll('*').forEach((el) => {
|
|
|
|
|
const r = el.getBoundingClientRect();
|
|
|
|
|
if (r.right > vw + 2 && (!worst || r.right > worst.right)) {
|
|
|
|
|
worst = { right: Math.round(r.right), tag: el.tagName.toLowerCase(), cls: (el.getAttribute('class') || '').slice(0, 80) };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return { sw: document.documentElement.scrollWidth, iw: vw, worst };
|
|
|
|
|
})()
|
|
|
|
|
JS);
|
|
|
|
|
|
|
|
|
|
$sw = (int) ($result['sw'] ?? 0);
|
|
|
|
|
$iw = (int) ($result['iw'] ?? 375);
|
|
|
|
|
$worst = $result['worst'] ?? null;
|
|
|
|
|
$detail = $worst ? " widest: <{$worst['tag']} class=\"{$worst['cls']}\"> right={$worst['right']}" : '';
|
|
|
|
|
|
|
|
|
|
expect($sw - $iw)->toBeLessThanOrEqual(2, "{$label} overflows: scrollWidth {$sw} > innerWidth {$iw}.{$detail}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('key pages do not overflow horizontally on a phone', function () {
|
|
|
|
|
$user = User::factory()->create();
|
|
|
|
|
$workspace = Workspace::factory()->create([
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
// A long name previously stretched the AuthSplitLayout grid column past the viewport.
|
|
|
|
|
'name' => 'A Really Very Extremely Long Workspace Name That Should Truncate Instead Of Overflowing',
|
|
|
|
|
]);
|
|
|
|
|
$workspace->members()->attach($user->id, ['role' => Role::Member->value]);
|
|
|
|
|
$user->update(['current_workspace_id' => $workspace->id]);
|
|
|
|
|
|
|
|
|
|
$post = Post::factory()->create([
|
|
|
|
|
'workspace_id' => $workspace->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'content' => 'Launching our new mobile editor today!',
|
|
|
|
|
]);
|
|
|
|
|
PostPlatform::factory()->count(2)->create(['post_id' => $post->id]);
|
|
|
|
|
|
|
|
|
|
$this->actingAs($user);
|
|
|
|
|
|
|
|
|
|
$pages = [
|
|
|
|
|
'workspaces (AuthLayout)' => route('app.workspaces.index'),
|
|
|
|
|
'posts index (default)' => route('app.posts.index'),
|
|
|
|
|
'post editor (full-width)' => route('app.posts.edit', $post),
|
|
|
|
|
'calendar (full-width)' => route('app.calendar'),
|
|
|
|
|
'settings (tabs)' => route('app.api-keys.index'),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($pages as $label => $url) {
|
|
|
|
|
assertNoHorizontalOverflow(visit($url)->resize(375, 812), $label);
|
|
|
|
|
}
|
|
|
|
|
});
|