Skip to content

Commit eebe1dc

Browse files
authored
Merge pull request #44 from SpaceyaTech:test-dashboard-sidebar
Refactor dashboard components and enhance testing setup
2 parents 1c6fabf + f55323d commit eebe1dc

28 files changed

+345
-88
lines changed

e2e-tests/dashboard/dahsboard-root-page.spec.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { test, expect, Locator } from "@playwright/test";
2+
async function checkDashboardSectionElements(section: Locator) {
3+
// section has a search bar
4+
await expect(
5+
section.locator('[data-test="DashboardLayoutSearchbar"]'),
6+
).toBeVisible();
7+
// section has a post project button
8+
await expect(
9+
section.locator('[data-test="DashboardpostProjectButton"]'),
10+
).toBeVisible();
11+
// section has a user dropdown
12+
await expect(
13+
section.locator('[data-test="DashboardUserDropdown"]'),
14+
).toBeVisible();
15+
}
16+
17+
test("test dashboard navbar", async ({ page }) => {
18+
await page.goto("/dashboard");
19+
await expect(page).toHaveTitle(/Dashboard/);
20+
const desktopSection = await page.locator(
21+
'[data-test="DashboardLayoutHeaderDesktop"]',
22+
);
23+
const mobileSection = await page.locator(
24+
'[data-test="DashboardLayoutHeaderMobile"]',
25+
);
26+
// desktop section is visible and mobile is not
27+
await expect(desktopSection).toBeVisible();
28+
await expect(mobileSection).not.toBeVisible();
29+
// section has a logo
30+
// on mobile the logo section will be hidden and only shown in the sidebar
31+
await expect(
32+
desktopSection.locator('[data-test="DashboardLayoutHeaderLogo"]'),
33+
).toBeVisible();
34+
// check the section contains the expected elements
35+
await checkDashboardSectionElements(desktopSection);
36+
// mobile section is not visible
37+
38+
// resize the viewport to mobile
39+
await page.setViewportSize({ width: 400, height: 1000 });
40+
// monile section is visible and desktop is not
41+
await expect(mobileSection).toBeVisible();
42+
await expect(desktopSection).not.toBeVisible();
43+
// check the section contains the expected elements
44+
await checkDashboardSectionElements(mobileSection);
45+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { test, expect, Locator, Page } from "@playwright/test";
2+
import { dashboard_routes } from "../../src/components/navigation/routes";
3+
4+
async function checkLinkNameAndNavigateToIt(page: Page, locator: Locator,mobile=false) {
5+
for await (const [index, link] of dashboard_routes.entries()) {
6+
await expect(await locator.nth(index).textContent()).toBe(link.name);
7+
await locator.nth(index).click();
8+
await expect(page).toHaveTitle(`Dashboard - ${link.name}`);
9+
const breadCrumbs = await page.locator('[data-test="OneTSRBreadCrumb"]');
10+
await expect(await breadCrumbs.count()).toBeGreaterThan(0)
11+
const breadCrumbName = link.href.split("/").pop();
12+
await expect(await breadCrumbs.first().textContent()).toBe(breadCrumbName);
13+
await page.goBack();
14+
if(mobile){
15+
// open the mobile sidebar again
16+
await page.locator('[data-test="DashboardLayoutSidebarTrigger"]').click();
17+
}
18+
}
19+
}
20+
21+
test("test dashboard sidebar", async ({ page }) => {
22+
await page.goto("/dashboard");
23+
await expect(page).toHaveTitle(/Dashboard/);
24+
const sidebarTrigger = await page.locator(
25+
'[data-test="DashboardLayoutSidebarTrigger"]',
26+
);
27+
await expect(sidebarTrigger).toBeVisible();
28+
// sidebar nly shows icons by defalut unti expanded to show text
29+
const sidebarLinks = await page.locator(
30+
'[data-test="DashboardSidebarLinks"]',
31+
);
32+
// sidebar is visible by default when in dsktop mode
33+
await expect(sidebarLinks).toBeVisible();
34+
// select the first link in the sidebar
35+
const nestedSidebarLinks = await sidebarLinks.locator(
36+
'[data-test="DashboardSidebarLink"]',
37+
);
38+
await expect(await nestedSidebarLinks.count()).toBe(dashboard_routes.length);
39+
await expect(nestedSidebarLinks.first()).toBeVisible();
40+
const sidebarLinkName = await nestedSidebarLinks.locator(
41+
'[data-test="DashboardSidebarLinkName"]',
42+
);
43+
// sidebar link name is not visible by default until sidebar is expanded
44+
await expect(sidebarLinkName.first()).not.toBeVisible();
45+
// click the sidebar trigger to expand the sidebar
46+
await sidebarTrigger.click();
47+
// sidebar link name is now visible , and clicking should take us to the respective page
48+
await checkLinkNameAndNavigateToIt(page, sidebarLinkName);
49+
// click tp minimize the sidebar
50+
await sidebarTrigger.click();
51+
await expect(sidebarLinkName.first()).not.toBeVisible();
52+
// resize to mobile view
53+
await page.setViewportSize({ width: 400, height: 1000 });
54+
// sidebar is not visible by default when in mobile view
55+
await expect(sidebarLinks).not.toBeVisible();
56+
});
57+
58+
test("test mobile dashboard sidebar", async ({ page }) => {
59+
await page.setViewportSize({ width: 400, height: 1000 });
60+
await page.goto("/dashboard");
61+
await expect(page).toHaveTitle(/Dashboard/);
62+
const sidebarTrigger = await page.locator(
63+
'[data-test="DashboardLayoutSidebarTrigger"]',
64+
);
65+
await sidebarTrigger.click();
66+
// mobile version of the sidebar is visible
67+
const mobileSidebar = await page.locator(
68+
'[data-sidebar="sidebar"][data-mobile="true"]',
69+
);
70+
await expect(mobileSidebar).toBeVisible();
71+
const mobileSidebarLinks = await mobileSidebar.locator(
72+
'[data-test="DashboardSidebarLink"]',
73+
);
74+
await expect(await mobileSidebarLinks.count()).toBe(dashboard_routes.length);
75+
await checkLinkNameAndNavigateToIt(page, mobileSidebarLinks,true);
76+
});
77+
78+

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
"vitest": "vitest",
1212
"vitest:ui": "vitest --ui",
1313
"playwright": "playwright test --ui",
14-
"test": "npm run vitest && npm run playwright",
14+
"test": "vitest run && playwright test",
1515
"build": "tsc -b && vite build",
16-
"dryrun":"npm run lint && npm run test && npm run build",
17-
"preview": "vite preview"
16+
"dryrun": "npm run lint && npm run test && npm run build",
17+
"preview": "vite preview",
18+
"test-ct": "playwright test -c playwright-ct.config.ts"
1819
},
1920
"dependencies": {
2021
"@hookform/resolvers": "^3.9.0",
@@ -69,6 +70,7 @@
6970
},
7071
"devDependencies": {
7172
"@eslint/js": "^9.13.0",
73+
"@playwright/experimental-ct-react": "^1.49.0",
7274
"@playwright/test": "^1.49.0",
7375
"@tanstack/eslint-plugin-query": "^5.61.6",
7476
"@tanstack/react-query-devtools": "^5.59.19",

playwright-ct.config.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { defineConfig, devices } from '@playwright/experimental-ct-react';
2+
3+
/**
4+
* See https://playwright.dev/docs/test-configuration.
5+
*/
6+
export default defineConfig({
7+
testDir: './',
8+
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
9+
snapshotDir: './__snapshots__',
10+
/* Maximum time one test can run for. */
11+
timeout: 10 * 1000,
12+
/* Run tests in files in parallel */
13+
fullyParallel: true,
14+
/* Fail the build on CI if you accidentally left test.only in the source code. */
15+
forbidOnly: !!process.env.CI,
16+
/* Retry on CI only */
17+
retries: process.env.CI ? 2 : 0,
18+
/* Opt out of parallel tests on CI. */
19+
workers: process.env.CI ? 1 : undefined,
20+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
21+
reporter: 'html',
22+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
23+
use: {
24+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
25+
trace: 'on-first-retry',
26+
27+
/* Port to use for Playwright component endpoint. */
28+
ctPort: 3100,
29+
},
30+
31+
/* Configure projects for major browsers */
32+
projects: [
33+
{
34+
name: 'chromium',
35+
use: { ...devices['Desktop Chrome'] },
36+
},
37+
{
38+
name: 'firefox',
39+
use: { ...devices['Desktop Firefox'] },
40+
},
41+
{
42+
name: 'webkit',
43+
use: { ...devices['Desktop Safari'] },
44+
},
45+
],
46+
});

playwright/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Testing Page</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="./index.tsx"></script>
11+
</body>
12+
</html>

playwright/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Import styles, initialize component theme here.
2+
// import '../src/common.css';
3+
import "../src/routes/styles.css";

pnpm-lock.yaml

Lines changed: 43 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/navigation/routes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const dashboard_routes = [
2424
icon: <Trophy />,
2525
},
2626
{
27-
name: "OS projects",
27+
name: "OS Projects",
2828
href: "/dashboard/os-projects",
2929
icon: <Layers />,
3030
},

src/lib/tanstack/router/TSRBreadCrumbs.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export function TSRBreadCrumbs({}: TSRBreadCrumbsProps) {
2222
const { breadcrumb_routes } = useTSRBreadCrumbs();
2323
if (breadcrumb_routes.length < 2) return null;
2424
return (
25-
<div className="gap-0.1 flex flex-wrap p-1 px-3 md:justify-end">
25+
<div
26+
data-test="TSRBreadCrumbs"
27+
className="gap-0.1 flex flex-wrap p-1 px-3 md:justify-end"
28+
>
2629
<Breadcrumb>
2730
<BreadcrumbList>
2831
{breadcrumb_routes.map((crumb) => {
@@ -32,7 +35,10 @@ export function TSRBreadCrumbs({}: TSRBreadCrumbsProps) {
3235
) {
3336
return (
3437
<BreadcrumbItem key={crumb.path}>
35-
<BreadcrumbPage className="hover:text-accent-text line-clamp-1 cursor-pointer text-xs hover:max-w-fit hover:duration-300 hover:animate-in hover:fade-in">
38+
<BreadcrumbPage
39+
data-test="OneTSRBreadCrumb"
40+
className="hover:text-accent-text line-clamp-1 cursor-pointer text-xs hover:max-w-fit hover:duration-300 hover:animate-in hover:fade-in"
41+
>
3642
{crumb.name}
3743
</BreadcrumbPage>
3844
</BreadcrumbItem>

src/routes/dashboard/-components/dashoboard-sidebar/DashboardLayout.tsx renamed to src/routes/dashboard/-components/DashboardLayout.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import {
1212
} from "@/components/ui/sidebar";
1313
import { Separator } from "@/components/ui/separator";
1414
import { Outlet } from "@tanstack/react-router";
15-
import { DashboardSidebarHeader } from "./DashboardSidebarHeader";
16-
import { DashboardSidebarLinks } from "./DashboardSidebarLinks";
15+
import { DashboardSidebarHeader } from "./dashoboard-sidebar/DashboardSidebarHeader";
16+
import { DashboardSidebarLinks } from "./dashoboard-sidebar/DashboardSidebarLinks";
1717
import { TSRBreadCrumbs } from "@/lib/tanstack/router/TSRBreadCrumbs";
18-
import { DashboardTheme } from "./DashboardTheme";
19-
import { DashboardLayoutHeader } from "../dashboard-layout/DashboardLayoutHeader";
20-
import { DashboardSidebarActions } from "./DashboardSidebarActions";
18+
import { DashboardTheme } from "./dashoboard-sidebar/DashboardTheme";
19+
import { DashboardLayoutHeader } from "./dashboard-layout/DashboardLayoutHeader";
20+
import { DashboardSidebarActions } from "./dashoboard-sidebar/DashboardSidebarActions";
2121
import { Helmet } from "@/components/wrappers/custom-helmet";
2222

2323
interface DashboardLayoutProps {
@@ -29,19 +29,22 @@ export function DashboardLayout({ sidebar_props }: DashboardLayoutProps) {
2929
return (
3030
<SidebarProvider defaultOpen={false}>
3131
<Helmet title="Dashboard" description="Dashboard" />
32-
<SidebarInset>
32+
<SidebarInset >
3333
<div className="h-full " >
3434
<header className="sticky top-0 z-30 flex w-full flex-col gap-2 bg-base-100">
3535
<DashboardLayoutHeader />
3636
<div className="flex items-center gap-2 px-4">
37-
<SidebarTrigger className="-ml-1" />
37+
<SidebarTrigger className="-ml-1" data-test="DashboardLayoutSidebarTrigger"/>
3838
<Separator orientation="vertical" className="mr-2 h-4" />
3939
<TSRBreadCrumbs />
4040
</div>
4141
</header>
4242
{/* main content */}
4343
<div className="flex h-full w-full gap-2 ">
44-
<Sidebar className="top-[20%]" collapsible="icon" {...sidebar_props}>
44+
<Sidebar
45+
data-test="DashboardLayoutSidebar"
46+
className="top-[20%]"
47+
collapsible="icon" {...sidebar_props}>
4548
<SidebarHeader>
4649
<DashboardSidebarHeader />
4750
</SidebarHeader>

0 commit comments

Comments
 (0)