Skip to content

Refactor Video assets to allow sound, poster support to replayable videos, text fixes. #406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/Asset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export default {
type: Boolean,
default: () => true,
},
videoMuted: {
type: Boolean,
default: true,
},
},
computed: {
rawAsset() {
Expand Down Expand Up @@ -96,6 +100,7 @@ export default {
return {
variants: this.asset.variants,
showsControls: this.showsVideoControls,
muted: this.videoMuted,
autoplays: this.prefersReducedMotion ? false : this.videoAutoplays,
posterVariants: this.videoPoster ? this.videoPoster.variants : [],
};
Expand Down
34 changes: 30 additions & 4 deletions src/components/ReplayableVideoAsset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@
<VideoAsset
ref="asset"
:variants="variants"
@ended="onVideoEnd"
:showsControls="showsControls"
:autoplays="autoplays"
:showsControls="showsControls"
:muted="muted"
:posterVariants="posterVariants"
@pause="onPause"
@playing="onVideoPlaying"
@ended="onVideoEnd"
/>
<a
class="replay-button"
href="#"
:class="{ visible: this.showsReplayButton }"
@click.prevent="replay"
>
Replay
{{ text }}
<InlineReplayIcon class="replay-icon icon-inline" />
</a>
</div>
Expand Down Expand Up @@ -52,10 +56,22 @@ export default {
type: Boolean,
default: () => true,
},
muted: {
type: Boolean,
default: true,
},
posterVariants: {
type: Array,
default: () => [],
},
},
computed: {
text: ({ played }) => (played ? 'Replay' : 'Play'),
},
data() {
return {
showsReplayButton: false,
showsReplayButton: !(this.autoplays && this.muted),
played: false,
};
},
methods: {
Expand All @@ -68,6 +84,16 @@ export default {
},
onVideoEnd() {
this.showsReplayButton = true;
this.played = true;
},
onVideoPlaying() {
this.showsReplayButton = false;
},
onPause() {
// if the video pauses, and we are hiding the controls, show the replay button
if (!this.showsControls && !this.showsReplayButton) {
this.showsReplayButton = true;
}
},
},
};
Expand Down
7 changes: 6 additions & 1 deletion src/components/VideoAsset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
:controls="showsControls"
:autoplay="autoplays"
:poster="normalizeAssetUrl(defaultPosterAttributes.url)"
muted
:muted="muted"
playsinline
@playing="$emit('playing')"
@pause="$emit('pause')"
@ended="$emit('ended')"
>
<!--
Expand Down Expand Up @@ -53,6 +54,10 @@ export default {
required: false,
default: () => [],
},
muted: {
type: Boolean,
default: true,
},
},
data: () => ({ appState: AppStore.state }),
computed: {
Expand Down
120 changes: 58 additions & 62 deletions tests/unit/components/Asset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ import ImageAsset from 'docc-render/components/ImageAsset.vue';
import VideoAsset from 'docc-render/components/VideoAsset.vue';
import ReplayableVideoAsset from 'docc-render/components/ReplayableVideoAsset.vue';

const video = {
type: 'video',
poster: 'image',
variants: [
{
url: 'foo.mp4',
traits: ['2x'],
size: {
width: 42,
height: 42,
},
},
],
};

describe('Asset', () => {
beforeAll(() => {
Object.defineProperty(window, 'matchMedia', {
Expand Down Expand Up @@ -60,20 +75,6 @@ describe('Asset', () => {
});

it('renders a `VideoAsset` for video', () => {
const foo = {
type: 'video',
poster: 'image',
variants: [
{
url: 'foo.mp4',
traits: ['2x'],
size: {
width: 42,
height: 42,
},
},
],
};
const image = {
type: 'image',
variants: [
Expand All @@ -83,10 +84,10 @@ describe('Asset', () => {
},
],
};
const wrapper = mountAsset('foo', { foo, image });
const wrapper = mountAsset('video', { video, image });

const videoAsset = wrapper.find(VideoAsset);
expect(videoAsset.props('variants')).toBe(foo.variants);
expect(videoAsset.props('variants')).toBe(video.variants);
expect(videoAsset.props('posterVariants')).toBe(image.variants);
expect(videoAsset.props('showsControls')).toBe(true);
expect(videoAsset.props('autoplays')).toBe(true);
Expand All @@ -97,68 +98,63 @@ describe('Asset', () => {
});

it('renders a `VideoAsset` without poster variants', () => {
const foo = {
type: 'video',
poster: 'image',
variants: [
{
url: 'foo.mp4',
traits: ['2x'],
size: {
width: 42,
height: 42,
},
},
],
};
const videoAsset = mountAsset('foo', { foo }).find(VideoAsset);
expect(videoAsset.props('variants')).toBe(foo.variants);
const videoAsset = mountAsset('video', { video }).find(VideoAsset);
expect(videoAsset.props('variants')).toBe(video.variants);
expect(videoAsset.props('posterVariants')).toEqual([]);
});

it('renders a `ReplayableVideoAsset` for video with `showsReplayButton=true`', () => {
const foo = {
type: 'video',
variants: [
{
url: 'foo.mp4',
traits: ['2x'],
size: {
width: 42,
height: 42,
},
},
],
};
const wrapper = shallowMount(Asset, {
propsData: {
identifier: 'foo',
identifier: 'video',
showsReplayButton: true,
showsVideoControls: true,
},
provide: { references: { foo } },
provide: { references: { video } },
});

const videoAsset = wrapper.find(ReplayableVideoAsset);
expect(videoAsset.props('variants')).toBe(foo.variants);
expect(videoAsset.props('variants')).toBe(video.variants);
expect(videoAsset.props('showsControls')).toBe(true);
expect(videoAsset.props('muted')).toBe(true);
});

it('renders a `VideoAsset`, without muting it', () => {
const wrapper = shallowMount(Asset, {
propsData: {
identifier: 'video',
showsVideoControls: true,
videoAutoplays: true,
videoMuted: false,
},
provide: { references: { video } },
});

expect(wrapper.find(ReplayableVideoAsset).exists()).toBe(false);
const videoAsset = wrapper.find(VideoAsset);
expect(videoAsset.props('variants')).toBe(video.variants);
expect(videoAsset.props('showsControls')).toBe(true);
expect(videoAsset.props('muted')).toBe(false);
expect(videoAsset.props('autoplays')).toBe(true);
});

it('renders a `ReplayableVideoAsset` without it being muted', () => {
const wrapper = shallowMount(Asset, {
propsData: {
identifier: 'video',
showsReplayButton: true,
showsVideoControls: true,
videoMuted: false,
},
provide: { references: { video } },
});

const videoAsset = wrapper.find(ReplayableVideoAsset);
expect(videoAsset.props('showsControls')).toBe(true);
expect(videoAsset.props('muted')).toBe(false);
});

describe('ReduceMotion aware', () => {
const video = {
type: 'video',
poster: 'image',
variants: [
{
url: 'foo.mp4',
traits: ['2x'],
size: {
width: 42,
height: 42,
},
},
],
};
const image = {
type: 'image',
alt: 'blah',
Expand Down
52 changes: 52 additions & 0 deletions tests/unit/components/ReplayableVideoAsset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import InlineReplayIcon from 'theme/components/Icons/InlineReplayIcon.vue';
import { flushPromises } from '../../../test-utils';

const variants = [{ traits: ['dark', '1x'], url: 'https://www.example.com/myvideo.mp4' }];
const posterVariants = [{ traits: ['dark', '1x'], url: 'https://www.example.com/image.jpg' }];

const propsData = {
variants,
posterVariants,
};
describe('ReplayableVideoAsset', () => {
const mountWithProps = props => shallowMount(ReplayableVideoAsset, {
Expand Down Expand Up @@ -46,6 +48,7 @@ describe('ReplayableVideoAsset', () => {

const video = wrapper.find(VideoAsset);
expect(video.props('variants')).toBe(variants);
expect(video.props('posterVariants')).toBe(posterVariants);
expect(video.props('showsControls')).toBe(true);
expect(video.props('autoplays')).toBe(true);
});
Expand Down Expand Up @@ -79,4 +82,53 @@ describe('ReplayableVideoAsset', () => {
await flushPromises();
expect(playMock).toHaveBeenCalledTimes(1);
});

it('shows the Replay on first mount, if not set to autoplay', async () => {
const wrapper = mountWithProps({
autoplays: false,
});
const replay = wrapper.find('.replay-button');
expect(replay.text()).toBe('Play');
expect(replay.classes()).toContain('visible');
replay.trigger('click');
await flushPromises();
// text is not changed, but its invisible
expect(replay.text()).toBe('Play');
expect(replay.classes()).not.toContain('visible');
// now end the video
wrapper.find({ ref: 'asset' }).trigger('ended');
// assert text changed and its visible
expect(replay.text()).toBe('Replay');
expect(replay.classes()).toContain('visible');
});

it('shows the Replay on `pause` if no `showsControls` is false', async () => {
const wrapper = mountWithProps({
autoplays: false,
showsControls: false,
});
const replay = wrapper.find('.replay-button');
expect(replay.classes()).toContain('visible');
// play
replay.trigger('click');
await flushPromises();
// assert button is hidden
expect(replay.classes()).not.toContain('visible');
// pause
wrapper.find({ ref: 'asset' }).trigger('pause');
// assert button is visible again
expect(replay.classes()).toContain('visible');
});

it('hides the button when @playing fired', () => {
const wrapper = mountWithProps({
autoplays: false,
});
const replay = wrapper.find('.replay-button');
expect(replay.classes()).toContain('visible');
// Simulate browser starts playing
wrapper.find({ ref: 'asset' }).trigger('playing');
// assert button is hidden
expect(replay.classes()).not.toContain('visible');
});
});
1 change: 1 addition & 0 deletions tests/unit/components/Tutorial/SectionIntro.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe('SectionIntro', () => {
showsReplayButton: true,
showsVideoControls: false,
videoAutoplays: true,
videoMuted: true,
});
});

Expand Down
13 changes: 12 additions & 1 deletion tests/unit/components/VideoAsset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('VideoAsset', () => {

it('renders a video', () => {
expect(wrapper.is('video')).toBe(true);
expect(wrapper.element.muted).toBe(true);
});

it('adds a poster to the `video`, using light by default', () => {
Expand Down Expand Up @@ -86,12 +87,15 @@ describe('VideoAsset', () => {
expect(source.attributes('controls')).toBe(undefined);
});

it('forwards `playing` and `ended` events', () => {
it('forwards `playing`, `pause` and `ended` events', () => {
const video = wrapper.find('video');

video.trigger('playing');
expect(wrapper.emitted().playing.length).toBe(1);

video.trigger('pause');
expect(wrapper.emitted().pause.length).toBe(1);

video.trigger('ended');
expect(wrapper.emitted().ended.length).toBe(1);
});
Expand Down Expand Up @@ -143,4 +147,11 @@ describe('VideoAsset', () => {
expect(source.exists()).toBe(true);
expect(source.attributes('src')).toBe(propsData.variants[1].url);
});

it('renders a video as none-muted', () => {
wrapper.setProps({
muted: false,
});
expect(wrapper.element.muted).toBeFalsy();
});
});