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  });