Allow heading tags (h1-h6) in ContentSanitizer so news editor headings render
This commit is contained in:
@@ -10,6 +10,7 @@ use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyEvent;
|
||||
use App\Models\AcademyLesson;
|
||||
use App\Models\AcademyLike;
|
||||
use App\Models\AcademyPromptPack;
|
||||
use App\Models\AcademyPromptTemplate;
|
||||
use App\Models\AcademySave;
|
||||
use App\Models\AcademySearchLog;
|
||||
@@ -23,6 +24,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class AcademyAnalyticsTest extends TestCase
|
||||
@@ -406,6 +408,80 @@ final class AcademyAnalyticsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_prompt_library_index_exposes_dedicated_page_analytics_content_type(): void
|
||||
{
|
||||
$this->createPrompt('library-analytics-prompt');
|
||||
|
||||
$this->get(route('academy.prompts.index'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('analytics.contentType', AcademyAnalyticsContentType::PROMPT_LIBRARY)
|
||||
->where('analytics.contentId', null)
|
||||
->where('analytics.pageName', 'academy_prompts_index')
|
||||
);
|
||||
}
|
||||
|
||||
public function test_popular_prompts_page_exposes_dedicated_page_analytics_content_type(): void
|
||||
{
|
||||
$prompt = $this->createPrompt('popular-page-prompt');
|
||||
$this->createMetric(AcademyAnalyticsContentType::PROMPT, $prompt->id, [
|
||||
'views' => 18,
|
||||
'prompt_copies' => 4,
|
||||
'popularity_score' => 51.2,
|
||||
]);
|
||||
|
||||
$this->get(route('academy.prompts.popular'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('analytics.contentType', AcademyAnalyticsContentType::PROMPT_POPULAR)
|
||||
->where('analytics.contentId', null)
|
||||
->where('analytics.pageName', 'academy_prompts_popular')
|
||||
);
|
||||
}
|
||||
|
||||
public function test_popular_prompts_page_exposes_selected_period_in_analytics_metadata(): void
|
||||
{
|
||||
$prompt = $this->createPrompt('popular-period-analytics-prompt');
|
||||
$this->createMetric(AcademyAnalyticsContentType::PROMPT, $prompt->id, [
|
||||
'views' => 28,
|
||||
'prompt_copies' => 6,
|
||||
'popularity_score' => 64.4,
|
||||
]);
|
||||
|
||||
$this->get(route('academy.prompts.popular', ['period' => '7d']))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('analytics.metadata.period', '7d')
|
||||
->where('analytics.metadata.period_days', 7)
|
||||
->where('analytics.trackingKey', 'period:7d')
|
||||
);
|
||||
}
|
||||
|
||||
public function test_prompt_pack_library_index_exposes_dedicated_page_analytics_content_type(): void
|
||||
{
|
||||
AcademyPromptPack::query()->create([
|
||||
'title' => 'Starter Pack',
|
||||
'slug' => 'starter-pack',
|
||||
'excerpt' => 'A free pack.',
|
||||
'description' => 'Pack description.',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$this->get(route('academy.packs.index'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('analytics.contentType', AcademyAnalyticsContentType::PROMPT_PACK_LIBRARY)
|
||||
->where('analytics.contentId', null)
|
||||
->where('analytics.pageName', 'academy_packs_index')
|
||||
);
|
||||
}
|
||||
|
||||
public function test_rollup_counts_search_result_clicks_for_clicked_content(): void
|
||||
{
|
||||
$prompt = $this->createPrompt('rollup-search-click-prompt');
|
||||
@@ -948,6 +1024,151 @@ final class AcademyAnalyticsTest extends TestCase
|
||||
$this->assertSame(20.0, (float) $metric->conversion_score);
|
||||
}
|
||||
|
||||
public function test_admin_prompt_library_analytics_page_uses_dedicated_prompt_library_filter(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.academy.analytics.prompt-library'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Admin/Academy/AnalyticsContent')
|
||||
->where('title', 'Prompt library analytics')
|
||||
->where('filters.content_type', AcademyAnalyticsContentType::PROMPT_LIBRARY)
|
||||
->where('contentTypeOptions', fn ($options): bool => collect($options)->contains(
|
||||
fn (array $option): bool => ($option['value'] ?? null) === AcademyAnalyticsContentType::PROMPT_LIBRARY
|
||||
&& ($option['label'] ?? null) === 'Prompt library'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
public function test_admin_prompt_library_analytics_page_includes_prompt_library_summary(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$this->createMetric(AcademyAnalyticsContentType::PROMPT_LIBRARY, null, [
|
||||
'views' => 42,
|
||||
'unique_visitors' => 18,
|
||||
'engaged_views' => 9,
|
||||
'scroll_50' => 12,
|
||||
'scroll_100' => 4,
|
||||
'avg_engaged_seconds' => 31,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.academy.analytics.prompt-library'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Admin/Academy/AnalyticsContent')
|
||||
->where('summary.views', 42)
|
||||
->where('summary.uniqueVisitors', 18)
|
||||
->where('summary.engagedViews', 9)
|
||||
->where('summary.avgEngagedSeconds', 31)
|
||||
->where('summary.engagementRate', 50)
|
||||
->where('summary.deepScrollRate', 22.2)
|
||||
);
|
||||
}
|
||||
|
||||
public function test_admin_analytics_overview_includes_prompt_library_trend(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$today = now()->toDateString();
|
||||
$yesterday = now()->subDay()->toDateString();
|
||||
|
||||
$this->createMetric(AcademyAnalyticsContentType::PROMPT_LIBRARY, null, [
|
||||
'date' => $today,
|
||||
'views' => 40,
|
||||
'unique_visitors' => 20,
|
||||
'engaged_views' => 10,
|
||||
'popularity_score' => 55,
|
||||
]);
|
||||
|
||||
$this->createMetric(AcademyAnalyticsContentType::PROMPT_LIBRARY, null, [
|
||||
'date' => $yesterday,
|
||||
'views' => 20,
|
||||
'unique_visitors' => 10,
|
||||
'engaged_views' => 5,
|
||||
'popularity_score' => 25,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.academy.analytics.overview', [
|
||||
'range' => 'custom',
|
||||
'from' => $today,
|
||||
'to' => $today,
|
||||
]))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Admin/Academy/AnalyticsOverview')
|
||||
->where('promptLibraryTrend.current.views', 40)
|
||||
->where('promptLibraryTrend.current.uniqueVisitors', 20)
|
||||
->where('promptLibraryTrend.current.engagementRate', 50)
|
||||
->where('promptLibraryTrend.previous.views', 20)
|
||||
->where('promptLibraryTrend.deltas.views', 100)
|
||||
->where('promptLibraryTrend.deltas.engagementRate', 0)
|
||||
);
|
||||
}
|
||||
|
||||
public function test_admin_analytics_overview_includes_popular_prompt_period_usage(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
AcademyEvent::query()->create([
|
||||
'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
|
||||
'content_type' => AcademyAnalyticsContentType::PROMPT_POPULAR,
|
||||
'visitor_id' => 'visitor-30-a',
|
||||
'is_logged_in' => false,
|
||||
'is_subscriber' => false,
|
||||
'is_admin' => false,
|
||||
'is_bot' => false,
|
||||
'is_crawler' => false,
|
||||
'is_suspicious' => false,
|
||||
'metadata' => ['period' => '30d', 'period_days' => 30],
|
||||
'occurred_at' => now()->subHour(),
|
||||
]);
|
||||
|
||||
AcademyEvent::query()->create([
|
||||
'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
|
||||
'content_type' => AcademyAnalyticsContentType::PROMPT_POPULAR,
|
||||
'visitor_id' => 'visitor-30-b',
|
||||
'is_logged_in' => false,
|
||||
'is_subscriber' => false,
|
||||
'is_admin' => false,
|
||||
'is_bot' => false,
|
||||
'is_crawler' => false,
|
||||
'is_suspicious' => false,
|
||||
'metadata' => ['period' => '30d', 'period_days' => 30],
|
||||
'occurred_at' => now()->subMinutes(30),
|
||||
]);
|
||||
|
||||
AcademyEvent::query()->create([
|
||||
'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
|
||||
'content_type' => AcademyAnalyticsContentType::PROMPT_POPULAR,
|
||||
'visitor_id' => 'visitor-7-a',
|
||||
'is_logged_in' => false,
|
||||
'is_subscriber' => false,
|
||||
'is_admin' => false,
|
||||
'is_bot' => false,
|
||||
'is_crawler' => false,
|
||||
'is_suspicious' => false,
|
||||
'metadata' => ['period' => '7d', 'period_days' => 7],
|
||||
'occurred_at' => now()->subMinutes(10),
|
||||
]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->get(route('admin.academy.analytics.overview'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Admin/Academy/AnalyticsOverview')
|
||||
->where('popularPromptPeriodUsage.totalViews', 3)
|
||||
->where('popularPromptPeriodUsage.totalVisitors', 3)
|
||||
->where('popularPromptPeriodUsage.periods.0.period', '30d')
|
||||
->where('popularPromptPeriodUsage.periods.0.views', 2)
|
||||
->where('popularPromptPeriodUsage.periods.0.share', 66.7)
|
||||
->where('popularPromptPeriodUsage.periods.1.period', '7d')
|
||||
->where('popularPromptPeriodUsage.periods.1.views', 1));
|
||||
}
|
||||
|
||||
public function test_prune_events_command_removes_only_old_raw_events(): void
|
||||
{
|
||||
$oldEvent = AcademyEvent::query()->create([
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Http\Middleware\ConditionalValidateCsrfToken;
|
||||
use App\Http\Middleware\HandleInertiaRequests;
|
||||
use App\Models\AcademyAiComparisonResult;
|
||||
use App\Models\AcademyChallenge;
|
||||
use App\Models\AcademyContentMetricDaily;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyCourseEnrollment;
|
||||
use App\Models\AcademyCourseLesson;
|
||||
@@ -15,15 +16,19 @@ use App\Models\AcademyCourseSection;
|
||||
use App\Models\AcademyLesson;
|
||||
use App\Models\AcademyLessonBlock;
|
||||
use App\Models\AcademyLessonProgress;
|
||||
use App\Models\AcademyPromptPack;
|
||||
use App\Models\AcademyPromptTemplate;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Cashier\Subscription;
|
||||
use Laravel\Cashier\SubscriptionItem;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class AcademyFeatureTest extends TestCase
|
||||
@@ -43,7 +48,53 @@ final class AcademyFeatureTest extends TestCase
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/Index')
|
||||
->where('featureFlags.paymentsEnabled', false));
|
||||
->where('links.promptPopular', route('academy.prompts.popular'))
|
||||
->where('academyAccess.signedIn', false)
|
||||
->where('academyAccess.status', 'guest')
|
||||
->where('academyAccess.billingUrl', route('academy.pricing')));
|
||||
}
|
||||
|
||||
public function test_academy_homepage_exposes_access_summary_for_active_paid_user(): void
|
||||
{
|
||||
config()->set('academy_billing.plans', [
|
||||
'pro_monthly' => [
|
||||
'tier' => 'pro',
|
||||
'stripe_price_id' => 'price_pro_test',
|
||||
],
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$subscription = Subscription::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'type' => 'academy',
|
||||
'stripe_id' => 'sub_pro_test',
|
||||
'stripe_status' => 'active',
|
||||
'stripe_price' => 'price_pro_test',
|
||||
'quantity' => 1,
|
||||
'ends_at' => null,
|
||||
]);
|
||||
|
||||
SubscriptionItem::query()->create([
|
||||
'subscription_id' => $subscription->id,
|
||||
'stripe_id' => 'si_pro_test',
|
||||
'stripe_product' => 'prod_pro_test',
|
||||
'stripe_price' => 'price_pro_test',
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/academy')
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/Index')
|
||||
->where('academyAccess.signedIn', true)
|
||||
->where('academyAccess.tier', 'pro')
|
||||
->where('academyAccess.tierLabel', 'Pro')
|
||||
->where('academyAccess.status', 'active')
|
||||
->where('academyAccess.statusLabel', 'Renews automatically')
|
||||
->where('academyAccess.renewsAutomatically', true)
|
||||
->where('academyAccess.billingUrl', route('academy.billing.account'))
|
||||
->where('academyAccess.source', 'subscription'));
|
||||
}
|
||||
|
||||
public function test_academy_routes_are_hidden_when_feature_is_disabled(): void
|
||||
@@ -1149,6 +1200,271 @@ final class AcademyFeatureTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_prompt_library_index_exposes_breadcrumbs_and_discovery_payloads(): void
|
||||
{
|
||||
$featured = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Featured Prompt',
|
||||
'slug' => 'featured-prompt',
|
||||
'excerpt' => 'Featured prompt excerpt.',
|
||||
'prompt' => 'Featured prompt body',
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'featured' => true,
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$popular = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Popular Prompt',
|
||||
'slug' => 'popular-prompt',
|
||||
'excerpt' => 'Popular prompt excerpt.',
|
||||
'prompt' => 'Popular prompt body',
|
||||
'difficulty' => 'intermediate',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinutes(2),
|
||||
]);
|
||||
|
||||
AcademyContentMetricDaily::query()->create([
|
||||
'date' => now()->toDateString(),
|
||||
'content_type' => 'academy_prompt',
|
||||
'content_id' => $popular->id,
|
||||
'views' => 42,
|
||||
'prompt_copies' => 9,
|
||||
'popularity_score' => 88.5,
|
||||
]);
|
||||
|
||||
Cache::flush();
|
||||
|
||||
$this->get(route('academy.prompts.index'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('breadcrumbs.0.label', 'Academy')
|
||||
->where('breadcrumbs.0.href', route('academy.index'))
|
||||
->where('breadcrumbs.1.label', 'Prompt Library')
|
||||
->where('coursesUrl', route('academy.courses.index'))
|
||||
->where('packsUrl', route('academy.packs.index'))
|
||||
->where('featuredPrompts.0.slug', $featured->slug)
|
||||
->where('featuredPrompts.0.spotlight.eyebrow', 'Featured pick')
|
||||
->where('popularPrompts.0.slug', $popular->slug)
|
||||
->where('popularPrompts.0.spotlight.eyebrow', '9 copies this month')
|
||||
->where('academyAccess.signedIn', false)
|
||||
->where('academyAccess.status', 'guest')
|
||||
->where('academyAccess.billingUrl', route('academy.pricing')));
|
||||
}
|
||||
|
||||
public function test_prompt_library_index_exposes_current_access_summary_for_grace_period_subscription(): void
|
||||
{
|
||||
config()->set('academy_billing.plans', [
|
||||
'creator_monthly' => [
|
||||
'tier' => 'creator',
|
||||
'stripe_price_id' => 'price_creator_test',
|
||||
],
|
||||
]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$subscription = Subscription::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'type' => 'academy',
|
||||
'stripe_id' => 'sub_creator_test',
|
||||
'stripe_status' => 'active',
|
||||
'stripe_price' => 'price_creator_test',
|
||||
'quantity' => 1,
|
||||
'ends_at' => now()->addDays(12),
|
||||
]);
|
||||
|
||||
SubscriptionItem::query()->create([
|
||||
'subscription_id' => $subscription->id,
|
||||
'stripe_id' => 'si_creator_test',
|
||||
'stripe_product' => 'prod_creator_test',
|
||||
'stripe_price' => 'price_creator_test',
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('academy.prompts.index'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('academyAccess.signedIn', true)
|
||||
->where('academyAccess.tier', 'creator')
|
||||
->where('academyAccess.tierLabel', 'Creator')
|
||||
->where('academyAccess.status', 'grace_period')
|
||||
->where('academyAccess.statusLabel', 'Cancels soon')
|
||||
->where('academyAccess.dateLabel', 'Access ends')
|
||||
->where('academyAccess.renewsAutomatically', false)
|
||||
->where('academyAccess.source', 'subscription')
|
||||
->where('academyAccess.billingUrl', route('academy.billing.account'))
|
||||
->where('academyAccess.expiresAt', $subscription->ends_at?->toISOString()));
|
||||
}
|
||||
|
||||
public function test_popular_prompts_page_displays_ranked_prompt_payloads(): void
|
||||
{
|
||||
$featured = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Featured Prompt',
|
||||
'slug' => 'featured-popular-page-prompt',
|
||||
'excerpt' => 'Featured prompt excerpt.',
|
||||
'prompt' => 'Featured prompt body',
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'featured' => true,
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$topPrompt = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Top Prompt',
|
||||
'slug' => 'top-prompt',
|
||||
'excerpt' => 'Top prompt excerpt.',
|
||||
'prompt' => 'Top prompt body',
|
||||
'difficulty' => 'advanced',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinutes(2),
|
||||
]);
|
||||
|
||||
$runnerUpPrompt = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Runner Up Prompt',
|
||||
'slug' => 'runner-up-prompt',
|
||||
'excerpt' => 'Runner up prompt excerpt.',
|
||||
'prompt' => 'Runner up prompt body',
|
||||
'difficulty' => 'intermediate',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinutes(3),
|
||||
]);
|
||||
|
||||
AcademyContentMetricDaily::query()->create([
|
||||
'date' => now()->toDateString(),
|
||||
'content_type' => 'academy_prompt',
|
||||
'content_id' => $topPrompt->id,
|
||||
'views' => 74,
|
||||
'prompt_copies' => 11,
|
||||
'popularity_score' => 128.7,
|
||||
]);
|
||||
|
||||
AcademyContentMetricDaily::query()->create([
|
||||
'date' => now()->toDateString(),
|
||||
'content_type' => 'academy_prompt',
|
||||
'content_id' => $runnerUpPrompt->id,
|
||||
'views' => 48,
|
||||
'prompt_copies' => 5,
|
||||
'popularity_score' => 91.4,
|
||||
]);
|
||||
|
||||
Cache::flush();
|
||||
|
||||
$this->get(route('academy.prompts.popular'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('promptView', 'popular')
|
||||
->where('popularPeriod.value', '30d')
|
||||
->where('breadcrumbs.2.label', 'Popular Prompts')
|
||||
->where('promptLibraryUrl', route('academy.prompts.index'))
|
||||
->where('items.data.0.slug', $topPrompt->slug)
|
||||
->where('items.data.0.ranking.rank', 1)
|
||||
->where('items.data.0.ranking.prompt_copies', 11)
|
||||
->where('items.data.1.slug', $runnerUpPrompt->slug)
|
||||
->where('items.data.1.ranking.rank', 2)
|
||||
->where('popularPeriods.0.value', '7d')
|
||||
->where('popularPeriods.1.active', true)
|
||||
->where('featuredPrompts.0.slug', $featured->slug));
|
||||
}
|
||||
|
||||
public function test_popular_prompts_page_can_filter_to_last_seven_days(): void
|
||||
{
|
||||
$recentPrompt = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Recent Prompt',
|
||||
'slug' => 'recent-prompt',
|
||||
'excerpt' => 'Recent prompt excerpt.',
|
||||
'prompt' => 'Recent prompt body',
|
||||
'difficulty' => 'advanced',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$olderPrompt = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Older Prompt',
|
||||
'slug' => 'older-prompt',
|
||||
'excerpt' => 'Older prompt excerpt.',
|
||||
'prompt' => 'Older prompt body',
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinutes(2),
|
||||
]);
|
||||
|
||||
AcademyContentMetricDaily::query()->create([
|
||||
'date' => now()->toDateString(),
|
||||
'content_type' => 'academy_prompt',
|
||||
'content_id' => $recentPrompt->id,
|
||||
'views' => 31,
|
||||
'prompt_copies' => 7,
|
||||
'popularity_score' => 79.5,
|
||||
]);
|
||||
|
||||
AcademyContentMetricDaily::query()->create([
|
||||
'date' => now()->subDays(20)->toDateString(),
|
||||
'content_type' => 'academy_prompt',
|
||||
'content_id' => $olderPrompt->id,
|
||||
'views' => 200,
|
||||
'prompt_copies' => 22,
|
||||
'popularity_score' => 240.1,
|
||||
]);
|
||||
|
||||
Cache::flush();
|
||||
|
||||
$this->get(route('academy.prompts.popular', ['period' => '7d']))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('popularPeriod.value', '7d')
|
||||
->where('popularPeriod.label', '7 days')
|
||||
->where('items.data.0.slug', $recentPrompt->slug)
|
||||
->where('items.data.0.spotlight.eyebrow', '7 copies in the last 7 days')
|
||||
->missing('items.data.1')
|
||||
->where('popularPeriods.0.active', true)
|
||||
->where('popularPeriods.1.active', false));
|
||||
}
|
||||
|
||||
public function test_prompt_pack_index_does_not_include_nested_prompts_until_pack_detail(): void
|
||||
{
|
||||
$prompt = AcademyPromptTemplate::query()->create([
|
||||
'title' => 'Pack Prompt',
|
||||
'slug' => 'pack-prompt',
|
||||
'excerpt' => 'Prompt excerpt.',
|
||||
'prompt' => 'Pack prompt body',
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$pack = AcademyPromptPack::query()->create([
|
||||
'title' => 'Starter Prompt Pack',
|
||||
'slug' => 'starter-prompt-pack',
|
||||
'excerpt' => 'Pack excerpt.',
|
||||
'description' => 'Pack description.',
|
||||
'access_level' => 'free',
|
||||
'active' => true,
|
||||
'published_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$pack->prompts()->attach($prompt->id, ['order_num' => 0]);
|
||||
|
||||
$this->get(route('academy.packs.index'))
|
||||
->assertOk()
|
||||
->assertInertia(fn (AssertableInertia $page) => $page
|
||||
->component('Academy/List')
|
||||
->where('items.data.0.slug', 'starter-prompt-pack')
|
||||
->where('items.data.0.prompts', [])
|
||||
->where('analytics.contentType', 'academy_prompt_pack_library')
|
||||
);
|
||||
}
|
||||
|
||||
public function test_logged_in_user_can_submit_artwork_to_active_challenge(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
Reference in New Issue
Block a user