status-components.test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // Status Components Test Suite
  2. // Tests for Task 5: Implement status badges with integrated progress display
  3. describe('Status Badge Components', () => {
  4. let app;
  5. beforeEach(() => {
  6. // Set up DOM structure
  7. document.body.innerHTML = `
  8. <div class="video-item" data-video-id="test-video">
  9. <div class="status-column">
  10. <span class="status-badge ready">Ready</span>
  11. </div>
  12. </div>
  13. `;
  14. // Initialize app instance
  15. app = {
  16. createIntegratedStatusBadge: function(status, progress = null) {
  17. const badge = document.createElement('span');
  18. badge.className = `status-badge ${status.toLowerCase()}`;
  19. badge.setAttribute('role', 'status');
  20. badge.setAttribute('aria-live', 'polite');
  21. let badgeText = '';
  22. let ariaLabel = '';
  23. switch (status.toLowerCase()) {
  24. case 'ready':
  25. badgeText = 'Ready';
  26. ariaLabel = 'Video ready for download';
  27. badge.classList.remove('has-progress');
  28. badge.removeAttribute('data-progress');
  29. badge.style.removeProperty('--progress-width');
  30. break;
  31. case 'downloading':
  32. if (progress !== null) {
  33. // Clamp progress between 0 and 100
  34. const clampedProgress = Math.max(0, Math.min(100, progress));
  35. const roundedProgress = Math.round(clampedProgress);
  36. badgeText = `Downloading ${roundedProgress}%`;
  37. ariaLabel = `Downloading ${roundedProgress}%`;
  38. badge.setAttribute('data-progress', roundedProgress.toString());
  39. badge.classList.add('has-progress');
  40. badge.style.setProperty('--progress-width', `${roundedProgress}%`);
  41. } else {
  42. badgeText = 'Downloading';
  43. ariaLabel = 'Downloading video';
  44. badge.setAttribute('data-progress', '0');
  45. badge.classList.add('has-progress');
  46. badge.style.setProperty('--progress-width', '0%');
  47. }
  48. break;
  49. case 'converting':
  50. if (progress !== null) {
  51. // Clamp progress between 0 and 100
  52. const clampedProgress = Math.max(0, Math.min(100, progress));
  53. const roundedProgress = Math.round(clampedProgress);
  54. badgeText = `Converting ${roundedProgress}%`;
  55. ariaLabel = `Converting ${roundedProgress}%`;
  56. badge.setAttribute('data-progress', roundedProgress.toString());
  57. badge.classList.add('has-progress');
  58. badge.style.setProperty('--progress-width', `${roundedProgress}%`);
  59. } else {
  60. badgeText = 'Converting';
  61. ariaLabel = 'Converting video';
  62. badge.setAttribute('data-progress', '0');
  63. badge.classList.add('has-progress');
  64. badge.style.setProperty('--progress-width', '0%');
  65. }
  66. break;
  67. case 'completed':
  68. badgeText = 'Completed';
  69. ariaLabel = 'Video download completed';
  70. badge.classList.remove('has-progress');
  71. badge.removeAttribute('data-progress');
  72. badge.style.removeProperty('--progress-width');
  73. break;
  74. case 'error':
  75. badgeText = 'Error';
  76. ariaLabel = 'Video download failed';
  77. badge.classList.remove('has-progress');
  78. badge.removeAttribute('data-progress');
  79. badge.style.removeProperty('--progress-width');
  80. break;
  81. default:
  82. badgeText = status;
  83. ariaLabel = `Video status: ${status}`;
  84. badge.classList.remove('has-progress');
  85. badge.removeAttribute('data-progress');
  86. badge.style.removeProperty('--progress-width');
  87. }
  88. badge.textContent = badgeText;
  89. badge.setAttribute('aria-label', ariaLabel);
  90. return badge;
  91. },
  92. updateVideoStatus: function(videoId, status, progress = null) {
  93. const videoElement = document.querySelector(`[data-video-id="${videoId}"]`);
  94. if (!videoElement) return;
  95. const statusColumn = videoElement.querySelector('.status-column');
  96. if (!statusColumn) return;
  97. statusColumn.innerHTML = '';
  98. const statusBadge = this.createIntegratedStatusBadge(status, progress);
  99. statusColumn.appendChild(statusBadge);
  100. }
  101. };
  102. });
  103. test('should create ready status badge', () => {
  104. const badge = app.createIntegratedStatusBadge('ready');
  105. expect(badge.textContent).toBe('Ready');
  106. expect(badge.className).toContain('status-badge ready');
  107. expect(badge.getAttribute('aria-label')).toBe('Video ready for download');
  108. expect(badge.hasAttribute('data-progress')).toBe(false);
  109. expect(badge.classList.contains('has-progress')).toBe(false);
  110. });
  111. test('should create downloading status badge with progress', () => {
  112. const badge = app.createIntegratedStatusBadge('downloading', 65);
  113. expect(badge.textContent).toBe('Downloading 65%');
  114. expect(badge.className).toContain('status-badge downloading');
  115. expect(badge.getAttribute('aria-label')).toBe('Downloading 65%');
  116. expect(badge.getAttribute('data-progress')).toBe('65');
  117. expect(badge.classList.contains('has-progress')).toBe(true);
  118. expect(badge.style.getPropertyValue('--progress-width')).toBe('65%');
  119. });
  120. test('should create converting status badge with progress', () => {
  121. const badge = app.createIntegratedStatusBadge('converting', 42);
  122. expect(badge.textContent).toBe('Converting 42%');
  123. expect(badge.className).toContain('status-badge converting');
  124. expect(badge.getAttribute('aria-label')).toBe('Converting 42%');
  125. expect(badge.getAttribute('data-progress')).toBe('42');
  126. expect(badge.classList.contains('has-progress')).toBe(true);
  127. expect(badge.style.getPropertyValue('--progress-width')).toBe('42%');
  128. });
  129. test('should create completed status badge', () => {
  130. const badge = app.createIntegratedStatusBadge('completed');
  131. expect(badge.textContent).toBe('Completed');
  132. expect(badge.className).toContain('status-badge completed');
  133. expect(badge.getAttribute('aria-label')).toBe('Video download completed');
  134. expect(badge.hasAttribute('data-progress')).toBe(false);
  135. expect(badge.classList.contains('has-progress')).toBe(false);
  136. });
  137. test('should create error status badge', () => {
  138. const badge = app.createIntegratedStatusBadge('error');
  139. expect(badge.textContent).toBe('Error');
  140. expect(badge.className).toContain('status-badge error');
  141. expect(badge.getAttribute('aria-label')).toBe('Video download failed');
  142. expect(badge.hasAttribute('data-progress')).toBe(false);
  143. expect(badge.classList.contains('has-progress')).toBe(false);
  144. });
  145. test('should handle progress bounds correctly', () => {
  146. // Test negative progress
  147. const badgeNegative = app.createIntegratedStatusBadge('downloading', -10);
  148. expect(badgeNegative.getAttribute('data-progress')).toBe('0');
  149. expect(badgeNegative.style.getPropertyValue('--progress-width')).toBe('0%');
  150. // Test progress over 100
  151. const badgeOver = app.createIntegratedStatusBadge('downloading', 150);
  152. expect(badgeOver.getAttribute('data-progress')).toBe('100');
  153. expect(badgeOver.style.getPropertyValue('--progress-width')).toBe('100%');
  154. // Test decimal progress
  155. const badgeDecimal = app.createIntegratedStatusBadge('converting', 65.7);
  156. expect(badgeDecimal.getAttribute('data-progress')).toBe('66');
  157. expect(badgeDecimal.style.getPropertyValue('--progress-width')).toBe('66%');
  158. });
  159. test('should update video status in DOM', () => {
  160. app.updateVideoStatus('test-video', 'downloading', 75);
  161. const statusBadge = document.querySelector('[data-video-id="test-video"] .status-badge');
  162. expect(statusBadge.textContent).toBe('Downloading 75%');
  163. expect(statusBadge.className).toContain('downloading');
  164. expect(statusBadge.getAttribute('data-progress')).toBe('75');
  165. });
  166. test('should have proper accessibility attributes', () => {
  167. const badge = app.createIntegratedStatusBadge('downloading', 50);
  168. expect(badge.getAttribute('role')).toBe('status');
  169. expect(badge.getAttribute('aria-live')).toBe('polite');
  170. expect(badge.getAttribute('aria-label')).toBe('Downloading 50%');
  171. });
  172. test('should handle status transitions correctly', () => {
  173. // Start with ready
  174. app.updateVideoStatus('test-video', 'ready');
  175. let statusBadge = document.querySelector('[data-video-id="test-video"] .status-badge');
  176. expect(statusBadge.textContent).toBe('Ready');
  177. expect(statusBadge.classList.contains('has-progress')).toBe(false);
  178. // Transition to downloading
  179. app.updateVideoStatus('test-video', 'downloading', 30);
  180. statusBadge = document.querySelector('[data-video-id="test-video"] .status-badge');
  181. expect(statusBadge.textContent).toBe('Downloading 30%');
  182. expect(statusBadge.classList.contains('has-progress')).toBe(true);
  183. // Transition to converting
  184. app.updateVideoStatus('test-video', 'converting', 80);
  185. statusBadge = document.querySelector('[data-video-id="test-video"] .status-badge');
  186. expect(statusBadge.textContent).toBe('Converting 80%');
  187. expect(statusBadge.classList.contains('has-progress')).toBe(true);
  188. // Transition to completed
  189. app.updateVideoStatus('test-video', 'completed');
  190. statusBadge = document.querySelector('[data-video-id="test-video"] .status-badge');
  191. expect(statusBadge.textContent).toBe('Completed');
  192. expect(statusBadge.classList.contains('has-progress')).toBe(false);
  193. });
  194. });
  195. // Export for Node.js testing if needed
  196. if (typeof module !== 'undefined' && module.exports) {
  197. module.exports = { describe, test, expect, beforeEach };
  198. }