Skip to content

Commit 9365f18

Browse files
committed
fix: add story tests for proposal details and input interactions
1 parent 6ee1797 commit 9365f18

2 files changed

Lines changed: 220 additions & 0 deletions

File tree

src/components/cfp/CompactProposalList.stories.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '@/lib/proposal/types'
1111
import { Flags, Speaker } from '@/lib/speaker/types'
1212
import { convertStringToPortableTextBlocks } from '@/lib/proposal'
13+
import { expect, within } from 'storybook/test'
1314

1415
const mockSpeakers: Speaker[] = [
1516
{
@@ -258,6 +259,112 @@ export const WithVideo: Story = {
258259
},
259260
}
260261

262+
export const RendersAllStatusBadges: Story = {
263+
args: {
264+
proposals: mixedStatusProposals,
265+
canEdit: true,
266+
},
267+
play: async ({ canvasElement }) => {
268+
const canvas = within(canvasElement)
269+
expect(canvas.getByText('Confirmed')).toBeInTheDocument()
270+
expect(canvas.getByText('Accepted')).toBeInTheDocument()
271+
expect(canvas.getByText('Submitted')).toBeInTheDocument()
272+
expect(canvas.getByText('Waitlisted')).toBeInTheDocument()
273+
// When an approved talk exists, rejected shows "Not selected"
274+
expect(canvas.getByText('Not selected')).toBeInTheDocument()
275+
},
276+
}
277+
278+
export const SortsByStatusPriority: Story = {
279+
args: {
280+
proposals: [
281+
createMockProposal('talk-1', 'Rejected Talk', Status.rejected),
282+
createMockProposal('talk-2', 'Confirmed Talk', Status.confirmed),
283+
createMockProposal('talk-3', 'Submitted Talk', Status.submitted),
284+
],
285+
canEdit: true,
286+
},
287+
play: async ({ canvasElement }) => {
288+
const canvas = within(canvasElement)
289+
const links = canvas.getAllByRole('link', {
290+
name: /Talk$/,
291+
})
292+
// Confirmed should be first, Submitted second, Rejected last
293+
expect(links[0]).toHaveTextContent('Confirmed Talk')
294+
expect(links[1]).toHaveTextContent('Submitted Talk')
295+
expect(links[2]).toHaveTextContent('Rejected Talk')
296+
},
297+
}
298+
299+
export const EditableShowsEditLinks: Story = {
300+
args: {
301+
proposals: [createMockProposal('talk-1', 'My Talk', Status.submitted)],
302+
canEdit: true,
303+
},
304+
play: async ({ canvasElement }) => {
305+
const canvas = within(canvasElement)
306+
const editLink = canvas.getByTitle('Edit proposal')
307+
expect(editLink).toBeInTheDocument()
308+
expect(editLink).toHaveAttribute(
309+
'href',
310+
expect.stringContaining('/cfp/proposal?id=talk-1'),
311+
)
312+
},
313+
}
314+
315+
export const ReadOnlyHidesEditLinks: Story = {
316+
args: {
317+
proposals: [createMockProposal('talk-1', 'My Talk', Status.submitted)],
318+
canEdit: false,
319+
},
320+
play: async ({ canvasElement }) => {
321+
const canvas = within(canvasElement)
322+
expect(canvas.queryByTitle('Edit proposal')).not.toBeInTheDocument()
323+
},
324+
}
325+
326+
export const RejectedShowsNotSelectedWithApprovedTalk: Story = {
327+
args: {
328+
proposals: [
329+
createMockProposal('talk-1', 'Accepted Talk', Status.confirmed),
330+
createMockProposal('talk-2', 'Other Proposal', Status.rejected),
331+
],
332+
canEdit: true,
333+
},
334+
play: async ({ canvasElement }) => {
335+
const canvas = within(canvasElement)
336+
// When speaker has an approved talk, rejected shows "Not selected"
337+
expect(canvas.getByText('Not selected')).toBeInTheDocument()
338+
expect(canvas.queryByText('Rejected')).not.toBeInTheDocument()
339+
},
340+
}
341+
342+
export const WithdrawnStatusRendered: Story = {
343+
args: {
344+
proposals: [
345+
createMockProposal('talk-1', 'Withdrawn Talk', Status.withdrawn),
346+
createMockProposal('talk-2', 'Active Talk', Status.submitted),
347+
],
348+
canEdit: true,
349+
},
350+
play: async ({ canvasElement }) => {
351+
const canvas = within(canvasElement)
352+
expect(canvas.getByText('Withdrawn')).toBeInTheDocument()
353+
expect(canvas.getByText('Submitted')).toBeInTheDocument()
354+
},
355+
}
356+
357+
export const ShowsProposalCount: Story = {
358+
args: {
359+
proposals: allSubmittedProposals,
360+
canEdit: true,
361+
},
362+
play: async ({ canvasElement }) => {
363+
const canvas = within(canvasElement)
364+
expect(canvas.getByText('Proposals (3)')).toBeInTheDocument()
365+
},
366+
}
367+
261368
export const ConferenceEnded: Story = {
262369
args: {
263370
proposals: [

src/components/proposal/ProposalDetailsForm.stories.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { Conference } from '@/lib/conference/types'
1111
import { Topic } from '@/lib/topic/types'
1212
import { fn } from 'storybook/test'
13+
import { expect, userEvent, waitFor, within } from 'storybook/test'
1314
import { convertStringToPortableTextBlocks } from '@/lib/proposal'
1415

1516
const mockTopics: Topic[] = [
@@ -173,3 +174,115 @@ export const WorkshopFormat: Story = {
173174
allowedFormats: [Format.workshop_120],
174175
},
175176
}
177+
178+
export const TitleInputCallsSetProposal: Story = {
179+
args: {
180+
proposal: emptyProposal,
181+
setProposal: fn(),
182+
conference: mockConference,
183+
allowedFormats: [Format.lightning_10, Format.presentation_25],
184+
},
185+
play: async ({ args, canvasElement }) => {
186+
const canvas = within(canvasElement)
187+
const titleInput = canvas.getByLabelText('Title')
188+
await userEvent.type(titleInput, 'My New Talk')
189+
await waitFor(() => {
190+
expect(args.setProposal).toHaveBeenCalledWith(
191+
expect.objectContaining({ title: 'My New Talk' }),
192+
)
193+
})
194+
},
195+
}
196+
197+
export const FormatDropdownChange: Story = {
198+
args: {
199+
proposal: emptyProposal,
200+
setProposal: fn(),
201+
conference: mockConference,
202+
allowedFormats: [
203+
Format.lightning_10,
204+
Format.presentation_25,
205+
Format.presentation_45,
206+
],
207+
},
208+
play: async ({ args, canvasElement }) => {
209+
const canvas = within(canvasElement)
210+
const formatSelect = canvas.getByLabelText('Presentation Format')
211+
await userEvent.selectOptions(formatSelect, Format.presentation_45)
212+
await waitFor(() => {
213+
expect(args.setProposal).toHaveBeenCalledWith(
214+
expect.objectContaining({ format: Format.presentation_45 }),
215+
)
216+
})
217+
},
218+
}
219+
220+
export const TosCheckboxToggle: Story = {
221+
args: {
222+
proposal: emptyProposal,
223+
setProposal: fn(),
224+
conference: mockConference,
225+
allowedFormats: [Format.lightning_10],
226+
},
227+
play: async ({ args, canvasElement }) => {
228+
const canvas = within(canvasElement)
229+
const tosCheckbox = canvas.getByLabelText('I agree to the Code of Conduct')
230+
expect(tosCheckbox).not.toBeChecked()
231+
await userEvent.click(tosCheckbox)
232+
await waitFor(() => {
233+
expect(args.setProposal).toHaveBeenCalledWith(
234+
expect.objectContaining({ tos: true }),
235+
)
236+
})
237+
},
238+
}
239+
240+
export const ReadOnlyShowsContent: Story = {
241+
args: {
242+
proposal: filledProposal,
243+
setProposal: fn(),
244+
conference: mockConference,
245+
allowedFormats: [Format.presentation_45],
246+
readOnly: true,
247+
},
248+
play: async ({ canvasElement }) => {
249+
const canvas = within(canvasElement)
250+
// Read-only mode renders text, not inputs
251+
expect(
252+
canvas.getByText('Building Scalable Kubernetes Applications'),
253+
).toBeInTheDocument()
254+
expect(canvas.getByText('English')).toBeInTheDocument()
255+
expect(canvas.getByText('Intermediate')).toBeInTheDocument()
256+
// Should not have editable inputs
257+
expect(canvas.queryByLabelText('Title')).not.toBeInTheDocument()
258+
},
259+
}
260+
261+
export const WorkshopShowsPrerequisites: Story = {
262+
args: {
263+
proposal: {
264+
...emptyProposal,
265+
format: Format.workshop_120,
266+
},
267+
setProposal: fn(),
268+
conference: mockConference,
269+
allowedFormats: [Format.workshop_120],
270+
},
271+
play: async ({ canvasElement }) => {
272+
const canvas = within(canvasElement)
273+
expect(canvas.getByLabelText('Prerequisites')).toBeInTheDocument()
274+
},
275+
}
276+
277+
export const NonWorkshopHidesPrerequisites: Story = {
278+
args: {
279+
proposal: emptyProposal,
280+
setProposal: fn(),
281+
conference: mockConference,
282+
allowedFormats: [Format.lightning_10],
283+
},
284+
play: async ({ canvasElement }) => {
285+
const canvas = within(canvasElement)
286+
expect(canvas.queryByLabelText('Prerequisites')).not.toBeInTheDocument()
287+
},
288+
}

0 commit comments

Comments
 (0)