|
1 | 1 | <script module> |
2 | 2 | import { defineMeta } from '@storybook/addon-svelte-csf'; |
3 | 3 | import ChatForm from '$lib/components/app/chat/ChatForm/ChatForm.svelte'; |
4 | | - import { expect } from 'storybook/internal/test'; |
| 4 | + import { expect, waitFor } from 'storybook/internal/test'; |
| 5 | + import { mockServerProps, mockConfigs } from './fixtures/storybook-mocks'; |
5 | 6 |
|
6 | 7 | const { Story } = defineMeta({ |
7 | 8 | title: 'Components/ChatForm', |
|
10 | 11 | layout: 'centered' |
11 | 12 | } |
12 | 13 | }); |
| 14 | +
|
| 15 | + // Mock uploaded files with working data URLs for Storybook |
| 16 | + const mockFileAttachments = [ |
| 17 | + // { |
| 18 | + // id: '1', |
| 19 | + // name: '1.jpg', |
| 20 | + // type: 'image/jpeg', |
| 21 | + // size: 44891, |
| 22 | + // url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iIzMzNzNkYyIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LXNpemU9IjE4IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkltYWdlPC90ZXh0Pjwvc3ZnPg==', |
| 23 | + // file: new File([''], '1.jpg', { type: 'image/jpeg' }) |
| 24 | + // }, |
| 25 | + // { |
| 26 | + // id: '2', |
| 27 | + // name: 'beautiful-flowers-lotus.webp', |
| 28 | + // type: 'image/webp', |
| 29 | + // size: 817630, |
| 30 | + // url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iIzMzNzNkYyIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LXNpemU9IjE4IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkZsb3dlcnM8L3RleHQ+PC9zdmc+', |
| 31 | + // file: new File([''], 'beautiful-flowers-lotus.webp', { type: 'image/webp' }) |
| 32 | + // }, |
| 33 | + // { |
| 34 | + // id: '3', |
| 35 | + // name: 'recording.wav', |
| 36 | + // type: 'audio/wav', |
| 37 | + // size: 512000, |
| 38 | + // url: 'data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuBzvLZiTYIG2m98OScTgwOUarm7blmGgU7k9n1unEiBC13yO/eizEIHWq+8+OWT', |
| 39 | + // file: new File(['test audio content'], 'recording.wav', { type: 'audio/wav' }) |
| 40 | + // }, |
| 41 | + { |
| 42 | + id: '4', |
| 43 | + name: 'example.pdf', |
| 44 | + type: 'application/pdf', |
| 45 | + size: 351048, |
| 46 | + url: 'data:application/pdf;base64,JVBERi0xLjQKJcOkw7zDtsO4CjIgMCBvYmoKPDwKL0xlbmd0aCAzIDAgUgovRmlsdGVyIC9GbGF0ZURlY29kZQo+PgpzdHJlYW0KeJxLy8wpTVWwUshIzStRyE9VqFYoLU4tykvMTVUozy/KSVGwUsjNTFGwUsrIyFGwUsrJTFGyMjJQUKhWykvMTbVSqAUAXYsZGAplbmRzdHJlYW0KZW5kb2JqCgozIDAgb2JqCjw8Ci9MZW5ndGggNDcKPj4Kc3RyZWFtCkJUCi9GMSAxMiBUZgoxIDAgMCAxIDcwIDc1MCBUbQooSGVsbG8gV29ybGQpIFRqCkVUCmVuZHN0cmVhbQplbmRvYmoKCjQgMCBvYmoKPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvSGVsdmV0aWNhCj4+CmVuZG9iagoKNSAwIG9iago8PAovVHlwZSAvUGFnZQovUGFyZW50IDYgMCBSCi9SZXNvdXJjZXMgPDwKL0ZvbnQgPDwKL0YxIDQgMCBSCj4+Cj4+Ci9NZWRpYUJveCBbMCAwIDYxMiA3OTJdCi9Db250ZW50cyAzIDAgUgo+PgplbmRvYmoKCjYgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFs1IDAgUl0KL0NvdW50IDEKL01lZGlhQm94IFswIDAgNjEyIDc5Ml0KPj4KZW5kb2JqCgo3IDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyA2IDAgUgo+PgplbmRvYmoKCnhyZWYKMCA4CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAwOSAwMDAwMCBuIAowMDAwMDAwMDc0IDAwMDAwIG4gCjAwMDAwMDAxNzkgMDAwMDAgbiAKMDAwMDAwMDI3MyAwMDAwMCBuIAowMDAwMDAwMzQ4IDAwMDAwIG4gCjAwMDAwMDA0ODYgMDAwMDAgbiAKMDAwMDAwMDU2MyAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDgKL1Jvb3QgNyAwIFIKPj4Kc3RhcnR4cmVmCjYxMwolJUVPRgo=', |
| 47 | + file: new File(['%PDF-1.4 test content'], 'example.pdf', { type: 'application/pdf' }) |
| 48 | + } |
| 49 | + ]; |
13 | 50 | </script> |
14 | 51 |
|
15 | 52 | <Story |
|
31 | 68 | await userEvent.type(textarea, text); |
32 | 69 |
|
33 | 70 | await expect(textarea).toHaveValue(text); |
| 71 | + |
| 72 | + const fileInput = document.querySelector('input[type="file"]'); |
| 73 | + const acceptAttr = fileInput?.getAttribute('accept'); |
| 74 | + await expect(fileInput).toHaveAttribute('accept'); |
| 75 | + await expect(acceptAttr).not.toContain('image/'); |
| 76 | + await expect(acceptAttr).not.toContain('audio/'); |
| 77 | + |
| 78 | + |
| 79 | + const fileUploadButton = canvas.getByText('Attach files'); |
| 80 | + |
| 81 | + await userEvent.click(fileUploadButton); |
| 82 | + |
| 83 | + const recordButton = canvas.getAllByRole('button', { name: 'Start recording' })[1]; |
| 84 | + const imagesButton = document.querySelector('.images-button'); |
| 85 | + const audioButton = document.querySelector('.audio-button'); |
| 86 | + |
| 87 | + |
| 88 | + await expect(recordButton).toBeDisabled(); |
| 89 | + await expect(imagesButton).toHaveAttribute('data-disabled'); |
| 90 | + await expect(audioButton).toHaveAttribute('data-disabled'); |
34 | 91 | }} |
35 | 92 | /> |
36 | 93 |
|
37 | 94 | <Story |
38 | 95 | name="Loading" |
39 | 96 | args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', isLoading: true }} |
40 | 97 | /> |
| 98 | + |
| 99 | +<Story |
| 100 | + name="VisionModality" |
| 101 | + args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }} |
| 102 | + play={async ({ canvas, userEvent }) => { |
| 103 | + mockServerProps(mockConfigs.visionOnly); |
| 104 | + |
| 105 | + await waitFor(() => { |
| 106 | + const fileInput = document.querySelector('input[type="file"]'); |
| 107 | + const acceptAttr = fileInput?.getAttribute('accept'); |
| 108 | + return acceptAttr; |
| 109 | + }); |
| 110 | + |
| 111 | + // Test initial file input state (should not accept images/audio without dropdown selection) |
| 112 | + const fileInput = document.querySelector('input[type="file"]'); |
| 113 | + const acceptAttr = fileInput?.getAttribute('accept'); |
| 114 | + console.log(acceptAttr); |
| 115 | + await expect(fileInput).toHaveAttribute('accept'); |
| 116 | + await expect(acceptAttr).toContain('image/'); |
| 117 | + await expect(acceptAttr).not.toContain('audio/'); |
| 118 | + |
| 119 | + const fileUploadButton = canvas.getByText('Attach files'); |
| 120 | + await userEvent.click(fileUploadButton); |
| 121 | + |
| 122 | + // Test that record button is disabled (no audio support) |
| 123 | + const recordButton = canvas.getAllByRole('button', { name: 'Start recording' })[1]; |
| 124 | + await expect(recordButton).toBeDisabled(); |
| 125 | + |
| 126 | + // Test that Images button is enabled (vision support) |
| 127 | + const imagesButton = document.querySelector('.images-button'); |
| 128 | + await expect(imagesButton).not.toHaveAttribute('data-disabled'); |
| 129 | + |
| 130 | + // Test that Audio button is disabled (no audio support) |
| 131 | + const audioButton = document.querySelector('.audio-button'); |
| 132 | + await expect(audioButton).toHaveAttribute('data-disabled'); |
| 133 | + |
| 134 | + console.log('✅ Vision modality: Images enabled, Audio/Recording disabled'); |
| 135 | + }} |
| 136 | +/> |
| 137 | + |
| 138 | + |
| 139 | +<Story |
| 140 | + name="FileAttachments" |
| 141 | + args={{ |
| 142 | + class: 'max-w-[56rem] w-[calc(100vw-2rem)]', |
| 143 | + uploadedFiles: mockFileAttachments |
| 144 | + }} |
| 145 | + play={async ({ canvasElement }) => { |
| 146 | + // Test that both vision and audio modalities are enabled |
| 147 | + const fileUploadButton = canvasElement.querySelector('button[aria-label*="Upload"], button:has([data-lucide="paperclip"])'); |
| 148 | + |
| 149 | + if (fileUploadButton && fileUploadButton instanceof HTMLButtonElement) { |
| 150 | + fileUploadButton.click(); |
| 151 | + |
| 152 | + // Wait for dropdown to appear |
| 153 | + await new Promise(resolve => setTimeout(resolve, 100)); |
| 154 | + |
| 155 | + // Check if both Images and Audio options are available |
| 156 | + const imagesOption = canvasElement.querySelector('[data-testid="upload-images"], button:contains("Images")'); |
| 157 | + const audioOption = canvasElement.querySelector('[data-testid="upload-audio"], button:contains("Audio")'); |
| 158 | + |
| 159 | + if (imagesOption && imagesOption instanceof HTMLButtonElement && !imagesOption.disabled) { |
| 160 | + console.log('✅ File Attachments: Vision modality enabled'); |
| 161 | + } |
| 162 | + |
| 163 | + if (audioOption && audioOption instanceof HTMLButtonElement && !audioOption.disabled) { |
| 164 | + console.log('✅ File Attachments: Audio modality enabled'); |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + // Test microphone availability |
| 169 | + const micButton = canvasElement.querySelector('button[aria-label*="Record"], button:has([data-lucide="mic"])'); |
| 170 | + if (micButton && micButton instanceof HTMLButtonElement && !micButton.disabled) { |
| 171 | + console.log('✅ File Attachments: Microphone recording enabled'); |
| 172 | + } |
| 173 | + }} |
| 174 | +/> |
0 commit comments