From d89dc2ee7083dcd149cce1b74895cf322e674d35 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Fri, 24 Apr 2026 08:38:00 +0300 Subject: [PATCH] chore: add vitest/globals types, remove redundant vitest imports, fix pre-existing lint issues --- .gitignore | 2 + .../SectionAccordion.stories.tsx | 22 +++--- .../SectionAccordion.test.tsx | 69 +++++++++---------- .../ui/SectionAccordion/SectionAccordion.tsx | 21 +++--- .../Section/ui/SectionAccordion/index.ts | 2 +- src/entities/Section/ui/index.ts | 2 +- .../experience/ui/ExperienceCard.test.tsx | 1 - src/entities/index.ts | 2 +- src/entities/project/index.ts | 4 +- .../project/ui/DetailedProjectCard.test.tsx | 6 +- .../project/ui/DetailedProjectCard.tsx | 9 +-- src/entities/project/ui/ProjectCard.test.tsx | 6 +- src/entities/project/ui/ProjectCard.tsx | 7 +- .../project/ui/ProjectMetadata.test.tsx | 7 +- src/shared/api/index.ts | 2 +- src/shared/api/types.ts | 13 ++-- src/shared/index.ts | 4 +- src/shared/lib/cn.test.ts | 1 - src/shared/lib/cn.ts | 2 +- src/shared/lib/formatDate.test.ts | 1 - src/shared/ui/Badge/index.ts | 2 +- src/shared/ui/Badge/ui/Badge.test.tsx | 1 - src/shared/ui/Button/index.ts | 2 +- src/shared/ui/Button/ui/Button.test.tsx | 1 - src/shared/ui/Card/index.ts | 2 +- src/shared/ui/Card/ui/Card.stories.tsx | 2 +- src/shared/ui/Card/ui/Card.test.tsx | 3 +- src/shared/ui/Input/ui/Input.test.tsx | 5 +- src/shared/ui/Input/ui/Input.tsx | 2 +- src/shared/ui/Section/index.ts | 4 +- src/shared/ui/Section/ui/Section.stories.tsx | 2 +- src/shared/ui/Section/ui/Section.test.tsx | 7 +- .../ui/TechStack/ui/TechStack.stories.tsx | 2 +- src/shared/ui/TechStack/ui/TechStack.test.tsx | 5 +- src/shared/ui/TechStack/ui/TechStack.tsx | 4 +- src/widgets/Navigation/index.ts | 2 +- src/widgets/Navigation/ui/MobileNav.test.tsx | 5 +- src/widgets/Navigation/ui/MobileNav.tsx | 2 + src/widgets/Navigation/ui/SidebarNav.test.tsx | 1 - src/widgets/Navigation/ui/SidebarNav.tsx | 7 +- src/widgets/Navigation/ui/UtilityBar.test.tsx | 1 - tsconfig.json | 1 + 42 files changed, 116 insertions(+), 130 deletions(-) diff --git a/.gitignore b/.gitignore index 2fc9084..ed89f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ # production /build +/docs + # misc .DS_Store *.pem diff --git a/src/entities/Section/ui/SectionAccordion/SectionAccordion.stories.tsx b/src/entities/Section/ui/SectionAccordion/SectionAccordion.stories.tsx index 6a98d0f..1ad4866 100644 --- a/src/entities/Section/ui/SectionAccordion/SectionAccordion.stories.tsx +++ b/src/entities/Section/ui/SectionAccordion/SectionAccordion.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/nextjs-vite' -import { SectionAccordion } from './SectionAccordion' +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; +import { SectionAccordion } from './SectionAccordion'; const meta: Meta = { title: 'Shared/SectionAccordion', @@ -11,11 +11,11 @@ const meta: Meta = { ), ], -} +}; -export default meta +export default meta; -type Story = StoryObj +type Story = StoryObj; export const Active: Story = { args: { @@ -24,11 +24,9 @@ export const Active: Story = { id: 'bio', isActive: true, onClick: () => {}, - children: ( -

This is the expanded section content. It is visible because isActive is true.

- ), + children:

This is the expanded section content. It is visible because isActive is true.

