ChatForm.stories.svelte 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <script module lang="ts">
  2. import { defineMeta } from '@storybook/addon-svelte-csf';
  3. import ChatForm from '$lib/components/app/chat/ChatForm/ChatForm.svelte';
  4. import { expect } from 'storybook/test';
  5. import { mockServerProps, mockConfigs } from './fixtures/storybook-mocks';
  6. import jpgAsset from './fixtures/assets/1.jpg?url';
  7. import svgAsset from './fixtures/assets/hf-logo.svg?url';
  8. import pdfAsset from './fixtures/assets/example.pdf?raw';
  9. const { Story } = defineMeta({
  10. title: 'Components/ChatScreen/ChatForm',
  11. component: ChatForm,
  12. parameters: {
  13. layout: 'centered'
  14. }
  15. });
  16. let fileAttachments = $state([
  17. {
  18. id: '1',
  19. name: '1.jpg',
  20. type: 'image/jpeg',
  21. size: 44891,
  22. preview: jpgAsset,
  23. file: new File([''], '1.jpg', { type: 'image/jpeg' })
  24. },
  25. {
  26. id: '2',
  27. name: 'hf-logo.svg',
  28. type: 'image/svg+xml',
  29. size: 1234,
  30. preview: svgAsset,
  31. file: new File([''], 'hf-logo.svg', { type: 'image/svg+xml' })
  32. },
  33. {
  34. id: '3',
  35. name: 'example.pdf',
  36. type: 'application/pdf',
  37. size: 351048,
  38. file: new File([pdfAsset], 'example.pdf', { type: 'application/pdf' })
  39. }
  40. ]);
  41. </script>
  42. <Story
  43. name="Default"
  44. args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
  45. play={async ({ canvas, userEvent }) => {
  46. mockServerProps(mockConfigs.noModalities);
  47. const textarea = await canvas.findByRole('textbox');
  48. const submitButton = await canvas.findByRole('button', { name: 'Send' });
  49. // Expect the input to be focused after the component is mounted
  50. await expect(textarea).toHaveFocus();
  51. // Expect the submit button to be disabled
  52. await expect(submitButton).toBeDisabled();
  53. const text = 'What is the meaning of life?';
  54. await userEvent.clear(textarea);
  55. await userEvent.type(textarea, text);
  56. await expect(textarea).toHaveValue(text);
  57. const fileInput = document.querySelector('input[type="file"]');
  58. const acceptAttr = fileInput?.getAttribute('accept');
  59. await expect(fileInput).toHaveAttribute('accept');
  60. await expect(acceptAttr).not.toContain('image/');
  61. await expect(acceptAttr).not.toContain('audio/');
  62. // Open file attachments dropdown
  63. const fileUploadButton = canvas.getByText('Attach files');
  64. await userEvent.click(fileUploadButton);
  65. // Check dropdown menu items are disabled (no modalities)
  66. const imagesButton = document.querySelector('.images-button');
  67. const audioButton = document.querySelector('.audio-button');
  68. await expect(imagesButton).toHaveAttribute('data-disabled');
  69. await expect(audioButton).toHaveAttribute('data-disabled');
  70. // Close dropdown by pressing Escape
  71. await userEvent.keyboard('{Escape}');
  72. }}
  73. />
  74. <Story name="Loading" args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', isLoading: true }} />
  75. <Story
  76. name="VisionModality"
  77. args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
  78. play={async ({ canvas, userEvent }) => {
  79. mockServerProps(mockConfigs.visionOnly);
  80. // Open file attachments dropdown and verify it works
  81. const fileUploadButton = canvas.getByText('Attach files');
  82. await userEvent.click(fileUploadButton);
  83. // Verify dropdown menu items exist
  84. const imagesButton = document.querySelector('.images-button');
  85. const audioButton = document.querySelector('.audio-button');
  86. await expect(imagesButton).toBeInTheDocument();
  87. await expect(audioButton).toBeInTheDocument();
  88. // Close dropdown by pressing Escape
  89. await userEvent.keyboard('{Escape}');
  90. console.log('✅ Vision modality: Dropdown menu verified');
  91. }}
  92. />
  93. <Story
  94. name="AudioModality"
  95. args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
  96. play={async ({ canvas, userEvent }) => {
  97. mockServerProps(mockConfigs.audioOnly);
  98. // Open file attachments dropdown and verify it works
  99. const fileUploadButton = canvas.getByText('Attach files');
  100. await userEvent.click(fileUploadButton);
  101. // Verify dropdown menu items exist
  102. const imagesButton = document.querySelector('.images-button');
  103. const audioButton = document.querySelector('.audio-button');
  104. await expect(imagesButton).toBeInTheDocument();
  105. await expect(audioButton).toBeInTheDocument();
  106. // Close dropdown by pressing Escape
  107. await userEvent.keyboard('{Escape}');
  108. console.log('✅ Audio modality: Dropdown menu verified');
  109. }}
  110. />
  111. <Story
  112. name="FileAttachments"
  113. args={{
  114. class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
  115. uploadedFiles: fileAttachments
  116. }}
  117. play={async ({ canvas }) => {
  118. mockServerProps(mockConfigs.bothModalities);
  119. const jpgAttachment = canvas.getByAltText('1.jpg');
  120. const svgAttachment = canvas.getByAltText('hf-logo.svg');
  121. const pdfFileExtension = canvas.getByText('PDF');
  122. const pdfAttachment = canvas.getByText('example.pdf');
  123. const pdfSize = canvas.getByText('342.82 KB');
  124. await expect(jpgAttachment).toBeInTheDocument();
  125. await expect(jpgAttachment).toHaveAttribute('src', jpgAsset);
  126. await expect(svgAttachment).toBeInTheDocument();
  127. await expect(svgAttachment).toHaveAttribute('src', svgAsset);
  128. await expect(pdfFileExtension).toBeInTheDocument();
  129. await expect(pdfAttachment).toBeInTheDocument();
  130. await expect(pdfSize).toBeInTheDocument();
  131. }}
  132. />