Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor dashboard components and enhance testing setup #44

Merged
merged 6 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions e2e-tests/dashboard/dahsboard-root-page.spec.ts

This file was deleted.

45 changes: 45 additions & 0 deletions e2e-tests/dashboard/dashboard-layout-navbar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { test, expect, Locator } from "@playwright/test";
async function checkDashboardSectionElements(section: Locator) {
// section has a search bar
await expect(
section.locator('[data-test="DashboardLayoutSearchbar"]'),
).toBeVisible();
// section has a post project button
await expect(
section.locator('[data-test="DashboardpostProjectButton"]'),
).toBeVisible();
// section has a user dropdown
await expect(
section.locator('[data-test="DashboardUserDropdown"]'),
).toBeVisible();
}

test("test dashboard navbar", async ({ page }) => {
await page.goto("/dashboard");
await expect(page).toHaveTitle(/Dashboard/);
const desktopSection = await page.locator(
'[data-test="DashboardLayoutHeaderDesktop"]',
);
const mobileSection = await page.locator(
'[data-test="DashboardLayoutHeaderMobile"]',
);
// desktop section is visible and mobile is not
await expect(desktopSection).toBeVisible();
await expect(mobileSection).not.toBeVisible();
// section has a logo
// on mobile the logo section will be hidden and only shown in the sidebar
await expect(
desktopSection.locator('[data-test="DashboardLayoutHeaderLogo"]'),
).toBeVisible();
// check the section contains the expected elements
await checkDashboardSectionElements(desktopSection);
// mobile section is not visible

// resize the viewport to mobile
await page.setViewportSize({ width: 400, height: 1000 });
// monile section is visible and desktop is not
await expect(mobileSection).toBeVisible();
await expect(desktopSection).not.toBeVisible();
// check the section contains the expected elements
await checkDashboardSectionElements(mobileSection);
});
78 changes: 78 additions & 0 deletions e2e-tests/dashboard/dashboard-layout-sidebar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { test, expect, Locator, Page } from "@playwright/test";
import { dashboard_routes } from "../../src/components/navigation/routes";

async function checkLinkNameAndNavigateToIt(page: Page, locator: Locator,mobile=false) {
for await (const [index, link] of dashboard_routes.entries()) {
await expect(await locator.nth(index).textContent()).toBe(link.name);
await locator.nth(index).click();
await expect(page).toHaveTitle(`Dashboard - ${link.name}`);
const breadCrumbs = await page.locator('[data-test="OneTSRBreadCrumb"]');
await expect(await breadCrumbs.count()).toBeGreaterThan(0)
const breadCrumbName = link.href.split("/").pop();
await expect(await breadCrumbs.first().textContent()).toBe(breadCrumbName);
await page.goBack();
if(mobile){
// open the mobile sidebar again
await page.locator('[data-test="DashboardLayoutSidebarTrigger"]').click();
}
}
}

test("test dashboard sidebar", async ({ page }) => {
await page.goto("/dashboard");
await expect(page).toHaveTitle(/Dashboard/);
const sidebarTrigger = await page.locator(
'[data-test="DashboardLayoutSidebarTrigger"]',
);
await expect(sidebarTrigger).toBeVisible();
// sidebar nly shows icons by defalut unti expanded to show text
const sidebarLinks = await page.locator(
'[data-test="DashboardSidebarLinks"]',
);
// sidebar is visible by default when in dsktop mode
await expect(sidebarLinks).toBeVisible();
// select the first link in the sidebar
const nestedSidebarLinks = await sidebarLinks.locator(
'[data-test="DashboardSidebarLink"]',
);
await expect(await nestedSidebarLinks.count()).toBe(dashboard_routes.length);
await expect(nestedSidebarLinks.first()).toBeVisible();
const sidebarLinkName = await nestedSidebarLinks.locator(
'[data-test="DashboardSidebarLinkName"]',
);
// sidebar link name is not visible by default until sidebar is expanded
await expect(sidebarLinkName.first()).not.toBeVisible();
// click the sidebar trigger to expand the sidebar
await sidebarTrigger.click();
// sidebar link name is now visible , and clicking should take us to the respective page
await checkLinkNameAndNavigateToIt(page, sidebarLinkName);
// click tp minimize the sidebar
await sidebarTrigger.click();
await expect(sidebarLinkName.first()).not.toBeVisible();
// resize to mobile view
await page.setViewportSize({ width: 400, height: 1000 });
// sidebar is not visible by default when in mobile view
await expect(sidebarLinks).not.toBeVisible();
});

test("test mobile dashboard sidebar", async ({ page }) => {
await page.setViewportSize({ width: 400, height: 1000 });
await page.goto("/dashboard");
await expect(page).toHaveTitle(/Dashboard/);
const sidebarTrigger = await page.locator(
'[data-test="DashboardLayoutSidebarTrigger"]',
);
await sidebarTrigger.click();
// mobile version of the sidebar is visible
const mobileSidebar = await page.locator(
'[data-sidebar="sidebar"][data-mobile="true"]',
);
await expect(mobileSidebar).toBeVisible();
const mobileSidebarLinks = await mobileSidebar.locator(
'[data-test="DashboardSidebarLink"]',
);
await expect(await mobileSidebarLinks.count()).toBe(dashboard_routes.length);
await checkLinkNameAndNavigateToIt(page, mobileSidebarLinks,true);
});


