github.com/derat/nup@v0.0.0-20230418113745-15592ba7c620/test/web/unit/song-table.test.js (about) 1 // Copyright 2021 Daniel Erat. 2 // All rights reserved. 3 4 import { afterEach, beforeEach, expectEq, suite, test } from './test.js'; 5 import MockWindow from './mock-window.js'; 6 7 import { formatDuration } from './common.js'; 8 import {} from './song-table.js'; 9 10 suite('songTable', () => { 11 const makeSong = (n) => ({ 12 songId: `${n}`, 13 artist: `ar${n}`, 14 title: `ti${n}`, 15 album: `al${n}`, 16 albumId: `ai${n}`, 17 length: n, 18 }); 19 const song1 = makeSong(1); 20 const song2 = makeSong(2); 21 const song3 = makeSong(3); 22 const song4 = makeSong(4); 23 const song5 = makeSong(5); 24 25 // Returns an array of song objects returned by getSong(). 26 const getSongs = () => 27 [...Array(table.numSongs).keys()].map((i) => table.getSong(i)); 28 29 // Returns an array of objects describing HTML song rows. 30 const getRows = () => 31 Array.from(table.shadowRoot.querySelectorAll('tbody tr')).map((tr) => { 32 const td = tr.querySelector('td.checkbox'); 33 const checked = 34 window.getComputedStyle(td).getPropertyValue('display') === 'none' 35 ? undefined 36 : td.querySelector("input[type='checkbox']").checked; 37 38 return { 39 artist: tr.querySelector('td.artist').innerText, 40 title: tr.querySelector('td.title').innerText, 41 album: tr.querySelector('td.album').innerText, 42 time: tr.querySelector('td.time').innerText, 43 checked, // undefined if checkbox not shown, true/false otherwise 44 }; 45 }); 46 47 // Convert a song from makeSong() to a row as returned by getRows(). 48 const songToRow = (s, checked) => ({ 49 artist: s.artist, 50 title: s.title, 51 album: s.album, 52 time: formatDuration(s.length), 53 checked, 54 }); 55 56 // Returns the checkbox from the header. 57 const getHeaderCheckbox = () => 58 table.shadowRoot.querySelector(`thead input[type='checkbox']`); 59 60 // Returns the checkbox from the row with 0-based index |i|. 61 const getRowCheckbox = (i) => 62 table.shadowRoot.querySelector( 63 `tbody tr:nth-of-type(${i + 1}) input[type='checkbox']` 64 ); 65 66 let w = null; 67 let table = null; 68 69 beforeEach(() => { 70 w = new MockWindow(); 71 table = document.createElement('song-table'); 72 document.body.appendChild(table); 73 }); 74 afterEach(() => { 75 document.body.removeChild(table); 76 table = null; 77 w.finish(); 78 }); 79 80 test('setSongs', () => { 81 expectEq(getSongs(), []); 82 expectEq(getRows(), []); 83 84 for (const songs of [ 85 [song1], 86 [song1, song2], 87 [song3, song1, song2, song4], 88 [song3, song1, song5, song2, song4], 89 [song1, song2, song3, song4, song5], 90 [song2, song3, song4], 91 [song2, song4], 92 [], 93 ]) { 94 table.setSongs(songs); 95 expectEq(table.numSongs, songs.length, 'Num songs'); 96 expectEq(getSongs(), songs, 'Song list'); 97 expectEq( 98 getRows(), 99 songs.map((s) => songToRow(s)) 100 ); 101 } 102 }); 103 104 test('setSongs (title attributes)', () => { 105 const longSong = { 106 songId: 6, 107 artist: 108 'Very very long artist name, really far too long to fit in any reasonably-sized window', 109 title: 110 "This song also has a very long title, I can't believe it, can you, probably not", 111 album: 112 'Even the album name is too long, I kid you not. Why would someone do this?', 113 albumId: 'ai6', 114 length: 360, 115 }; 116 table.setSongs([song1, longSong, song2]); 117 118 // Longs strings should be copied to the title attribute so they'll be 119 // displayed in tooltips, but short ones shouldn't. 120 const rowAttrs = Array.from( 121 table.shadowRoot.querySelectorAll('tbody tr') 122 ).map((tr) => [ 123 tr.querySelector('td.artist').getAttribute('title'), 124 tr.querySelector('td.title').getAttribute('title'), 125 tr.querySelector('td.album').getAttribute('title'), 126 ]); 127 expectEq(rowAttrs, [ 128 [null, null, null], 129 [longSong.artist, longSong.title, longSong.album], 130 [null, null, null], 131 ]); 132 133 console.log(JSON.stringify(rowAttrs)); 134 }); 135 136 test('checkboxes', () => { 137 // All of the rows should initially be unchecked. 138 table.setAttribute('use-checkboxes', ''); 139 const songs = [song1, song2, song3]; 140 table.setSongs(songs); 141 expectEq( 142 getRows(), 143 songs.map((s) => songToRow(s, false)) 144 ); 145 expectEq(table.checkedSongs, []); 146 147 // Click the second checkbox. 148 getRowCheckbox(1).click(); 149 expectEq(getRows(), [ 150 songToRow(song1, false), 151 songToRow(song2, true), 152 songToRow(song3, false), 153 ]); 154 expectEq(table.checkedSongs, [song2]); 155 156 // Click the third checkbox. 157 getRowCheckbox(2).click(); 158 expectEq(getRows(), [ 159 songToRow(song1, false), 160 songToRow(song2, true), 161 songToRow(song3, true), 162 ]); 163 expectEq(table.checkedSongs, [song2, song3]); 164 165 // Click the header's checkbox to uncheck all rows. 166 getHeaderCheckbox().click(); 167 expectEq( 168 getRows(), 169 songs.map((s) => songToRow(s, false)) 170 ); 171 expectEq(table.checkedSongs, []); 172 173 // Click the header's checkbox again to check all rows. 174 getHeaderCheckbox().click(); 175 expectEq( 176 getRows(), 177 songs.map((s) => songToRow(s, true)) 178 ); 179 expectEq(table.checkedSongs, songs); 180 181 // Replacing the songs should uncheck all rows. 182 const songs2 = [song4, song5]; 183 table.setSongs(songs2); 184 expectEq( 185 getRows(), 186 songs2.map((s) => songToRow(s, false)) 187 ); 188 expectEq(table.checkedSongs, []); 189 190 // Programatically check all rows. 191 table.setAllCheckboxes(true); 192 expectEq( 193 getRows(), 194 songs2.map((s) => songToRow(s, true)) 195 ); 196 expectEq(table.checkedSongs, songs2); 197 }); 198 });