Make podcast-playback to be controllable via keyboard (#14139)

* Make podcast-playback to be controllable via keyboard

* Add improved state handling for the play/pause controller

* Fix business logic + Improve HTML semantics

* Remove manual tabIndex addition (not required now)

* Refactor redudant event-listener

* Keep aria-pressed label in sync in all cases

* Code refactor

* Add seed data to create a test podcast

* Add a static file and use that as example podcast episode (#1)

Rather than relying at test time on external media, and requiring the
podcast episode import flow to run during e2e testing, just insert a
canned episode that shows.

Confirmed in cypress this does show after visit('/pod') in the
togglePodcastPlayback.spec.js

* Add cypress test

* Move cypress tests

* Update cypress/integration/seededFlows/podcastFlows/togglePodcastPlayback.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* fix button name in test

Co-authored-by: Daniel Uber <djuber@gmail.com>
Co-authored-by: rhymes <github@rhymes.dev>
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
Aayush Gupta 2021-08-20 16:15:53 +05:30 committed by GitHub
parent 0479d61d02
commit 4280434aed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 7 deletions

View file

@ -82,14 +82,18 @@ function initializePodcastPlayback() {
function spinPodcastRecord(customMessage) {
if (audioExistAndIsPlaying() && recordExist()) {
getById(`record-${window.activeEpisode}`).classList.add('playing');
var podcastPlaybackButton = getById(`record-${window.activeEpisode}`);
podcastPlaybackButton.classList.add('playing');
podcastPlaybackButton.setAttribute('aria-pressed', 'true');
changeStatusMessage(customMessage);
}
}
function stopRotatingActivePodcastIfExist() {
if (window.activeEpisode && getById(`record-${window.activeEpisode}`)) {
getById(`record-${window.activeEpisode}`).classList.remove('playing');
var podcastPlaybackButton = getById(`record-${window.activeEpisode}`);
podcastPlaybackButton.classList.remove('playing');
podcastPlaybackButton.setAttribute('aria-pressed', 'false');
window.activeEpisode = undefined;
}
}
@ -183,7 +187,8 @@ function initializePodcastPlayback() {
Array.prototype.forEach.call(records, function (record) {
var episodeSlug = record.getAttribute('data-episode');
var podcastSlug = record.getAttribute('data-podcast');
record.onclick = function () {
var togglePodcastState = function (e) {
if (podcastBarAlreadyExistAndPlayingTargetEpisode(episodeSlug)) {
var audio = getById('audio');
if (audio) {
@ -194,6 +199,7 @@ function initializePodcastPlayback() {
loadAndPlayNewPodcast(episodeSlug);
}
};
record.addEventListener('click', togglePodcastState);
});
}

View file

@ -124,6 +124,11 @@
}
}
.record-wrapper {
background-color: transparent;
border: 0;
}
.playing {
.record {
.main-image {

View file

@ -49,18 +49,18 @@
</time>
<% end %>
</div>
<div id="record-<%= @episode.slug %>" class="record-wrapper" data-podcast="<%= @podcast.slug %>" data-episode="<%= @episode.slug %>">
<button id="record-<%= @episode.slug %>" class="record-wrapper" data-podcast="<%= @podcast.slug %>" data-episode="<%= @episode.slug %>" aria-pressed="false">
<div class="record" id="record">
<%= optimized_image_tag(@podcast.image_url,
optimizer_options: { width: 420, height: 420, crop: "fill" },
image_options: { class: "main-image", alt: @podcast.title }) %>
<img alt="Play Button" class="butt play-butt" src="<%= image_path("playbutt.png") %>" />
<img alt="Pause Button" class="butt pause-butt" src="<%= image_path("pausebutt.png") %>" />
<img alt="Play podcast" class="butt play-butt" src="<%= image_path("playbutt.png") %>" />
<img alt="Play podcast" class="butt pause-butt" src="<%= image_path("pausebutt.png") %>" />
<div class="status-message" id="status-message-<%= @episode.slug %>">
play
</div>
</div>
</div>
</button>
</div>
<%# checking both podcast_episode and podcast status for now %>
<% unless @episode.https? %>

View file

@ -0,0 +1,20 @@
describe('Toggle Podcast playback', () => {
beforeEach(() => {
cy.testSetup();
});
it('should toggle podcast playback', () => {
cy.visit('/pod');
cy.contains('div', 'Example media | crow call').click();
cy.findByRole('button', { name: 'Developer on Fire Play podcast' }).as(
'toggleButton',
);
cy.get('@toggleButton')
.invoke('attr', 'aria-pressed')
.should('eq', 'false');
cy.get('@toggleButton').click();
cy.get('@toggleButton').invoke('attr', 'aria-pressed').should('eq', 'true');
});
});

BIN
public/media/crow-call.mp3 Normal file

Binary file not shown.

View file

@ -549,6 +549,45 @@ end
##############################################################################
seeder.create_if_doesnt_exist(Podcast, "title", "Test podcast") do
podcast_attributes = {
title: "Developer on Fire",
description: "",
feed_url: "http://developeronfire.com/rss.xml",
itunes_url: "https://itunes.apple.com/us/podcast/developer-on-fire/id1006105326",
slug: "developeronfire",
twitter_username: "raelyard",
website_url: "http://developeronfire.com",
main_color_hex: "343d46",
overcast_url: "https://overcast.fm/itunes1006105326/developer-on-fire",
android_url: "http://subscribeonandroid.com/developeronfire.com/rss.xml",
image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
published: true
}
podcast = Podcast.create!(podcast_attributes)
podcast_episode_attributes = {
body: "<p>A real good crow call</p>",
guid: "<guid isPermaLink=\"true\">/media/crow-call.mp3</guid>",
https: false,
itunes_url: nil,
image: nil,
media_url: "/media/crow-call.mp3",
processed_html: "<p>A real good crow call</p>",
published_at: Date.new(2021,1,1),
slug: "crow-call",
subtitle: "Example media: Crow Call",
summary: "<p>6 seconds of bird song</p>",
title: "Example media | crow call",
website_url: "https://github.com/forem/",
tag_list: nil,
podcast_id: podcast.id
}
PodcastEpisode.create!(podcast_episode_attributes)
end
##############################################################################
seeder.create_if_none(Reaction) do
user = User.find_by(username: "trusted_user_1")
admin_user.reactions.create!(category: :vomit, reactable: user)