Redraw the calendar as an almanac: gridless cells, typed bands
Day cells lose their borders and fills; red days, klämdagar and Saturdays are told apart by the numeral's ink with a 12px R/K mark, today is a filled circle, overrides and exclusions mark the numeral. Bands carry their funding type by fill, ink and hatching and their owner by a left cap. The preset rail becomes a chip row, the legend a help popover, the period popover's three delete buttons fold behind one "Ta bort…", and solo users pick a type directly in the day modal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBRLomdLK9jF7wU7a8AA6H
This commit is contained in:
@@ -23,11 +23,47 @@ describe('PeriodPopover', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
/** The three delete scopes sit behind "Ta bort…" — press it first. */
|
||||
function openDeleteMenu() {
|
||||
fireEvent.click(screen.getByTestId('period-delete-menu-button'))
|
||||
}
|
||||
|
||||
it('returns null when not open', () => {
|
||||
const { container } = render(<PeriodPopover {...defaultProps} isOpen={false} />)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
describe('the delete menu', () => {
|
||||
it('keeps the three delete scopes hidden until "Ta bort…" is pressed', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
expect(screen.queryByTestId('period-delete-group')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('period-delete-menu-button')).toHaveTextContent('Ta bort…')
|
||||
expect(screen.getByTestId('period-delete-menu-button')).toHaveAttribute('aria-expanded', 'false')
|
||||
|
||||
openDeleteMenu()
|
||||
expect(screen.getByTestId('period-delete-menu-button')).toHaveAttribute('aria-expanded', 'true')
|
||||
expect(screen.getByTestId('period-delete-group')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('period-delete-day-button')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('period-delete-year-button')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('period-delete-all-years-button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('folds the scopes away again on a second press', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
openDeleteMenu()
|
||||
expect(screen.queryByTestId('period-delete-group')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('starts folded again each time the popover reopens', () => {
|
||||
const { rerender } = render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
rerender(<PeriodPopover {...defaultProps} isOpen={false} />)
|
||||
rerender(<PeriodPopover {...defaultProps} isOpen={true} />)
|
||||
expect(screen.queryByTestId('period-delete-group')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ad-hoc band (no wish)', () => {
|
||||
it('shows the ad-hoc notice and date range instead of editable fields', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
@@ -43,6 +79,7 @@ describe('PeriodPopover', () => {
|
||||
|
||||
it('shows the ad-hoc "this period" label and disables "every year" (nothing recurring to remove)', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
expect(screen.getByTestId('period-delete-year-button')).toHaveTextContent('Ta bort dessa dagar')
|
||||
expect(screen.getByTestId('period-delete-day-button')).not.toBeDisabled()
|
||||
expect(screen.getByTestId('period-delete-all-years-button')).toBeDisabled()
|
||||
@@ -54,6 +91,7 @@ describe('PeriodPopover', () => {
|
||||
|
||||
it('shows an inline confirmation instead of deleting immediately', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-year-button'))
|
||||
expect(mockOnDelete).not.toHaveBeenCalled()
|
||||
expect(screen.getByTestId('period-delete-confirm')).toBeInTheDocument()
|
||||
@@ -62,6 +100,7 @@ describe('PeriodPopover', () => {
|
||||
it('calls onDelete with the "year" scope after confirming inline', async () => {
|
||||
mockOnDelete.mockResolvedValue(undefined)
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-year-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-confirm-button'))
|
||||
await waitFor(() => expect(mockOnDelete).toHaveBeenCalledWith('year'))
|
||||
@@ -70,6 +109,7 @@ describe('PeriodPopover', () => {
|
||||
it('calls onDelete with the "day" scope for the day button', async () => {
|
||||
mockOnDelete.mockResolvedValue(undefined)
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-day-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-confirm-button'))
|
||||
await waitFor(() => expect(mockOnDelete).toHaveBeenCalledWith('day'))
|
||||
@@ -77,6 +117,7 @@ describe('PeriodPopover', () => {
|
||||
|
||||
it('does not call onDelete when the inline confirmation is cancelled', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-year-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-cancel-button'))
|
||||
expect(mockOnDelete).not.toHaveBeenCalled()
|
||||
@@ -394,6 +435,7 @@ describe('PeriodPopover', () => {
|
||||
// the option is live — see Calendar.tsx's `excludeDay`.
|
||||
it('enables "this day" for a wish-linked band and explains what it does', () => {
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
expect(screen.getByTestId('period-delete-day-button')).not.toBeDisabled()
|
||||
expect(screen.getByTestId('period-delete-day-button')).toHaveAttribute(
|
||||
'title',
|
||||
@@ -406,6 +448,7 @@ describe('PeriodPopover', () => {
|
||||
it('calls onDelete with the "day" scope for a wish-linked band after confirmation', async () => {
|
||||
mockOnDelete.mockResolvedValue(undefined)
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-day-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-confirm-button'))
|
||||
await waitFor(() => expect(mockOnDelete).toHaveBeenCalledWith('day'))
|
||||
@@ -420,6 +463,7 @@ describe('PeriodPopover', () => {
|
||||
removeDayParentName="Alex"
|
||||
/>,
|
||||
)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-day-button'))
|
||||
expect(screen.getByTestId('period-delete-confirm')).toHaveTextContent(
|
||||
'Ta bort 17 juni 2028 för Alex?',
|
||||
@@ -428,6 +472,7 @@ describe('PeriodPopover', () => {
|
||||
|
||||
it('falls back to the generic confirmation when the parent is unknown', () => {
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} removeDayLabel="17 juni 2028" />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-day-button'))
|
||||
expect(screen.getByTestId('period-delete-confirm')).toHaveTextContent(
|
||||
'Är du säker på att du vill ta bort den här dagen?',
|
||||
@@ -437,6 +482,7 @@ describe('PeriodPopover', () => {
|
||||
it('displays a generic local error when onDelete rejects with a non-Error value', async () => {
|
||||
mockOnDelete.mockRejectedValue('boom')
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-year-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-confirm-button'))
|
||||
await waitFor(() => {
|
||||
@@ -447,6 +493,7 @@ describe('PeriodPopover', () => {
|
||||
it('calls onDelete with the "allYears" scope for a wish-linked band after confirmation', async () => {
|
||||
mockOnDelete.mockResolvedValue(undefined)
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-all-years-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-confirm-button'))
|
||||
await waitFor(() => expect(mockOnDelete).toHaveBeenCalledWith('allYears'))
|
||||
@@ -455,6 +502,7 @@ describe('PeriodPopover', () => {
|
||||
it('calls onDelete with the "year" scope for a wish-linked band', async () => {
|
||||
mockOnDelete.mockResolvedValue(undefined)
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-year-button'))
|
||||
fireEvent.click(screen.getByTestId('period-delete-confirm-button'))
|
||||
await waitFor(() => expect(mockOnDelete).toHaveBeenCalledWith('year'))
|
||||
@@ -462,12 +510,14 @@ describe('PeriodPopover', () => {
|
||||
|
||||
it('shows the wish-linked "this period" and "every year" labels', () => {
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
expect(screen.getByTestId('period-delete-year-button')).toHaveTextContent('Den här perioden')
|
||||
expect(screen.getByTestId('period-delete-all-years-button')).toHaveTextContent('Den här perioden, varje år')
|
||||
})
|
||||
|
||||
it('shows a scope-specific confirmation message for each delete button', () => {
|
||||
render(<PeriodPopover {...defaultProps} wish={wish} />)
|
||||
openDeleteMenu()
|
||||
fireEvent.click(screen.getByTestId('period-delete-year-button'))
|
||||
expect(screen.getByTestId('period-delete-confirm')).toHaveTextContent(
|
||||
'Är du säker på att du vill ta bort den här perioden (bara det här året)?',
|
||||
@@ -519,7 +569,9 @@ describe('PeriodPopover', () => {
|
||||
describe('close behaviour', () => {
|
||||
it('calls onClose on the close button', () => {
|
||||
render(<PeriodPopover {...defaultProps} />)
|
||||
fireEvent.click(screen.getByLabelText('Stäng'))
|
||||
const closeButton = screen.getByLabelText('Stäng')
|
||||
expect(closeButton.querySelector('[data-icon="close"]')).toBeInTheDocument()
|
||||
fireEvent.click(closeButton)
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user