There is an error, so we need to deal with that.
The error is: TypeError: Cannot read properties of undefined (reading 'playerVars')
Here is the code that causes that to occur.
const playlist = options.playerVars.playlist;
That code is good. We are trying to access options.playerVars, but they don’t exist yet.
After the videoPlayer has been inited, the normal process that occurs is that the youtube iframe API automatically runs the onYouTubeIframeAPIReady() function.
In the tests we need to do that too when initing the code.
A previous test called “makes onYouTubeIframeAPIReady available” checks that onYouTubeIframeAPIReady() exists. We just need to run it after the init, because we aren’t using the real iframe API. We are using a fake version of it instead.
In the beforeAll function, use this updated code instead to run the onYouTubeIframeAPIReady() function after initing things.
videoPlayer.originalInit.call(context, params, testYoutubeIframeApi);
window.onYouTubeIframeAPIReady();
That will fix that problem and get us back to passing tests.
Like this: https://jsfiddle.net/41pkvb05/
Now it both passes and fails.
TypeError: Cannot read properties of undefined (reading ‘playerVars’)
describe("videoPlayer tests", function() {
beforeAll(function() {
videoPlayer.originalInit = videoPlayer.init;
videoPlayer.init = function testInit(params) {
const context = null;
const testYoutubeIframeApi = "https://www.example.com/iframe_api";
return videoPlayer.originalInit.call(context, params, testYoutubeIframeApi);
window.onYouTubeIframeAPIReady();
};
});
You haven’t done all that you were asked to do. The return keyword must be removed from that code.
After doing that, the call to the onYouTubeIframeAPiReady() function can produce errors if playerReady tests run before other tests that we have there.
ReferenceError: YT is not defined
The call to the onYouTubeIframeAPiReady() function needs access to the fake YT object that we use when testing, so we to have run stubYT() before doing that.
Currently that is being done in a beforeEach() section, so we need to move those up to the beforeAll() section instead.
That means moving let iframe
and the call to stubYT() up to the beforeAll() section.
let iframe;
beforeAll(function() {
iframe = stubYT();
And removing the stubYT() function call from all of the other places in the tests.
describe("init", function() {
// beforeEach(function() {
// iframe = stubYT();
// });
...
describe("addPlayer", function() {
let video;
beforeEach(function() {
// stubYT();
video = createVideo();
});
...
describe("playerReady", function() {
...
beforeEach(function() {
afterPlayerReadySpy = initVideoPlayer();
// stubYT();
video = createVideo();
});
The tests then go back to being fully reliable again, regardless of the random order in which they are run, which is good.
1 Like
The proper way to do things from here is to use a second test with a different list of videos, as evidence that the videoPlayer code needs to be capable of dealing with different sets of arrays of videos ids.
That second set is not always done, but we will do it here this time so that the benefits of doing things that way can be experienced.
Below the existing list of videos test, add a copy of that test and put the videos in a different order. For example, switch the middle two around. That switch happens in that copied test to the list of videos and in the expectation.
We will then get a suitable fail, which provided incentive to come up with a proper solution.
After we come up with a proper solution, we can then clean up by removing the duplicate test.
1 Like
Here is a failing test: https://jsfiddle.net/dtoyj207/2/
it("init with separate items in a list of videos for the playlist", function() {
//given
//when
videoPlayer.init([
"0dgNc5S8cLI",
"mnfmQe8Mv1g",
"CHahce95B1g",
"2VwsvrPFr9w"
]);
//then
const playlist = options.playerVars.playlist;
expect(playlist).toBe("0dgNc5S8cLI,mnfmQe8Mv1g,CHahce95B1g,2VwsvrPFr9w");
});
it("init with comma-separated list of videos for the playlist", function() {
//given
//when
videoPlayer.init([
"0dgNc5S8cLI",
"mnfmQe8Mv1g",
"CHahce95B1g",
"2VwsvrPFr9w"
]);
//then
const playlist = options.playerVars.playlist;
expect(playlist).toBe("0dgNc5S8cLI,CHahce95B1g,mnfmQe8Mv1g,2VwsvrPFr9w");
});
That second test will never pass, even with perfectly working videoPlayer code because the array and the expected playlist are in a different orders.
Please reorder the array in the second playlist test so that the two middle ones in the array match the ordrr in the ecpectation.
1 Like
https://jsfiddle.net/9eowp0ha/
it("init with comma-separated list of videos for the playlist", function() {
//given
//when
videoPlayer.init([
"0dgNc5S8cLI",
"mnfmQe8Mv1g",
"CHahce95B1g",
"2VwsvrPFr9w"
]);
//then
const playlist = options.playerVars.playlist;
expect(playlist).toBe("0dgNc5S8cLI,mnfmQe8Mv1g,CHahce95B1g,2VwsvrPFr9w");
});
You have manage to do the complete opposite of what you were told to do.
In the two tests that have arrays of video ids, the second test needs to have the array of videos in a different order than in the first test.
I don’t want to waste your time using 10 different posts to get you to make the second test different from the first, so here’s the update to use for the second test where the two middle video ids are in a different order than from the first test.
videoPlayer.init([
"0dgNc5S8cLI",
"CHahce95B1g",
"mnfmQe8Mv1g",
"2VwsvrPFr9w"
]);
Also in the second test, this is the expectation to use, which has the video ids in the same different order of the array.
expect(playlist).toBe("0dgNc5S8cLI,CHahce95B1g,mnfmQe8Mv1g,2VwsvrPFr9w");
1 Like
https://jsfiddle.net/ersq7624/
it("init with comma-separated list of videos for the playlist", function() {
//given
//when
videoPlayer.init([
"0dgNc5S8cLI",
"CHahce95B1g",
"mnfmQe8Mv1g",
"2VwsvrPFr9w"
]);
//then
const playlist = options.playerVars.playlist;
expect(playlist).toBe("0dgNc5S8cLI,CHahce95B1g,mnfmQe8Mv1g,2VwsvrPFr9w");
});
We now have a suitably failing test, giving us good evidence that the only way to make the test pass is to update the videoPlayer code.
There are several things that we need to do in there.
In the addPlayer function the playlist variable cannot be the string value that it’s currently set to.
Set that playlist variable to null, and check the test result, to be sure that we are changing the right thing in the right place.
You want me to do this?
const playlist = "null";
https://jsfiddle.net/rsb2goLw/
Expected ‘null’ to be ‘0dgNc5S8cLI,CHahce95B1g,mnfmQe8Mv1g,2VwsvrPFr9w’.
Good, we have confirmation that we are working with the right thing.
That information needs to be passed in to the addPlayer() function, so remove that null playlist line and add a function parameter called playlist.
1 Like
https://jsfiddle.net/rygd5tz1/
function addPlayer(video) {
checksVideo(video);
const playlist = {};
You were told to remove the null playlist line and add a function parameter called playlist, but you didn’t do that. Instead, you changed the null value to be an object. That didn’t help to achieve what is needed at all.
We need to be able to pass playlist information into the addPlayer() function, so remove that const playlist
line and add to the addPlayer() function a parameter called playlist.
https://jsfiddle.net/fgmo2j3k/
function addPlayer(video, playlist) {
It is up in the onYouTubeIframeReady() function where addPlayer is being called.
function onYouTubeIframeAPIReady() {
const cover = document.querySelector(".play");
const wrapper = cover.parentElement;
const frameContainer = wrapper.querySelector(".video");
videoPlayer.addPlayer(frameContainer);
}
Add a second function argument of playlist
to the addPlayer call.
https://jsfiddle.net/3aw0dsqL/
function onYouTubeIframeAPIReady() {
const cover = document.querySelector(".play");
const wrapper = cover.parentElement;
const frameContainer = wrapper.querySelector(".video");
videoPlayer.addPlayer(frameContainer, player);
}
Sorry, my mistake there. The second argument should be called playlist instead of player.
https://jsfiddle.net/562c7dhu/1/
function onYouTubeIframeAPIReady() {
const cover = document.querySelector(".play");
const wrapper = cover.parentElement;
const frameContainer = wrapper.querySelector(".video");
videoPlayer.addPlayer(frameContainer, playlist);
}
Why not, config.playlist
instead?
It’s not necessary or needed?
const config = {};
function onYouTubeIframeAPIReady() {
const cover = document.querySelector(".play");
const wrapper = cover.parentElement;
const frameContainer = wrapper.querySelector(".video");
videoPlayer.addPlayer(frameContainer, config.playlist);
}