, }, -} +}; export const Collapsed: Story = { args: { @@ -37,8 +35,6 @@ export const Collapsed: Story = { id: 'work', isActive: false, onClick: () => console.log('section clicked'), - children: ( -

This content is hidden in collapsed state.

- ), + children:

This content is hidden in collapsed state.

, }, -} +}; diff --git a/src/entities/Section/ui/SectionAccordion/SectionAccordion.test.tsx b/src/entities/Section/ui/SectionAccordion/SectionAccordion.test.tsx index e68d60b..2d01031 100644 --- a/src/entities/Section/ui/SectionAccordion/SectionAccordion.test.tsx +++ b/src/entities/Section/ui/SectionAccordion/SectionAccordion.test.tsx @@ -1,7 +1,6 @@ -import { describe, it, expect, vi } from 'vitest' -import { render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { SectionAccordion } from './SectionAccordion' +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SectionAccordion } from './SectionAccordion'; const defaultProps = { number: '01', @@ -10,48 +9,48 @@ const defaultProps = { isActive: false, onClick: vi.fn(), children:

Content here

, -} +}; describe('SectionAccordion', () => { describe('collapsed state (isActive=false)', () => { it('renders a section element with the given id', () => { - const { container } = render() - expect(container.querySelector('section#about')).toBeInTheDocument() - }) + const { container } = render(); + expect(container.querySelector('section#about')).toBeInTheDocument(); + }); it('renders a button with number and title', () => { - render() - expect(screen.getByRole('button', { name: /01.*About/i })).toBeInTheDocument() - }) + render(); + expect(screen.getByRole('button', { name: /01.*About/i })).toBeInTheDocument(); + }); it('does not render children', () => { - render() - expect(screen.queryByText('Content here')).not.toBeInTheDocument() - }) + render(); + expect(screen.queryByText('Content here')).not.toBeInTheDocument(); + }); it('calls onClick when button is clicked', async () => { - const onClick = vi.fn() - render() - await userEvent.click(screen.getByRole('button')) - expect(onClick).toHaveBeenCalledOnce() - }) - }) + const onClick = vi.fn(); + render(); + await userEvent.click(screen.getByRole('button')); + expect(onClick).toHaveBeenCalledOnce(); + }); + }); describe('active state (isActive=true)', () => { - const activeProps = { ...defaultProps, isActive: true } + const activeProps = { ...defaultProps, isActive: true }; it('renders an h1 with number and title', () => { - render() - expect(screen.getByRole('heading', { level: 1, name: /01.*About/i })).toBeInTheDocument() - }) + render(); + expect(screen.getByRole('heading', { level: 1, name: /01.*About/i })).toBeInTheDocument(); + }); it('renders children', () => { - render() - expect(screen.getByText('Content here')).toBeInTheDocument() - }) + render(); + expect(screen.getByText('Content here')).toBeInTheDocument(); + }); it('does not render a button', () => { - render() - expect(screen.queryByRole('button')).not.toBeInTheDocument() - }) + render(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); it('content wrapper has animate-fadeIn class', () => { - const { container } = render() - expect(container.querySelector('.animate-fadeIn')).toBeInTheDocument() - }) - }) -}) + const { container } = render(); + expect(container.querySelector('.animate-fadeIn')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/entities/Section/ui/SectionAccordion/SectionAccordion.tsx b/src/entities/Section/ui/SectionAccordion/SectionAccordion.tsx index 88fc0de..27efb08 100644 --- a/src/entities/Section/ui/SectionAccordion/SectionAccordion.tsx +++ b/src/entities/Section/ui/SectionAccordion/SectionAccordion.tsx @@ -1,30 +1,30 @@ -import type { ReactNode } from 'react' +import type { ReactNode } from 'react'; interface SectionAccordionProps { /** * Display number prefix (e.g. "01") */ - number: string + number: string; /** * Section title */ - title: string + title: string; /** * HTML id for anchor navigation */ - id: string + id: string; /** * Whether this section is expanded */ - isActive: boolean + isActive: boolean; /** * Called when the collapsed header is clicked */ - onClick: () => void + onClick: () => void; /** * Section content, shown when active */ - children: ReactNode + children: ReactNode; } /** @@ -43,12 +43,11 @@ export function SectionAccordion({ number, title, id, isActive, onClick, childre {number}. {title} -
- {children} -
+
{children}
) : ( )} - ) + ); } diff --git a/src/entities/Section/ui/SectionAccordion/index.ts b/src/entities/Section/ui/SectionAccordion/index.ts index 09d3e82..25000d2 100644 --- a/src/entities/Section/ui/SectionAccordion/index.ts +++ b/src/entities/Section/ui/SectionAccordion/index.ts @@ -1 +1 @@ -export * from './SectionAccordion' +export * from './SectionAccordion'; diff --git a/src/entities/Section/ui/index.ts b/src/entities/Section/ui/index.ts index 09d3e82..25000d2 100644 --- a/src/entities/Section/ui/index.ts +++ b/src/entities/Section/ui/index.ts @@ -1 +1 @@ -export * from './SectionAccordion' +export * from './SectionAccordion'; diff --git a/src/entities/experience/ui/ExperienceCard.test.tsx b/src/entities/experience/ui/ExperienceCard.test.tsx index fd53874..4f80111 100644 --- a/src/entities/experience/ui/ExperienceCard.test.tsx +++ b/src/entities/experience/ui/ExperienceCard.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { ExperienceCard } from './ExperienceCard'; diff --git a/src/entities/index.ts b/src/entities/index.ts index 6f47d12..084a7e6 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -1,2 +1,2 @@ -export * from './project'; export * from './experience'; +export * from './project'; diff --git a/src/entities/project/index.ts b/src/entities/project/index.ts index 705e072..d3f3649 100644 --- a/src/entities/project/index.ts +++ b/src/entities/project/index.ts @@ -1,3 +1,3 @@ -export { ProjectMetadata } from './ui/ProjectMetadata'; -export { ProjectCard } from './ui/ProjectCard'; export { DetailedProjectCard } from './ui/DetailedProjectCard'; +export { ProjectCard } from './ui/ProjectCard'; +export { ProjectMetadata } from './ui/ProjectMetadata'; diff --git a/src/entities/project/ui/DetailedProjectCard.test.tsx b/src/entities/project/ui/DetailedProjectCard.test.tsx index 4f86de2..140e951 100644 --- a/src/entities/project/ui/DetailedProjectCard.test.tsx +++ b/src/entities/project/ui/DetailedProjectCard.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { DetailedProjectCard } from './DetailedProjectCard'; @@ -78,13 +77,12 @@ describe('DetailedProjectCard', () => { it('renders image when imageUrl is provided', () => { render(); - const img = screen.getByRole('img'); - expect(img).toHaveAttribute('src', '/detail.jpg'); + expect(screen.getByRole('img')).toBeInTheDocument(); }); it('image wrapper has aspect-video and brutal-border when imageUrl is provided', () => { const { container } = render(); - const imgWrapper = container.querySelector('img')!.parentElement; + const imgWrapper = container.querySelector('img')?.parentElement; expect(imgWrapper).toHaveClass('aspect-video', 'brutal-border'); }); }); diff --git a/src/entities/project/ui/DetailedProjectCard.tsx b/src/entities/project/ui/DetailedProjectCard.tsx index 25c0ed6..ca9a2d9 100644 --- a/src/entities/project/ui/DetailedProjectCard.tsx +++ b/src/entities/project/ui/DetailedProjectCard.tsx @@ -1,3 +1,4 @@ +import Image from 'next/image'; import { Card } from '$shared/ui'; import { ProjectMetadata } from './ProjectMetadata'; @@ -53,14 +54,14 @@ export function DetailedProjectCard({ title, year, role, stack, description, det

{description}

{imageUrl && ( -
- {title} +
+ {title}
)}
- {details.map((detail, index) => ( -

+ {details.map((detail) => ( +

{detail}

))} diff --git a/src/entities/project/ui/ProjectCard.test.tsx b/src/entities/project/ui/ProjectCard.test.tsx index d757241..90af4ef 100644 --- a/src/entities/project/ui/ProjectCard.test.tsx +++ b/src/entities/project/ui/ProjectCard.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { ProjectCard } from './ProjectCard'; @@ -73,13 +72,12 @@ describe('ProjectCard', () => { it('renders image when imageUrl is provided', () => { render(); - const img = screen.getByRole('img'); - expect(img).toHaveAttribute('src', '/project.jpg'); + expect(screen.getByRole('img')).toBeInTheDocument(); }); it('image wrapper has aspect-video and overflow-hidden when imageUrl is provided', () => { const { container } = render(); - const imgWrapper = container.querySelector('img')!.parentElement; + const imgWrapper = container.querySelector('img')?.parentElement; expect(imgWrapper).toHaveClass('aspect-video', 'overflow-hidden', 'brutal-border'); }); }); diff --git a/src/entities/project/ui/ProjectCard.tsx b/src/entities/project/ui/ProjectCard.tsx index a41b588..28faa8b 100644 --- a/src/entities/project/ui/ProjectCard.tsx +++ b/src/entities/project/ui/ProjectCard.tsx @@ -1,5 +1,6 @@ -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from '$shared/ui'; +import Image from 'next/image'; import { cn } from '$shared/lib'; +import { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$shared/ui'; type Props = { /** @@ -44,8 +45,8 @@ export function ProjectCard({ title, year, description, tags, imageUrl }: Props) {imageUrl && ( -
- {title} +
+ {title}
)} diff --git a/src/entities/project/ui/ProjectMetadata.test.tsx b/src/entities/project/ui/ProjectMetadata.test.tsx index 3f4161f..ef5770b 100644 --- a/src/entities/project/ui/ProjectMetadata.test.tsx +++ b/src/entities/project/ui/ProjectMetadata.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { ProjectMetadata } from './ProjectMetadata'; @@ -51,19 +50,19 @@ describe('ProjectMetadata', () => { it('year section has no brutal-border-top (first section)', () => { const { container } = render(); - const sections = container.firstChild!.childNodes; + const sections = container.firstChild?.childNodes; expect(sections[0]).not.toHaveClass('brutal-border-top'); }); it('role section has brutal-border-top and pt-6', () => { const { container } = render(); - const sections = container.firstChild!.childNodes; + const sections = container.firstChild?.childNodes; expect(sections[1]).toHaveClass('brutal-border-top', 'pt-6'); }); it('stack section has brutal-border-top and pt-6', () => { const { container } = render(); - const sections = container.firstChild!.childNodes; + const sections = container.firstChild?.childNodes; expect(sections[2]).toHaveClass('brutal-border-top', 'pt-6'); }); diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts index 70f3c79..d860d06 100644 --- a/src/shared/api/index.ts +++ b/src/shared/api/index.ts @@ -1,2 +1,2 @@ -export * from './types'; export * from './client'; +export * from './types'; diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index a640d9b..453d68b 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -21,14 +21,13 @@ export type BaseRecord = { /** * Record last update timestamp (ISO 8601) */ - updated: string; - }; - - /** - * PocketBase collection for simple text blocks (Intro, Bio). - */ - export type PageContentRecord = BaseRecord & { + updated: string; +}; +/** + * PocketBase collection for simple text blocks (Intro, Bio). + */ +export type PageContentRecord = BaseRecord & { /** * Slug corresponding to the parent section */ diff --git a/src/shared/index.ts b/src/shared/index.ts index bdcbcdb..49f26a6 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,3 +1,3 @@ -export * from './ui'; -export * from './lib'; export * from './api'; +export * from './lib'; +export * from './ui'; diff --git a/src/shared/lib/cn.test.ts b/src/shared/lib/cn.test.ts index d6163cf..7b13bbf 100644 --- a/src/shared/lib/cn.test.ts +++ b/src/shared/lib/cn.test.ts @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { cn } from './cn'; describe('cn', () => { diff --git a/src/shared/lib/cn.ts b/src/shared/lib/cn.ts index e5aba12..99c92fd 100644 --- a/src/shared/lib/cn.ts +++ b/src/shared/lib/cn.ts @@ -1,4 +1,4 @@ -import { clsx, type ClassValue } from 'clsx'; +import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; /** diff --git a/src/shared/lib/formatDate.test.ts b/src/shared/lib/formatDate.test.ts index 02ff72e..c4e0518 100644 --- a/src/shared/lib/formatDate.test.ts +++ b/src/shared/lib/formatDate.test.ts @@ -1,4 +1,3 @@ -import { describe, expect, it } from 'vitest'; import { formatYearRange } from './formatDate'; describe('formatYearRange', () => { diff --git a/src/shared/ui/Badge/index.ts b/src/shared/ui/Badge/index.ts index fc21fb9..774798f 100644 --- a/src/shared/ui/Badge/index.ts +++ b/src/shared/ui/Badge/index.ts @@ -1,2 +1,2 @@ -export { Badge } from './ui/Badge'; export type { BadgeVariant } from './ui/Badge'; +export { Badge } from './ui/Badge'; diff --git a/src/shared/ui/Badge/ui/Badge.test.tsx b/src/shared/ui/Badge/ui/Badge.test.tsx index 1dda3cd..d64295c 100644 --- a/src/shared/ui/Badge/ui/Badge.test.tsx +++ b/src/shared/ui/Badge/ui/Badge.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { Badge } from './Badge'; diff --git a/src/shared/ui/Button/index.ts b/src/shared/ui/Button/index.ts index 5ba8d18..24cb05f 100644 --- a/src/shared/ui/Button/index.ts +++ b/src/shared/ui/Button/index.ts @@ -1,2 +1,2 @@ +export type { ButtonSize, ButtonVariant } from './ui/Button'; export { Button } from './ui/Button'; -export type { ButtonVariant, ButtonSize } from './ui/Button'; diff --git a/src/shared/ui/Button/ui/Button.test.tsx b/src/shared/ui/Button/ui/Button.test.tsx index ffa2d81..7ad5775 100644 --- a/src/shared/ui/Button/ui/Button.test.tsx +++ b/src/shared/ui/Button/ui/Button.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect, vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Button } from './Button'; diff --git a/src/shared/ui/Card/index.ts b/src/shared/ui/Card/index.ts index 4c0afdc..e15b0d7 100644 --- a/src/shared/ui/Card/index.ts +++ b/src/shared/ui/Card/index.ts @@ -1,2 +1,2 @@ -export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './ui/Card'; export type { CardBackground } from './ui/Card'; +export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './ui/Card'; diff --git a/src/shared/ui/Card/ui/Card.stories.tsx b/src/shared/ui/Card/ui/Card.stories.tsx index 6aa8002..a5e62d3 100644 --- a/src/shared/ui/Card/ui/Card.stories.tsx +++ b/src/shared/ui/Card/ui/Card.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/nextjs-vite'; -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './Card'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './Card'; const meta: Meta = { title: 'Shared/Card', diff --git a/src/shared/ui/Card/ui/Card.test.tsx b/src/shared/ui/Card/ui/Card.test.tsx index 3148804..6ad0e0e 100644 --- a/src/shared/ui/Card/ui/Card.test.tsx +++ b/src/shared/ui/Card/ui/Card.test.tsx @@ -1,6 +1,5 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './Card'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './Card'; describe('Card', () => { describe('rendering', () => { diff --git a/src/shared/ui/Input/ui/Input.test.tsx b/src/shared/ui/Input/ui/Input.test.tsx index 814650a..407bf4b 100644 --- a/src/shared/ui/Input/ui/Input.test.tsx +++ b/src/shared/ui/Input/ui/Input.test.tsx @@ -1,4 +1,3 @@ -import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { Input, Textarea } from './Input'; @@ -35,7 +34,7 @@ describe('Input', () => { const input = screen.getByRole('textbox'); const errorId = input.getAttribute('aria-describedby'); expect(errorId).toBeTruthy(); - expect(document.getElementById(errorId!)).toHaveTextContent('Required'); + expect(document.getElementById(errorId as string)).toHaveTextContent('Required'); }); it('no aria-describedby when no error', () => { render(); @@ -100,7 +99,7 @@ describe('Textarea', () => { const textarea = screen.getByRole('textbox'); const errorId = textarea.getAttribute('aria-describedby'); expect(errorId).toBeTruthy(); - expect(document.getElementById(errorId!)).toHaveTextContent('Too short'); + expect(document.getElementById(errorId as string)).toHaveTextContent('Too short'); }); it('no aria-describedby when no error', () => { render(