chore: add vitest/globals types, remove redundant vitest imports, fix pre-existing lint issues
This commit is contained in:
@@ -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<typeof SectionAccordion> = {
|
||||
title: 'Shared/SectionAccordion',
|
||||
@@ -11,11 +11,11 @@ const meta: Meta<typeof SectionAccordion> = {
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export default meta
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof SectionAccordion>
|
||||
type Story = StoryObj<typeof SectionAccordion>;
|
||||
|
||||
export const Active: Story = {
|
||||
args: {
|
||||
@@ -24,11 +24,9 @@ export const Active: Story = {
|
||||
id: 'bio',
|
||||
isActive: true,
|
||||
onClick: () => {},
|
||||
children: (
|
||||
<p>This is the expanded section content. It is visible because isActive is true.</p>
|
||||
),
|
||||
children: <p>This is the expanded section content. It is visible because isActive is true.</p>,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const Collapsed: Story = {
|
||||
args: {
|
||||
@@ -37,8 +35,6 @@ export const Collapsed: Story = {
|
||||
id: 'work',
|
||||
isActive: false,
|
||||
onClick: () => console.log('section clicked'),
|
||||
children: (
|
||||
<p>This content is hidden in collapsed state.</p>
|
||||
),
|
||||
children: <p>This content is hidden in collapsed state.</p>,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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: <p>Content here</p>,
|
||||
}
|
||||
};
|
||||
|
||||
describe('SectionAccordion', () => {
|
||||
describe('collapsed state (isActive=false)', () => {
|
||||
it('renders a section element with the given id', () => {
|
||||
const { container } = render(<SectionAccordion {...defaultProps} />)
|
||||
expect(container.querySelector('section#about')).toBeInTheDocument()
|
||||
})
|
||||
const { container } = render(<SectionAccordion {...defaultProps} />);
|
||||
expect(container.querySelector('section#about')).toBeInTheDocument();
|
||||
});
|
||||
it('renders a button with number and title', () => {
|
||||
render(<SectionAccordion {...defaultProps} />)
|
||||
expect(screen.getByRole('button', { name: /01.*About/i })).toBeInTheDocument()
|
||||
})
|
||||
render(<SectionAccordion {...defaultProps} />);
|
||||
expect(screen.getByRole('button', { name: /01.*About/i })).toBeInTheDocument();
|
||||
});
|
||||
it('does not render children', () => {
|
||||
render(<SectionAccordion {...defaultProps} />)
|
||||
expect(screen.queryByText('Content here')).not.toBeInTheDocument()
|
||||
})
|
||||
render(<SectionAccordion {...defaultProps} />);
|
||||
expect(screen.queryByText('Content here')).not.toBeInTheDocument();
|
||||
});
|
||||
it('calls onClick when button is clicked', async () => {
|
||||
const onClick = vi.fn()
|
||||
render(<SectionAccordion {...defaultProps} onClick={onClick} />)
|
||||
await userEvent.click(screen.getByRole('button'))
|
||||
expect(onClick).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
const onClick = vi.fn();
|
||||
render(<SectionAccordion {...defaultProps} onClick={onClick} />);
|
||||
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(<SectionAccordion {...activeProps} />)
|
||||
expect(screen.getByRole('heading', { level: 1, name: /01.*About/i })).toBeInTheDocument()
|
||||
})
|
||||
render(<SectionAccordion {...activeProps} />);
|
||||
expect(screen.getByRole('heading', { level: 1, name: /01.*About/i })).toBeInTheDocument();
|
||||
});
|
||||
it('renders children', () => {
|
||||
render(<SectionAccordion {...activeProps} />)
|
||||
expect(screen.getByText('Content here')).toBeInTheDocument()
|
||||
})
|
||||
render(<SectionAccordion {...activeProps} />);
|
||||
expect(screen.getByText('Content here')).toBeInTheDocument();
|
||||
});
|
||||
it('does not render a button', () => {
|
||||
render(<SectionAccordion {...activeProps} />)
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument()
|
||||
})
|
||||
render(<SectionAccordion {...activeProps} />);
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||
});
|
||||
it('content wrapper has animate-fadeIn class', () => {
|
||||
const { container } = render(<SectionAccordion {...activeProps} />)
|
||||
expect(container.querySelector('.animate-fadeIn')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
const { container } = render(<SectionAccordion {...activeProps} />);
|
||||
expect(container.querySelector('.animate-fadeIn')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="animate-fadeIn">
|
||||
{children}
|
||||
</div>
|
||||
<div className="animate-fadeIn">{children}</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="w-full text-left mb-3 py-3 transition-all duration-200 hover:opacity-60 group"
|
||||
>
|
||||
@@ -61,5 +60,5 @@ export function SectionAccordion({ number, title, id, isActive, onClick, childre
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './SectionAccordion'
|
||||
export * from './SectionAccordion';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './SectionAccordion'
|
||||
export * from './SectionAccordion';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ExperienceCard } from './ExperienceCard';
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './project';
|
||||
export * from './experience';
|
||||
export * from './project';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(<DetailedProjectCard {...DEFAULT_PROPS} imageUrl="/detail.jpg" />);
|
||||
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(<DetailedProjectCard {...DEFAULT_PROPS} imageUrl="/detail.jpg" />);
|
||||
const imgWrapper = container.querySelector('img')!.parentElement;
|
||||
const imgWrapper = container.querySelector('img')?.parentElement;
|
||||
expect(imgWrapper).toHaveClass('aspect-video', 'brutal-border');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
<p className="text-lg mb-6">{description}</p>
|
||||
|
||||
{imageUrl && (
|
||||
<div className="brutal-border aspect-video bg-slate-indigo overflow-hidden">
|
||||
<img src={imageUrl} alt={title} className="w-full h-full object-cover" />
|
||||
<div className="brutal-border aspect-video bg-slate-indigo overflow-hidden relative">
|
||||
<Image src={imageUrl} alt={title} fill className="object-cover" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="max-w-[700px] space-y-4 brutal-border-top pt-6">
|
||||
{details.map((detail, index) => (
|
||||
<p key={index} className="text-base">
|
||||
{details.map((detail) => (
|
||||
<p key={detail} className="text-base">
|
||||
{detail}
|
||||
</p>
|
||||
))}
|
||||
|
||||
@@ -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(<ProjectCard {...DEFAULT_PROPS} imageUrl="/project.jpg" />);
|
||||
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(<ProjectCard {...DEFAULT_PROPS} imageUrl="/project.jpg" />);
|
||||
const imgWrapper = container.querySelector('img')!.parentElement;
|
||||
const imgWrapper = container.querySelector('img')?.parentElement;
|
||||
expect(imgWrapper).toHaveClass('aspect-video', 'overflow-hidden', 'brutal-border');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
</CardHeader>
|
||||
|
||||
{imageUrl && (
|
||||
<div className="brutal-border my-6 aspect-video bg-slate-indigo overflow-hidden">
|
||||
<img src={imageUrl} alt={title} className="w-full h-full object-cover" />
|
||||
<div className="brutal-border my-6 aspect-video bg-slate-indigo overflow-hidden relative">
|
||||
<Image src={imageUrl} alt={title} fill className="object-cover" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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(<ProjectMetadata {...DEFAULT_PROPS} />);
|
||||
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(<ProjectMetadata {...DEFAULT_PROPS} />);
|
||||
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(<ProjectMetadata {...DEFAULT_PROPS} />);
|
||||
const sections = container.firstChild!.childNodes;
|
||||
const sections = container.firstChild?.childNodes;
|
||||
expect(sections[2]).toHaveClass('brutal-border-top', 'pt-6');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user