feat: add experience entity (ExperienceCard) and entities barrel export

This commit is contained in:
Ilia Mashkov
2026-04-19 08:37:24 +03:00
parent eeef679f69
commit 590147adb1
4 changed files with 119 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
export { ExperienceCard } from './ui/ExperienceCard'
@@ -0,0 +1,72 @@
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { ExperienceCard } from './ExperienceCard'
const DEFAULT_PROPS = {
title: 'Senior Developer',
company: 'Acme Corp',
period: '2021 2024',
description: 'Built scalable frontend systems.',
}
describe('ExperienceCard', () => {
describe('rendering', () => {
it('renders the job title', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
expect(screen.getByText('Senior Developer')).toBeInTheDocument()
})
it('renders the company name', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
expect(screen.getByText('Acme Corp')).toBeInTheDocument()
})
it('renders the period badge', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
expect(screen.getByText('2021 2024')).toBeInTheDocument()
})
it('renders the description', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
expect(screen.getByText('Built scalable frontend systems.')).toBeInTheDocument()
})
})
describe('structure', () => {
it('title is rendered as an h4', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent('Senior Developer')
})
it('period badge has brutal-border, bg-carbon-black, text-ochre-clay, text-sm', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
const badge = screen.getByText('2021 2024')
expect(badge).toHaveClass('brutal-border', 'bg-carbon-black', 'text-ochre-clay', 'text-sm')
})
it('company paragraph has opacity-80', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
const company = screen.getByText('Acme Corp')
expect(company.tagName).toBe('P')
expect(company).toHaveClass('opacity-80')
})
it('description paragraph has text-base and max-w-[700px]', () => {
render(<ExperienceCard {...DEFAULT_PROPS} />)
const desc = screen.getByText('Built scalable frontend systems.')
expect(desc).toHaveClass('text-base', 'max-w-[700px]')
})
it('card has brutal-border class (from Card component)', () => {
const { container } = render(<ExperienceCard {...DEFAULT_PROPS} />)
expect(container.firstChild).toHaveClass('brutal-border')
})
})
describe('className passthrough', () => {
it('forwards className to the card', () => {
const { container } = render(<ExperienceCard {...DEFAULT_PROPS} className="custom-class" />)
expect(container.firstChild).toHaveClass('custom-class')
})
})
})
@@ -0,0 +1,44 @@
import { Card } from '$shared/ui'
type Props = {
/**
* Job title
*/
title: string
/**
* Company name
*/
company: string
/**
* Employment period (e.g. "2021 2024")
*/
period: string
/**
* Description of responsibilities and achievements
*/
description: string
/**
* Additional CSS classes forwarded to the card
*/
className?: string
}
/**
* Work experience card with title, company, period, and description.
*/
export function ExperienceCard({ title, company, period, description, className }: Props) {
return (
<Card className={className}>
<div className="flex flex-col md:flex-row md:items-start md:justify-between mb-4 gap-4">
<div className="flex-1 max-w-[700px]">
<h4>{title}</h4>
<p className="text-base opacity-80">{company}</p>
</div>
<span className="brutal-border px-4 py-2 bg-carbon-black text-ochre-clay text-sm self-start">
{period}
</span>
</div>
<p className="text-base max-w-[700px]">{description}</p>
</Card>
)
}
+2
View File
@@ -0,0 +1,2 @@
export * from './project'
export * from './experience'