8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
"vitest": "vitest",
"vitest:ui": "vitest --ui",
"playwright": "playwright test --ui",
"test": "npm run vitest && npm run playwright",
"test": "vitest run && playwright test",
"build": "tsc -b && vite build",
"dryrun":"npm run lint && npm run test && npm run build",
"preview": "vite preview"
"dryrun": "npm run lint && npm run test && npm run build",
"preview": "vite preview",
"test-ct": "playwright test -c playwright-ct.config.ts"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
Expand Down Expand Up @@ -69,6 +70,7 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@playwright/experimental-ct-react": "^1.49.0",
"@playwright/test": "^1.49.0",
"@tanstack/eslint-plugin-query": "^5.61.6",
"@tanstack/react-query-devtools": "^5.59.19",
Expand Down
46 changes: 46 additions & 0 deletions playwright-ct.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineConfig, devices } from '@playwright/experimental-ct-react';

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './',
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: './__snapshots__',
/* Maximum time one test can run for. */
timeout: 10 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',

/* Port to use for Playwright component endpoint. */
ctPort: 3100,
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
12 changes: 12 additions & 0 deletions playwright/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>
3 changes: 3 additions & 0 deletions playwright/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Import styles, initialize component theme here.
// import '../src/common.css';
import "../src/routes/styles.css";
44 changes: 43 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/components/navigation/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const dashboard_routes = [
icon: <Trophy />,
},
{
name: "OS projects",
name: "OS Projects",
href: "/dashboard/os-projects",
icon: <Layers />,
},
Expand Down
10 changes: 8 additions & 2 deletions src/lib/tanstack/router/TSRBreadCrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export function TSRBreadCrumbs({}: TSRBreadCrumbsProps) {
const { breadcrumb_routes } = useTSRBreadCrumbs();
if (breadcrumb_routes.length < 2) return null;
return (
<div className="gap-0.1 flex flex-wrap p-1 px-3 md:justify-end">
<div
data-test="TSRBreadCrumbs"
className="gap-0.1 flex flex-wrap p-1 px-3 md:justify-end"
>
<Breadcrumb>
<BreadcrumbList>
{breadcrumb_routes.map((crumb) => {
Expand All @@ -32,7 +35,10 @@ export function TSRBreadCrumbs({}: TSRBreadCrumbsProps) {
) {
return (
<BreadcrumbItem key={crumb.path}>
<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">
<BreadcrumbPage
data-test="OneTSRBreadCrumb"
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"
>
{crumb.name}
</BreadcrumbPage>
</BreadcrumbItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import {
} from "@/components/ui/sidebar";
import { Separator } from "@/components/ui/separator";
import { Outlet } from "@tanstack/react-router";
import { DashboardSidebarHeader } from "./DashboardSidebarHeader";
import { DashboardSidebarLinks } from "./DashboardSidebarLinks";
import { DashboardSidebarHeader } from "./dashoboard-sidebar/DashboardSidebarHeader";
import { DashboardSidebarLinks } from "./dashoboard-sidebar/DashboardSidebarLinks";
import { TSRBreadCrumbs } from "@/lib/tanstack/router/TSRBreadCrumbs";
import { DashboardTheme } from "./DashboardTheme";
import { DashboardLayoutHeader } from "../dashboard-layout/DashboardLayoutHeader";
import { DashboardSidebarActions } from "./DashboardSidebarActions";
import { DashboardTheme } from "./dashoboard-sidebar/DashboardTheme";
import { DashboardLayoutHeader } from "./dashboard-layout/DashboardLayoutHeader";
import { DashboardSidebarActions } from "./dashoboard-sidebar/DashboardSidebarActions";
import { Helmet } from "@/components/wrappers/custom-helmet";

interface DashboardLayoutProps {
Expand All @@ -29,19 +29,22 @@ export function DashboardLayout({ sidebar_props }: DashboardLayoutProps) {
return (
<SidebarProvider defaultOpen={false}>
<Helmet title="Dashboard" description="Dashboard" />
<SidebarInset>
<SidebarInset >
<div className="h-full " >
<header className="sticky top-0 z-30 flex w-full flex-col gap-2 bg-base-100">
<DashboardLayoutHeader />
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<SidebarTrigger className="-ml-1" data-test="DashboardLayoutSidebarTrigger"/>
<Separator orientation="vertical" className="mr-2 h-4" />
<TSRBreadCrumbs />
</div>
</header>
{/* main content */}
<div className="flex h-full w-full gap-2 ">
<Sidebar className="top-[20%]" collapsible="icon" {...sidebar_props}>
<Sidebar
data-test="DashboardLayoutSidebar"
className="top-[20%]"
collapsible="icon" {...sidebar_props}>
<SidebarHeader>
<DashboardSidebarHeader />
</SidebarHeader>
Expand Down
Loading