user = User::factory()->create(['setup' => Setup::Role]); }); // Step 1 tests test('step1 requires authentication', function () { $response = $this->get(route('app.onboarding.role')); $response->assertRedirect(route('login')); }); test('step1 shows persona selection', function () { $response = $this->actingAs($this->user)->get(route('app.onboarding.role')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('onboarding/Role', false) ->has('personas') ); }); // Store Step 1 tests test('store step1 requires authentication', function () { $response = $this->post(route('app.onboarding.role.store'), [ 'persona' => Persona::Founder->value, ]); $response->assertRedirect(route('login')); }); test('store step1 saves persona and redirects to brand step', function () { $response = $this->actingAs($this->user)->post(route('app.onboarding.role.store'), [ 'persona' => Persona::Founder->value, ]); $response->assertRedirect(route('app.onboarding.brand')); $this->user->refresh(); expect($this->user->persona)->toBe(Persona::Founder); expect($this->user->setup)->toBe(Setup::Brand); }); test('store step1 validates persona is required', function () { $response = $this->actingAs($this->user)->post(route('app.onboarding.role.store'), [ 'persona' => '', ]); $response->assertSessionHasErrors('persona'); }); test('store step1 validates persona is valid enum', function () { $response = $this->actingAs($this->user)->post(route('app.onboarding.role.store'), [ 'persona' => 'invalid', ]); $response->assertSessionHasErrors('persona'); }); // Brand step tests test('brand step requires authentication', function () { $response = $this->get(route('app.onboarding.brand')); $response->assertRedirect(route('login')); }); test('brand step redirects users not at the brand step', function () { $this->user->update(['setup' => Setup::Role]); $response = $this->actingAs($this->user)->get(route('app.onboarding.brand')); $response->assertRedirect(route('app.onboarding.role')); }); test('brand step shows the form with workspace defaults', function () { $workspace = Workspace::factory()->create([ 'user_id' => $this->user->id, 'brand_tone' => 'casual', 'content_language' => 'pt-BR', ]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); $response = $this->actingAs($this->user)->get(route('app.onboarding.brand')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('onboarding/Brand', false) ->where('workspace.brand_tone', 'casual') ->where('workspace.content_language', 'pt-BR') ); }); test('store brand persists workspace settings and advances to connections', function () { $workspace = Workspace::factory()->create(['user_id' => $this->user->id]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); $response = $this->actingAs($this->user)->post(route('app.onboarding.brand.store'), [ 'brand_website' => 'https://example.com', 'brand_description' => 'We build social tools.', 'brand_tone' => 'friendly', 'brand_voice_notes' => 'Short and punchy.', 'content_language' => 'pt-BR', ]); $response->assertRedirect(route('app.onboarding.account')); $workspace->refresh(); expect($workspace->brand_website)->toBe('https://example.com'); expect($workspace->brand_tone)->toBe('friendly'); expect($workspace->content_language)->toBe('pt-BR'); $this->user->refresh(); expect($this->user->setup)->toBe(Setup::Connections); }); test('store brand validates tone is in allowed list', function () { $workspace = Workspace::factory()->create(['user_id' => $this->user->id]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); $response = $this->actingAs($this->user)->post(route('app.onboarding.brand.store'), [ 'brand_tone' => 'invalid-tone', 'content_language' => 'en', ]); $response->assertSessionHasErrors('brand_tone'); }); test('store brand validates content_language is supported', function () { $workspace = Workspace::factory()->create(['user_id' => $this->user->id]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); $response = $this->actingAs($this->user)->post(route('app.onboarding.brand.store'), [ 'brand_tone' => 'professional', 'content_language' => 'fr', ]); $response->assertSessionHasErrors('content_language'); }); test('autofill brand returns extracted fields from the site', function () { $workspace = Workspace::factory()->create(['user_id' => $this->user->id]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); Http::fake([ 'acme.com' => Http::response(<<<'HTML' Acme | Best in class HTML, 200), ]); $response = $this->actingAs($this->user)->postJson( route('app.onboarding.brand.autofill'), ['url' => 'https://acme.com'], ); $response->assertOk(); $response->assertJson([ 'name' => 'Acme', 'brand_description' => 'Tools for creators.', 'content_language' => 'pt-BR', ]); }); test('autofill brand returns 422 when site cannot be reached', function () { $workspace = Workspace::factory()->create(['user_id' => $this->user->id]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); $response = $this->actingAs($this->user)->postJson( route('app.onboarding.brand.autofill'), ['url' => 'http://127.0.0.1'], ); $response->assertUnprocessable(); $response->assertJsonStructure(['message']); }); test('skip brand advances setup without saving workspace changes', function () { $workspace = Workspace::factory()->create([ 'user_id' => $this->user->id, 'brand_tone' => 'casual', ]); $this->user->update([ 'current_workspace_id' => $workspace->id, 'setup' => Setup::Brand, ]); $response = $this->actingAs($this->user)->post(route('app.onboarding.brand.skip')); $response->assertRedirect(route('app.onboarding.account')); $workspace->refresh(); expect($workspace->brand_tone)->toBe('casual'); $this->user->refresh(); expect($this->user->setup)->toBe(Setup::Connections); }); // Step 2 tests test('step2 requires authentication', function () { $response = $this->get(route('app.onboarding.account')); $response->assertRedirect(route('login')); }); test('step2 shows social accounts connection', function () { $this->user->update(['setup' => Setup::Connections]); $response = $this->actingAs($this->user)->get(route('app.onboarding.account')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('onboarding/Account', false) ->has('platforms') ->has('hasWorkspace') ); }); test('step2 shows connected accounts for workspace', function () { $this->user->update(['setup' => Setup::Connections]); $workspace = Workspace::factory()->create(['user_id' => $this->user->id]); $this->user->update(['current_workspace_id' => $workspace->id]); SocialAccount::factory()->create([ 'workspace_id' => $workspace->id, 'platform' => Platform::LinkedIn, ]); $response = $this->actingAs($this->user)->get(route('app.onboarding.account')); $response->assertOk(); $response->assertInertia(fn ($page) => $page ->where('hasWorkspace', true) ); }); // Store Step 2 tests test('store step2 requires authentication', function () { $response = $this->post(route('app.onboarding.account.store')); $response->assertRedirect(route('login')); }); test('store step2 completes setup in self-hosted mode', function () { config(['trypost.self_hosted' => true]); $this->user->update(['setup' => Setup::Connections]); $response = $this->actingAs($this->user)->post(route('app.onboarding.account.store')); $response->assertRedirect(route('app.calendar')); $this->user->refresh(); expect($this->user->setup)->toBe(Setup::Completed); }); // Complete tests test('complete requires authentication', function () { $response = $this->get(route('app.onboarding.account')); $response->assertRedirect(route('login')); }); test('completed user is redirected to calendar', function () { $this->user->update(['setup' => Setup::Completed]); $response = $this->actingAs($this->user)->get(route('app.onboarding.account')); $response->assertRedirect(route('app.calendar')); }); // Step enforcement tests test('step1 redirects to connect when user already completed role step', function () { $this->user->update(['setup' => Setup::Connections]); $response = $this->actingAs($this->user)->get(route('app.onboarding.role')); $response->assertRedirect(route('app.onboarding.account')); }); test('step1 redirects to calendar when setup is completed', function () { $this->user->update(['setup' => Setup::Completed]); $response = $this->actingAs($this->user)->get(route('app.onboarding.role')); $response->assertRedirect(route('app.calendar')); }); test('step2 redirects to role when user has not completed role step', function () { $this->user->update(['setup' => Setup::Role]); $response = $this->actingAs($this->user)->get(route('app.onboarding.account')); $response->assertRedirect(route('app.onboarding.role')); }); test('step2 redirects to calendar when setup is completed', function () { $this->user->update(['setup' => Setup::Completed]); $response = $this->actingAs($this->user)->get(route('app.onboarding.account')); $response->assertRedirect(route('app.calendar')); }); test('user on role step can access role page', function () { $this->user->update(['setup' => Setup::Role]); $response = $this->actingAs($this->user)->get(route('app.onboarding.role')); $response->assertOk(); }); test('user on connections step can access connect page', function () { $this->user->update(['setup' => Setup::Connections]); $response = $this->actingAs($this->user)->get(route('app.onboarding.account')); $response->assertOk(); });