Skip to content

Commit c42980c

Browse files
authored
Merge pull request #1 from hexawulf/public-view-system
feat: Implement public view system
2 parents 852ef0f + 3b327ba commit c42980c

File tree

11 files changed

+1017
-118
lines changed

11 files changed

+1017
-118
lines changed

INTEGRATION_TESTS.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Integration Test Outlines for Public View System
2+
3+
This document outlines key integration tests for the public view system, including the `PublicHome` page and routing behaviors. These tests would typically be implemented using a framework like Cypress or Playwright, combined with React Testing Library for component interactions where appropriate.
4+
5+
## I. PublicHome Page (`client/src/pages/PublicHome.tsx`)
6+
7+
**Setup:**
8+
* Ensure the backend server is running and accessible.
9+
* Mock or seed the database with a known set of public and private snippets. Include snippets with various languages and tags.
10+
* Use a testing utility or direct API calls to `/api/public/snippets` to verify data setup.
11+
12+
**Test Scenarios:**
13+
14+
1. **Initial Load and Display:**
15+
* **Description:** Verify that `PublicHome` fetches and displays public snippets correctly on initial load.
16+
* **Steps:**
17+
1. Navigate to the `/` route as an unauthenticated user.
18+
2. Observe that the `PublicHome` component renders.
19+
3. Check that a list/grid of snippets is displayed.
20+
4. Verify that only snippets marked `isPublic: true` in the database are shown.
21+
5. Confirm that elements like the header, tagline, and "Sign In" button are present.
22+
6. Check for loading states while data is being fetched.
23+
* **Assertions:**
24+
* Correct number of public snippets displayed.
25+
* Snippet cards show appropriate information (title, description snippet, language, "Public" badge).
26+
* No private snippets are visible.
27+
* Header and sign-in elements are visible.
28+
29+
2. **Search Functionality:**
30+
* **Description:** Test if searching filters the displayed public snippets.
31+
* **Steps:**
32+
1. On `PublicHome`, type a search term (e.g., a keyword from a known public snippet's title or description) into the search bar.
33+
2. Observe the list of snippets updating.
34+
* **Assertions:**
35+
* Only snippets matching the search term are displayed.
36+
* If the search term matches no public snippets, an appropriate "No public snippets found" message is shown.
37+
* The filtering should be reasonably fast (client-side or server-side depending on implementation).
38+
39+
3. **Filter Functionality (Language/Tags):**
40+
* **Description:** Test if filtering by language (and tags, if implemented) works correctly.
41+
* **Steps:**
42+
1. On `PublicHome`, select a language from the language filter dropdown.
43+
2. Observe the list of snippets updating.
44+
3. (If applicable) Select a tag from a tag filter dropdown and observe further filtering.
45+
* **Assertions:**
46+
* Only snippets matching the selected language (and/or tag) are displayed.
47+
* Combining search and filters works as expected.
48+
* If no snippets match the filter criteria, an appropriate message is shown.
49+
50+
4. **Empty State:**
51+
* **Description:** Verify the behavior when no public snippets are available or match filters.
52+
* **Steps:**
53+
1. Ensure the database has no snippets marked `isPublic: true`.
54+
2. Navigate to `PublicHome`.
55+
3. OR: Apply filters that result in no matches.
56+
* **Assertions:**
57+
* The correct "No public snippets found" (or similar) message is displayed.
58+
* The layout remains intact.
59+
60+
5. **Sign-In Button Navigation:**
61+
* **Description:** Ensure the "Sign In" button navigates to the login flow.
62+
* **Steps:**
63+
1. On `PublicHome`, click the "Sign In / Sign Up" button.
64+
* **Assertions:**
65+
* The user is redirected to the application's login page/mechanism (e.g., `/login` or triggers the Firebase auth flow).
66+
67+
## II. Routing and Authentication State
68+
69+
**Setup:**
70+
* As above, backend running with mixed public/private data.
71+
* Ability to simulate user login/logout within the test environment.
72+
73+
**Test Scenarios:**
74+
75+
1. **Unauthenticated User Access:**
76+
* **Description:** Verify routes accessible to unauthenticated users.
77+
* **Steps:**
78+
1. As an unauthenticated user, navigate to `/`.
79+
2. Navigate to `/shared/:shareId` (using a share ID of a public snippet).
80+
3. Navigate to `/shared/:shareId` (using a share ID of a private snippet).
81+
4. Attempt to navigate to an authenticated route (e.g., `/snippets` or `/settings`).
82+
* **Assertions:**
83+
* `/` loads `PublicHome`.
84+
* `/shared/:shareId` for a public snippet loads the `SharedSnippet` page and displays the snippet.
85+
* `/shared/:shareId` for a private snippet either shows a "not found/access denied" message within `SharedSnippet` or redirects (behavior depends on `SharedSnippet` implementation).
86+
* Access to `/snippets` or `/settings` redirects to `PublicHome` (or the login page).
87+
88+
2. **Authenticated User Access:**
89+
* **Description:** Verify routes and UI changes for authenticated users.
90+
* **Steps:**
91+
1. Log in as a user.
92+
2. Navigate to `/`.
93+
3. Navigate to other authenticated routes like `/snippets`, `/collections`.
94+
4. Navigate to `/shared/:shareId` (using a share ID of a snippet they own, and one they don't but is public).
95+
* **Assertions:**
96+
* `/` loads the authenticated dashboard (e.g., `Home.tsx` for authenticated users, not `PublicHome`).
97+
* Authenticated routes are accessible and render correctly.
98+
* Layout for authenticated users includes the sidebar and full header.
99+
* Snippet cards on authenticated pages show owner controls for owned snippets.
100+
* `SharedSnippet` page works correctly for owned and public shared snippets.
101+
102+
3. **Navigation from Public to Authenticated:**
103+
* **Description:** Test the transition when a user signs in from `PublicHome`.
104+
* **Steps:**
105+
1. Start on `PublicHome` as an unauthenticated user.
106+
2. Click "Sign In" and complete the login process.
107+
* **Assertions:**
108+
* After successful login, the user is redirected to the authenticated dashboard (e.g., `/`).
109+
* The UI updates to the authenticated layout (sidebar appears, etc.).
110+
111+
## III. Shared Snippet Page (`client/src/pages/SharedSnippet.tsx`)
112+
113+
**Note:** These depend heavily on `SharedSnippet.tsx`'s internal logic, which also needs to be context-aware.
114+
115+
1. **Public Shared Snippet (Unauthenticated User):**
116+
* **Description:** An unauthenticated user views a publicly shared snippet.
117+
* **Assertions:** Snippet content is visible. No owner controls.
118+
2. **Private Shared Snippet (Unauthenticated User):**
119+
* **Description:** An unauthenticated user attempts to view a private shared snippet.
120+
* **Assertions:** Snippet content is NOT visible. A "not found" or "access denied" message is shown.
121+
3. **Private Shared Snippet (Authenticated Owner):**
122+
* **Description:** The owner views their own private shared snippet.
123+
* **Assertions:** Snippet content is visible. Owner controls might be visible (depends on design).
124+
4. **Private Shared Snippet (Authenticated Non-Owner):**
125+
* **Description:** An authenticated user (not the owner) attempts to view a private shared snippet.
126+
* **Assertions:** Snippet content is NOT visible. A "not found" or "access denied" message.
127+
5. **Public Shared Snippet (Authenticated User):**
128+
* **Description:** An authenticated user views a public snippet (owned by someone else).
129+
* **Assertions:** Snippet content is visible. No owner controls.
130+
```

client/src/App.tsx

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ import { SnippetProvider } from "@/contexts/SnippetContext";
99
import { CollectionProvider } from "@/contexts/CollectionContext";
1010
import { useAuthContext } from "@/contexts/AuthContext";
1111
import NotFound from "@/pages/not-found";
12-
import Home from "@/pages/Home";
12+
import Home from "@/pages/Home"; // This is the authenticated dashboard
13+
import PublicHome from '@/pages/PublicHome';
1314
import Snippets from "@/pages/Snippets";
1415
import Collections from "@/pages/Collections";
1516
import CollectionDetail from "@/pages/CollectionDetail";
1617
import Tags from "@/pages/Tags";
1718
import Settings from "@/pages/Settings";
1819
import SharedSnippet from "@/pages/SharedSnippet";
1920

20-
function Router() {
21+
// Renamed from Router to AuthenticatedRouter
22+
function AuthenticatedRouter() {
2123
return (
2224
<Switch>
23-
<Route path="/" component={Home} />
25+
<Route path="/" component={Home} /> {/* Authenticated home/dashboard */}
2426
<Route path="/snippets" component={Snippets} />
2527
<Route path="/collections" component={Collections} />
2628
<Route path="/collections/:id" component={CollectionDetail} />
@@ -32,6 +34,17 @@ function Router() {
3234
);
3335
}
3436

37+
function PublicRouter() {
38+
return (
39+
<Switch>
40+
<Route path="/" component={PublicHome} />
41+
<Route path="/shared/:shareId" component={SharedSnippet} />
42+
{/* For non-matched routes, redirect to PublicHome */}
43+
<Route component={PublicHome} />
44+
</Switch>
45+
);
46+
}
47+
3548
// Added debug component to show authentication state
3649
function AuthDebug({ user, loading }: { user: any, loading: boolean }) {
3750
return (
@@ -48,7 +61,7 @@ function AuthDebug({ user, loading }: { user: any, loading: boolean }) {
4861
}
4962

5063
export default function App() {
51-
const { user, loading, signIn } = useAuthContext();
64+
const { user, loading } = useAuthContext(); // Removed signIn from here as it's not used directly for button anymore
5265
const [showDebug, setShowDebug] = useState(false);
5366

5467
// Add explicit debugging to track auth state
@@ -105,38 +118,16 @@ export default function App() {
105118
);
106119
}
107120

108-
// 2) Not signed in → show Google button
109-
if (!user) {
110-
console.log("[App] No user detected, showing login button");
111-
return (
112-
<div className="h-screen flex flex-col items-center justify-center">
113-
<div className="mb-4">
114-
<h1 className="text-xl font-bold mb-2">CodePatchwork</h1>
115-
<p>Please sign in to access your snippets</p>
116-
</div>
117-
<button
118-
onClick={() => {
119-
console.log("[App] Sign in button clicked");
120-
signIn();
121-
}}
122-
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
123-
>
124-
Sign in with Google
125-
</button>
126-
{showDebug && <AuthDebug user={user} loading={loading} />}
127-
</div>
128-
);
129-
}
130-
131-
// 3) Signed in → render the full app
132-
console.log("[App] User authenticated, rendering full app:", user);
121+
// 2) Routing logic based on authentication state
122+
// PublicHome will now handle the sign-in prompt.
123+
console.log(`[App] Rendering routers. User: ${user ? user.id : 'null'}, Loading: ${loading}`);
133124
return (
134125
<ThemeProvider>
135126
<CodeThemeProvider>
136-
<SnippetProvider>
137-
<CollectionProvider>
127+
<SnippetProvider> {/* SnippetProvider might be needed by SharedSnippet too */}
128+
<CollectionProvider> {/* CollectionProvider might be needed by SharedSnippet too */}
138129
<TooltipProvider>
139-
<Router />
130+
{user ? <AuthenticatedRouter /> : <PublicRouter />}
140131
{showDebug && <AuthDebug user={user} loading={loading} />}
141132
</TooltipProvider>
142133
</CollectionProvider>

client/src/components/Layout.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import Header from "./Header";
44

55
interface LayoutProps {
66
children: ReactNode;
7+
isPublicView?: boolean;
78
}
89

9-
export default function Layout({ children }: LayoutProps) {
10+
export default function Layout({ children, isPublicView = false }: LayoutProps) {
1011
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
1112

1213
const toggleMobileMenu = () => {
@@ -19,25 +20,29 @@ export default function Layout({ children }: LayoutProps) {
1920

2021
return (
2122
<div className="flex flex-col md:flex-row h-screen overflow-hidden">
22-
{/* Sidebar - hidden on mobile, visible on larger screens */}
23-
<Sidebar className="w-full md:w-64 bg-white dark:bg-gray-900 border-r border-slate-200 dark:border-slate-700 flex-shrink-0 hidden md:block h-full overflow-y-auto" />
23+
{/* Sidebar - hidden on mobile, visible on larger screens, hidden in public view */}
24+
{!isPublicView && (
25+
<Sidebar className="w-full md:w-64 bg-white dark:bg-gray-900 border-r border-slate-200 dark:border-slate-700 flex-shrink-0 hidden md:block h-full overflow-y-auto" />
26+
)}
2427

25-
{/* Mobile Sidebar */}
26-
<div className={`fixed inset-0 z-40 ${mobileMenuOpen ? "" : "hidden"}`}>
27-
<div
28-
className="absolute inset-0 bg-black/50 dark:bg-black/70"
29-
onClick={closeMobileMenu}
30-
></div>
31-
<div className={`absolute top-0 left-0 h-full w-64 bg-white dark:bg-gray-900 border-r border-slate-200 dark:border-slate-700 z-50 transform transition-transform duration-300 ${mobileMenuOpen ? "" : "-translate-x-full"}`}>
32-
<Sidebar onClose={closeMobileMenu} />
28+
{/* Mobile Sidebar - Entire mechanism hidden in public view */}
29+
{!isPublicView && (
30+
<div className={`fixed inset-0 z-40 ${mobileMenuOpen ? "" : "hidden"}`}>
31+
<div
32+
className="absolute inset-0 bg-black/50 dark:bg-black/70"
33+
onClick={closeMobileMenu}
34+
></div>
35+
<div className={`absolute top-0 left-0 h-full w-64 bg-white dark:bg-gray-900 border-r border-slate-200 dark:border-slate-700 z-50 transform transition-transform duration-300 ${mobileMenuOpen ? "" : "-translate-x-full"}`}>
36+
<Sidebar onClose={closeMobileMenu} />
37+
</div>
3338
</div>
34-
</div>
39+
)}
3540

3641
<div className="flex-grow flex flex-col overflow-hidden">
37-
{/* Header */}
38-
<Header toggleMobileMenu={toggleMobileMenu} />
42+
{/* Header - Pass isPublicView to handle mobile menu toggle visibility */}
43+
<Header toggleMobileMenu={toggleMobileMenu} isPublicView={isPublicView} />
3944

40-
{/* Main Content Area - FIXED */}
45+
{/* Main Content Area - Should expand if sidebar is not present */}
4146
<main className="flex-1 overflow-y-auto p-4 bg-slate-50 dark:bg-gray-800 min-h-0">
4247
{children}
4348
</main>

0 commit comments

Comments
 (0)