fix(#338): Gate mock data behind NODE_ENV check

- Create ComingSoon component for production placeholders
- Federation connections page shows Coming Soon in production
- Workspaces settings page shows Coming Soon in production
- Teams page shows Coming Soon in production
- Add comprehensive tests for environment-based rendering

Refs #338

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-05 17:15:35 -06:00
parent 344e5df3bb
commit 587272e2d0
8 changed files with 447 additions and 5 deletions

View File

@@ -0,0 +1,51 @@
/**
* Federation Connections Page Tests
* Tests for page structure and component integration
*/
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
// Mock the federation components
vi.mock("@/components/federation/ConnectionList", () => ({
ConnectionList: (): React.JSX.Element => <div data-testid="connection-list">ConnectionList</div>,
}));
vi.mock("@/components/federation/InitiateConnectionDialog", () => ({
InitiateConnectionDialog: (): React.JSX.Element => (
<div data-testid="initiate-dialog">Dialog</div>
),
}));
describe("ConnectionsPage", (): void => {
// Note: NODE_ENV is "test" during test runs, which triggers the Coming Soon view
// This tests the production-like behavior where mock data is hidden
it("should render the Coming Soon view in non-development environments", async (): Promise<void> => {
// Dynamic import to ensure fresh module state
const { default: ConnectionsPage } = await import("./page");
render(<ConnectionsPage />);
// In test mode (non-development), should show Coming Soon
expect(screen.getByText("Coming Soon")).toBeInTheDocument();
expect(screen.getByText("Federation Connections")).toBeInTheDocument();
});
it("should display appropriate description for federation feature", async (): Promise<void> => {
const { default: ConnectionsPage } = await import("./page");
render(<ConnectionsPage />);
expect(
screen.getByText(/connect and manage relationships with other mosaic stack instances/i)
).toBeInTheDocument();
});
it("should not render mock data in Coming Soon view", async (): Promise<void> => {
const { default: ConnectionsPage } = await import("./page");
render(<ConnectionsPage />);
// Should not show the connection list or dialog in non-development mode
expect(screen.queryByTestId("connection-list")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /connect to instance/i })).not.toBeInTheDocument();
});
});

View File

