@wopr-network/platform-ui-core 1.27.7 → 1.27.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/next.config.ts +1 -2
- package/package.json +17 -17
- package/src/__tests__/account-switcher.test.tsx +21 -20
- package/src/__tests__/activity-page.test.tsx +2 -6
- package/src/__tests__/add-payment-method-dialog.test.tsx +9 -32
- package/src/__tests__/admin-api.test.ts +1 -6
- package/src/__tests__/admin-gpu-api.test.ts +1 -3
- package/src/__tests__/admin-marketplace-api.test.ts +1 -4
- package/src/__tests__/admin-middleware.test.ts +76 -83
- package/src/__tests__/affiliate-dashboard.test.tsx +3 -3
- package/src/__tests__/api-401-redirect.test.ts +46 -9
- package/src/__tests__/api-client.test.ts +3 -5
- package/src/__tests__/api-config.test.ts +22 -42
- package/src/__tests__/api-fleet-resources.test.ts +1 -2
- package/src/__tests__/api-fleet-trpc.test.ts +2 -8
- package/src/__tests__/api-null-guards.test.ts +3 -1
- package/src/__tests__/audit-log-table-pagination.test.tsx +2 -6
- package/src/__tests__/auth-password-reset.test.tsx +7 -21
- package/src/__tests__/auth-redirect.test.tsx +8 -2
- package/src/__tests__/auth.test.tsx +25 -23
- package/src/__tests__/auto-topup-card.test.tsx +4 -12
- package/src/__tests__/backups-tab.test.tsx +3 -4
- package/src/__tests__/billing-layout-nav-hidden.test.tsx +5 -37
- package/src/__tests__/billing-payment-org-invoices.test.tsx +2 -18
- package/src/__tests__/billing.test.tsx +8 -39
- package/src/__tests__/bot-settings/resources-tab.test.tsx +1 -3
- package/src/__tests__/bot-settings/storage-tab.test.tsx +1 -3
- package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +1 -3
- package/src/__tests__/bot-settings-restart.test.tsx +1 -3
- package/src/__tests__/bot-settings.test.tsx +2 -6
- package/src/__tests__/brand.test.ts +6 -26
- package/src/__tests__/buy-credits-panel.test.tsx +1 -3
- package/src/__tests__/buy-crypto-credits-panel.test.tsx +101 -119
- package/src/__tests__/capability-conflicts.test.ts +2 -8
- package/src/__tests__/capability-resolver.test.tsx +2 -12
- package/src/__tests__/channel-wizard.test.tsx +4 -17
- package/src/__tests__/chat/chat-panel.test.tsx +1 -4
- package/src/__tests__/chat-store.test.ts +5 -15
- package/src/__tests__/command-center.test.tsx +10 -12
- package/src/__tests__/compliance-retention-edit.test.tsx +3 -6
- package/src/__tests__/confirmation-tracker.test.tsx +3 -18
- package/src/__tests__/coupon-input.test.tsx +1 -3
- package/src/__tests__/create-instance.test.tsx +1 -3
- package/src/__tests__/credit-balance.test.tsx +4 -12
- package/src/__tests__/credits.test.tsx +32 -85
- package/src/__tests__/email-verification-banner.test.tsx +2 -6
- package/src/__tests__/error-boundaries.test.tsx +0 -1
- package/src/__tests__/fetch-pricing.test.ts +2 -1
- package/src/__tests__/field-oauth.test.tsx +2 -6
- package/src/__tests__/fixtures/mock-manifests-data.js +1 -3
- package/src/__tests__/fixtures/mock-manifests.ts +2 -4
- package/src/__tests__/fleet-health-timestamp.test.tsx +1 -8
- package/src/__tests__/fleet-health-update.test.tsx +1 -8
- package/src/__tests__/gpu-dashboard.test.tsx +2 -6
- package/src/__tests__/instance-detail.test.tsx +3 -9
- package/src/__tests__/instance-list.test.tsx +1 -5
- package/src/__tests__/layout-snapshots.test.tsx +64 -11
- package/src/__tests__/marketplace-admin.test.tsx +2 -6
- package/src/__tests__/marketplace.test.tsx +11 -35
- package/src/__tests__/merge-api-rates.test.ts +1 -6
- package/src/__tests__/middleware.test.ts +32 -219
- package/src/__tests__/next-config-headers.test.ts +1 -3
- package/src/__tests__/notifications.test.tsx +4 -11
- package/src/__tests__/oauth-buttons.test.tsx +36 -59
- package/src/__tests__/oauth-error-mapping.test.tsx +2 -6
- package/src/__tests__/observability.test.tsx +23 -36
- package/src/__tests__/onboarding-page.test.tsx +4 -6
- package/src/__tests__/org-billing-api.test.tsx +1 -6
- package/src/__tests__/plugin-install-flow.test.tsx +28 -58
- package/src/__tests__/plugin-registry.test.tsx +3 -11
- package/src/__tests__/plugin-tool-sync.test.ts +1 -3
- package/src/__tests__/plugins-catalog-error.test.tsx +2 -6
- package/src/__tests__/plugins-toggle-race.test.tsx +3 -5
- package/src/__tests__/portfolio-chart.test.tsx +2 -6
- package/src/__tests__/promotion-form.test.tsx +2 -6
- package/src/__tests__/promotions-list.test.tsx +1 -3
- package/src/__tests__/provider-key-api.test.ts +2 -1
- package/src/__tests__/resend-verification-button.test.tsx +8 -24
- package/src/__tests__/secrets-audit-pagination.test.tsx +1 -3
- package/src/__tests__/settings.test.tsx +11 -21
- package/src/__tests__/setup-checklist.test.tsx +3 -9
- package/src/__tests__/setup.ts +25 -6
- package/src/__tests__/snapshot-api.test.ts +2 -1
- package/src/__tests__/step-superpowers.test.tsx +1 -3
- package/src/__tests__/tenant-context.test.tsx +1 -6
- package/src/__tests__/tenant-keys-api.test.ts +3 -4
- package/src/__tests__/tenant-table-pagination.test.tsx +2 -6
- package/src/__tests__/terminal-log-cleanup.test.tsx +0 -1
- package/src/__tests__/transaction-history.test.tsx +190 -238
- package/src/__tests__/trpc-types.test.ts +2 -6
- package/src/__tests__/use-chat.test.ts +1 -3
- package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +1 -4
- package/src/__tests__/use-sidecar-bridge.test.tsx +105 -0
- package/src/__tests__/use-webmcp.test.ts +1 -3
- package/src/__tests__/validate-elevenlabs-key.test.ts +2 -1
- package/src/__tests__/verify-page.test.tsx +4 -13
- package/src/__tests__/verify-redirect.test.tsx +2 -6
- package/src/app/(auth)/error.tsx +1 -7
- package/src/app/(auth)/forgot-password/page.tsx +4 -18
- package/src/app/(auth)/login/page.tsx +5 -22
- package/src/app/(auth)/reset-password/page.tsx +2 -12
- package/src/app/(auth)/signup/page.tsx +10 -44
- package/src/app/(auth)/verify/page.tsx +47 -0
- package/src/app/(dashboard)/billing/credits/page.tsx +14 -67
- package/src/app/(dashboard)/billing/error.tsx +2 -10
- package/src/app/(dashboard)/billing/layout.tsx +12 -62
- package/src/app/(dashboard)/billing/payment/page.tsx +17 -68
- package/src/app/(dashboard)/billing/plans/page.tsx +3 -9
- package/src/app/(dashboard)/billing/usage/hosted/page.tsx +8 -25
- package/src/app/(dashboard)/billing/usage/page.tsx +63 -103
- package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +9 -27
- package/src/app/(dashboard)/changesets/[id]/error.tsx +2 -6
- package/src/app/(dashboard)/changesets/error.tsx +1 -7
- package/src/app/(dashboard)/chat/page.tsx +2 -6
- package/src/app/(dashboard)/dashboard/network/page.tsx +5 -19
- package/src/app/(dashboard)/error.tsx +1 -7
- package/src/app/(dashboard)/layout.tsx +15 -36
- package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +14 -51
- package/src/app/(dashboard)/marketplace/error.tsx +1 -7
- package/src/app/(dashboard)/marketplace/page.tsx +6 -27
- package/src/app/(dashboard)/not-found.tsx +2 -5
- package/src/app/(dashboard)/onboarding/page.tsx +5 -22
- package/src/app/(dashboard)/settings/account/page.tsx +1 -6
- package/src/app/(dashboard)/settings/activity/page.tsx +8 -34
- package/src/app/(dashboard)/settings/api-keys/page.tsx +15 -60
- package/src/app/(dashboard)/settings/brain/page.tsx +9 -31
- package/src/app/(dashboard)/settings/error.tsx +2 -10
- package/src/app/(dashboard)/settings/notifications/page.tsx +2 -6
- package/src/app/(dashboard)/settings/org/page.tsx +13 -56
- package/src/app/(dashboard)/settings/page.tsx +1 -0
- package/src/app/(dashboard)/settings/profile/page.tsx +126 -73
- package/src/app/(dashboard)/settings/providers/page.tsx +21 -78
- package/src/app/(dashboard)/settings/secrets/page.tsx +13 -58
- package/src/app/(dashboard)/settings/security/page.tsx +31 -111
- package/src/app/admin/email-templates/email-templates-client.tsx +15 -58
- package/src/app/admin/error.tsx +1 -7
- package/src/app/admin/fleet-updates/error.tsx +1 -7
- package/src/app/admin/fleet-updates/fleet-updates-client.tsx +10 -50
- package/src/app/admin/layout.tsx +4 -0
- package/src/app/admin/payment-methods/page.tsx +9 -38
- package/src/app/admin/products/error.tsx +2 -7
- package/src/app/admin/products/page.tsx +1 -4
- package/src/app/admin/promotions/[id]/page.tsx +9 -38
- package/src/app/admin/promotions/page.tsx +9 -36
- package/src/app/admin/rate-overrides/page.tsx +9 -45
- package/src/app/auth/callback/[provider]/page.tsx +1 -8
- package/src/app/auth/verify/page.tsx +9 -36
- package/src/app/channels/error.tsx +2 -10
- package/src/app/channels/layout.tsx +9 -0
- package/src/app/channels/page.tsx +8 -20
- package/src/app/channels/setup/[plugin]/page.tsx +3 -5
- package/src/app/error.tsx +1 -7
- package/src/app/fleet/error.tsx +1 -7
- package/src/app/fleet/layout.tsx +5 -0
- package/src/app/fleet/settings/page.tsx +1 -3
- package/src/app/global-error.tsx +2 -10
- package/src/app/globals.css +1 -4
- package/src/app/instances/[id]/instance-detail-client.tsx +51 -125
- package/src/app/instances/error.tsx +2 -10
- package/src/app/instances/instance-list-client.tsx +20 -69
- package/src/app/instances/layout.tsx +9 -0
- package/src/app/instances/new/create-instance-client.tsx +10 -31
- package/src/app/layout.tsx +2 -10
- package/src/app/not-found.tsx +1 -3
- package/src/app/page.tsx +1 -2
- package/src/app/plugins/error.tsx +2 -10
- package/src/app/plugins/layout.tsx +5 -0
- package/src/app/plugins/page.tsx +16 -48
- package/src/app/pricing/error.tsx +1 -7
- package/src/app/privacy/page.tsx +93 -150
- package/src/app/status/error.tsx +1 -7
- package/src/app/terms/page.tsx +89 -144
- package/src/components/account-switcher.tsx +25 -52
- package/src/components/admin/accounting-dashboard.tsx +1 -3
- package/src/components/admin/admin-guard.tsx +1 -3
- package/src/components/admin/admin-nav.tsx +1 -3
- package/src/components/admin/affiliate-dashboard.tsx +25 -94
- package/src/components/admin/audit-log-table.tsx +13 -49
- package/src/components/admin/billing-health-dashboard.tsx +7 -25
- package/src/components/admin/bulk-actions-bar.test.tsx +1 -7
- package/src/components/admin/bulk-actions-bar.tsx +1 -3
- package/src/components/admin/bulk-export-dialog.test.tsx +1 -7
- package/src/components/admin/bulk-export-dialog.tsx +6 -32
- package/src/components/admin/bulk-grant-dialog.test.tsx +2 -6
- package/src/components/admin/bulk-grant-dialog.tsx +4 -15
- package/src/components/admin/bulk-preview-dialog.tsx +3 -12
- package/src/components/admin/bulk-reactivate-dialog.tsx +1 -7
- package/src/components/admin/bulk-select-all-banner.tsx +1 -6
- package/src/components/admin/bulk-suspend-dialog.tsx +5 -12
- package/src/components/admin/bulk-undo-toast.tsx +1 -2
- package/src/components/admin/compliance-dashboard.tsx +31 -101
- package/src/components/admin/gpu-dashboard.tsx +21 -70
- package/src/components/admin/grant-credits-dialog.tsx +4 -17
- package/src/components/admin/incident-dashboard.tsx +10 -25
- package/src/components/admin/inference-dashboard.tsx +14 -54
- package/src/components/admin/marketplace-admin.tsx +18 -60
- package/src/components/admin/migrations-dashboard.tsx +9 -42
- package/src/components/admin/onboarding-dashboard.tsx +14 -64
- package/src/components/admin/pool-config-dashboard.tsx +4 -10
- package/src/components/admin/products/fleet-form.tsx +2 -11
- package/src/components/admin/products/nav-editor.tsx +3 -10
- package/src/components/admin/promotions/promotion-form.tsx +9 -42
- package/src/components/admin/roles-dashboard.tsx +7 -34
- package/src/components/admin/suspend-dialog.tsx +4 -11
- package/src/components/admin/tenant-notes-panel.tsx +1 -3
- package/src/components/admin/tenant-row-actions.tsx +4 -20
- package/src/components/admin/tenant-table.tsx +12 -49
- package/src/components/auth/auth-redirect.tsx +11 -3
- package/src/components/auth/email-verification-result-banner.tsx +1 -3
- package/src/components/auth/resend-verification-button.tsx +2 -10
- package/src/components/auth/wopr-wordmark.tsx +1 -3
- package/src/components/billing/add-payment-method-dialog.tsx +1 -2
- package/src/components/billing/affiliate-dashboard.tsx +4 -16
- package/src/components/billing/amount-selector.tsx +1 -3
- package/src/components/billing/auto-topup-card.tsx +2 -11
- package/src/components/billing/buy-credits-panel.tsx +14 -17
- package/src/components/billing/byok-callout.tsx +6 -8
- package/src/components/billing/confirmation-tracker.tsx +4 -14
- package/src/components/billing/credit-balance-badge.tsx +22 -0
- package/src/components/billing/credit-balance.tsx +3 -9
- package/src/components/billing/crypto-checkout.tsx +5 -24
- package/src/components/billing/degraded-state-banner.tsx +1 -3
- package/src/components/billing/deposit-view.tsx +301 -41
- package/src/components/billing/dividend-banner.tsx +1 -3
- package/src/components/billing/dividend-eligibility.tsx +3 -12
- package/src/components/billing/dividend-pool-stats.tsx +6 -20
- package/src/components/billing/first-dividend-dialog.tsx +2 -2
- package/src/components/billing/org-billing-page.tsx +8 -31
- package/src/components/billing/payment-method-picker.tsx +2 -10
- package/src/components/billing/suspension-banner.tsx +2 -7
- package/src/components/billing/transaction-history.tsx +10 -58
- package/src/components/billing/unified-checkout.tsx +547 -0
- package/src/components/bot-settings/backups-tab.tsx +9 -33
- package/src/components/bot-settings/bot-settings-client.tsx +32 -134
- package/src/components/bot-settings/resources-tab.tsx +2 -9
- package/src/components/bot-settings/storage-tab.tsx +19 -48
- package/src/components/bot-settings/vps-info-panel.tsx +3 -11
- package/src/components/bot-settings/vps-upgrade-card.tsx +3 -4
- package/src/components/brand-hydrator.tsx +13 -0
- package/src/components/channel-wizard/field-interactive.tsx +1 -3
- package/src/components/channel-wizard/field-qr.tsx +10 -39
- package/src/components/channel-wizard/step-renderer.tsx +5 -28
- package/src/components/channel-wizard/wizard.tsx +6 -31
- package/src/components/chat/chat-message.tsx +1 -4
- package/src/components/chat/chat-panel.tsx +4 -18
- package/src/components/chat/chat-widget.tsx +3 -14
- package/src/components/dashboard/command-center.tsx +15 -61
- package/src/components/fleet/update-settings-card.tsx +7 -23
- package/src/components/instance-update-banner.tsx +130 -0
- package/src/components/instances/friends-tab.test.tsx +2 -9
- package/src/components/instances/friends-tab.tsx +18 -74
- package/src/components/instances/update-available-badge.tsx +2 -11
- package/src/components/landing/hero.tsx +3 -9
- package/src/components/landing/landing-page.tsx +1 -3
- package/src/components/landing/portfolio-chart.tsx +4 -9
- package/src/components/landing/story-sections.tsx +1 -3
- package/src/components/landing/terminal-sequence.tsx +4 -17
- package/src/components/marketplace/empty-state.tsx +2 -6
- package/src/components/marketplace/first-visit-hero.tsx +1 -3
- package/src/components/marketplace/install-wizard.tsx +20 -77
- package/src/components/marketplace/marketplace-tabs.tsx +1 -4
- package/src/components/marketplace/plugin-card.tsx +2 -9
- package/src/components/marketplace/superpower-content.tsx +1 -3
- package/src/components/marketplace/terminal-search.tsx +2 -8
- package/src/components/oauth-buttons.tsx +29 -14
- package/src/components/observability/fleet-health.tsx +5 -18
- package/src/components/observability/health-overview.tsx +7 -20
- package/src/components/observability/logs-viewer.tsx +8 -32
- package/src/components/observability/metrics-dashboard.tsx +2 -15
- package/src/components/onboarding/fallback-setup.tsx +6 -25
- package/src/components/onboarding/setup-checklist.tsx +18 -51
- package/src/components/onboarding/step-superpowers.tsx +1 -4
- package/src/components/plugin-setup/setup-chat-panel.tsx +6 -22
- package/src/components/pricing/dividend-calculator.tsx +6 -12
- package/src/components/pricing/dividend-stats.tsx +5 -17
- package/src/components/pricing/pricing-page.tsx +17 -36
- package/src/components/settings/create-org-wizard.tsx +2 -5
- package/src/components/sidebar.tsx +7 -42
- package/src/components/sidecar-frame.tsx +78 -0
- package/src/components/status/status-page.tsx +6 -28
- package/src/components/ui/alert-dialog.tsx +8 -25
- package/src/components/ui/badge.tsx +2 -8
- package/src/components/ui/banner.tsx +1 -6
- package/src/components/ui/card.tsx +5 -24
- package/src/components/ui/checkbox.tsx +1 -5
- package/src/components/ui/collapsible.tsx +3 -8
- package/src/components/ui/dialog.tsx +4 -10
- package/src/components/ui/dropdown-menu.tsx +9 -18
- package/src/components/ui/form.tsx +2 -16
- package/src/components/ui/popover.tsx +3 -23
- package/src/components/ui/progress.tsx +1 -5
- package/src/components/ui/radio-group.tsx +3 -15
- package/src/components/ui/select.tsx +4 -17
- package/src/components/ui/sheet.tsx +5 -19
- package/src/components/ui/skeleton.tsx +1 -7
- package/src/components/ui/table.tsx +5 -22
- package/src/components/ui/tabs.tsx +3 -13
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/components/unified-sidebar.tsx +493 -0
- package/src/hooks/__tests__/use-fleet-sse.test.ts +1 -4
- package/src/hooks/__tests__/use-save-queue.test.ts +2 -8
- package/src/hooks/use-credit-balance.ts +27 -0
- package/src/hooks/use-my-org-role.ts +1 -3
- package/src/hooks/use-plugin-registry.ts +8 -14
- package/src/hooks/use-plugin-setup-chat.ts +2 -5
- package/src/hooks/use-sidecar-bridge.tsx +148 -0
- package/src/hooks/use-webmcp.ts +1 -4
- package/src/lib/__tests__/admin-api.test.ts +1 -3
- package/src/lib/__tests__/api-bot-crud.test.ts +8 -18
- package/src/lib/__tests__/api-fetch.test.ts +4 -16
- package/src/lib/__tests__/org-billing-api.test.ts +1 -3
- package/src/lib/__tests__/pricing-data.test.ts +0 -8
- package/src/lib/__tests__/settings-api.test.ts +1 -3
- package/src/lib/admin-affiliate-api.ts +2 -7
- package/src/lib/admin-api.ts +6 -26
- package/src/lib/admin-incident-api.ts +11 -19
- package/src/lib/admin-marketplace-api.ts +1 -5
- package/src/lib/api-config.test.ts +5 -50
- package/src/lib/api.ts +143 -122
- package/src/lib/auth-client.ts +1 -2
- package/src/lib/bot-settings-data.ts +11 -36
- package/src/lib/brand-config.ts +56 -115
- package/src/lib/brand.ts +2 -15
- package/src/lib/chat/use-chat.ts +2 -7
- package/src/lib/cost-comparison-data.test.ts +1 -3
- package/src/lib/cost-comparison-data.ts +1 -4
- package/src/lib/errors.ts +1 -4
- package/src/lib/fetch-utils.test.ts +26 -9
- package/src/lib/fetch-utils.ts +40 -11
- package/src/lib/logger.ts +2 -0
- package/src/lib/marketplace-data.ts +3 -11
- package/src/lib/oauth-errors.ts +2 -4
- package/src/lib/onboarding-data.ts +3 -11
- package/src/lib/org-api.ts +2 -10
- package/src/lib/org-billing-api.ts +5 -19
- package/src/lib/plugin/tool-definitions.ts +1 -2
- package/src/lib/require-auth.ts +57 -0
- package/src/lib/settings-api.ts +1 -4
- package/src/lib/sidecar-routes.ts +43 -0
- package/src/lib/trpc-server.ts +49 -0
- package/src/lib/trpc-types.ts +4 -6
- package/src/lib/trpc.tsx +12 -4
- package/src/lib/validate-redirect-url.ts +1 -4
- package/src/lib/webmcp/marketplace-onboarding-tools.ts +6 -16
- package/src/lib/webmcp/register.ts +1 -4
- package/src/lib/webmcp/tools.ts +2 -9
- package/src/proxy.ts +35 -212
- package/src/types/missing-deps.d.ts +2 -8
- package/tsconfig.json +1 -8
- package/biome.json +0 -52
- package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +0 -741
- package/src/__tests__/billing-byok-callout.test.tsx +0 -76
- package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +0 -112
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { trpcVanillaProxy } from "./setup.js";
|
|
2
3
|
|
|
3
4
|
vi.mock("@/lib/api-config", () => ({
|
|
4
5
|
API_BASE_URL: "https://test-api.local/api",
|
|
@@ -6,7 +7,7 @@ vi.mock("@/lib/api-config", () => ({
|
|
|
6
7
|
}));
|
|
7
8
|
|
|
8
9
|
vi.mock("@/lib/trpc", () => ({
|
|
9
|
-
trpcVanilla:
|
|
10
|
+
trpcVanilla: trpcVanillaProxy,
|
|
10
11
|
}));
|
|
11
12
|
|
|
12
13
|
const mockHandleUnauthorized = vi.fn(() => {
|
|
@@ -122,10 +123,7 @@ describe("apiFetch (via getProfile)", () => {
|
|
|
122
123
|
const { getProfile } = await import("@/lib/api");
|
|
123
124
|
await getProfile();
|
|
124
125
|
|
|
125
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
126
|
-
"https://test-api.local/api/settings/profile",
|
|
127
|
-
expect.any(Object),
|
|
128
|
-
);
|
|
126
|
+
expect(mockFetch).toHaveBeenCalledWith("https://test-api.local/api/settings/profile", expect.any(Object));
|
|
129
127
|
});
|
|
130
128
|
|
|
131
129
|
it("throws on network failure", async () => {
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for api-config.ts —
|
|
2
|
+
* Tests for api-config.ts — URL resolution behaviour.
|
|
3
3
|
*
|
|
4
|
-
* The module
|
|
4
|
+
* The module resolves API URL at import time, so each test
|
|
5
5
|
* uses vi.resetModules() + a dynamic import to re-evaluate the module
|
|
6
6
|
* with the desired environment variables.
|
|
7
7
|
*/
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
9
|
|
|
10
|
-
const VALID_PRODUCTION_URL = "https://api.example.com";
|
|
11
|
-
const INTERNAL_URL = "http://localhost:3001";
|
|
12
|
-
|
|
13
10
|
function setEnv(vars: Record<string, string | undefined>) {
|
|
14
11
|
for (const [k, v] of Object.entries(vars)) {
|
|
15
12
|
if (v === undefined) {
|
|
@@ -20,7 +17,7 @@ function setEnv(vars: Record<string, string | undefined>) {
|
|
|
20
17
|
}
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
describe("
|
|
20
|
+
describe("api-config URL resolution", () => {
|
|
24
21
|
const originalEnv = { ...process.env };
|
|
25
22
|
|
|
26
23
|
beforeEach(() => {
|
|
@@ -35,55 +32,38 @@ describe("validateProductionApiUrl", () => {
|
|
|
35
32
|
Object.assign(process.env, originalEnv);
|
|
36
33
|
});
|
|
37
34
|
|
|
38
|
-
it("
|
|
35
|
+
it("uses NEXT_PUBLIC_API_URL when set", async () => {
|
|
39
36
|
setEnv({
|
|
40
|
-
|
|
41
|
-
NEXT_RUNTIME: undefined,
|
|
42
|
-
NEXT_PHASE: undefined,
|
|
43
|
-
NEXT_PUBLIC_API_URL: INTERNAL_URL,
|
|
37
|
+
NEXT_PUBLIC_API_URL: "https://api.example.com",
|
|
44
38
|
});
|
|
45
|
-
await
|
|
39
|
+
const mod = await import("../lib/api-config");
|
|
40
|
+
expect(mod.PLATFORM_BASE_URL).toBe("https://api.example.com");
|
|
41
|
+
expect(mod.API_BASE_URL).toBe("https://api.example.com/api");
|
|
46
42
|
});
|
|
47
43
|
|
|
48
|
-
it("
|
|
49
|
-
// This is the key regression test: CI sets NODE_ENV=production and
|
|
50
|
-
// NEXT_PUBLIC_API_URL=http://localhost:3001. The build must not crash.
|
|
44
|
+
it("falls back to localhost:3001 when no env var and no window", async () => {
|
|
51
45
|
setEnv({
|
|
52
|
-
|
|
53
|
-
NEXT_PHASE: "phase-production-build",
|
|
54
|
-
NEXT_RUNTIME: "nodejs",
|
|
55
|
-
NEXT_PUBLIC_API_URL: INTERNAL_URL,
|
|
56
|
-
});
|
|
57
|
-
await expect(import("../lib/api-config")).resolves.toHaveProperty("API_BASE_URL");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("throws at production runtime with an internal URL", async () => {
|
|
61
|
-
setEnv({
|
|
62
|
-
NODE_ENV: "production",
|
|
63
|
-
NEXT_PHASE: undefined,
|
|
64
|
-
NEXT_RUNTIME: "nodejs",
|
|
65
|
-
NEXT_PUBLIC_API_URL: INTERNAL_URL,
|
|
46
|
+
NEXT_PUBLIC_API_URL: undefined,
|
|
66
47
|
});
|
|
67
|
-
await
|
|
48
|
+
const mod = await import("../lib/api-config");
|
|
49
|
+
// In test env (jsdom with localhost), should resolve to localhost
|
|
50
|
+
expect(mod.PLATFORM_BASE_URL).toContain("localhost");
|
|
68
51
|
});
|
|
69
52
|
|
|
70
|
-
it("
|
|
53
|
+
it("exports SITE_URL from NEXT_PUBLIC_SITE_URL env var", async () => {
|
|
71
54
|
setEnv({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
NEXT_RUNTIME: "nodejs",
|
|
75
|
-
NEXT_PUBLIC_API_URL: undefined,
|
|
55
|
+
NEXT_PUBLIC_SITE_URL: "https://mysite.com",
|
|
56
|
+
NEXT_PUBLIC_API_URL: "http://localhost:3001",
|
|
76
57
|
});
|
|
77
|
-
await
|
|
58
|
+
const mod = await import("../lib/api-config");
|
|
59
|
+
expect(mod.SITE_URL).toBe("https://mysite.com");
|
|
78
60
|
});
|
|
79
61
|
|
|
80
|
-
it("
|
|
62
|
+
it("exports API_BASE_URL as PLATFORM_BASE_URL + /api", async () => {
|
|
81
63
|
setEnv({
|
|
82
|
-
|
|
83
|
-
NEXT_PHASE: undefined,
|
|
84
|
-
NEXT_RUNTIME: "nodejs",
|
|
85
|
-
NEXT_PUBLIC_API_URL: VALID_PRODUCTION_URL,
|
|
64
|
+
NEXT_PUBLIC_API_URL: "https://api.test.com",
|
|
86
65
|
});
|
|
87
|
-
await
|
|
66
|
+
const mod = await import("../lib/api-config");
|
|
67
|
+
expect(mod.API_BASE_URL).toBe(`${mod.PLATFORM_BASE_URL}/api`);
|
|
88
68
|
});
|
|
89
69
|
});
|
|
@@ -18,8 +18,7 @@ describe("getFleetResources", () => {
|
|
|
18
18
|
it("calls /api/fleet/resources with credentials", async () => {
|
|
19
19
|
mockFetch.mockResolvedValue({
|
|
20
20
|
ok: true,
|
|
21
|
-
json: () =>
|
|
22
|
-
Promise.resolve({ totalCpuPercent: 42, totalMemoryMb: 512, memoryCapacityMb: 1024 }),
|
|
21
|
+
json: () => Promise.resolve({ totalCpuPercent: 42, totalMemoryMb: 512, memoryCapacityMb: 1024 }),
|
|
23
22
|
});
|
|
24
23
|
|
|
25
24
|
const { getFleetResources } = await import("@/lib/api");
|
|
@@ -61,10 +61,7 @@ describe("listInstances uses tRPC", () => {
|
|
|
61
61
|
const result = await listInstances();
|
|
62
62
|
|
|
63
63
|
expect(mockListInstances).toHaveBeenCalled();
|
|
64
|
-
expect(fetchSpy).not.toHaveBeenCalledWith(
|
|
65
|
-
expect.stringContaining("/fleet/"),
|
|
66
|
-
expect.anything(),
|
|
67
|
-
);
|
|
64
|
+
expect(fetchSpy).not.toHaveBeenCalledWith(expect.stringContaining("/fleet/"), expect.anything());
|
|
68
65
|
expect(result).toHaveLength(1);
|
|
69
66
|
expect(result[0].id).toBe("bot-1");
|
|
70
67
|
expect(result[0].status).toBe("running");
|
|
@@ -187,10 +184,7 @@ describe("getInstanceLogs uses tRPC", () => {
|
|
|
187
184
|
|
|
188
185
|
it("calls trpcVanilla.fleet.getInstanceLogs.query", async () => {
|
|
189
186
|
mockGetInstanceLogs.mockResolvedValueOnce({
|
|
190
|
-
logs: [
|
|
191
|
-
"2026-02-20T10:00:00Z [INFO] Bot started",
|
|
192
|
-
"2026-02-20T10:00:01Z [ERROR] Connection failed",
|
|
193
|
-
],
|
|
187
|
+
logs: ["2026-02-20T10:00:00Z [INFO] Bot started", "2026-02-20T10:00:01Z [ERROR] Connection failed"],
|
|
194
188
|
});
|
|
195
189
|
|
|
196
190
|
const { getInstanceLogs } = await import("@/lib/api");
|
|
@@ -22,6 +22,7 @@ interface MockTrpcVanilla {
|
|
|
22
22
|
billing: {
|
|
23
23
|
creditsBalance: MockQuery;
|
|
24
24
|
creditsHistory: MockQuery;
|
|
25
|
+
creditsDailySummary: MockQuery;
|
|
25
26
|
creditOptions: MockQuery;
|
|
26
27
|
affiliateStats: MockQuery;
|
|
27
28
|
affiliateReferrals: MockQuery;
|
|
@@ -61,6 +62,7 @@ vi.mock("@/lib/trpc", () => ({
|
|
|
61
62
|
billing: {
|
|
62
63
|
creditsBalance: { query: vi.fn() },
|
|
63
64
|
creditsHistory: { query: vi.fn() },
|
|
65
|
+
creditsDailySummary: { query: vi.fn() },
|
|
64
66
|
creditOptions: { query: vi.fn() },
|
|
65
67
|
affiliateStats: { query: vi.fn() },
|
|
66
68
|
affiliateReferrals: { query: vi.fn() },
|
|
@@ -154,7 +156,7 @@ describe("API null guards", () => {
|
|
|
154
156
|
it("getCreditHistory handles empty response", async () => {
|
|
155
157
|
const { trpcVanilla } = await import("@/lib/trpc");
|
|
156
158
|
const { billing } = trpcVanilla as unknown as MockTrpcVanilla;
|
|
157
|
-
billing.
|
|
159
|
+
billing.creditsDailySummary.query.mockResolvedValue({});
|
|
158
160
|
|
|
159
161
|
const { getCreditHistory } = await import("@/lib/api");
|
|
160
162
|
const result = await getCreditHistory();
|
|
@@ -56,9 +56,7 @@ describe("AuditLogTable pagination", () => {
|
|
|
56
56
|
render(<AuditLogTable />);
|
|
57
57
|
|
|
58
58
|
await screen.findByText("Bot 0");
|
|
59
|
-
expect(mockFetchAuditLog).toHaveBeenCalledWith(
|
|
60
|
-
expect.objectContaining({ offset: 0, limit: 50 }),
|
|
61
|
-
);
|
|
59
|
+
expect(mockFetchAuditLog).toHaveBeenCalledWith(expect.objectContaining({ offset: 0, limit: 50 }));
|
|
62
60
|
});
|
|
63
61
|
|
|
64
62
|
it("clicking Next requests offset 50 (page 2)", async () => {
|
|
@@ -79,9 +77,7 @@ describe("AuditLogTable pagination", () => {
|
|
|
79
77
|
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
80
78
|
await screen.findByText("Bot 50");
|
|
81
79
|
|
|
82
|
-
expect(mockFetchAuditLog).toHaveBeenLastCalledWith(
|
|
83
|
-
expect.objectContaining({ offset: 50, limit: 50 }),
|
|
84
|
-
);
|
|
80
|
+
expect(mockFetchAuditLog).toHaveBeenLastCalledWith(expect.objectContaining({ offset: 50, limit: 50 }));
|
|
85
81
|
});
|
|
86
82
|
|
|
87
83
|
it("disables Next when hasMore is false", async () => {
|
|
@@ -26,12 +26,8 @@ vi.mock("better-auth/react", () => ({
|
|
|
26
26
|
|
|
27
27
|
vi.mock("framer-motion", () => ({
|
|
28
28
|
motion: {
|
|
29
|
-
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) =>
|
|
30
|
-
|
|
31
|
-
),
|
|
32
|
-
p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
|
|
33
|
-
<p {...props}>{children}</p>
|
|
34
|
-
),
|
|
29
|
+
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <div {...props}>{children}</div>,
|
|
30
|
+
p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <p {...props}>{children}</p>,
|
|
35
31
|
},
|
|
36
32
|
}));
|
|
37
33
|
|
|
@@ -150,12 +146,8 @@ describe("Reset password page", () => {
|
|
|
150
146
|
beforeEach(() => {
|
|
151
147
|
mockFetch = vi.fn();
|
|
152
148
|
mockPush.mockClear();
|
|
153
|
-
vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<
|
|
154
|
-
|
|
155
|
-
>);
|
|
156
|
-
vi.mocked(useSearchParams).mockReturnValue(
|
|
157
|
-
new URLSearchParams() as ReturnType<typeof useSearchParams>,
|
|
158
|
-
);
|
|
149
|
+
vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<typeof useRouter>);
|
|
150
|
+
vi.mocked(useSearchParams).mockReturnValue(new URLSearchParams() as ReturnType<typeof useSearchParams>);
|
|
159
151
|
});
|
|
160
152
|
|
|
161
153
|
it("shows access denied when no token is present", async () => {
|
|
@@ -346,9 +338,7 @@ describe("Reset password page", () => {
|
|
|
346
338
|
await user.click(screen.getByRole("button", { name: "Reset password" }));
|
|
347
339
|
|
|
348
340
|
await waitFor(() => {
|
|
349
|
-
expect(
|
|
350
|
-
screen.getByText("Password must contain at least one uppercase letter"),
|
|
351
|
-
).toBeInTheDocument();
|
|
341
|
+
expect(screen.getByText("Password must contain at least one uppercase letter")).toBeInTheDocument();
|
|
352
342
|
});
|
|
353
343
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
354
344
|
});
|
|
@@ -366,9 +356,7 @@ describe("Reset password page", () => {
|
|
|
366
356
|
await user.click(screen.getByRole("button", { name: "Reset password" }));
|
|
367
357
|
|
|
368
358
|
await waitFor(() => {
|
|
369
|
-
expect(
|
|
370
|
-
screen.getByText("Password must contain at least one lowercase letter"),
|
|
371
|
-
).toBeInTheDocument();
|
|
359
|
+
expect(screen.getByText("Password must contain at least one lowercase letter")).toBeInTheDocument();
|
|
372
360
|
});
|
|
373
361
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
374
362
|
});
|
|
@@ -404,9 +392,7 @@ describe("Reset password page", () => {
|
|
|
404
392
|
await user.click(screen.getByRole("button", { name: "Reset password" }));
|
|
405
393
|
|
|
406
394
|
await waitFor(() => {
|
|
407
|
-
expect(
|
|
408
|
-
screen.getByText("Password must contain at least one special character"),
|
|
409
|
-
).toBeInTheDocument();
|
|
395
|
+
expect(screen.getByText("Password must contain at least one special character")).toBeInTheDocument();
|
|
410
396
|
});
|
|
411
397
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
412
398
|
});
|
|
@@ -4,6 +4,12 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
4
4
|
const mockReplace = vi.fn();
|
|
5
5
|
vi.mock("next/navigation", () => ({
|
|
6
6
|
useRouter: () => ({ replace: mockReplace }),
|
|
7
|
+
useSearchParams: () => new URLSearchParams(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("@/lib/utils", () => ({
|
|
11
|
+
sanitizeRedirectUrl: (url: string) => url,
|
|
12
|
+
cn: (...args: unknown[]) => args.filter(Boolean).join(" "),
|
|
7
13
|
}));
|
|
8
14
|
|
|
9
15
|
const mockUseSession = vi.fn();
|
|
@@ -20,14 +26,14 @@ describe("AuthRedirect", () => {
|
|
|
20
26
|
mockReplace.mockClear();
|
|
21
27
|
});
|
|
22
28
|
|
|
23
|
-
it("redirects authenticated user to
|
|
29
|
+
it("redirects authenticated user to homePath", async () => {
|
|
24
30
|
mockUseSession.mockReturnValue({
|
|
25
31
|
data: { user: { id: "1", email: "test@test.com" } },
|
|
26
32
|
isPending: false,
|
|
27
33
|
});
|
|
28
34
|
render(<AuthRedirect />);
|
|
29
35
|
await waitFor(() => {
|
|
30
|
-
expect(mockReplace).toHaveBeenCalledWith("/
|
|
36
|
+
expect(mockReplace).toHaveBeenCalledWith("/");
|
|
31
37
|
});
|
|
32
38
|
});
|
|
33
39
|
|
|
@@ -27,31 +27,37 @@ vi.mock("better-auth/react", () => ({
|
|
|
27
27
|
}),
|
|
28
28
|
}));
|
|
29
29
|
|
|
30
|
-
// Mock
|
|
31
|
-
vi.mock("@/lib/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
useQuery: () => ({ data: ["github", "discord", "google"], isLoading: false }),
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
TRPCProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
30
|
+
// Mock @/lib/api-config for OAuthButtons fetch URL
|
|
31
|
+
vi.mock("@/lib/api-config", () => ({
|
|
32
|
+
API_BASE_URL: "https://api.test/api",
|
|
33
|
+
PLATFORM_BASE_URL: "https://api.test",
|
|
34
|
+
SITE_URL: "https://api.test",
|
|
40
35
|
}));
|
|
41
36
|
|
|
42
37
|
// Mock framer-motion to prevent animation issues in JSDOM
|
|
43
38
|
vi.mock("framer-motion", () => ({
|
|
44
39
|
motion: {
|
|
45
|
-
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) =>
|
|
46
|
-
|
|
47
|
-
),
|
|
48
|
-
p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
|
|
49
|
-
<p {...props}>{children}</p>
|
|
50
|
-
),
|
|
40
|
+
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <div {...props}>{children}</div>,
|
|
41
|
+
p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <p {...props}>{children}</p>,
|
|
51
42
|
},
|
|
52
43
|
}));
|
|
53
44
|
|
|
54
45
|
describe("Login page", () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// OAuthButtons now fetches providers from API_BASE_URL/auth/providers via fetch
|
|
48
|
+
vi.stubGlobal(
|
|
49
|
+
"fetch",
|
|
50
|
+
vi.fn().mockResolvedValue({
|
|
51
|
+
ok: true,
|
|
52
|
+
json: () => Promise.resolve(["github", "discord", "google"]),
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.unstubAllGlobals();
|
|
59
|
+
});
|
|
60
|
+
|
|
55
61
|
it("renders email and password fields", async () => {
|
|
56
62
|
const { default: LoginPage } = await import("../app/(auth)/login/page");
|
|
57
63
|
render(<LoginPage />);
|
|
@@ -71,7 +77,7 @@ describe("Login page", () => {
|
|
|
71
77
|
const { default: LoginPage } = await import("../app/(auth)/login/page");
|
|
72
78
|
render(<LoginPage />);
|
|
73
79
|
|
|
74
|
-
expect(screen.
|
|
80
|
+
expect(await screen.findByRole("button", { name: "Continue with GitHub" })).toBeInTheDocument();
|
|
75
81
|
expect(screen.getByRole("button", { name: "Continue with Discord" })).toBeInTheDocument();
|
|
76
82
|
expect(screen.getByRole("button", { name: "Continue with Google" })).toBeInTheDocument();
|
|
77
83
|
});
|
|
@@ -152,13 +158,9 @@ describe("OAuth callback page", () => {
|
|
|
152
158
|
beforeEach(() => {
|
|
153
159
|
vi.useFakeTimers();
|
|
154
160
|
mockPush.mockClear();
|
|
155
|
-
vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<
|
|
156
|
-
typeof useRouter
|
|
157
|
-
>);
|
|
161
|
+
vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<typeof useRouter>);
|
|
158
162
|
vi.mocked(useParams).mockReturnValue({ provider: "github" });
|
|
159
|
-
vi.mocked(useSearchParams).mockReturnValue(
|
|
160
|
-
new URLSearchParams() as ReturnType<typeof useSearchParams>,
|
|
161
|
-
);
|
|
163
|
+
vi.mocked(useSearchParams).mockReturnValue(new URLSearchParams() as ReturnType<typeof useSearchParams>);
|
|
162
164
|
});
|
|
163
165
|
|
|
164
166
|
afterEach(() => {
|
|
@@ -12,9 +12,7 @@ const { mockGetAutoTopupSettings, mockUpdateAutoTopupSettings } = vi.hoisted(()
|
|
|
12
12
|
// Mock framer-motion to prevent animation issues in JSDOM
|
|
13
13
|
vi.mock("framer-motion", () => ({
|
|
14
14
|
motion: {
|
|
15
|
-
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) =>
|
|
16
|
-
<div {...props}>{children}</div>
|
|
17
|
-
),
|
|
15
|
+
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <div {...props}>{children}</div>,
|
|
18
16
|
},
|
|
19
17
|
}));
|
|
20
18
|
|
|
@@ -163,9 +161,7 @@ describe("AutoTopupCard", () => {
|
|
|
163
161
|
const { AutoTopupCard } = await import("../components/billing/auto-topup-card");
|
|
164
162
|
render(<AutoTopupCard />);
|
|
165
163
|
|
|
166
|
-
expect(
|
|
167
|
-
await screen.findByText("Add a payment method to enable auto-topup."),
|
|
168
|
-
).toBeInTheDocument();
|
|
164
|
+
expect(await screen.findByText("Add a payment method to enable auto-topup.")).toBeInTheDocument();
|
|
169
165
|
});
|
|
170
166
|
|
|
171
167
|
it("shows error state with retry on fetch failure", async () => {
|
|
@@ -229,9 +225,7 @@ describe("AutoTopupCard", () => {
|
|
|
229
225
|
render(<AutoTopupCard />);
|
|
230
226
|
|
|
231
227
|
expect(
|
|
232
|
-
await screen.findByText(
|
|
233
|
-
"Tip: scheduled top-ups keep you in the dividend pool automatically.",
|
|
234
|
-
),
|
|
228
|
+
await screen.findByText("Tip: scheduled top-ups keep you in the dividend pool automatically."),
|
|
235
229
|
).toBeInTheDocument();
|
|
236
230
|
});
|
|
237
231
|
|
|
@@ -241,9 +235,7 @@ describe("AutoTopupCard", () => {
|
|
|
241
235
|
render(<AutoTopupCard />);
|
|
242
236
|
|
|
243
237
|
expect(
|
|
244
|
-
await screen.findByText(
|
|
245
|
-
"Tip: scheduled top-ups keep you in the dividend pool for 7 days each month.",
|
|
246
|
-
),
|
|
238
|
+
await screen.findByText("Tip: scheduled top-ups keep you in the dividend pool for 7 days each month."),
|
|
247
239
|
).toBeInTheDocument();
|
|
248
240
|
});
|
|
249
241
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { trpcVanillaProxy } from "./setup.js";
|
|
4
5
|
|
|
5
6
|
vi.mock("@/lib/api", () => ({
|
|
6
7
|
listSnapshots: vi.fn(),
|
|
@@ -20,7 +21,7 @@ vi.mock("@/lib/fetch-utils", () => ({
|
|
|
20
21
|
}));
|
|
21
22
|
|
|
22
23
|
vi.mock("@/lib/trpc", () => ({
|
|
23
|
-
trpcVanilla:
|
|
24
|
+
trpcVanilla: trpcVanillaProxy,
|
|
24
25
|
}));
|
|
25
26
|
|
|
26
27
|
vi.mock("sonner", () => ({
|
|
@@ -183,9 +184,7 @@ describe("BackupsTab", () => {
|
|
|
183
184
|
});
|
|
184
185
|
|
|
185
186
|
it("retries load when retry button is clicked", async () => {
|
|
186
|
-
mockListSnapshots
|
|
187
|
-
.mockRejectedValueOnce(new Error("Network error"))
|
|
188
|
-
.mockResolvedValueOnce(MOCK_SNAPSHOTS);
|
|
187
|
+
mockListSnapshots.mockRejectedValueOnce(new Error("Network error")).mockResolvedValueOnce(MOCK_SNAPSHOTS);
|
|
189
188
|
const user = userEvent.setup();
|
|
190
189
|
render(<BackupsTab botId="bot-1" />);
|
|
191
190
|
await waitFor(() => {
|
|
@@ -1,47 +1,15 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
-
import { expect, test
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
3
|
import BillingLayout from "@/app/(dashboard)/billing/layout";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
vi.mock("next/navigation", () => ({
|
|
7
|
-
usePathname: () => "/billing/plans",
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
// Mock next/link
|
|
11
|
-
vi.mock("next/link", () => ({
|
|
12
|
-
default: ({
|
|
13
|
-
children,
|
|
14
|
-
href,
|
|
15
|
-
...props
|
|
16
|
-
}: {
|
|
17
|
-
children: React.ReactNode;
|
|
18
|
-
href: string;
|
|
19
|
-
[key: string]: unknown;
|
|
20
|
-
}) => (
|
|
21
|
-
<a href={href} {...props}>
|
|
22
|
-
{children}
|
|
23
|
-
</a>
|
|
24
|
-
),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
// Mock API — mode never resolves (stays null)
|
|
28
|
-
vi.mock("@/lib/api", () => ({
|
|
29
|
-
apiFetch: vi.fn(),
|
|
30
|
-
getInferenceMode: () =>
|
|
31
|
-
new Promise((_resolve) => {
|
|
32
|
-
/* never resolves */
|
|
33
|
-
}),
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
test("hostedOnly nav item is hidden (not just invisible) while mode is loading", () => {
|
|
5
|
+
test("billing layout renders children and footer links", () => {
|
|
37
6
|
render(
|
|
38
7
|
<BillingLayout>
|
|
39
8
|
<div>child</div>
|
|
40
9
|
</BillingLayout>,
|
|
41
10
|
);
|
|
42
11
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expect(
|
|
46
|
-
expect(li?.className).not.toMatch(/\binvisible\b/);
|
|
12
|
+
expect(screen.getByText("child")).toBeInTheDocument();
|
|
13
|
+
expect(screen.getByText("Terms of Service")).toBeInTheDocument();
|
|
14
|
+
expect(screen.getByText("Privacy Policy")).toBeInTheDocument();
|
|
47
15
|
});
|
|
@@ -58,27 +58,11 @@ vi.mock("framer-motion", () => ({
|
|
|
58
58
|
AnimatePresence: ({ children }: { children: React.ReactNode }) => children,
|
|
59
59
|
motion: {
|
|
60
60
|
div: ({ children, ...props }: Record<string, unknown>) => {
|
|
61
|
-
const {
|
|
62
|
-
initial: _i,
|
|
63
|
-
animate: _a,
|
|
64
|
-
exit: _e,
|
|
65
|
-
variants: _v,
|
|
66
|
-
custom: _c,
|
|
67
|
-
transition: _t,
|
|
68
|
-
...rest
|
|
69
|
-
} = props;
|
|
61
|
+
const { initial: _i, animate: _a, exit: _e, variants: _v, custom: _c, transition: _t, ...rest } = props;
|
|
70
62
|
return <div {...(rest as Record<string, unknown>)}>{children as React.ReactNode}</div>;
|
|
71
63
|
},
|
|
72
64
|
tr: ({ children, ...props }: Record<string, unknown>) => {
|
|
73
|
-
const {
|
|
74
|
-
initial: _i,
|
|
75
|
-
animate: _a,
|
|
76
|
-
exit: _e,
|
|
77
|
-
variants: _v,
|
|
78
|
-
custom: _c,
|
|
79
|
-
transition: _t,
|
|
80
|
-
...rest
|
|
81
|
-
} = props;
|
|
65
|
+
const { initial: _i, animate: _a, exit: _e, variants: _v, custom: _c, transition: _t, ...rest } = props;
|
|
82
66
|
return <tr {...(rest as Record<string, unknown>)}>{children as React.ReactNode}</tr>;
|
|
83
67
|
},
|
|
84
68
|
},
|
|
@@ -233,16 +233,6 @@ describe("Plans page", () => {
|
|
|
233
233
|
expect(screen.getByText(/\/month/)).toBeInTheDocument();
|
|
234
234
|
});
|
|
235
235
|
|
|
236
|
-
it("shows BYOK callout", async () => {
|
|
237
|
-
const { default: PlansPage } = await import("../app/(dashboard)/billing/plans/page");
|
|
238
|
-
render(<PlansPage />);
|
|
239
|
-
|
|
240
|
-
expect(await screen.findByText("Bring Your Own Keys")).toBeInTheDocument();
|
|
241
|
-
expect(
|
|
242
|
-
screen.getAllByText(/Platform never touches your inference/).length,
|
|
243
|
-
).toBeGreaterThanOrEqual(1);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
236
|
it("links to full pricing page", async () => {
|
|
247
237
|
const { default: PlansPage } = await import("../app/(dashboard)/billing/plans/page");
|
|
248
238
|
render(<PlansPage />);
|
|
@@ -251,14 +241,13 @@ describe("Plans page", () => {
|
|
|
251
241
|
expect(link).toHaveAttribute("href", "/pricing");
|
|
252
242
|
});
|
|
253
243
|
|
|
254
|
-
it("
|
|
244
|
+
it("renders features section (empty by default brand config)", async () => {
|
|
255
245
|
const { default: PlansPage } = await import("../app/(dashboard)/billing/plans/page");
|
|
256
246
|
render(<PlansPage />);
|
|
257
247
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
expect(screen.getByText("
|
|
261
|
-
expect(screen.getByText("All providers")).toBeInTheDocument();
|
|
248
|
+
// Default brand config has empty planFeatures[], so no feature items render.
|
|
249
|
+
// Just verify the page doesn't crash.
|
|
250
|
+
expect(screen.getByText("Your Plan")).toBeInTheDocument();
|
|
262
251
|
});
|
|
263
252
|
});
|
|
264
253
|
|
|
@@ -310,9 +299,7 @@ describe("Usage page", () => {
|
|
|
310
299
|
|
|
311
300
|
expect(await screen.findByText("Anthropic")).toBeInTheDocument();
|
|
312
301
|
expect(screen.getByText("OpenAI")).toBeInTheDocument();
|
|
313
|
-
expect(
|
|
314
|
-
screen.getByText((_, element) => element?.textContent === "~$23.40"),
|
|
315
|
-
).toBeInTheDocument();
|
|
302
|
+
expect(screen.getByText((_, element) => element?.textContent === "~$23.40")).toBeInTheDocument();
|
|
316
303
|
expect(screen.getByText((_, element) => element?.textContent === "~$8.12")).toBeInTheDocument();
|
|
317
304
|
expect(screen.getByText(/Platform does not charge for inference/)).toBeInTheDocument();
|
|
318
305
|
});
|
|
@@ -455,7 +442,7 @@ describe("Payment page", () => {
|
|
|
455
442
|
});
|
|
456
443
|
|
|
457
444
|
describe("Billing layout", () => {
|
|
458
|
-
it("renders
|
|
445
|
+
it("renders child content and footer", async () => {
|
|
459
446
|
const { default: BillingLayout } = await import("../app/(dashboard)/billing/layout");
|
|
460
447
|
render(
|
|
461
448
|
<BillingLayout>
|
|
@@ -463,27 +450,9 @@ describe("Billing layout", () => {
|
|
|
463
450
|
</BillingLayout>,
|
|
464
451
|
);
|
|
465
452
|
|
|
466
|
-
expect(screen.getByText("Your Plan")).toBeInTheDocument();
|
|
467
|
-
expect(screen.getByText("Usage")).toBeInTheDocument();
|
|
468
|
-
expect(screen.getByText("Payment")).toBeInTheDocument();
|
|
469
453
|
expect(screen.getByText("child content")).toBeInTheDocument();
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
it("shows Hosted Usage nav for hosted users", async () => {
|
|
473
|
-
const api = await import("@/lib/api");
|
|
474
|
-
vi.mocked(api.getInferenceMode).mockResolvedValue("hosted");
|
|
475
|
-
|
|
476
|
-
const { default: BillingLayout } = await import("../app/(dashboard)/billing/layout");
|
|
477
|
-
render(
|
|
478
|
-
<BillingLayout>
|
|
479
|
-
<div>child content</div>
|
|
480
|
-
</BillingLayout>,
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
expect(await screen.findByText("Hosted Usage")).toBeInTheDocument();
|
|
484
|
-
|
|
485
|
-
// Reset for other tests
|
|
486
|
-
vi.mocked(api.getInferenceMode).mockResolvedValue("byok");
|
|
454
|
+
expect(screen.getByText("Terms of Service")).toBeInTheDocument();
|
|
455
|
+
expect(screen.getByText("Privacy Policy")).toBeInTheDocument();
|
|
487
456
|
});
|
|
488
457
|
});
|
|
489
458
|
|
|
@@ -99,9 +99,7 @@ describe("ResourcesTab error state", () => {
|
|
|
99
99
|
|
|
100
100
|
it("retries loading when Retry button is clicked", async () => {
|
|
101
101
|
const { getResourceTier } = await import("@/lib/bot-settings-data");
|
|
102
|
-
vi.mocked(getResourceTier)
|
|
103
|
-
.mockRejectedValueOnce(new Error("fail"))
|
|
104
|
-
.mockResolvedValueOnce({ tier: "pro" });
|
|
102
|
+
vi.mocked(getResourceTier).mockRejectedValueOnce(new Error("fail")).mockResolvedValueOnce({ tier: "pro" });
|
|
105
103
|
|
|
106
104
|
render(<ResourcesTab botId="bot-1" />);
|
|
107
105
|
await waitFor(() => {
|
|
@@ -72,9 +72,7 @@ describe("StorageTab", () => {
|
|
|
72
72
|
it("shows billing explanation text", async () => {
|
|
73
73
|
render(<StorageTab botId="bot-1" />);
|
|
74
74
|
await waitFor(() => {
|
|
75
|
-
expect(
|
|
76
|
-
screen.getByText(/Storage costs are billed daily from your credit balance/),
|
|
77
|
-
).toBeInTheDocument();
|
|
75
|
+
expect(screen.getByText(/Storage costs are billed daily from your credit balance/)).toBeInTheDocument();
|
|
78
76
|
});
|
|
79
77
|
});
|
|
80
78
|
});
|
|
@@ -23,9 +23,7 @@ describe("VpsUpgradeCard", () => {
|
|
|
23
23
|
|
|
24
24
|
it("displays the description", () => {
|
|
25
25
|
render(<VpsUpgradeCard botId="bot-1" />);
|
|
26
|
-
expect(
|
|
27
|
-
screen.getByText(/dedicated persistent container with fixed monthly pricing/),
|
|
28
|
-
).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText(/dedicated persistent container with fixed monthly pricing/)).toBeInTheDocument();
|
|
29
27
|
});
|
|
30
28
|
|
|
31
29
|
it("lists all VPS features", () => {
|