таски готовы
This commit is contained in:
@@ -58,6 +58,13 @@
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "reports",
|
||||
label: $t.nav?.reports || "REPORTS",
|
||||
icon: "M4 5h16M4 12h16M4 19h10",
|
||||
path: "/reports",
|
||||
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
label: $t.nav?.admin || "ADMIN",
|
||||
@@ -118,6 +125,13 @@
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "reports",
|
||||
label: $t.nav?.reports || "REPORTS",
|
||||
icon: "M4 5h16M4 12h16M4 19h10",
|
||||
path: "/reports",
|
||||
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
label: $t.nav?.admin || "ADMIN",
|
||||
|
||||
@@ -54,6 +54,11 @@
|
||||
closeDrawer();
|
||||
}
|
||||
|
||||
function goToTasksPage() {
|
||||
closeDrawer();
|
||||
window.location.href = "/tasks";
|
||||
}
|
||||
|
||||
// Handle overlay click
|
||||
function handleOverlayClick(event) {
|
||||
if (event.target === event.currentTarget) {
|
||||
@@ -239,23 +244,31 @@
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="p-1.5 rounded-md text-slate-500 bg-transparent border-none cursor-pointer transition-all hover:text-slate-100 hover:bg-slate-800"
|
||||
on:click={handleClose}
|
||||
aria-label="Close drawer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="px-2.5 py-1 text-xs font-semibold rounded-md border border-slate-700 text-slate-300 bg-slate-800/60 hover:bg-slate-800 transition-colors"
|
||||
on:click={goToTasksPage}
|
||||
>
|
||||
<path d="M18 6L6 18M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
{$t.nav?.tasks || "Tasks"}
|
||||
</button>
|
||||
<button
|
||||
class="p-1.5 rounded-md text-slate-500 bg-transparent border-none cursor-pointer transition-all hover:text-slate-100 hover:bg-slate-800"
|
||||
on:click={handleClose}
|
||||
aria-label="Close drawer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M18 6L6 18M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
@@ -318,4 +331,3 @@
|
||||
|
||||
<!-- [/DEF:TaskDrawer:Component] -->
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// [DEF:__tests__/test_breadcrumbs:Module]
|
||||
// @TIER: STANDARD
|
||||
// @PURPOSE: Contract-focused unit tests for Breadcrumbs.svelte logic and UX annotations
|
||||
// @LAYER: UI
|
||||
// @RELATION: VERIFIES -> frontend/src/lib/components/layout/Breadcrumbs.svelte
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const COMPONENT_PATH = path.resolve(
|
||||
process.cwd(),
|
||||
'src/lib/components/layout/Breadcrumbs.svelte'
|
||||
);
|
||||
|
||||
function getBreadcrumbs(pathname, maxVisible = 3) {
|
||||
const segments = pathname.split('/').filter(Boolean);
|
||||
const allItems = [{ label: 'Home', path: '/' }];
|
||||
|
||||
let currentPath = '';
|
||||
segments.forEach((segment, index) => {
|
||||
currentPath += `/${segment}`;
|
||||
const label = formatBreadcrumbLabel(segment);
|
||||
allItems.push({
|
||||
label,
|
||||
path: currentPath,
|
||||
isLast: index === segments.length - 1
|
||||
});
|
||||
});
|
||||
|
||||
if (allItems.length > maxVisible) {
|
||||
const firstItem = allItems[0];
|
||||
const itemsToShow = [];
|
||||
itemsToShow.push(firstItem);
|
||||
itemsToShow.push({ isEllipsis: true });
|
||||
|
||||
const startFromIndex = allItems.length - (maxVisible - 1);
|
||||
for (let i = startFromIndex; i < allItems.length; i++) {
|
||||
itemsToShow.push(allItems[i]);
|
||||
}
|
||||
return itemsToShow;
|
||||
}
|
||||
|
||||
return allItems;
|
||||
}
|
||||
|
||||
function formatBreadcrumbLabel(segment) {
|
||||
const specialCases = {
|
||||
dashboards: 'Dashboards',
|
||||
datasets: 'Datasets',
|
||||
storage: 'Storage',
|
||||
admin: 'Admin',
|
||||
settings: 'Settings',
|
||||
git: 'Git'
|
||||
};
|
||||
|
||||
if (specialCases[segment]) {
|
||||
return specialCases[segment];
|
||||
}
|
||||
|
||||
return segment
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
describe('Breadcrumbs Component Contract & Logic', () => {
|
||||
it('contains required UX tags and semantic header for STANDARD module', () => {
|
||||
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
|
||||
|
||||
expect(source).toContain('@TIER: STANDARD');
|
||||
expect(source).toContain('@UX_STATE: Idle');
|
||||
expect(source).toContain('@UX_FEEDBACK');
|
||||
expect(source).toContain('@UX_RECOVERY');
|
||||
expect(source).toContain('@RELATION: DEPENDS_ON -> page store');
|
||||
});
|
||||
|
||||
it('returns Home for root path (Short-Path UX state)', () => {
|
||||
const result = getBreadcrumbs('/', 3);
|
||||
|
||||
expect(result).toEqual([{ label: 'Home', path: '/' }]);
|
||||
});
|
||||
|
||||
it('maps known segments to expected labels', () => {
|
||||
expect(formatBreadcrumbLabel('dashboards')).toBe('Dashboards');
|
||||
expect(formatBreadcrumbLabel('datasets')).toBe('Datasets');
|
||||
expect(formatBreadcrumbLabel('settings')).toBe('Settings');
|
||||
});
|
||||
|
||||
it('formats unknown kebab-case segment to title case', () => {
|
||||
expect(formatBreadcrumbLabel('data-quality-rules')).toBe('Data Quality Rules');
|
||||
});
|
||||
|
||||
it('truncates long paths with ellipsis and keeps tail segments', () => {
|
||||
const result = getBreadcrumbs('/dashboards/segment-a/segment-b/segment-c', 3);
|
||||
|
||||
expect(result[0]).toEqual({ label: 'Home', path: '/' });
|
||||
expect(result[1]).toEqual({ isEllipsis: true });
|
||||
const lastItem = result[result.length - 1];
|
||||
expect('label' in lastItem && lastItem.label).toBe('Segment C');
|
||||
expect(result.length).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
// [/DEF:__tests__/test_breadcrumbs:Module]
|
||||
Reference in New Issue
Block a user