diff --git a/admin/components/release/release-list-item.html b/admin/components/release/release-list-item.html index 677318d..4b8f41e 100644 --- a/admin/components/release/release-list-item.html +++ b/admin/components/release/release-list-item.html @@ -7,7 +7,7 @@

{{.Title}} - {{.GetReleaseYear}} + {{.ReleaseDate.Year}} {{if not .Visible}}(hidden){{end}}

diff --git a/cursor/cursor.go b/cursor/cursor.go index 4e4504f..4ed59e3 100644 --- a/cursor/cursor.go +++ b/cursor/cursor.go @@ -88,13 +88,13 @@ func handleClient(client *CursorClient) { client.Route = args[1] mutex.Lock() - for _, otherClient := range clients { - if otherClient.ID == client.ID { continue } - if otherClient.Route != client.Route { continue } - client.Send([]byte(fmt.Sprintf("join:%d", otherClient.ID))) - client.Send([]byte(fmt.Sprintf("pos:%d:%f:%f", otherClient.ID, otherClient.X, otherClient.Y))) + for otherClientID, otherClient := range clients { + if otherClientID == client.ID || otherClient.Route != client.Route { continue } + client.Send([]byte(fmt.Sprintf("join:%d", otherClientID))) + client.Send([]byte(fmt.Sprintf("pos:%d:%f:%f", otherClientID, otherClient.X, otherClient.Y))) } mutex.Unlock() + broadcast <- CursorMessage{ []byte(fmt.Sprintf("join:%d", client.ID)), client.Route, diff --git a/model/artist.go b/model/artist.go index 733e537..63871c7 100644 --- a/model/artist.go +++ b/model/artist.go @@ -9,10 +9,6 @@ type ( } ) -func (artist Artist) GetWebsite() string { - return artist.Website -} - func (artist Artist) GetAvatar() string { if artist.Avatar == "" { return "/img/default-avatar.png" diff --git a/model/artist_test.go b/model/artist_test.go new file mode 100644 index 0000000..feb9a18 --- /dev/null +++ b/model/artist_test.go @@ -0,0 +1,23 @@ +package model + +import ( + "testing" +) + +func Test_Artist_GetAvatar(t *testing.T) { + want := "testavatar.png" + artist := Artist{ Avatar: want } + + got := artist.GetAvatar() + if want != got { + t.Errorf(`correct value not returned when avatar is populated (want "%s", got "%s")`, want, got) + } + + artist = Artist{} + + want = "/img/default-avatar.png" + got = artist.GetAvatar() + if want != got { + t.Errorf(`default value not returned when avatar is empty (want "%s", got "%s")`, want, got) + } +} diff --git a/model/link.go b/model/link.go index 8b48ced..1a5bb8f 100644 --- a/model/link.go +++ b/model/link.go @@ -11,6 +11,6 @@ type Link struct { } func (link Link) NormaliseName() string { - rgx := regexp.MustCompile(`[^a-z0-9]`) - return strings.ToLower(rgx.ReplaceAllString(link.Name, "")) + rgx := regexp.MustCompile(`[^a-z0-9\-]`) + return rgx.ReplaceAllString(strings.ToLower(link.Name), "") } diff --git a/model/link_test.go b/model/link_test.go new file mode 100644 index 0000000..b368094 --- /dev/null +++ b/model/link_test.go @@ -0,0 +1,23 @@ +package model + +import ( + "testing" +) + +func Test_Link_NormaliseName(t *testing.T) { + link := Link{ + Name: "!c@o#o$l%-^a&w*e(s)o_m=e+-[l{i]n}k-0123456789ABCDEF", + } + + want := "cool-awesome-link-0123456789abcdef" + got := link.NormaliseName() + if want != got { + t.Errorf(`name with invalid characters not properly formatted (want "%s", got "%s")`, want, got) + } + + link.Name = want + got = link.NormaliseName() + if want != got { + t.Errorf(`valid name mangled by formatter (want "%s", got "%s")`, want, got) + } +} diff --git a/model/release.go b/model/release.go index 42d6fba..afaacca 100644 --- a/model/release.go +++ b/model/release.go @@ -50,10 +50,6 @@ func (release Release) PrintReleaseDate() string { return release.ReleaseDate.Format("02 January 2006") } -func (release Release) GetReleaseYear() int { - return release.ReleaseDate.Year() -} - func (release Release) GetArtwork() string { if release.Artwork == "" { return "/img/default-cover-art.png" diff --git a/model/release_test.go b/model/release_test.go new file mode 100644 index 0000000..11a58a1 --- /dev/null +++ b/model/release_test.go @@ -0,0 +1,157 @@ +package model + +import ( + "testing" + "time" +) + +func Test_Release_DescriptionHTML(t *testing.T) { + release := Release{ + Description: "this is\na test\ndescription!", + } + + // descriptions are set by privileged users, + // so we'll allow HTML injection here + want := "this is
a test
description!" + got := release.GetDescriptionHTML() + if want != string(got) { + t.Errorf(`release description incorrectly formatted (want "%s", got "%s")`, want, got) + } +} + +func Test_Release_ReleaseDate(t *testing.T) { + release := Release{ + ReleaseDate: time.Date(2025, time.July, 26, 16, 0, 0, 0, time.UTC), + } + + want := "2025-07-26T16:00" + got := release.TextReleaseDate() + if want != got { + t.Errorf(`release date incorrectly formatted (want "%s", got "%s")`, want, got) + } + + want = "26 July 2025" + got = release.PrintReleaseDate() + if want != got { + t.Errorf(`release date (print) incorrectly formatted (want "%s", got "%s")`, want, got) + } +} + +func Test_Release_Artwork(t *testing.T) { + want := "testartwork.png" + release := Release{ Artwork: want } + + got := release.GetArtwork() + if want != got { + t.Errorf(`correct value not returned when artwork is populated (want "%s", got "%s")`, want, got) + } + + release = Release{} + + want = "/img/default-cover-art.png" + got = release.GetArtwork() + if want != got { + t.Errorf(`default value not returned when artwork is empty (want "%s", got "%s")`, want, got) + } +} + +func Test_Release_IsSingle(t *testing.T) { + release := Release{ + Tracks: []*Track{}, + } + + if release.IsSingle() { + t.Errorf("IsSingle() == true when no tracks are present") + } + + release.Tracks = append(release.Tracks, &Track{}) + if !release.IsSingle() { + t.Errorf("IsSingle() == false when one track is present") + } + + release.Tracks = append(release.Tracks, &Track{}) + if release.IsSingle() { + t.Errorf("IsSingle() == true when >1 tracks are present") + } +} + +func Test_Release_IsReleased(t *testing.T) { + release := Release { + ReleaseDate: time.Now(), + } + + if !release.IsReleased() { + t.Errorf("IsRelease() == false when release date in the past") + } + + release.ReleaseDate = time.Now().Add(time.Hour) + if release.IsReleased() { + t.Errorf("IsRelease() == true when release date in the future") + } +} + +func Test_Release_PrintArtists(t *testing.T) { + artist1 := "ari melody" + artist2 := "aridoodle" + artist3 := "idk" + artist4 := "guest" + + release := Release { + Credits: []*Credit{ + { Artist: Artist{ Name: artist1 }, Primary: true }, + { Artist: Artist{ Name: artist2 }, Primary: true }, + { Artist: Artist{ Name: artist3 }, Primary: false }, + { Artist: Artist{ Name: artist4 }, Primary: true }, + }, + } + + { + want := []string{ artist1, artist2, artist4 } + got := release.GetUniqueArtistNames(true) + if len(want) != len(got) { + t.Errorf(`len(GetUniqueArtistNames) (primary only) == %d, want %d`, len(got), len(want)) + } + for i := range got { + if want[i] != got[i] { + t.Errorf(`GetUniqueArtistNames[%d] (primary only) == %s, want %s`, i, got[i], want[i]) + } + } + + want = []string{ artist1, artist2, artist3, artist4 } + got = release.GetUniqueArtistNames(false) + if len(want) != len(got) { + t.Errorf(`len(GetUniqueArtistNames) == %d, want %d`, len(got), len(want)) + } + for i := range got { + if want[i] != got[i] { + t.Errorf(`GetUniqueArtistNames[%d] == %s, want %s`, i, got[i], want[i]) + } + } + } + + { + want := "ari melody, aridoodle & guest" + got := release.PrintArtists(true, true) + if want != got { + t.Errorf(`PrintArtists (primary only, ampersand) == "%s", want "%s"`, want, got) + } + + want = "ari melody, aridoodle, guest" + got = release.PrintArtists(true, false) + if want != got { + t.Errorf(`PrintArtists (primary only) == "%s", want "%s"`, want, got) + } + + want = "ari melody, aridoodle, idk & guest" + got = release.PrintArtists(false, true) + if want != got { + t.Errorf(`PrintArtists (all, ampersand) == "%s", want "%s"`, want, got) + } + + want = "ari melody, aridoodle, idk, guest" + got = release.PrintArtists(false, false) + if want != got { + t.Errorf(`PrintArtists (all) == "%s", want "%s"`, want, got) + } + } +} diff --git a/model/track_test.go b/model/track_test.go new file mode 100644 index 0000000..fd500d7 --- /dev/null +++ b/model/track_test.go @@ -0,0 +1,43 @@ +package model + +import ( + "testing" +) + +func Test_Track_DescriptionHTML(t *testing.T) { + track := Track{ + Description: "this is\na test\ndescription!", + } + + // descriptions are set by privileged users, + // so we'll allow HTML injection here + want := "this is
a test
description!" + got := track.GetDescriptionHTML() + if want != string(got) { + t.Errorf(`track description incorrectly formatted (want "%s", got "%s")`, want, got) + } +} + +func Test_Track_LyricsHTML(t *testing.T) { + track := Track{ + Lyrics: "these are\ntest\nlyrics!", + } + + // lyrics are set by privileged users, + // so we'll allow HTML injection here + want := "these are
test
lyrics!" + got := track.GetLyricsHTML() + if want != string(got) { + t.Errorf(`track lyrics incorrectly formatted (want "%s", got "%s")`, want, got) + } +} + +func Test_Track_Add(t *testing.T) { + track := Track{} + + want := 4 + got := track.Add(2, 2) + if want != got { + t.Errorf(`somehow, we screwed up addition. (want %d, got %d)`, want, got) + } +} diff --git a/public/script/config.js b/public/script/config.js index 1ab8b5a..402a74b 100644 --- a/public/script/config.js +++ b/public/script/config.js @@ -55,6 +55,7 @@ class Config { get crt() { return this._crt } set crt(/** @type boolean */ enabled) { this._crt = enabled; + this.save(); if (enabled) { document.body.classList.add("crt"); @@ -66,26 +67,24 @@ class Config { this.#listeners.get('crt').forEach(callback => { callback(this._crt); }) - - this.save(); } get cursor() { return this._cursor } set cursor(/** @type boolean */ value) { this._cursor = value; + this.save(); this.#listeners.get('cursor').forEach(callback => { callback(this._cursor); }) - this.save(); } get cursorFunMode() { return this._cursorFunMode } set cursorFunMode(/** @type boolean */ value) { this._cursorFunMode = value; + this.save(); this.#listeners.get('cursorFunMode').forEach(callback => { callback(this._cursorFunMode); }) - this.save(); } } diff --git a/public/script/cursor.js b/public/script/cursor.js index 188cdbb..79637fe 100644 --- a/public/script/cursor.js +++ b/public/script/cursor.js @@ -328,7 +328,7 @@ function cursorSetup() { switch (args[0]) { case 'id': { - myCursor.id = Number(args[1]); + myCursor.id = id; break; } case 'join': { @@ -385,8 +385,6 @@ function cursorDestroy() { cursors.clear(); myCursor = null; - cursorContainer.remove(); - console.log(`Cursor no longer tracking.`); running = false; } diff --git a/views/music-gateway.html b/views/music-gateway.html index 1bf3a2f..0f4441c 100644 --- a/views/music-gateway.html +++ b/views/music-gateway.html @@ -4,7 +4,7 @@ - + @@ -54,7 +54,7 @@

{{.Title}}

- {{.GetReleaseYear}} + {{.ReleaseDate.Year}}

{{.PrintArtists true true}}

{{if .IsReleased}} @@ -91,7 +91,7 @@ {{end}} {{if and .Copyright .CopyrightURL}} - + {{end}} @@ -105,8 +105,8 @@