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:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
118
apps/web/src/app/settings/workspaces/[id]/teams/page.test.tsx
Normal file
118
apps/web/src/app/settings/workspaces/[id]/teams/page.test.tsx
Normal 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");
|
||||
});
|
||||
});
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
51
apps/web/src/components/ui/ComingSoon.test.tsx
Normal file
51
apps/web/src/components/ui/ComingSoon.test.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* ComingSoon Component Tests
|
||||
* Tests for the production placeholder component
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { ComingSoon } from "./ComingSoon";
|
||||
|
||||
describe("ComingSoon", (): void => {
|
||||
it("should render with default props", (): void => {
|
||||
render(<ComingSoon feature="Test Feature" />);
|
||||
|
||||
expect(screen.getByText("Coming Soon")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Feature")).toBeInTheDocument();
|
||||
expect(screen.getByText(/This feature is currently under development/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render custom description", (): void => {
|
||||
render(<ComingSoon feature="Custom Feature" description="Custom description text" />);
|
||||
|
||||
expect(screen.getByText("Custom Feature")).toBeInTheDocument();
|
||||
expect(screen.getByText("Custom description text")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render children when provided", (): void => {
|
||||
render(
|
||||
<ComingSoon feature="Feature">
|
||||
<div data-testid="child-content">Child content</div>
|
||||
</ComingSoon>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("child-content")).toBeInTheDocument();
|
||||
expect(screen.getByText("Child content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should apply custom className", (): void => {
|
||||
render(<ComingSoon feature="Test" className="custom-class" />);
|
||||
|
||||
const container = screen.getByRole("main");
|
||||
expect(container).toHaveClass("custom-class");
|
||||
});
|
||||
|
||||
it("should render construction icon by default", (): void => {
|
||||
render(<ComingSoon feature="Test" />);
|
||||
|
||||
// The icon should be present (as an SVG)
|
||||
const svg = document.querySelector("svg");
|
||||
expect(svg).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
72
apps/web/src/components/ui/ComingSoon.tsx
Normal file
72
apps/web/src/components/ui/ComingSoon.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* ComingSoon Component
|
||||
* Displays a placeholder for features not yet available in production.
|
||||
* Used to prevent mock data from being shown to production users.
|
||||
*/
|
||||
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
|
||||
export interface ComingSoonProps {
|
||||
/** The name of the feature being developed */
|
||||
feature: string;
|
||||
/** Optional custom description */
|
||||
description?: string;
|
||||
/** Optional children to render below the message */
|
||||
children?: ReactNode;
|
||||
/** Optional className for the container */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ComingSoon displays a friendly placeholder for incomplete features.
|
||||
* Use this in production when a feature is under development to avoid
|
||||
* showing mock or placeholder data to users.
|
||||
*/
|
||||
export function ComingSoon({
|
||||
feature,
|
||||
description,
|
||||
children,
|
||||
className = "",
|
||||
}: ComingSoonProps): ReactElement {
|
||||
const defaultDescription =
|
||||
"This feature is currently under development and will be available soon.";
|
||||
|
||||
return (
|
||||
<main className={`container mx-auto px-4 py-8 max-w-3xl ${className}`} role="main">
|
||||
<div className="flex flex-col items-center justify-center min-h-[400px] bg-gray-50 rounded-lg border border-gray-200 p-8 text-center">
|
||||
{/* Construction Icon */}
|
||||
<svg
|
||||
className="w-16 h-16 text-blue-400 mb-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Coming Soon Badge */}
|
||||
<span className="inline-block px-4 py-1.5 bg-blue-100 text-blue-700 text-sm font-medium rounded-full mb-4">
|
||||
Coming Soon
|
||||
</span>
|
||||
|
||||
{/* Feature Name */}
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-3">{feature}</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-600 max-w-md mb-6">{description ?? defaultDescription}</p>
|
||||
|
||||
{/* Optional Children */}
|
||||
{children && <div className="mt-4">{children}</div>}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default ComingSoon;
|
||||
Reference in New Issue
Block a user