@@ -8,6 +8,7 @@
import { useState, useEffect } from "react";
import { ConnectionList } from "@/components/federation/ConnectionList";
import { InitiateConnectionDialog } from "@/components/federation/InitiateConnectionDialog";
import { ComingSoon } from "@/components/ui/ComingSoon";
import {
mockConnections,
FederationConnectionStatus,
@@ -23,7 +24,14 @@ import {
// disconnectConnection,
// } from "@/lib/api/federation";
export default function ConnectionsPage(): React.JSX.Element {
// Check if we're in development mode
const isDevelopment = process.env.NODE_ENV === "development";
/**
* Federation Connections Page - Development Only
* Shows mock data in development, Coming Soon in production
*/
function ConnectionsPageContent(): React.JSX.Element {
const [connections, setConnections] = useState<ConnectionDetails[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [showDialog, setShowDialog] = useState(false);
@@ -44,7 +52,7 @@ export default function ConnectionsPage(): React.JSX.Element {
// TODO: Replace with real API call when backend is integrated
// const data = await fetchConnections();
// Using mock data for now
// Using mock data for now (development only)
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate network delay
setConnections(mockConnections);
} catch (err) {
@@ -218,3 +226,22 @@ export default function ConnectionsPage(): React.JSX.Element {
</main>
);
}
/**
* Federation Connections Page Entry Point
* Shows development content or Coming Soon based on environment
*/
export default function ConnectionsPage(): React.JSX.Element {
// In production, show Coming Soon placeholder
if (!isDevelopment) {
return (
<ComingSoon
feature="Federation Connections"
description="Connect and manage relationships with other Mosaic Stack instances. Federation support is currently under development."
/>
);
}
// In development, show the full page with mock data
return <ConnectionsPageContent />;
}

View File

@@ -0,0 +1,60 @@
/**
* Workspaces Page Tests
* Tests for page structure and component integration
*/
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
// Mock next/link
vi.mock("next/link", () => ({
default: ({ children, href }: { children: React.ReactNode; href: string }): React.JSX.Element => (
<a href={href}>{children}</a>
),
}));
// Mock the WorkspaceCard component
vi.mock("@/components/workspace/WorkspaceCard", () => ({
WorkspaceCard: (): React.JSX.Element => <div data-testid="workspace-card">WorkspaceCard</div>,
}));
describe("WorkspacesPage", (): void => {
// Note: NODE_ENV is "test" during test runs, which triggers the Coming Soon view
// This tests the production-like behavior where mock data is hidden
it("should render the Coming Soon view in non-development environments", async (): Promise<void> => {
const { default: WorkspacesPage } = await import("./page");
render(<WorkspacesPage />);
// In test mode (non-development), should show Coming Soon
expect(screen.getByText("Coming Soon")).toBeInTheDocument();
expect(screen.getByText("Workspace Management")).toBeInTheDocument();
});
it("should display appropriate description for workspace feature", async (): Promise<void> => {
const { default: WorkspacesPage } = await import("./page");
render(<WorkspacesPage />);
expect(
screen.getByText(/create and manage workspaces to organize your projects/i)
).toBeInTheDocument();
});
it("should not render mock workspace data in Coming Soon view", async (): Promise<void> => {
const { default: WorkspacesPage } = await import("./page");
render(<WorkspacesPage />);
// Should not show workspace cards or create form in non-development mode
expect(screen.queryByTestId("workspace-card")).not.toBeInTheDocument();
expect(screen.queryByText("Create New Workspace")).not.toBeInTheDocument();
});
it("should include link back to settings", async (): Promise<void> => {
const { default: WorkspacesPage } = await import("./page");
render(<WorkspacesPage />);
const link = screen.getByRole("link", { name: /back to settings/i });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute("href", "/settings");
});
});

View File

@@ -4,10 +4,14 @@ import type { ReactElement } from "react";
import { useState } from "react";
import { WorkspaceCard } from "@/components/workspace/WorkspaceCard";
import { ComingSoon } from "@/components/ui/ComingSoon";
import { WorkspaceMemberRole } from "@mosaic/shared";
import Link from "next/link";
// Mock data - TODO: Replace with real API calls
// Check if we're in development mode
const isDevelopment = process.env.NODE_ENV === "development";
// Mock data - TODO: Replace with real API calls (development only)
const mockWorkspaces = [
{
id: "ws-1",
@@ -32,7 +36,11 @@ const mockMemberships = [
{ workspaceId: "ws-2", role: WorkspaceMemberRole.MEMBER, memberCount: 5 },
];
export default function WorkspacesPage(): ReactElement {
/**
* Workspaces Page Content - Development Only
* Shows mock workspace data for development purposes
*/
function WorkspacesPageContent(): ReactElement {
const [isCreating, setIsCreating] = useState(false);
const [newWorkspaceName, setNewWorkspaceName] = useState("");
@@ -140,3 +148,26 @@ export default function WorkspacesPage(): ReactElement {
</main>
);
}
/**
* Workspaces Page Entry Point
* Shows development content or Coming Soon based on environment
*/
export default function WorkspacesPage(): ReactElement {
// In production, show Coming Soon placeholder
if (!isDevelopment) {
return (
<ComingSoon
feature="Workspace Management"
description="Create and manage workspaces to organize your projects and collaborate with your team. This feature is currently under development."
>
<Link href="/settings" className="text-sm text-blue-600 hover:text-blue-700">
Back to Settings
</Link>
</ComingSoon>
);
}
// In development, show the full page with mock data
return <WorkspacesPageContent />;
}

View File

@@ -0,0 +1,118 @@
/**
* Teams Page Tests
* Tests for page structure and component integration
*/
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
// Mock next/navigation
vi.mock("next/navigation", () => ({
useParams: (): { id: string } => ({ id: "workspace-1" }),
}));
// Mock next/link
vi.mock("next/link", () => ({
default: ({ children, href }: { children: React.ReactNode; href: string }): React.JSX.Element => (
<a href={href}>{children}</a>
),
}));
// Mock the TeamCard component
vi.mock("@/components/team/TeamCard", () => ({
TeamCard: (): React.JSX.Element => <div data-testid="team-card">TeamCard</div>,
}));
// Mock @mosaic/ui components
vi.mock("@mosaic/ui", () => ({
Button: ({
children,
onClick,
disabled,
}: {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
}): React.JSX.Element => (
<button onClick={onClick} disabled={disabled}>
{children}
</button>
),
Input: ({
label,
value,
onChange,
placeholder,
disabled,
}: {
label: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
disabled?: boolean;
}): React.JSX.Element => (
<div>
<label>{label}</label>
<input value={value} onChange={onChange} placeholder={placeholder} disabled={disabled} />
</div>
),
Modal: ({
isOpen,
onClose,
title,
children,
}: {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}): React.JSX.Element | null =>
isOpen ? (
<div data-testid="modal">
<h2>{title}</h2>
<button onClick={onClose}>Close</button>
{children}
</div>
) : null,
}));
describe("TeamsPage", (): void => {
// Note: NODE_ENV is "test" during test runs, which triggers the Coming Soon view
// This tests the production-like behavior where mock data is hidden
it("should render the Coming Soon view in non-development environments", async (): Promise<void> => {
const { default: TeamsPage } = await import("./page");
render(<TeamsPage />);
// In test mode (non-development), should show Coming Soon
expect(screen.getByText("Coming Soon")).toBeInTheDocument();
expect(screen.getByText("Team Management")).toBeInTheDocument();
});
it("should display appropriate description for team feature", async (): Promise<void> => {
const { default: TeamsPage } = await import("./page");
render(<TeamsPage />);
expect(
screen.getByText(/organize workspace members into teams for better collaboration/i)
).toBeInTheDocument();
});
it("should not render mock team data in Coming Soon view", async (): Promise<void> => {
const { default: TeamsPage } = await import("./page");
render(<TeamsPage />);
// Should not show team cards or create button in non-development mode
expect(screen.queryByTestId("team-card")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /create team/i })).not.toBeInTheDocument();
});
it("should include link back to settings", async (): Promise<void> => {
const { default: TeamsPage } = await import("./page");
render(<TeamsPage />);
const link = screen.getByRole("link", { name: /back to settings/i });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute("href", "/settings");
});
});

View File

@@ -5,10 +5,19 @@ import type { ReactElement } from "react";
import { useState } from "react";
import { useParams } from "next/navigation";
import { TeamCard } from "@/components/team/TeamCard";
import { ComingSoon } from "@/components/ui/ComingSoon";
import { Button, Input, Modal } from "@mosaic/ui";
import { mockTeams } from "@/lib/api/teams";
import Link from "next/link";
export default function TeamsPage(): ReactElement {
// Check if we're in development mode
const isDevelopment = process.env.NODE_ENV === "development";
/**
* Teams Page Content - Development Only
* Shows mock team data for development purposes
*/
function TeamsPageContent(): ReactElement {
const params = useParams();
const workspaceId = params.id as string;
@@ -160,3 +169,26 @@ export default function TeamsPage(): ReactElement {
</main>
);
}
/**
* Teams Page Entry Point
* Shows development content or Coming Soon based on environment
*/
export default function TeamsPage(): ReactElement {
// In production, show Coming Soon placeholder
if (!isDevelopment) {
return (
<ComingSoon
feature="Team Management"
description="Organize workspace members into teams for better collaboration. Team management is currently under development."
>
<Link href="/settings" className="text-sm text-blue-600 hover:text-blue-700">
Back to Settings
</Link>
</ComingSoon>
);
}
// In development, show the full page with mock data
return <TeamsPageContent />;
}