From 7d391c29e90c4b3fd66c19ba0a4dfc9363b14c96 Mon Sep 17 00:00:00 2001 From: Yiannis <1515939+styiannis@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:40:26 +0200 Subject: [PATCH] feat: utils/stores unit tests --- .../static/js/utils/stores/MediaPageStore.js | 1706 +++++++++-------- .../js/utils/stores/VideoViewerStore.js | 145 +- frontend/tests/tests-constants.ts | 385 ++++ .../tests/utils/stores/MediaPageStore.test.ts | 739 +++++++ frontend/tests/utils/stores/PageStore.test.ts | 162 ++ .../utils/stores/PlaylistPageStore.test.ts | 390 ++++ .../utils/stores/PlaylistViewStore.test.ts | 83 + .../utils/stores/ProfilePageStore.test.ts | 168 ++ .../utils/stores/SearchFieldStore.test.ts | 64 + .../utils/stores/VideoViewerStore.test.ts | 147 ++ 10 files changed, 3085 insertions(+), 904 deletions(-) create mode 100644 frontend/tests/tests-constants.ts create mode 100644 frontend/tests/utils/stores/MediaPageStore.test.ts create mode 100644 frontend/tests/utils/stores/PageStore.test.ts create mode 100644 frontend/tests/utils/stores/PlaylistPageStore.test.ts create mode 100644 frontend/tests/utils/stores/PlaylistViewStore.test.ts create mode 100644 frontend/tests/utils/stores/ProfilePageStore.test.ts create mode 100644 frontend/tests/utils/stores/SearchFieldStore.test.ts create mode 100644 frontend/tests/utils/stores/VideoViewerStore.test.ts diff --git a/frontend/src/static/js/utils/stores/MediaPageStore.js b/frontend/src/static/js/utils/stores/MediaPageStore.js index 1a53782a..2c041f25 100644 --- a/frontend/src/static/js/utils/stores/MediaPageStore.js +++ b/frontend/src/static/js/utils/stores/MediaPageStore.js @@ -1,898 +1,940 @@ import EventEmitter from 'events'; import { exportStore, getRequest, postRequest, putRequest, deleteRequest, csrfToken } from '../helpers'; -import { config as mediacmsConfig } from '../settings/config.js'; +import { config as mediacmsConfig } from '../settings/config'; import UrlParse from 'url-parse'; import PageStore from './PageStore.js'; function extractPlaylistId() { - let playlistId = null; + let playlistId = null; - const getParamsString = window.location.search; + const getParamsString = window.location.search; - if ('' !== getParamsString) { - let tmp = getParamsString.split('?'); + if ('' !== getParamsString) { + let tmp = getParamsString.split('?'); - if (2 === tmp.length) { - tmp = tmp[1].split('&'); + if (2 === tmp.length) { + tmp = tmp[1].split('&'); - let x; + let x; - let i = 0; - while (i < tmp.length) { - x = tmp[i].split('='); + let i = 0; + while (i < tmp.length) { + x = tmp[i].split('='); - if ('pl' === x[0]) { - if (2 === x.length) { - playlistId = x[1]; - } + if ('pl' === x[0]) { + if (2 === x.length) { + playlistId = x[1]; + } - break; + break; + } + + i += 1; + } } - - i += 1; - } } - } - return playlistId; + return playlistId; +} + +function extractMediaId() { + let result = undefined; + + let urlParams = (function () { + let ret = new UrlParse(window.location.href).query; + if (!ret) { + ret = []; + } else { + ret = ret.substring(1); + ret = ret.split('&'); + ret = ret.length ? ret.map((v) => v.split('=')).flat() : []; + } + return ret; + })(); + + if (urlParams.length) { + let i = 0; + while (i < urlParams.length) { + if ('m' === urlParams[i]) { + // NOTE: "m" => media id/token. + result = urlParams[i + 1]; + } + i += 2; + } + } + + return result; } const MediaPageStoreData = {}; class MediaPageStore extends EventEmitter { - constructor() { - super(); + constructor() { + super(); - this.mediacms_config = mediacmsConfig(window.MediaCMS); + this.mediacms_config = mediacmsConfig(window.MediaCMS); - this._MEDIA = null; + this._MEDIA = null; - this.pagePlaylistId = null; - this.pagePlaylistData = null; - this.userList = null; - - MediaPageStoreData[ - Object.defineProperty(this, 'id', { value: 'MediaPageStoreData_' + Object.keys(MediaPageStoreData).length }).id - ] = { - likedMedia: false, - dislikedMedia: false, - reported_times: 0, - while: { - deleteMedia: false, - submitComment: false, - deleteCommentId: null, - }, - }; - - this.removeMediaResponse = this.removeMediaResponse.bind(this); - this.removeMediaFail = this.removeMediaFail.bind(this); - - this.submitCommentFail = this.submitCommentFail.bind(this); - this.submitCommentResponse = this.submitCommentResponse.bind(this); - - this.removeCommentFail = this.removeCommentFail.bind(this); - this.removeCommentResponse = this.removeCommentResponse.bind(this); - } - - loadData() { - if (!MediaPageStoreData[this.id].mediaId) { - let urlParams = (function () { - let ret = new UrlParse(window.location.href).query; - if (!ret) { - ret = []; - } else { - ret = ret.substring(1); - ret.split('&'); - ret = ret.length ? ret.split('=') : []; - } - return ret; - })(); - - if (urlParams.length) { - let i = 0; - while (i < urlParams.length) { - if ('m' === urlParams[i]) { - // NOTE: "m" => media id/token. - MediaPageStoreData[this.id].mediaId = urlParams[i + 1]; - } - i += 2; - } - } - } - - if (!MediaPageStoreData[this.id].mediaId) { - console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); - return false; - } - - this.mediaAPIUrl = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId; - this.dataResponse = this.dataResponse.bind(this); - this.dataErrorResponse = this.dataErrorResponse.bind(this); - getRequest(this.mediaAPIUrl, !1, this.dataResponse, this.dataErrorResponse); - } - - loadPlaylistData() { - const playlistApiUrl = this.mediacms_config.api.playlists + '/' + this.pagePlaylistId; - this.playlistDataResponse = this.playlistDataResponse.bind(this); - this.playlistDataErrorResponse = this.playlistDataErrorResponse.bind(this); - getRequest(playlistApiUrl, !1, this.playlistDataResponse, this.playlistDataErrorResponse); - } - - playlistDataResponse(response) { - if (response && response.data) { - let validPlaylistMedia = false; - - let i = 0; - while (i < response.data.playlist_media.length) { - if (MediaPageStoreData[this.id].mediaId === response.data.playlist_media[i].friendly_token) { - validPlaylistMedia = true; - break; - } - - i += 1; - } - - if (validPlaylistMedia) { - this.pagePlaylistData = response.data; - } else { this.pagePlaylistId = null; - } + this.pagePlaylistData = null; + this.userList = null; - this.emit('loaded_viewer_playlist_data'); - } else { - this.pagePlaylistId = null; + MediaPageStoreData[ + Object.defineProperty(this, 'id', { + value: 'MediaPageStoreData_' + Object.keys(MediaPageStoreData).length, + }).id + ] = { + mediaId: extractMediaId(), + likedMedia: false, + dislikedMedia: false, + reported_times: 0, + while: { + deleteMedia: false, + submitComment: false, + deleteCommentId: null, + }, + }; + + this.removeMediaResponse = this.removeMediaResponse.bind(this); + this.removeMediaFail = this.removeMediaFail.bind(this); + + this.submitCommentFail = this.submitCommentFail.bind(this); + this.submitCommentResponse = this.submitCommentResponse.bind(this); + + this.removeCommentFail = this.removeCommentFail.bind(this); + this.removeCommentResponse = this.removeCommentResponse.bind(this); } - this.emit('loaded_page_playlist_data'); - } + loadData() { + MediaPageStoreData[this.id].mediaId = MediaPageStoreData[this.id].mediaId ?? extractMediaId(); - playlistDataErrorResponse(response) { - this.emit('loaded_viewer_playlist_error'); - this.emit('loaded_page_playlist_data'); - } + if (!MediaPageStoreData[this.id].mediaId) { + console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); + return false; + } - loadComments() { - this.commentsAPIUrl = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/comments'; - this.commentsResponse = this.commentsResponse.bind(this); - getRequest(this.commentsAPIUrl, !1, this.commentsResponse); - } - - loadUsers() { - this.usersAPIUrl = this.mediacms_config.api.users; - this.usersResponse = this.usersResponse.bind(this); - getRequest(this.usersAPIUrl, !1, this.usersResponse); - } - - loadPlaylists() { - if (!this.mediacms_config.member.can.saveMedia) { - return; + this.mediaAPIUrl = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId; + this.dataResponse = this.dataResponse.bind(this); + this.dataErrorResponse = this.dataErrorResponse.bind(this); + getRequest(this.mediaAPIUrl, !1, this.dataResponse, this.dataErrorResponse); } - this.playlistsAPIUrl = this.mediacms_config.api.user.playlists + this.mediacms_config.member.username; - - this.playlistsResponse = this.playlistsResponse.bind(this); - - getRequest(this.playlistsAPIUrl, !1, this.playlistsResponse); - } - - dataResponse(response) { - if (response && response.data) { - MediaPageStoreData[this.id].data = response.data; - MediaPageStoreData[this.id].reported_times = !!MediaPageStoreData[this.id].data.reported_times; - - switch (this.get('media-type')) { - case 'video': - case 'audio': - this.emit('loaded_video_data'); - break; - case 'image': - this.emit('loaded_' + this.get('media-type') + '_data'); - break; - } - - this.emit('loaded_media_data'); + loadPlaylistData() { + const playlistApiUrl = this.mediacms_config.api.playlists + '/' + this.pagePlaylistId; + this.playlistDataResponse = this.playlistDataResponse.bind(this); + this.playlistDataErrorResponse = this.playlistDataErrorResponse.bind(this); + getRequest(playlistApiUrl, !1, this.playlistDataResponse, this.playlistDataErrorResponse); } - this.loadPlaylists(); - if (MediaCMS.features.media.actions.comment_mention === true) { - this.loadUsers(); - } + playlistDataResponse(response) { + if (response && response.data) { + let validPlaylistMedia = false; - if (this.mediacms_config.member.can.readComment) { - this.loadComments(); - } - } - - dataErrorResponse(response) { - if (void 0 !== response.type) { - switch (response.type) { - case 'network': - case 'private': - case 'unavailable': - MediaPageStoreData[this.id].loadErrorType = response.type; - MediaPageStoreData[this.id].loadErrorMessage = - void 0 !== response.message ? response.message : "Αn error occurred while loading the media's data"; - this.emit('loaded_media_error'); - break; - } - } - } - - commentsResponse(response) { - if (response && response.data) { - MediaPageStoreData[this.id].comments = response.data.count ? response.data.results : []; - this.emit('comments_load'); - } - } - - usersResponse(response) { - if (response && response.data) { - MediaPageStoreData.userList = response.data.count ? response.data.results : []; - this.emit('users_load'); - } - } - - playlistsResponse(response) { - if (response && response.data) { - let tmp_playlists = response.data.count ? response.data.results : []; - - MediaPageStoreData[this.id].playlists = []; - - let i = 0; - let cntr = 0; - - while (i < tmp_playlists.length) { - (function (pos) { - let _this = this; - - if (tmp_playlists[pos].user === this.mediacms_config.member.username) { - let playlistsIndex = MediaPageStoreData[_this.id].playlists.length; - - MediaPageStoreData[_this.id].playlists[playlistsIndex] = { - playlist_id: (function (_url_) { - let ret = _url_.split('/'); - return 1 < ret.length ? ret[ret.length - 1] : null; - })(tmp_playlists[pos].url), - title: tmp_playlists[pos].title, - description: tmp_playlists[pos].description, - add_date: tmp_playlists[pos].add_date, - }; - - getRequest( - this.mediacms_config.site.url + '/' + tmp_playlists[pos].api_url.replace(/^\//g, ''), - !1, - function (resp) { - if (!!resp && !!resp.data) { - MediaPageStoreData[_this.id].playlists[playlistsIndex].media_list = []; - - let f = 0; - let arr; - - while (f < resp.data.playlist_media.length) { - arr = resp.data.playlist_media[f].url.split('m='); - if (2 === arr.length) { - MediaPageStoreData[_this.id].playlists[playlistsIndex].media_list.push(arr[1]); - } - f += 1; - } + let i = 0; + while (i < response.data.playlist_media.length) { + if (MediaPageStoreData[this.id].mediaId === response.data.playlist_media[i].friendly_token) { + validPlaylistMedia = true; + break; } - cntr += 1; - - if (cntr === tmp_playlists.length) { - this.emit('playlists_load'); - } - } - ); - } - }.bind(this)(i)); - - i += 1; - } - } - } - - requestMediaLike() { - if (!MediaPageStoreData[this.id].mediaId) { - console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); - return false; - } - - const url = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/actions'; - - this.likeActionResponse = this.likeActionResponse.bind(this); - - postRequest( - url, - { - type: 'like', - // `headers` are custom headers to be sent - }, - { - headers: { - 'X-CSRFToken': csrfToken(), - }, - }, - false, - this.likeActionResponse, - function () { - this.emit('liked_media_failed_request'); - }.bind(this) - ); - } - - likeActionResponse(response) { - if (response) { - if (response instanceof Error) { - } else if (response.data) { - MediaPageStoreData[this.id].likedMedia = true; - this.emit('liked_media'); - } - } - } - - requestMediaDislike() { - if (!MediaPageStoreData[this.id].mediaId) { - console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); - return false; - } - - const url = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/actions'; - this.dislikeActionResponse = this.dislikeActionResponse.bind(this); - postRequest( - url, - { - type: 'dislike', - }, - { - headers: { - 'X-CSRFToken': csrfToken(), - }, - }, - false, - this.dislikeActionResponse, - function () { - this.emit('disliked_media_failed_request'); - }.bind(this) - ); - } - - dislikeActionResponse(response) { - if (response) { - if (response instanceof Error) { - } else if (response.data) { - MediaPageStoreData[this.id].dislikedMedia = true; - this.emit('disliked_media'); - } - } - } - - requestMediaReport(descr) { - if (!MediaPageStoreData[this.id].mediaId) { - console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); - return false; - } - - const url = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/actions'; - this.reportActionResponse = this.reportActionResponse.bind(this); - postRequest( - url, - { - type: 'report', - extra_info: descr, - }, - { - headers: { - 'X-CSRFToken': csrfToken(), - }, - }, - false, - this.reportActionResponse, - this.reportActionResponse - ); - } - - reportActionResponse(response) { - if (response) { - if (response instanceof Error) { - } else if (response.data) { - MediaPageStoreData[this.id].reported_times += 1; - this.emit('reported_media'); - } - } - } - - set(type, value) { - switch (type) { - case 'media-load-error-type': - MediaPageStoreData[this.id].loadErrorType = value; - break; - case 'media-load-error-message': - MediaPageStoreData[this.id].loadErrorMessage = value; - break; - } - } - - get(type) { - let tmp, - activeItem, - browserCache, - i, - r = null; - switch (type) { - case 'users': - r = MediaPageStoreData.userList || []; - break; - case 'playlists': - r = MediaPageStoreData[this.id].playlists || []; - break; - case 'media-load-error-type': - r = void 0 !== MediaPageStoreData[this.id].loadErrorType ? MediaPageStoreData[this.id].loadErrorType : null; - break; - case 'media-load-error-message': - r = - void 0 !== MediaPageStoreData[this.id].loadErrorMessage ? MediaPageStoreData[this.id].loadErrorMessage : null; - break; - case 'media-comments': - r = MediaPageStoreData[this.id].comments || []; - break; - case 'media-data': - r = MediaPageStoreData[this.id].data || null; - break; - case 'media-id': - r = MediaPageStoreData[this.id].mediaId; - break; - case 'media-url': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.url - ? MediaPageStoreData[this.id].data.url - : 'N/A'; - break; - case 'media-edit-subtitle-url': - r = - void 0 !== MediaPageStoreData[this.id].data && - 'string' === typeof MediaPageStoreData[this.id].data.add_subtitle_url - ? MediaPageStoreData[this.id].data.add_subtitle_url - : null; - break; - case 'media-likes': - tmp = MediaPageStoreData[this.id].likedMedia ? 1 : 0; - if (tmp) { - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.likes - ? MediaPageStoreData[this.id].data.likes + tmp - : tmp; - } else { - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.likes - ? MediaPageStoreData[this.id].data.likes - : 'N/A'; - } - break; - case 'media-dislikes': - tmp = MediaPageStoreData[this.id].dislikedMedia ? 1 : 0; - if (tmp) { - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.dislikes - ? MediaPageStoreData[this.id].data.dislikes + tmp - : tmp; - } else { - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.dislikes - ? MediaPageStoreData[this.id].data.dislikes - : 'N/A'; - } - break; - case 'media-summary': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.summary - ? MediaPageStoreData[this.id].data.summary - : null; - break; - case 'media-categories': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.categories_info - ? MediaPageStoreData[this.id].data.categories_info - : []; - break; - case 'media-tags': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.tags_info - ? MediaPageStoreData[this.id].data.tags_info - : []; - break; - case 'media-type': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.media_type - ? MediaPageStoreData[this.id].data.media_type - : null; - break; - case 'media-original-url': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.original_media_url - ? MediaPageStoreData[this.id].data.original_media_url - : null; - break; - case 'media-thumbnail-url': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.thumbnail_url - ? MediaPageStoreData[this.id].data.thumbnail_url - : null; - break; - case 'user-liked-media': - r = MediaPageStoreData[this.id].likedMedia; - break; - case 'user-disliked-media': - r = MediaPageStoreData[this.id].dislikedMedia; - break; - case 'media-author-thumbnail-url': - r = - void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.author_thumbnail - ? this.mediacms_config.site.url + - '/' + - MediaPageStoreData[this.id].data.author_thumbnail.replace(/^\//g, '') - : null; - break; - case 'playlist-data': - r = this.pagePlaylistData; - break; - case 'playlist-id': - r = this.pagePlaylistId; - break; - case 'playlist-next-media-url': - if (!this.pagePlaylistData) { - break; - } - - activeItem = 0; - i = 0; - while (i < this.pagePlaylistData.playlist_media.length) { - if (MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token) { - activeItem = i; - break; - } - - i += 1; - } - - let nextItem = activeItem + 1; - - if (nextItem === this.pagePlaylistData.playlist_media.length) { - browserCache = PageStore.get('browser-cache'); - if (true === browserCache.get('loopPlaylist[' + this.pagePlaylistId + ']')) { - nextItem = 0; - } - } - - if (void 0 !== this.pagePlaylistData.playlist_media[nextItem]) { - r = this.pagePlaylistData.playlist_media[nextItem].url + '&pl=' + this.pagePlaylistId; - } - - break; - case 'playlist-previous-media-url': - if (!this.pagePlaylistData) { - break; - } - - activeItem = 0; - i = 0; - while (i < this.pagePlaylistData.playlist_media.length) { - if (MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token) { - activeItem = i; - break; - } - - i += 1; - } - - let previousItem = activeItem - 1; - - if (0 === activeItem) { - previousItem = null; - - browserCache = PageStore.get('browser-cache'); - - if (true === browserCache.get('loopPlaylist[' + this.pagePlaylistId + ']')) { - previousItem = this.pagePlaylistData.playlist_media.length - 1; - } - } - - if (void 0 !== this.pagePlaylistData.playlist_media[previousItem]) { - r = this.pagePlaylistData.playlist_media[previousItem].url + '&pl=' + this.pagePlaylistId; - } - - break; - } - return r; - } - - isVideo() { - return 'video' === this.get('media-type') || 'audio' === this.get('media-type'); - } - - onPlaylistCreationCompleted(response) { - if (response && response.data) { - this.emit('playlist_creation_completed', response.data); - } - } - - onPlaylistCreationFailed() { - this.emit('playlist_creation_failed'); - } - - onPlaylistMediaAdditionCompleted(playlist_id, response) { - if (response) { - let i = 0; - while (i < MediaPageStoreData[this.id].playlists.length) { - if (playlist_id === MediaPageStoreData[this.id].playlists[i].playlist_id) { - MediaPageStoreData[this.id].playlists[i].media_list.push(MediaPageStoreData[this.id].mediaId); - break; - } - i += 1; - } - this.emit('media_playlist_addition_completed', playlist_id); - } - } - - onPlaylistMediaAdditionFailed(playlist_id, response) { - this.emit('media_playlist_addition_failed'); - } - - onPlaylistMediaRemovalCompleted(playlist_id, response) { - if (response) { - let j, new_playlist_media; - let i = 0; - while (i < MediaPageStoreData[this.id].playlists.length) { - if (playlist_id === MediaPageStoreData[this.id].playlists[i].playlist_id) { - new_playlist_media = []; - j = 0; - while (j < MediaPageStoreData[this.id].playlists[i].media_list.length) { - if (MediaPageStoreData[this.id].mediaId !== MediaPageStoreData[this.id].playlists[i].media_list[j]) { - new_playlist_media.push(MediaPageStoreData[this.id].playlists[i].media_list[j]); + i += 1; } - j += 1; - } - MediaPageStoreData[this.id].playlists[i].media_list = new_playlist_media; - break; - } - i += 1; - } - this.emit('media_playlist_removal_completed', playlist_id); - } - } + if (validPlaylistMedia) { + this.pagePlaylistData = response.data; + } else { + this.pagePlaylistId = null; + } - onPlaylistMediaRemovalFailed(playlist_id, response) { - this.emit('media_playlist_removal_failed'); - } - - actions_handler(action) { - switch (action.type) { - case 'LOAD_MEDIA_DATA': - MediaPageStoreData[this.id].mediaId = window.MediaCMS.mediaId || MediaPageStoreData[this.id].mediaId; - - this.pagePlaylistId = extractPlaylistId(); - - if (this.pagePlaylistId) { - this.loadPlaylistData(); - this.loadData(); + this.emit('loaded_viewer_playlist_data'); } else { - this.emit('loaded_page_playlist_data'); - this.loadData(); + this.pagePlaylistId = null; } - break; - case 'LIKE_MEDIA': - if (!MediaPageStoreData[this.id].likedMedia && !MediaPageStoreData[this.id].dislikedMedia) { - this.requestMediaLike(); - } - break; - case 'DISLIKE_MEDIA': - if (!MediaPageStoreData[this.id].likedMedia && !MediaPageStoreData[this.id].dislikedMedia) { - this.requestMediaDislike(); - } - break; - case 'REPORT_MEDIA': - if (!MediaPageStoreData[this.id].reported_times) { - if ('' !== action.reportDescription) { - this.requestMediaReport(action.reportDescription); - } - } - break; - case 'COPY_SHARE_LINK': - if (action.inputElement instanceof HTMLElement) { - action.inputElement.select(); - document.execCommand('copy'); - this.emit('copied_media_link'); - } - break; - case 'COPY_EMBED_MEDIA_CODE': - if (action.inputElement instanceof HTMLElement) { - action.inputElement.select(); - document.execCommand('copy'); - this.emit('copied_embed_media_code'); - } - break; - case 'REMOVE_MEDIA': - if (MediaPageStoreData[this.id].while.deleteMedia) { - return; - } - MediaPageStoreData[this.id].while.deleteMedia = true; - deleteRequest( - this.mediaAPIUrl, - { headers: { 'X-CSRFToken': csrfToken() } }, - false, - this.removeMediaResponse, - this.removeMediaFail - ); - break; - case 'SUBMIT_COMMENT': - if (MediaPageStoreData[this.id].while.submitComment) { - return; + this.emit('loaded_page_playlist_data'); + } + + playlistDataErrorResponse(response) { + this.emit('loaded_viewer_playlist_error'); + this.emit('loaded_page_playlist_data'); + } + + loadComments() { + this.commentsAPIUrl = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/comments'; + this.commentsResponse = this.commentsResponse.bind(this); + getRequest(this.commentsAPIUrl, !1, this.commentsResponse); + } + + loadUsers() { + this.usersAPIUrl = this.mediacms_config.api.users; + this.usersResponse = this.usersResponse.bind(this); + getRequest(this.usersAPIUrl, !1, this.usersResponse); + } + + loadPlaylists() { + if (!this.mediacms_config.member.can.saveMedia) { + return; } - MediaPageStoreData[this.id].while.submitComment = true; + this.playlistsAPIUrl = this.mediacms_config.api.user.playlists + this.mediacms_config.member.username; + + this.playlistsResponse = this.playlistsResponse.bind(this); + + getRequest(this.playlistsAPIUrl, !1, this.playlistsResponse); + } + + dataResponse(response) { + if (response && response.data) { + MediaPageStoreData[this.id].data = response.data; + MediaPageStoreData[this.id].reported_times = !!MediaPageStoreData[this.id].data.reported_times; + + switch (this.get('media-type')) { + case 'video': + case 'audio': + this.emit('loaded_video_data'); + break; + case 'image': + this.emit('loaded_' + this.get('media-type') + '_data'); + break; + } + + this.emit('loaded_media_data'); + } + + this.loadPlaylists(); + if (MediaCMS.features.media.actions.comment_mention === true) { + this.loadUsers(); + } + + if (this.mediacms_config.member.can.readComment) { + this.loadComments(); + } + } + + dataErrorResponse(response) { + if (void 0 !== response.type) { + switch (response.type) { + case 'network': + case 'private': + case 'unavailable': + MediaPageStoreData[this.id].loadErrorType = response.type; + MediaPageStoreData[this.id].loadErrorMessage = + void 0 !== response.message + ? response.message + : "Αn error occurred while loading the media's data"; + this.emit('loaded_media_error'); + break; + } + } + } + + commentsResponse(response) { + if (response && response.data) { + MediaPageStoreData[this.id].comments = response.data.count ? response.data.results : []; + this.emit('comments_load'); + } + } + + usersResponse(response) { + if (response && response.data) { + MediaPageStoreData.userList = response.data.count ? response.data.results : []; + this.emit('users_load'); + } + } + + playlistsResponse(response) { + if (response && response.data) { + let tmp_playlists = response.data.count ? response.data.results : []; + + MediaPageStoreData[this.id].playlists = []; + + let i = 0; + let cntr = 0; + + while (i < tmp_playlists.length) { + (function (pos) { + let _this = this; + + if (tmp_playlists[pos].user === this.mediacms_config.member.username) { + let playlistsIndex = MediaPageStoreData[_this.id].playlists.length; + + MediaPageStoreData[_this.id].playlists[playlistsIndex] = { + playlist_id: (function (_url_) { + let ret = _url_.split('/'); + return 1 < ret.length ? ret[ret.length - 1] : null; + })(tmp_playlists[pos].url), + title: tmp_playlists[pos].title, + description: tmp_playlists[pos].description, + add_date: tmp_playlists[pos].add_date, + }; + + getRequest( + this.mediacms_config.site.url + '/' + tmp_playlists[pos].api_url.replace(/^\//g, ''), + !1, + function (resp) { + if (!!resp && !!resp.data) { + MediaPageStoreData[_this.id].playlists[playlistsIndex].media_list = []; + + let f = 0; + let arr; + + while (f < resp.data.playlist_media.length) { + arr = resp.data.playlist_media[f].url.split('m='); + if (2 === arr.length) { + MediaPageStoreData[_this.id].playlists[playlistsIndex].media_list.push( + arr[1] + ); + } + f += 1; + } + } + + cntr += 1; + + if (cntr === tmp_playlists.length) { + _this.emit('playlists_load'); + } + } + ); + } + }).bind(this)(i); + + i += 1; + } + } + } + + requestMediaLike() { + if (!MediaPageStoreData[this.id].mediaId) { + console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); + return false; + } + + const url = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/actions'; + + this.likeActionResponse = this.likeActionResponse.bind(this); postRequest( - this.commentsAPIUrl, - { text: action.commentText }, - { headers: { 'X-CSRFToken': csrfToken() } }, - false, - this.submitCommentResponse, - this.submitCommentFail + url, + { + type: 'like', + // `headers` are custom headers to be sent + }, + { + headers: { + 'X-CSRFToken': csrfToken(), + }, + }, + false, + this.likeActionResponse, + function () { + this.emit('liked_media_failed_request'); + }.bind(this) ); - break; - case 'DELETE_COMMENT': - if (null !== MediaPageStoreData[this.id].while.deleteCommentId) { - return; + } + + likeActionResponse(response) { + if (response) { + if (response instanceof Error) { + } else if (response.data) { + MediaPageStoreData[this.id].likedMedia = true; + this.emit('liked_media'); + } + } + } + + requestMediaDislike() { + if (!MediaPageStoreData[this.id].mediaId) { + console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); + return false; } - MediaPageStoreData[this.id].while.deleteCommentId = action.commentId; - deleteRequest( - this.commentsAPIUrl + '/' + action.commentId, - { headers: { 'X-CSRFToken': csrfToken() } }, - false, - this.removeCommentResponse, - this.removeCommentFail - ); - break; - case 'CREATE_PLAYLIST': + const url = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/actions'; + this.dislikeActionResponse = this.dislikeActionResponse.bind(this); postRequest( - this.mediacms_config.api.playlists, - { - title: action.playlist_data.title, - description: action.playlist_data.description, - }, - { - headers: { - 'X-CSRFToken': csrfToken(), + url, + { + type: 'dislike', }, - }, - false, - this.onPlaylistCreationCompleted.bind(this), - this.onPlaylistCreationFailed.bind(this) - ); - break; - case 'ADD_MEDIA_TO_PLAYLIST': - putRequest( - this.mediacms_config.api.playlists + '/' + action.playlist_id, - { - type: 'add', - media_friendly_token: action.media_id, - }, - { - headers: { - 'X-CSRFToken': csrfToken(), + { + headers: { + 'X-CSRFToken': csrfToken(), + }, }, - }, - false, - this.onPlaylistMediaAdditionCompleted.bind(this, action.playlist_id), - this.onPlaylistMediaAdditionFailed.bind(this, action.playlist_id) + false, + this.dislikeActionResponse, + function () { + this.emit('disliked_media_failed_request'); + }.bind(this) ); - break; - case 'REMOVE_MEDIA_FROM_PLAYLIST': - putRequest( - this.mediacms_config.api.playlists + '/' + action.playlist_id, - { - type: 'remove', - media_friendly_token: action.media_id, - }, - { - headers: { - 'X-CSRFToken': csrfToken(), - }, - }, - false, - this.onPlaylistMediaRemovalCompleted.bind(this, action.playlist_id), - this.onPlaylistMediaRemovalFailed.bind(this, action.playlist_id) - ); - break; - case 'APPEND_NEW_PLAYLIST': - MediaPageStoreData[this.id].playlists.push(action.playlist_data); - this.emit('playlists_load'); - break; } - } - removeMediaResponse(response) { - if (response && 204 === response.status) { - this.emit('media_delete', MediaPageStoreData[this.id].mediaId); - } - } - - removeMediaFail() { - this.emit('media_delete_fail', MediaPageStoreData[this.id].mediaId); - setTimeout( - function (ins) { - MediaPageStoreData[ins.id].while.deleteMedia = null; - }, - 100, - this - ); - } - - removeCommentFail(err) { - this.emit('comment_delete_fail', MediaPageStoreData[this.id].while.deleteCommentId); - setTimeout( - function (ins) { - MediaPageStoreData[ins.id].while.deleteCommentId = null; - }, - 100, - this - ); - } - - removeCommentResponse(response) { - if (response && 204 === response.status) { - let k; - let newComments = []; - for (k in MediaPageStoreData[this.id].comments) { - if (MediaPageStoreData[this.id].comments.hasOwnProperty(k)) { - if (MediaPageStoreData[this.id].while.deleteCommentId !== MediaPageStoreData[this.id].comments[k].uid) { - newComments.push(MediaPageStoreData[this.id].comments[k]); - } + dislikeActionResponse(response) { + if (response) { + if (response instanceof Error) { + } else if (response.data) { + MediaPageStoreData[this.id].dislikedMedia = true; + this.emit('disliked_media'); + } } - } - MediaPageStoreData[this.id].comments = newComments; - newComments = null; - - this.emit('comment_delete', MediaPageStoreData[this.id].while.deleteCommentId); } - setTimeout( - function (ins) { - MediaPageStoreData[ins.id].while.deleteCommentId = null; - }, - 100, - this - ); - } - submitCommentFail(err) { - this.emit('comment_submit_fail'); - setTimeout( - function (ins) { - MediaPageStoreData[ins.id].while.submitComment = false; - }, - 100, - this - ); - } + requestMediaReport(descr) { + if (!MediaPageStoreData[this.id].mediaId) { + console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); + return false; + } - submitCommentResponse(response) { - if (response && 201 === response.status && response.data && Object.keys(response.data)) { - MediaPageStoreData[this.id].comments.push(response.data); - this.emit('comment_submit', response.data.uid); + const url = this.mediacms_config.api.media + '/' + MediaPageStoreData[this.id].mediaId + '/actions'; + this.reportActionResponse = this.reportActionResponse.bind(this); + postRequest( + url, + { + type: 'report', + extra_info: descr, + }, + { + headers: { + 'X-CSRFToken': csrfToken(), + }, + }, + false, + this.reportActionResponse, + this.reportActionResponse + ); + } + + reportActionResponse(response) { + if (response) { + if (response instanceof Error) { + } else if (response.data) { + MediaPageStoreData[this.id].reported_times += 1; + this.emit('reported_media'); + } + } + } + + set(type, value) { + switch (type) { + case 'media-load-error-type': + MediaPageStoreData[this.id].loadErrorType = value; + break; + case 'media-load-error-message': + MediaPageStoreData[this.id].loadErrorMessage = value; + break; + } + } + + get(type) { + let tmp, + activeItem, + browserCache, + i, + r = null; + switch (type) { + case 'users': + r = MediaPageStoreData.userList || []; + break; + case 'playlists': + r = MediaPageStoreData[this.id].playlists || []; + break; + case 'media-load-error-type': + r = + void 0 !== MediaPageStoreData[this.id].loadErrorType + ? MediaPageStoreData[this.id].loadErrorType + : null; + break; + case 'media-load-error-message': + r = + void 0 !== MediaPageStoreData[this.id].loadErrorMessage + ? MediaPageStoreData[this.id].loadErrorMessage + : null; + break; + case 'media-comments': + r = MediaPageStoreData[this.id].comments || []; + break; + case 'media-data': + r = MediaPageStoreData[this.id].data || null; + break; + case 'media-id': + r = MediaPageStoreData[this.id].mediaId; + break; + case 'media-url': + r = + void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.url + ? MediaPageStoreData[this.id].data.url + : 'N/A'; + break; + case 'media-edit-subtitle-url': + r = + void 0 !== MediaPageStoreData[this.id].data && + 'string' === typeof MediaPageStoreData[this.id].data.add_subtitle_url + ? MediaPageStoreData[this.id].data.add_subtitle_url + : null; + break; + case 'media-likes': + tmp = MediaPageStoreData[this.id].likedMedia ? 1 : 0; + if (tmp) { + r = + void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.likes + ? MediaPageStoreData[this.id].data.likes + tmp + : tmp; + } else { + r = + void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.likes + ? MediaPageStoreData[this.id].data.likes + : 'N/A'; + } + break; + case 'media-dislikes': + tmp = MediaPageStoreData[this.id].dislikedMedia ? 1 : 0; + if (tmp) { + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.dislikes + ? MediaPageStoreData[this.id].data.dislikes + tmp + : tmp; + } else { + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.dislikes + ? MediaPageStoreData[this.id].data.dislikes + : 'N/A'; + } + break; + case 'media-summary': + r = + void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.summary + ? MediaPageStoreData[this.id].data.summary + : null; + break; + case 'media-categories': + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.categories_info + ? MediaPageStoreData[this.id].data.categories_info + : []; + break; + case 'media-tags': + r = + void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.tags_info + ? MediaPageStoreData[this.id].data.tags_info + : []; + break; + case 'media-type': + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.media_type + ? MediaPageStoreData[this.id].data.media_type + : null; + break; + case 'media-original-url': + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.original_media_url + ? MediaPageStoreData[this.id].data.original_media_url + : null; + break; + case 'media-thumbnail-url': + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.thumbnail_url + ? MediaPageStoreData[this.id].data.thumbnail_url + : null; + break; + case 'user-liked-media': + r = MediaPageStoreData[this.id].likedMedia; + break; + case 'user-disliked-media': + r = MediaPageStoreData[this.id].dislikedMedia; + break; + case 'media-author-thumbnail-url': + r = + void 0 !== MediaPageStoreData[this.id].data && + void 0 !== MediaPageStoreData[this.id].data.author_thumbnail + ? this.mediacms_config.site.url + + '/' + + MediaPageStoreData[this.id].data.author_thumbnail.replace(/^\//g, '') + : null; + break; + case 'playlist-data': + r = this.pagePlaylistData; + break; + case 'playlist-id': + r = this.pagePlaylistId; + break; + case 'playlist-next-media-url': + if (!this.pagePlaylistData) { + break; + } + + activeItem = 0; + i = 0; + while (i < this.pagePlaylistData.playlist_media.length) { + if ( + MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token + ) { + activeItem = i; + break; + } + + i += 1; + } + + let nextItem = activeItem + 1; + + if (nextItem === this.pagePlaylistData.playlist_media.length) { + browserCache = PageStore.get('browser-cache'); + if (true === browserCache.get('loopPlaylist[' + this.pagePlaylistId + ']')) { + nextItem = 0; + } + } + + if (void 0 !== this.pagePlaylistData.playlist_media[nextItem]) { + r = this.pagePlaylistData.playlist_media[nextItem].url + '&pl=' + this.pagePlaylistId; + } + + break; + case 'playlist-previous-media-url': + if (!this.pagePlaylistData) { + break; + } + + activeItem = 0; + i = 0; + while (i < this.pagePlaylistData.playlist_media.length) { + if ( + MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token + ) { + activeItem = i; + break; + } + + i += 1; + } + + let previousItem = activeItem - 1; + + if (0 === activeItem) { + previousItem = null; + + browserCache = PageStore.get('browser-cache'); + + if (true === browserCache.get('loopPlaylist[' + this.pagePlaylistId + ']')) { + previousItem = this.pagePlaylistData.playlist_media.length - 1; + } + } + + if (void 0 !== this.pagePlaylistData.playlist_media[previousItem]) { + r = this.pagePlaylistData.playlist_media[previousItem].url + '&pl=' + this.pagePlaylistId; + } + + break; + } + return r; + } + + isVideo() { + return 'video' === this.get('media-type') || 'audio' === this.get('media-type'); + } + + onPlaylistCreationCompleted(response) { + if (response && response.data) { + this.emit('playlist_creation_completed', response.data); + } + } + + onPlaylistCreationFailed() { + this.emit('playlist_creation_failed'); + } + + onPlaylistMediaAdditionCompleted(playlist_id, response) { + if (response) { + let i = 0; + while (i < MediaPageStoreData[this.id].playlists.length) { + if (playlist_id === MediaPageStoreData[this.id].playlists[i].playlist_id) { + MediaPageStoreData[this.id].playlists[i].media_list.push(MediaPageStoreData[this.id].mediaId); + break; + } + i += 1; + } + this.emit('media_playlist_addition_completed', playlist_id); + } + } + + onPlaylistMediaAdditionFailed(playlist_id, response) { + this.emit('media_playlist_addition_failed'); + } + + onPlaylistMediaRemovalCompleted(playlist_id, response) { + if (response) { + let j, new_playlist_media; + let i = 0; + while (i < MediaPageStoreData[this.id].playlists.length) { + if (playlist_id === MediaPageStoreData[this.id].playlists[i].playlist_id) { + new_playlist_media = []; + j = 0; + while (j < MediaPageStoreData[this.id].playlists[i].media_list.length) { + if ( + MediaPageStoreData[this.id].mediaId !== + MediaPageStoreData[this.id].playlists[i].media_list[j] + ) { + new_playlist_media.push(MediaPageStoreData[this.id].playlists[i].media_list[j]); + } + j += 1; + } + MediaPageStoreData[this.id].playlists[i].media_list = new_playlist_media; + + break; + } + i += 1; + } + this.emit('media_playlist_removal_completed', playlist_id); + } + } + + onPlaylistMediaRemovalFailed(playlist_id, response) { + this.emit('media_playlist_removal_failed'); + } + + actions_handler(action) { + switch (action.type) { + case 'LOAD_MEDIA_DATA': + MediaPageStoreData[this.id].mediaId = window.MediaCMS.mediaId || MediaPageStoreData[this.id].mediaId; + + this.pagePlaylistId = extractPlaylistId(); + + if (this.pagePlaylistId) { + this.loadPlaylistData(); + this.loadData(); + } else { + this.emit('loaded_page_playlist_data'); + this.loadData(); + } + + break; + case 'LIKE_MEDIA': + if (!MediaPageStoreData[this.id].likedMedia && !MediaPageStoreData[this.id].dislikedMedia) { + this.requestMediaLike(); + } + break; + case 'DISLIKE_MEDIA': + if (!MediaPageStoreData[this.id].likedMedia && !MediaPageStoreData[this.id].dislikedMedia) { + this.requestMediaDislike(); + } + break; + case 'REPORT_MEDIA': + if (!MediaPageStoreData[this.id].reported_times) { + if ('' !== action.reportDescription) { + this.requestMediaReport(action.reportDescription); + } + } + break; + case 'COPY_SHARE_LINK': + if (action.inputElement instanceof HTMLElement) { + action.inputElement.select(); + document.execCommand('copy'); + this.emit('copied_media_link'); + } + break; + case 'COPY_EMBED_MEDIA_CODE': + if (action.inputElement instanceof HTMLElement) { + action.inputElement.select(); + document.execCommand('copy'); + this.emit('copied_embed_media_code'); + } + break; + case 'REMOVE_MEDIA': + if (MediaPageStoreData[this.id].while.deleteMedia) { + return; + } + MediaPageStoreData[this.id].while.deleteMedia = true; + deleteRequest( + this.mediaAPIUrl, + { headers: { 'X-CSRFToken': csrfToken() } }, + false, + this.removeMediaResponse, + this.removeMediaFail + ); + break; + case 'SUBMIT_COMMENT': + if (MediaPageStoreData[this.id].while.submitComment) { + return; + } + + MediaPageStoreData[this.id].while.submitComment = true; + + postRequest( + this.commentsAPIUrl, + { text: action.commentText }, + { headers: { 'X-CSRFToken': csrfToken() } }, + false, + this.submitCommentResponse, + this.submitCommentFail + ); + break; + case 'DELETE_COMMENT': + if (null !== MediaPageStoreData[this.id].while.deleteCommentId) { + return; + } + + MediaPageStoreData[this.id].while.deleteCommentId = action.commentId; + deleteRequest( + this.commentsAPIUrl + '/' + action.commentId, + { headers: { 'X-CSRFToken': csrfToken() } }, + false, + this.removeCommentResponse, + this.removeCommentFail + ); + break; + case 'CREATE_PLAYLIST': + postRequest( + this.mediacms_config.api.playlists, + { + title: action.playlist_data.title, + description: action.playlist_data.description, + }, + { + headers: { + 'X-CSRFToken': csrfToken(), + }, + }, + false, + this.onPlaylistCreationCompleted.bind(this), + this.onPlaylistCreationFailed.bind(this) + ); + break; + case 'ADD_MEDIA_TO_PLAYLIST': + putRequest( + this.mediacms_config.api.playlists + '/' + action.playlist_id, + { + type: 'add', + media_friendly_token: action.media_id, + }, + { + headers: { + 'X-CSRFToken': csrfToken(), + }, + }, + false, + this.onPlaylistMediaAdditionCompleted.bind(this, action.playlist_id), + this.onPlaylistMediaAdditionFailed.bind(this, action.playlist_id) + ); + break; + case 'REMOVE_MEDIA_FROM_PLAYLIST': + putRequest( + this.mediacms_config.api.playlists + '/' + action.playlist_id, + { + type: 'remove', + media_friendly_token: action.media_id, + }, + { + headers: { + 'X-CSRFToken': csrfToken(), + }, + }, + false, + this.onPlaylistMediaRemovalCompleted.bind(this, action.playlist_id), + this.onPlaylistMediaRemovalFailed.bind(this, action.playlist_id) + ); + break; + case 'APPEND_NEW_PLAYLIST': + MediaPageStoreData[this.id].playlists.push(action.playlist_data); + this.emit('playlists_load'); + break; + } + } + + removeMediaResponse(response) { + if (response && 204 === response.status) { + this.emit('media_delete', MediaPageStoreData[this.id].mediaId); + } + setTimeout( + function (ins) { + MediaPageStoreData[ins.id].while.deleteMedia = null; + }, + 100, + this + ); + } + + removeMediaFail() { + this.emit('media_delete_fail', MediaPageStoreData[this.id].mediaId); + setTimeout( + function (ins) { + MediaPageStoreData[ins.id].while.deleteMedia = null; + }, + 100, + this + ); + } + + removeCommentFail(err) { + this.emit('comment_delete_fail', MediaPageStoreData[this.id].while.deleteCommentId); + setTimeout( + function (ins) { + MediaPageStoreData[ins.id].while.deleteCommentId = null; + }, + 100, + this + ); + } + + removeCommentResponse(response) { + if (response && 204 === response.status) { + let k; + let newComments = []; + for (k in MediaPageStoreData[this.id].comments) { + if (MediaPageStoreData[this.id].comments.hasOwnProperty(k)) { + if ( + MediaPageStoreData[this.id].while.deleteCommentId !== + MediaPageStoreData[this.id].comments[k].uid + ) { + newComments.push(MediaPageStoreData[this.id].comments[k]); + } + } + } + MediaPageStoreData[this.id].comments = newComments; + newComments = null; + + this.emit('comment_delete', MediaPageStoreData[this.id].while.deleteCommentId); + } + setTimeout( + function (ins) { + MediaPageStoreData[ins.id].while.deleteCommentId = null; + }, + 100, + this + ); + } + + submitCommentFail(err) { + this.emit('comment_submit_fail'); + setTimeout( + function (ins) { + MediaPageStoreData[ins.id].while.submitComment = false; + }, + 100, + this + ); + } + + submitCommentResponse(response) { + if (response && 201 === response.status && response.data && Object.keys(response.data)) { + MediaPageStoreData[this.id].comments.push(response.data); + this.emit('comment_submit', response.data.uid); + } + setTimeout( + function (ins) { + MediaPageStoreData[ins.id].while.submitComment = false; + }, + 100, + this + ); } - setTimeout( - function (ins) { - MediaPageStoreData[ins.id].while.submitComment = false; - }, - 100, - this - ); - } } export default exportStore(new MediaPageStore(), 'actions_handler'); diff --git a/frontend/src/static/js/utils/stores/VideoViewerStore.js b/frontend/src/static/js/utils/stores/VideoViewerStore.js index 6938e96e..f1a5b99a 100755 --- a/frontend/src/static/js/utils/stores/VideoViewerStore.js +++ b/frontend/src/static/js/utils/stores/VideoViewerStore.js @@ -9,88 +9,89 @@ let browserCache; const _StoreData = {}; class VideoPlayerStore extends EventEmitter { - constructor() { - super(); + constructor() { + super(); - this.mediacms_config = mediacmsConfig(window.MediaCMS); + this.mediacms_config = mediacmsConfig(window.MediaCMS); - browserCache = new BrowserCache(this.mediacms_config.site.id, 86400); // Keep cache data "fresh" for one day. + browserCache = new BrowserCache(this.mediacms_config.site.id, 86400); // Keep cache data "fresh" for one day. - _StoreData.inTheaterMode = browserCache.get('in-theater-mode'); - _StoreData.inTheaterMode = null !== _StoreData.inTheaterMode ? _StoreData.inTheaterMode : !1; + _StoreData.inTheaterMode = browserCache.get('in-theater-mode'); + _StoreData.inTheaterMode = null !== _StoreData.inTheaterMode ? _StoreData.inTheaterMode : !1; - _StoreData.playerVolume = browserCache.get('player-volume'); - _StoreData.playerVolume = - null === _StoreData.playerVolume ? 1 : Math.max(Math.min(Number(_StoreData.playerVolume), 1), 0); + _StoreData.playerVolume = browserCache.get('player-volume'); + _StoreData.playerVolume = + null === _StoreData.playerVolume ? 1 : Math.max(Math.min(Number(_StoreData.playerVolume), 1), 0); - _StoreData.playerSoundMuted = browserCache.get('player-sound-muted'); - _StoreData.playerSoundMuted = null !== _StoreData.playerSoundMuted ? _StoreData.playerSoundMuted : !1; + _StoreData.playerSoundMuted = browserCache.get('player-sound-muted'); + _StoreData.playerSoundMuted = null !== _StoreData.playerSoundMuted ? _StoreData.playerSoundMuted : !1; - _StoreData.videoQuality = browserCache.get('video-quality'); - _StoreData.videoQuality = null !== _StoreData.videoQuality ? _StoreData.videoQuality : 'Auto'; + _StoreData.videoQuality = browserCache.get('video-quality'); + _StoreData.videoQuality = null !== _StoreData.videoQuality ? _StoreData.videoQuality : 'Auto'; - _StoreData.videoPlaybackSpeed = browserCache.get('video-playback-speed'); - _StoreData.videoPlaybackSpeed = null !== _StoreData.videoPlaybackSpeed ? _StoreData.videoPlaybackSpeed : !1; - } - - get(type) { - let r = null; - switch (type) { - case 'player-volume': - r = _StoreData.playerVolume; - break; - case 'player-sound-muted': - r = _StoreData.playerSoundMuted; - break; - case 'in-theater-mode': - r = _StoreData.inTheaterMode; - break; - case 'video-data': - r = _StoreData.videoData; - break; - case 'video-quality': - r = _StoreData.videoQuality; - break; - case 'video-playback-speed': - r = _StoreData.videoPlaybackSpeed; - break; + _StoreData.videoPlaybackSpeed = browserCache.get('video-playback-speed'); + _StoreData.videoPlaybackSpeed = null !== _StoreData.videoPlaybackSpeed ? _StoreData.videoPlaybackSpeed : !1; } - return r; - } - actions_handler(action) { - switch (action.type) { - case 'TOGGLE_VIEWER_MODE': - _StoreData.inTheaterMode = !_StoreData.inTheaterMode; - this.emit('changed_viewer_mode'); - break; - case 'SET_VIEWER_MODE': - _StoreData.inTheaterMode = action.inTheaterMode; - browserCache.set('in-theater-mode', _StoreData.inTheaterMode); - this.emit('changed_viewer_mode'); - break; - case 'SET_PLAYER_VOLUME': - _StoreData.playerVolume = action.playerVolume; - browserCache.set('player-volume', action.playerVolume); - this.emit('changed_player_volume'); - break; - case 'SET_PLAYER_SOUND_MUTED': - _StoreData.playerSoundMuted = action.playerSoundMuted; - browserCache.set('player-sound-muted', action.playerSoundMuted); - this.emit('changed_player_sound_muted'); - break; - case 'SET_VIDEO_QUALITY': - _StoreData.videoQuality = action.quality; - browserCache.set('video-quality', action.quality); - this.emit('changed_video_quality'); - break; - case 'SET_VIDEO_PLAYBACK_SPEED': - _StoreData.videoPlaybackSpeed = action.playbackSpeed; - browserCache.set('video-playback-speed', action.playbackSpeed); - this.emit('changed_video_playback_speed'); - break; + get(type) { + let r = null; + switch (type) { + case 'player-volume': + r = _StoreData.playerVolume; + break; + case 'player-sound-muted': + r = _StoreData.playerSoundMuted; + break; + case 'in-theater-mode': + r = _StoreData.inTheaterMode; + break; + case 'video-data': + r = _StoreData.videoData; + break; + case 'video-quality': + r = _StoreData.videoQuality; + break; + case 'video-playback-speed': + r = _StoreData.videoPlaybackSpeed; + break; + } + return r; + } + + actions_handler(action) { + switch (action.type) { + case 'TOGGLE_VIEWER_MODE': + _StoreData.inTheaterMode = !_StoreData.inTheaterMode; + browserCache.set('in-theater-mode', _StoreData.inTheaterMode); + this.emit('changed_viewer_mode'); + break; + case 'SET_VIEWER_MODE': + _StoreData.inTheaterMode = action.inTheaterMode; + browserCache.set('in-theater-mode', _StoreData.inTheaterMode); + this.emit('changed_viewer_mode'); + break; + case 'SET_PLAYER_VOLUME': + _StoreData.playerVolume = action.playerVolume; + browserCache.set('player-volume', action.playerVolume); + this.emit('changed_player_volume'); + break; + case 'SET_PLAYER_SOUND_MUTED': + _StoreData.playerSoundMuted = action.playerSoundMuted; + browserCache.set('player-sound-muted', action.playerSoundMuted); + this.emit('changed_player_sound_muted'); + break; + case 'SET_VIDEO_QUALITY': + _StoreData.videoQuality = action.quality; + browserCache.set('video-quality', action.quality); + this.emit('changed_video_quality'); + break; + case 'SET_VIDEO_PLAYBACK_SPEED': + _StoreData.videoPlaybackSpeed = action.playbackSpeed; + browserCache.set('video-playback-speed', action.playbackSpeed); + this.emit('changed_video_playback_speed'); + break; + } } - } } export default exportStore(new VideoPlayerStore(), 'actions_handler'); diff --git a/frontend/tests/tests-constants.ts b/frontend/tests/tests-constants.ts new file mode 100644 index 00000000..31619a16 --- /dev/null +++ b/frontend/tests/tests-constants.ts @@ -0,0 +1,385 @@ +export const sampleGlobalMediaCMS = { + profileId: 'john', + site: { + id: 'my-site', + url: 'https://example.com/', + api: 'https://example.com/api/', + title: 'Example', + theme: { mode: 'dark', switch: { enabled: true, position: 'sidebar' } }, + logo: { + lightMode: { img: '/img/light.png', svg: '/img/light.svg' }, + darkMode: { img: '/img/dark.png', svg: '/img/dark.svg' }, + }, + devEnv: false, + useRoundedCorners: true, + version: '1.0.0', + taxonomies: { + tags: { enabled: true, title: 'Topic Tags' }, + categories: { enabled: false, title: 'Kinds' }, + }, + pages: { + featured: { enabled: true, title: 'Featured picks' }, + latest: { enabled: true, title: 'Recent uploads' }, + members: { enabled: true, title: 'People' }, + recommended: { enabled: false, title: 'You may like' }, + }, + userPages: { + liked: { enabled: true, title: 'Favorites' }, + history: { enabled: true, title: 'Watched' }, + }, + }, + url: { + home: '/', + admin: '/admin', + error404: '/404', + latestMedia: '/latest', + featuredMedia: '/featured', + recommendedMedia: '/recommended', + signin: '/signin', + signout: '/signout', + register: '/register', + changePassword: '/password', + members: '/members', + search: '/search', + likedMedia: '/liked', + history: '/history', + addMedia: '/add', + editChannel: '/edit/channel', + editProfile: '/edit/profile', + tags: '/tags', + categories: '/categories', + manageMedia: '/manage/media', + manageUsers: '/manage/users', + manageComments: '/manage/comments', + }, + api: { + media: 'v1/media/', + playlists: 'v1/playlists', + members: 'v1/users', + liked: 'v1/user/liked', + history: 'v1/user/history', + tags: 'v1/tags', + categories: 'v1/categories', + manage_media: 'v1/manage/media', + manage_users: 'v1/manage/users', + manage_comments: 'v1/manage/comments', + search: 'v1/search', + actions: 'v1/actions', + comments: 'v1/comments', + }, + contents: { + header: { + right: '', + onLogoRight: '', + }, + notifications: { + messages: { addToLiked: 'Yay', removeFromLiked: 'Oops', addToDisliked: 'nay', removeFromDisliked: 'ok' }, + }, + sidebar: { + belowNavMenu: '__belowNavMenu__', + belowThemeSwitcher: '__belowThemeSwitcher__', + footer: '__footer__', + mainMenuExtraItems: [ + { text: '__text_1__', link: '__link_1__', icon: '__icon_1__', className: '__className_1__' }, + ], + navMenuItems: [ + { text: '__text_2__', link: '__link_2__', icon: '__icon_2__', className: '__className_2__' }, + ], + }, + uploader: { + belowUploadArea: '__belowUploadArea__', + postUploadMessage: '__postUploadMessage__', + }, + }, + pages: { + home: { + sections: { + latest: { title: 'Latest T' }, + featured: { title: 'Featured T' }, + recommended: { title: 'Recommended T' }, + }, + }, + media: { categoriesWithTitle: true, htmlInDescription: true, hideViews: true, related: { initialSize: 5 } }, + profile: { htmlInDescription: true, includeHistory: true, includeLikedMedia: true }, + search: { advancedFilters: true }, + }, + features: { + mediaItem: { hideAuthor: true, hideViews: false, hideDate: true }, + media: { + actions: { + like: true, + dislike: true, + report: true, + comment: true, + comment_mention: true, + download: true, + save: true, + share: true, + }, + shareOptions: ['embed', 'email'], + }, + playlists: { mediaTypes: ['audio'] }, + sideBar: { hideHomeLink: false, hideTagsLink: true, hideCategoriesLink: false }, + embeddedVideo: { initialDimensions: { width: 640, height: 360 } }, + headerBar: { hideLogin: false, hideRegister: true }, + }, + user: { + is: { anonymous: false, admin: true }, + name: ' John ', + username: ' john ', + thumbnail: ' /img/j.png ', + can: { + changePassword: true, + deleteProfile: true, + addComment: true, + mentionComment: true, + deleteComment: true, + editMedia: true, + deleteMedia: true, + editSubtitle: true, + manageMedia: true, + manageUsers: true, + manageComments: true, + contactUser: true, + canSeeMembersPage: true, + usersNeedsToBeApproved: false, + addMedia: true, + editProfile: true, + readComment: true, + }, + pages: { about: '/u/john/about ', media: '/u/john ', playlists: '/u/john/playlists ' }, + }, +}; + +export const sampleMediaCMSConfig = { + api: { + archive: { + tags: '', + categories: '', + }, + featured: '', + manage: { + media: '', + users: '', + comments: '', + }, + media: '', + playlists: '/v1/playlists', + recommended: '', + search: { + query: '', + titles: './search.html?titles=', + tag: '', + category: '', + }, + user: { + liked: '', + history: '', + playlists: '/playlists/?author=', + }, + users: '/users', + }, + contents: { + header: { + right: '', + onLogoRight: '', + }, + uploader: { + belowUploadArea: '', + postUploadMessage: '', + }, + sidebar: { + belowNavMenu: '__belowNavMenu__', + belowThemeSwitcher: '__belowThemeSwitcher__', + footer: '__footer__', + mainMenuExtra: { + items: [{ text: '__text_1__', link: '__link_1__', icon: '__icon_1__', className: '__className_1__' }], + }, + navMenu: { + items: [{ text: '__text_2__', link: '__link_2__', icon: '__icon_2__', className: '__className_2__' }], + }, + }, + }, + enabled: { + taxonomies: sampleGlobalMediaCMS.site.taxonomies, + pages: { + featured: { enabled: true, title: 'Featured picks' }, + latest: { enabled: true, title: 'Recent uploads' }, + members: { enabled: true, title: 'People' }, + recommended: { enabled: true, title: 'You may like' }, + liked: { enabled: true, title: 'Favorites' }, + history: { enabled: true, title: 'Watched' }, + }, + }, + member: { + name: null, + username: 'john', + thumbnail: null, + is: { + admin: false, + anonymous: false, + }, + can: { + addComment: false, + addMedia: false, + canSeeMembersPage: false, + changePassword: false, + contactUser: false, + deleteComment: false, + deleteMedia: false, + deleteProfile: false, + dislikeMedia: false, + downloadMedia: false, + editMedia: false, + editProfile: false, + editSubtitle: false, + likeMedia: false, + login: false, + manageComments: false, + manageMedia: false, + manageUsers: false, + mentionComment: false, + readComment: true, + register: false, + reportMedia: false, + saveMedia: true, + shareMedia: false, + usersNeedsToBeApproved: false, + }, + pages: { + home: null, + about: null, + media: null, + playlists: null, + }, + }, + media: { + item: { + displayAuthor: false, + displayViews: false, + displayPublishDate: false, + }, + share: { + options: [], + }, + }, + notifications: { + messages: { + addToLiked: '', + removeFromLiked: '', + addToDisliked: '', + removeFromDisliked: '', + }, + }, + options: { + pages: { + home: { + sections: { + latest: { + title: '', + }, + featured: { + title: '', + }, + recommended: { + title: '', + }, + }, + }, + search: { + advancedFilters: false, + }, + media: { + categoriesWithTitle: true, + htmlInDescription: true, + related: { initialSize: 5 }, + displayViews: true, + }, + profile: { + htmlInDescription: false, + includeHistory: false, + includeLikedMedia: false, + }, + }, + embedded: { + video: { + dimensions: { + width: 0, + widthUnit: 'px', + height: 0, + heightUnit: 'px', + }, + }, + }, + }, + playlists: { + mediaTypes: [], + }, + sidebar: { + hideHomeLink: false, + hideTagsLink: false, + hideCategoriesLink: false, + }, + site: { + api: '', + id: '', + title: '', + url: '', + useRoundedCorners: false, + version: '', + }, + theme: { + logo: { + lightMode: { img: '/img/light.png', svg: '/img/light.svg' }, + darkMode: { img: '/img/dark.png', svg: '/img/dark.svg' }, + }, + mode: 'dark', + switch: { + enabled: true, + position: 'sidebar', + }, + }, + url: { + admin: '', + archive: { + categories: '', + tags: '', + }, + changePassword: '', + embed: '', + error404: '', + featured: '', + home: '', + latest: '', + manage: { + comments: '', + media: '', + users: '', + }, + members: '', + profile: { + about: '', + media: '', + playlists: '', + shared_by_me: '', + shared_with_me: '', + }, + recommended: '', + register: '', + search: { + base: '', + category: '', + query: '', + tag: '', + }, + signin: '', + signout: '', + user: { + addMedia: '', + editChannel: '', + editProfile: '', + history: '', + liked: '', + }, + }, +}; diff --git a/frontend/tests/utils/stores/MediaPageStore.test.ts b/frontend/tests/utils/stores/MediaPageStore.test.ts new file mode 100644 index 00000000..2fca3f8e --- /dev/null +++ b/frontend/tests/utils/stores/MediaPageStore.test.ts @@ -0,0 +1,739 @@ +import { csrfToken, deleteRequest, getRequest, postRequest, putRequest } from '../../../src/static/js/utils/helpers'; + +const MEDIA_ID = 'MEDIA_ID'; +const PLAYLIST_ID = 'PLAYLIST_ID'; + +window.history.pushState({}, '', `/?m=${MEDIA_ID}&pl=${PLAYLIST_ID}`); + +import store from '../../../src/static/js/utils/stores/MediaPageStore'; + +import { sampleGlobalMediaCMS, sampleMediaCMSConfig } from '../../tests-constants'; + +jest.mock('../../../src/static/js/utils/classes/', () => ({ + BrowserCache: jest.fn().mockImplementation(() => ({ + get: jest.fn(), + set: jest.fn(), + })), +})); + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + BrowserEvents: jest.fn().mockImplementation(() => ({ + doc: jest.fn(), + win: jest.fn(), + })), + csrfToken: jest.fn(), + deleteRequest: jest.fn(), + exportStore: jest.fn((store) => store), + getRequest: jest.fn(), + postRequest: jest.fn(), + putRequest: jest.fn(), +})); + +describe('utils/store', () => { + describe('MediaPageStore', () => { + const handler = store.actions_handler.bind(store); + + const onLoadedViewerPlaylistData = jest.fn(); + const onLoadedPagePlaylistData = jest.fn(); + const onLoadedViewerPlaylistError = jest.fn(); + const onLoadedVideoData = jest.fn(); + const onLoadedImageData = jest.fn(); + const onLoadedMediaData = jest.fn(); + const onLoadedMediaError = jest.fn(); + const onCommentsLoad = jest.fn(); + const onUsersLoad = jest.fn(); + const onPlaylistsLoad = jest.fn(); + const onLikedMediaFailedRequest = jest.fn(); + const onLikedMedia = jest.fn(); + const onDislikedMediaFailedRequest = jest.fn(); + const onDislikedMedia = jest.fn(); + const onReportedMedia = jest.fn(); + const onPlaylistCreationCompleted = jest.fn(); + const onPlaylistCreationFailed = jest.fn(); + const onMediaPlaylistAdditionCompleted = jest.fn(); + const onMediaPlaylistAdditionFailed = jest.fn(); + const onMediaPlaylistRemovalCompleted = jest.fn(); + const onMediaPlaylistRemovalFailed = jest.fn(); + const onCopiedMediaLink = jest.fn(); + const onCopiedEmbedMediaCode = jest.fn(); + const onMediaDelete = jest.fn(); + const onMediaDeleteFail = jest.fn(); + const onCommentDeleteFail = jest.fn(); + const onCommentDelete = jest.fn(); + const onCommentSubmitFail = jest.fn(); + const onCommentSubmit = jest.fn(); + + store.on('loaded_viewer_playlist_data', onLoadedViewerPlaylistData); + store.on('loaded_page_playlist_data', onLoadedPagePlaylistData); + store.on('loaded_viewer_playlist_error', onLoadedViewerPlaylistError); + store.on('loaded_video_data', onLoadedVideoData); + store.on('loaded_image_data', onLoadedImageData); + store.on('loaded_media_data', onLoadedMediaData); + store.on('loaded_media_error', onLoadedMediaError); + store.on('comments_load', onCommentsLoad); + store.on('users_load', onUsersLoad); + store.on('playlists_load', onPlaylistsLoad); + store.on('liked_media_failed_request', onLikedMediaFailedRequest); + store.on('liked_media', onLikedMedia); + store.on('disliked_media_failed_request', onDislikedMediaFailedRequest); + store.on('disliked_media', onDislikedMedia); + store.on('reported_media', onReportedMedia); + store.on('playlist_creation_completed', onPlaylistCreationCompleted); + store.on('playlist_creation_failed', onPlaylistCreationFailed); + store.on('media_playlist_addition_completed', onMediaPlaylistAdditionCompleted); + store.on('media_playlist_addition_failed', onMediaPlaylistAdditionFailed); + store.on('media_playlist_removal_completed', onMediaPlaylistRemovalCompleted); + store.on('media_playlist_removal_failed', onMediaPlaylistRemovalFailed); + store.on('copied_media_link', onCopiedMediaLink); + store.on('copied_embed_media_code', onCopiedEmbedMediaCode); + store.on('media_delete', onMediaDelete); + store.on('media_delete_fail', onMediaDeleteFail); + store.on('comment_delete_fail', onCommentDeleteFail); + store.on('comment_delete', onCommentDelete); + store.on('comment_submit_fail', onCommentSubmitFail); + store.on('comment_submit', onCommentSubmit); + + beforeAll(() => { + (globalThis as any).window.MediaCMS = { + // mediaId: MEDIA_ID, // @note: It doesn't belong in 'sampleGlobalMediaCMS, but it could be used + features: sampleGlobalMediaCMS.features, + }; + }); + + afterAll(() => { + delete (globalThis as any).window.MediaCMS; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('Validate initial values', () => { + expect(store.get('users')).toStrictEqual([]); + expect(store.get('playlists')).toStrictEqual([]); + expect(store.get('media-load-error-type')).toBe(null); + expect(store.get('media-load-error-message')).toBe(null); + expect(store.get('media-comments')).toStrictEqual([]); + expect(store.get('media-data')).toBe(null); + expect(store.get('media-id')).toBe(MEDIA_ID); + expect(store.get('media-url')).toBe('N/A'); + expect(store.get('media-edit-subtitle-url')).toBe(null); + expect(store.get('media-likes')).toBe('N/A'); + expect(store.get('media-dislikes')).toBe('N/A'); + expect(store.get('media-summary')).toBe(null); + expect(store.get('media-categories')).toStrictEqual([]); + expect(store.get('media-tags')).toStrictEqual([]); + expect(store.get('media-type')).toBe(null); + expect(store.get('media-original-url')).toBe(null); + expect(store.get('media-thumbnail-url')).toBe(null); + expect(store.get('user-liked-media')).toBe(false); + expect(store.get('user-disliked-media')).toBe(false); + expect(store.get('media-author-thumbnail-url')).toBe(null); + expect(store.get('playlist-data')).toBe(null); + expect(store.get('playlist-id')).toBe(null); + expect(store.get('playlist-next-media-url')).toBe(null); + expect(store.get('playlist-previous-media-url')).toBe(null); + }); + + describe('Trigger and validate actions behavior', () => { + const MEDIA_DATA = { + add_subtitle_url: '/MEDIA_DATA_ADD_SUBTITLE_URL', + author_thumbnail: 'MEDIA_DATA_AUTHOR_THUMBNAIL', + categories_info: [ + { title: 'Art', url: '/search?c=Art' }, + { title: 'Documentary', url: '/search?c=Documentary' }, + ], + likes: 12, + dislikes: 4, + media_type: 'video', + original_media_url: 'MEDIA_DATA_ORIGINAL_MEDIA_URL', + reported_times: 0, + summary: 'MEDIA_DATA_SUMMARY', + tags_info: [ + { title: 'and', url: '/search?t=and' }, + { title: 'behavior', url: '/search?t=behavior' }, + ], + thumbnail_url: 'MEDIA_DATA_THUMBNAIL_URL', + url: '/MEDIA_DATA_URL', + }; + const PLAYLIST_DATA = { + playlist_media: [ + { friendly_token: `${MEDIA_ID}_2`, url: '/PLAYLIT_MEDIA_URL_2' }, + { friendly_token: MEDIA_ID, url: '/PLAYLIT_MEDIA_URL_1' }, + { friendly_token: `${MEDIA_ID}_3`, url: '/PLAYLIT_MEDIA_URL_3' }, + ], + }; + const USER_PLAYLIST_DATA = { playlist_media: [{ url: 'm=PLAYLIST_MEDIA_ID' }] }; + + test('Action type: "LOAD_MEDIA_DATA"', () => { + const MEDIA_API_URL = `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}`; + const MEDIA_COMMENTS_API_URL = `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments`; + const PLAYLIST_API_URL = `${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`; + const USERS_API_URL = sampleMediaCMSConfig.api.users; + const USER_PLAYLISTS_API_URL = `${sampleMediaCMSConfig.api.user.playlists}${sampleMediaCMSConfig.member.username}`; + const USER_PLAYLIST_API_URL = `${sampleMediaCMSConfig.site.url}/${'PLAYLIST_API_URL'.replace(/^\//g, '')}`; + + const MEDIA_COMMENTS_RESULTS = ['COMMENT_ID_1']; + const USERS_RESULTS = ['USER_ID_1']; + const USER_PLAYLISTS_RESULTS = [ + { + url: `/${PLAYLIST_ID}`, + user: sampleMediaCMSConfig.member.username, + title: 'PLAYLIST_TITLE', + description: 'PLAYLIST_DECRIPTION', + add_date: 'PLAYLIST_ADD_DATE', + api_url: 'PLAYLIST_API_URL', + }, + ]; + + (getRequest as jest.Mock).mockImplementation((url, _cache, successCallback, _failCallback) => { + if (url === PLAYLIST_API_URL) { + return successCallback({ data: PLAYLIST_DATA }); + } + + if (url === USER_PLAYLIST_API_URL) { + return successCallback({ data: USER_PLAYLIST_DATA }); + } + + if (url === MEDIA_API_URL) { + return successCallback({ data: MEDIA_DATA }); + } + + if (url === USERS_API_URL) { + return successCallback({ data: { count: USERS_RESULTS.length, results: USERS_RESULTS } }); + } + + if (url === MEDIA_COMMENTS_API_URL) { + return successCallback({ + data: { count: MEDIA_COMMENTS_RESULTS.length, results: MEDIA_COMMENTS_RESULTS }, + }); + } + + if (url === USER_PLAYLISTS_API_URL) { + return successCallback({ + data: { count: USER_PLAYLISTS_RESULTS.length, results: USER_PLAYLISTS_RESULTS }, + }); + } + }); + + handler({ type: 'LOAD_MEDIA_DATA' }); + + expect(getRequest).toHaveBeenCalledTimes(6); + + expect(getRequest).toHaveBeenCalledWith( + PLAYLIST_API_URL, + false, + store.playlistDataResponse, + store.playlistDataErrorResponse + ); + expect(getRequest).toHaveBeenCalledWith( + MEDIA_API_URL, + false, + store.dataResponse, + store.dataErrorResponse + ); + expect(getRequest).toHaveBeenCalledWith(MEDIA_COMMENTS_API_URL, false, store.commentsResponse); + expect(getRequest).toHaveBeenCalledWith(USERS_API_URL, false, store.usersResponse); + expect(getRequest).toHaveBeenCalledWith(USER_PLAYLISTS_API_URL, false, store.playlistsResponse); + expect(getRequest).toHaveBeenCalledWith(USER_PLAYLIST_API_URL, false, expect.any(Function)); + + expect(onLoadedViewerPlaylistData).toHaveBeenCalledTimes(1); + expect(onLoadedPagePlaylistData).toHaveBeenCalledTimes(1); + expect(onLoadedViewerPlaylistError).toHaveBeenCalledTimes(0); + expect(onLoadedVideoData).toHaveBeenCalledTimes(1); + expect(onLoadedImageData).toHaveBeenCalledTimes(0); + expect(onLoadedMediaData).toHaveBeenCalledTimes(1); + expect(onLoadedMediaError).toHaveBeenCalledTimes(0); + expect(onCommentsLoad).toHaveBeenCalledTimes(1); + expect(onUsersLoad).toHaveBeenCalledTimes(1); + expect(onPlaylistsLoad).toHaveBeenCalledTimes(1); + expect(onLikedMediaFailedRequest).toHaveBeenCalledTimes(0); + expect(onLikedMedia).toHaveBeenCalledTimes(0); + expect(onDislikedMediaFailedRequest).toHaveBeenCalledTimes(0); + expect(onDislikedMedia).toHaveBeenCalledTimes(0); + expect(onReportedMedia).toHaveBeenCalledTimes(0); + expect(onPlaylistCreationCompleted).toHaveBeenCalledTimes(0); + expect(onPlaylistCreationFailed).toHaveBeenCalledTimes(0); + expect(onMediaPlaylistAdditionCompleted).toHaveBeenCalledTimes(0); + expect(onMediaPlaylistAdditionFailed).toHaveBeenCalledTimes(0); + expect(onMediaPlaylistRemovalCompleted).toHaveBeenCalledTimes(0); + expect(onMediaPlaylistRemovalFailed).toHaveBeenCalledTimes(0); + expect(onCopiedMediaLink).toHaveBeenCalledTimes(0); + expect(onCopiedEmbedMediaCode).toHaveBeenCalledTimes(0); + expect(onMediaDelete).toHaveBeenCalledTimes(0); + expect(onMediaDeleteFail).toHaveBeenCalledTimes(0); + expect(onCommentDeleteFail).toHaveBeenCalledTimes(0); + expect(onCommentDelete).toHaveBeenCalledTimes(0); + expect(onCommentSubmitFail).toHaveBeenCalledTimes(0); + expect(onCommentSubmit).toHaveBeenCalledTimes(0); + + expect(store.isVideo()).toBeTruthy(); + + expect(store.get('users')).toStrictEqual(USERS_RESULTS); + expect(store.get('playlists')).toStrictEqual([ + { + playlist_id: PLAYLIST_ID, + title: 'PLAYLIST_TITLE', + description: 'PLAYLIST_DECRIPTION', + add_date: 'PLAYLIST_ADD_DATE', + media_list: ['PLAYLIST_MEDIA_ID'], + }, + ]); + expect(store.get('media-load-error-type')).toBe(null); + expect(store.get('media-load-error-message')).toBe(null); + expect(store.get('media-comments')).toStrictEqual(MEDIA_COMMENTS_RESULTS); + expect(store.get('media-data')).toBe(MEDIA_DATA); + expect(store.get('media-id')).toBe(MEDIA_ID); + expect(store.get('media-url')).toBe(MEDIA_DATA.url); + expect(store.get('media-edit-subtitle-url')).toBe(MEDIA_DATA.add_subtitle_url); + expect(store.get('media-likes')).toBe(MEDIA_DATA.likes); + expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes); + expect(store.get('media-summary')).toBe(MEDIA_DATA.summary); + expect(store.get('media-categories')).toStrictEqual(MEDIA_DATA.categories_info); + expect(store.get('media-tags')).toStrictEqual(MEDIA_DATA.tags_info); + expect(store.get('media-type')).toBe(MEDIA_DATA.media_type); + expect(store.get('media-original-url')).toBe(MEDIA_DATA.original_media_url); + expect(store.get('media-thumbnail-url')).toBe(MEDIA_DATA.thumbnail_url); + expect(store.get('user-liked-media')).toBe(false); + expect(store.get('user-disliked-media')).toBe(false); + expect(store.get('media-author-thumbnail-url')).toBe(`/${MEDIA_DATA.author_thumbnail}`); + expect(store.get('playlist-data')).toBe(PLAYLIST_DATA); + expect(store.get('playlist-id')).toBe(PLAYLIST_ID); + expect(store.get('playlist-next-media-url')).toBe( + `${PLAYLIST_DATA.playlist_media[2].url}&pl=${PLAYLIST_ID}` + ); + expect(store.get('playlist-previous-media-url')).toBe( + `${PLAYLIST_DATA.playlist_media[0].url}&pl=${PLAYLIST_ID}` + ); + }); + + test('Action type: "LIKE_MEDIA"', () => { + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: {} }) + ); + + handler({ type: 'LIKE_MEDIA' }); + + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/actions`, + { type: 'like' }, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.likeActionResponse, + expect.any(Function) + ); + + expect(onLikedMedia).toHaveBeenCalledTimes(1); + + expect(store.get('media-likes')).toBe(MEDIA_DATA.likes + 1); + expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes); + expect(store.get('user-liked-media')).toBe(true); + expect(store.get('user-disliked-media')).toBe(false); + }); + + test('Action type: "DISLIKE_MEDIA"', () => { + handler({ type: 'DISLIKE_MEDIA' }); + + expect(postRequest).toHaveBeenCalledTimes(0); + expect(onDislikedMedia).toHaveBeenCalledTimes(0); + + expect(store.get('media-likes')).toBe(MEDIA_DATA.likes + 1); + expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes); + expect(store.get('user-liked-media')).toBe(true); + expect(store.get('user-disliked-media')).toBe(false); + }); + + test('Action type: "REPORT_MEDIA"', () => { + const REPORT_DESCRIPTION = 'REPORT_DESCRIPTION'; + + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: {} }) + ); + + handler({ type: 'REPORT_MEDIA', reportDescription: REPORT_DESCRIPTION }); + + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/actions`, + { type: 'report', extra_info: REPORT_DESCRIPTION }, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.reportActionResponse, + store.reportActionResponse + ); + + expect(onReportedMedia).toHaveBeenCalledTimes(1); + }); + + test('Action type: "COPY_SHARE_LINK"', () => { + document.execCommand = jest.fn(); // @deprecated + const inputElement = document.createElement('input'); + handler({ type: 'COPY_SHARE_LINK', inputElement }); + expect(onCopiedMediaLink).toHaveBeenCalledTimes(1); + expect(document.execCommand).toHaveBeenCalledWith('copy'); + }); + + test('Action type: "COPY_EMBED_MEDIA_CODE"', () => { + document.execCommand = jest.fn(); // @deprecated + const inputElement = document.createElement('input'); + handler({ type: 'COPY_EMBED_MEDIA_CODE', inputElement }); + expect(onCopiedEmbedMediaCode).toHaveBeenCalledTimes(1); + expect(document.execCommand).toHaveBeenCalledWith('copy'); + }); + + describe('Action type: "REMOVE_MEDIA"', () => { + const mockCSRFtoken = 'test-csrf-token'; + + beforeEach(() => { + // Mock the CSRF token + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + jest.useFakeTimers(); + }); + + afterEach(() => { + // Verify deleteRequest was called with correct parameters + expect(deleteRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}`, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.removeMediaResponse, + store.removeMediaFail + ); + + // Fast-forward time + jest.advanceTimersByTime(100); + + jest.useRealTimers(); + }); + + test('Successful', () => { + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _configData, _sync, successCallback, _failCallback) => successCallback({ status: 204 }) + ); + + handler({ type: 'REMOVE_MEDIA' }); + + expect(onMediaDelete).toHaveBeenCalledTimes(1); + expect(onMediaDelete).toHaveBeenCalledWith(MEDIA_ID); + }); + + test('Failed', () => { + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _configData, _sync, _successCallback, failCallback) => failCallback({}) + ); + + handler({ type: 'REMOVE_MEDIA' }); + + expect(onMediaDeleteFail).toHaveBeenCalledTimes(1); + }); + }); + + describe('Action type: "SUBMIT_COMMENT"', () => { + const COMMENT_TEXT = 'COMMENT_TEXT'; + const COMMENT_UID = 'COMMENT_UID'; + + const mockCSRFtoken = 'test-csrf-token'; + + beforeEach(() => { + // Mock the CSRF token + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + jest.useFakeTimers(); + }); + + afterEach(() => { + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments`, + { text: COMMENT_TEXT }, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.submitCommentResponse, + store.submitCommentFail + ); + + // Fast-forward time + jest.advanceTimersByTime(100); + + jest.useRealTimers(); + }); + + test('Successful', () => { + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: { uid: COMMENT_UID }, status: 201 }) + ); + + handler({ type: 'SUBMIT_COMMENT', commentText: COMMENT_TEXT }); + + expect(onCommentSubmit).toHaveBeenCalledTimes(1); + expect(onCommentSubmit).toHaveBeenCalledWith(COMMENT_UID); + }); + + test('Failed', () => { + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback() + ); + + handler({ type: 'SUBMIT_COMMENT', commentText: COMMENT_TEXT }); + + expect(onCommentSubmitFail).toHaveBeenCalledTimes(1); + }); + }); + + describe('Action type: "DELETE_COMMENT"', () => { + const COMMENT_ID = 'COMMENT_ID'; + + const mockCSRFtoken = 'test-csrf-token'; + + beforeEach(() => { + // Mock the CSRF token + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + jest.useFakeTimers(); + }); + + afterEach(() => { + // Verify deleteRequest was called with correct parameters + expect(deleteRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments/${COMMENT_ID}`, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.removeCommentResponse, + store.removeCommentFail + ); + + // Fast-forward time + jest.advanceTimersByTime(100); + + jest.useRealTimers(); + }); + + test('Successful', () => { + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _configData, _sync, successCallback, _failCallback) => successCallback({ status: 204 }) + ); + + handler({ type: 'DELETE_COMMENT', commentId: COMMENT_ID }); + + expect(onCommentDelete).toHaveBeenCalledTimes(1); + }); + + test('Failed', () => { + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _configData, _sync, _successCallback, failCallback) => failCallback() + ); + + handler({ type: 'DELETE_COMMENT', commentId: COMMENT_ID }); + + expect(onCommentDeleteFail).toHaveBeenCalledTimes(1); + }); + }); + + describe('Action type: "CREATE_PLAYLIST"', () => { + const NEW_PLAYLIST_DATA = { + title: 'NEW_PLAYLIST_DATA_TITLE', + description: 'NEW_PLAYLIST_DATA_DESCRIPTION', + }; + + const mockCSRFtoken = 'test-csrf-token'; + + beforeEach(() => { + // Mock the CSRF token + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + }); + + afterEach(() => { + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + sampleMediaCMSConfig.api.playlists, + NEW_PLAYLIST_DATA, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + expect.any(Function), + expect.any(Function) + ); + }); + + test('Successful', () => { + const NEW_PLAYLIST_RESPONSE_DATA = { uid: 'COMMENT_UID' }; + + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: NEW_PLAYLIST_RESPONSE_DATA, status: 201 }) + ); + + handler({ type: 'CREATE_PLAYLIST', playlist_data: NEW_PLAYLIST_DATA }); + + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + sampleMediaCMSConfig.api.playlists, + NEW_PLAYLIST_DATA, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + expect.any(Function), + expect.any(Function) + ); + + expect(onPlaylistCreationCompleted).toHaveBeenCalledTimes(1); + expect(onPlaylistCreationCompleted).toHaveBeenCalledWith(NEW_PLAYLIST_RESPONSE_DATA); + }); + + test('Failed', () => { + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback() + ); + + handler({ type: 'CREATE_PLAYLIST', playlist_data: NEW_PLAYLIST_DATA }); + + expect(onPlaylistCreationFailed).toHaveBeenCalledTimes(1); + }); + }); + + describe('Action type: "ADD_MEDIA_TO_PLAYLIST"', () => { + const NEW_PLAYLIST_MEDIA_ID = 'NEW_PLAYLIST_MEDIA_ID'; + const mockCSRFtoken = 'test-csrf-token'; + + beforeEach(() => { + // Mock the CSRF token + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + }); + + afterEach(() => { + // Verify postRequest was called with correct parameters + expect(putRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`, + { type: 'add', media_friendly_token: NEW_PLAYLIST_MEDIA_ID }, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + expect.any(Function), + expect.any(Function) + ); + }); + + test('Successful', () => { + // Mock put request + (putRequest as jest.Mock).mockImplementation( + (_url, _putData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: {} }) + ); + + handler({ + type: 'ADD_MEDIA_TO_PLAYLIST', + playlist_id: PLAYLIST_ID, + media_id: NEW_PLAYLIST_MEDIA_ID, + }); + + expect(onMediaPlaylistAdditionCompleted).toHaveBeenCalledTimes(1); + }); + + test('Failed', () => { + // Mock put request + (putRequest as jest.Mock).mockImplementation( + (_url, _putData, _configData, _sync, _successCallback, failCallback) => failCallback() + ); + + handler({ + type: 'ADD_MEDIA_TO_PLAYLIST', + playlist_id: PLAYLIST_ID, + media_id: NEW_PLAYLIST_MEDIA_ID, + }); + + expect(onMediaPlaylistAdditionFailed).toHaveBeenCalledTimes(1); + }); + }); + + describe('Action type: "REMOVE_MEDIA_FROM_PLAYLIST"', () => { + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + afterEach(() => { + // Verify postRequest was called with correct parameters + expect(putRequest).toHaveBeenCalledWith( + `${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`, + { type: 'remove', media_friendly_token: MEDIA_ID }, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + expect.any(Function), + expect.any(Function) + ); + }); + + test('Successful', () => { + // Mock put request + (putRequest as jest.Mock).mockImplementation( + (_url, _putData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: {} }) + ); + + handler({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id: PLAYLIST_ID, media_id: MEDIA_ID }); + + expect(onMediaPlaylistRemovalCompleted).toHaveBeenCalledTimes(1); + }); + + test('Failed', () => { + // Mock put request + (putRequest as jest.Mock).mockImplementation( + (_url, _putData, _configData, _sync, _successCallback, failCallback) => failCallback() + ); + + handler({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id: PLAYLIST_ID, media_id: MEDIA_ID }); + + expect(onMediaPlaylistRemovalFailed).toHaveBeenCalledTimes(1); + }); + }); + + test('Action type: "APPEND_NEW_PLAYLIST"', () => { + const NEW_USER_PLAYLIST = { + add_date: 'PLAYLIST_ADD_DATE_2', + description: 'PLAYLIST_DECRIPTION_2', + media_list: ['PLAYLIST_MEDIA_ID'], + playlist_id: 'PLAYLIST_ID', + title: 'PLAYLIST_TITLE_2', + }; + + handler({ type: 'APPEND_NEW_PLAYLIST', playlist_data: NEW_USER_PLAYLIST }); + + expect(onPlaylistsLoad).toHaveBeenCalledTimes(1); + + expect(store.get('playlists')).toStrictEqual([ + { + add_date: 'PLAYLIST_ADD_DATE', + description: 'PLAYLIST_DECRIPTION', + media_list: ['PLAYLIST_MEDIA_ID'], + playlist_id: PLAYLIST_ID, + title: 'PLAYLIST_TITLE', + }, + NEW_USER_PLAYLIST, + ]); + }); + }); + }); +}); diff --git a/frontend/tests/utils/stores/PageStore.test.ts b/frontend/tests/utils/stores/PageStore.test.ts new file mode 100644 index 00000000..975ba1c4 --- /dev/null +++ b/frontend/tests/utils/stores/PageStore.test.ts @@ -0,0 +1,162 @@ +import { BrowserCache } from '../../../src/static/js/utils/classes'; +import store from '../../../src/static/js/utils/stores/PageStore'; + +import { sampleMediaCMSConfig } from '../../tests-constants'; + +jest.mock('../../../src/static/js/utils/classes/', () => ({ + BrowserCache: jest.fn().mockImplementation(() => ({ + get: (key: string) => (key === 'media-auto-play' ? false : undefined), + set: jest.fn(), + })), +})); + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + BrowserEvents: jest.fn().mockImplementation(() => ({ + doc: jest.fn(), + win: jest.fn(), + })), + exportStore: jest.fn((store) => store), +})); + +describe('utils/store', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + describe('PageStore', () => { + const handler = store.actions_handler.bind(store); + + const onInit = jest.fn(); + const onToggleAutoPlay = jest.fn(); + const onAddNotification = jest.fn(); + + store.on('page_init', onInit); + store.on('switched_media_auto_play', onToggleAutoPlay); + store.on('added_notification', onAddNotification); + + test('Validate initial values', () => { + // BrowserCache mock + expect(store.get('browser-cache').get('media-auto-play')).toBe(false); + expect(store.get('browser-cache').get('ANY')).toBe(undefined); + + // Autoplay media files + expect(store.get('media-auto-play')).toBe(false); + + // Configuration + expect(store.get('config-contents')).toStrictEqual(sampleMediaCMSConfig.contents); + expect(store.get('config-enabled')).toStrictEqual(sampleMediaCMSConfig.enabled); + expect(store.get('config-media-item')).toStrictEqual(sampleMediaCMSConfig.media.item); + expect(store.get('config-options')).toStrictEqual(sampleMediaCMSConfig.options); + expect(store.get('config-site')).toStrictEqual(sampleMediaCMSConfig.site); + + // Playlists API path + expect(store.get('api-playlists')).toStrictEqual(sampleMediaCMSConfig.api.playlists); + + // Notifications + expect(store.get('notifications')).toStrictEqual([]); + expect(store.get('notifications-size')).toBe(0); + + expect(store.get('current-page')).toBe(undefined); + }); + + test('Trigger and validate browser events behavior', () => { + const docVisChange = jest.fn(); + const winScroll = jest.fn(); + const winResize = jest.fn(); + + store.on('document_visibility_change', docVisChange); + store.on('window_scroll', winScroll); + store.on('window_resize', winResize); + + store.onDocumentVisibilityChange(); + store.onWindowScroll(); + store.onWindowResize(); + + expect(docVisChange).toHaveBeenCalled(); + expect(winScroll).toHaveBeenCalled(); + expect(winResize).toHaveBeenCalledTimes(1); + }); + + describe('Trigger and validate actions behavior', () => { + test('Action type: "INIT_PAGE"', () => { + handler({ type: 'INIT_PAGE', page: 'home' }); + expect(onInit).toHaveBeenCalledTimes(1); + expect(store.get('current-page')).toBe('home'); + + handler({ type: 'INIT_PAGE', page: 'about' }); + expect(onInit).toHaveBeenCalledTimes(2); + expect(store.get('current-page')).toBe('about'); + + handler({ type: 'INIT_PAGE', page: 'profile' }); + expect(onInit).toHaveBeenCalledTimes(3); + expect(store.get('current-page')).toBe('profile'); + + expect(onInit).toHaveBeenCalledWith(); + + expect(onToggleAutoPlay).toHaveBeenCalledTimes(0); + expect(onAddNotification).toHaveBeenCalledTimes(0); + }); + + test('Action type: "TOGGLE_AUTO_PLAY"', () => { + const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value; + const browserCacheSetSpy = browserCacheInstance.set; + + const initialValue = store.get('media-auto-play'); + + handler({ type: 'TOGGLE_AUTO_PLAY' }); + + expect(onToggleAutoPlay).toHaveBeenCalledWith(); + expect(onToggleAutoPlay).toHaveBeenCalledTimes(1); + + expect(store.get('media-auto-play')).toBe(!initialValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('media-auto-play', !initialValue); + + browserCacheSetSpy.mockRestore(); + }); + + test('Action type: "ADD_NOTIFICATION"', () => { + const notificationMsg1 = 'NOTIFICATION_MSG_1'; + const notificationMsg2 = 'NOTIFICATION_MSG_2'; + const invalidNotification = 44; + + // Add notification + handler({ type: 'ADD_NOTIFICATION', notification: notificationMsg1 }); + expect(onAddNotification).toHaveBeenCalledWith(); + expect(onAddNotification).toHaveBeenCalledTimes(1); + expect(store.get('notifications-size')).toBe(1); + + const currentNotifications = store.get('notifications'); + expect(currentNotifications.length).toBe(1); + expect(typeof currentNotifications[0][0]).toBe('string'); + expect(currentNotifications[0][1]).toBe(notificationMsg1); + + expect(store.get('notifications-size')).toBe(0); + expect(store.get('notifications')).toStrictEqual([]); + + // Add another notification + handler({ type: 'ADD_NOTIFICATION', notification: notificationMsg2 }); + + expect(onAddNotification).toHaveBeenCalledWith(); + expect(onAddNotification).toHaveBeenCalledTimes(2); + + expect(store.get('notifications-size')).toBe(1); + expect(store.get('notifications')[0][1]).toBe(notificationMsg2); + + expect(store.get('notifications-size')).toBe(0); + expect(store.get('notifications')).toStrictEqual([]); + + // Add invalid notification + handler({ type: 'ADD_NOTIFICATION', notification: invalidNotification }); + expect(onAddNotification).toHaveBeenCalledWith(); + expect(onAddNotification).toHaveBeenCalledTimes(3); + + expect(store.get('notifications-size')).toBe(0); + expect(store.get('notifications')).toStrictEqual([]); + }); + }); + }); +}); diff --git a/frontend/tests/utils/stores/PlaylistPageStore.test.ts b/frontend/tests/utils/stores/PlaylistPageStore.test.ts new file mode 100644 index 00000000..34143760 --- /dev/null +++ b/frontend/tests/utils/stores/PlaylistPageStore.test.ts @@ -0,0 +1,390 @@ +import { + publishedOnDate, + getRequest, + postRequest, + deleteRequest, + csrfToken, +} from '../../../src/static/js/utils/helpers'; +import store from '../../../src/static/js/utils/stores/PlaylistPageStore'; + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + publishedOnDate: jest.fn(), + exportStore: jest.fn((store) => store), + getRequest: jest.fn(), + postRequest: jest.fn(), + deleteRequest: jest.fn(), + csrfToken: jest.fn(), +})); + +describe('utils/store', () => { + beforeAll(() => { + (globalThis as any).window.MediaCMS = { playlistId: null }; + }); + + afterAll(() => { + delete (globalThis as any).window.MediaCMS; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('PlaylistPageStore', () => { + const handler = store.actions_handler.bind(store); + + const onLoadedPlaylistData = jest.fn(); + const onLoadedPlaylistEerror = jest.fn(); + const onLoadedMediaError = jest.fn(); + const onPlaylistUpdateCompleted = jest.fn(); + const onPlaylistUpdateFailed = jest.fn(); + const onPlaylistRemovalCompleted = jest.fn(); + const onPlaylistRemovalFailed = jest.fn(); + const onSavedUpdated = jest.fn(); + const onReorderedMediaInPlaylist = jest.fn(); + const onRemovedMediaFromPlaylist = jest.fn(); + + store.on('loaded_playlist_data', onLoadedPlaylistData); + store.on('loaded_playlist_error', onLoadedPlaylistEerror); + store.on('loaded_media_error', onLoadedMediaError); // @todo: It doesn't get called + store.on('playlist_update_completed', onPlaylistUpdateCompleted); + store.on('playlist_update_failed', onPlaylistUpdateFailed); + store.on('playlist_removal_completed', onPlaylistRemovalCompleted); + store.on('playlist_removal_failed', onPlaylistRemovalFailed); + store.on('saved-updated', onSavedUpdated); + store.on('reordered_media_in_playlist', onReorderedMediaInPlaylist); + store.on('removed_media_from_playlist', onRemovedMediaFromPlaylist); + + test('Validate initial values', () => { + expect(store.get('INVALID_TYPE')).toBe(null); + expect(store.get('playlistId')).toBe(null); + expect(store.get('logged-in-user-playlist')).toBe(false); + expect(store.get('playlist-media')).toStrictEqual([]); + expect(store.get('visibility')).toBe('public'); + expect(store.get('visibility-icon')).toBe(null); + // // expect(store.get('total-items')).toBe(0); // @todo: It throws error + expect(store.get('views-count')).toBe('N/A'); + expect(store.get('title')).toBe(null); + expect(store.get('edit-link')).toBe('#'); + expect(store.get('thumb')).toBe(null); + expect(store.get('description')).toBe(null); + expect(store.get('author-username')).toBe(null); + expect(store.get('author-name')).toBe(null); + expect(store.get('author-link')).toBe(null); + expect(store.get('author-thumb')).toBe(null); + expect(store.get('saved-playlist')).toBe(false); + expect(store.get('date-label')).toBe(null); + }); + + describe('Trigger and validate actions behavior', () => { + test('Action type: "LOAD_PLAYLIST_DATA" - failed', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const loadDataSpy = jest.spyOn(store, 'loadData'); + + handler({ type: 'LOAD_PLAYLIST_DATA' }); + + expect(loadDataSpy).toHaveBeenCalledTimes(1); + expect(loadDataSpy).toHaveReturnedWith(false); + + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith('Invalid playlist id:', ''); + + expect(store.get('playlistId')).toBe(null); + + loadDataSpy.mockRestore(); + warnSpy.mockRestore(); + }); + + test('Action type: "LOAD_PLAYLIST_DATA" - completed successful', () => { + const playlistId = 'PLAYLIST_ID_1'; + window.history.pushState({}, '', `/playlists/${playlistId}`); + + // Mock get request + const mockGetRequestResponse = { + data: { + add_date: Date.now(), + description: 'DESCRIPTION', + playlist_media: [], + title: 'TITLE', + user: 'USER', + user_thumbnail_url: 'USER_THUMB_URL', + }, + }; + + (getRequest as jest.Mock).mockImplementation((_url, _cache, successCallback, _failCallback) => + successCallback(mockGetRequestResponse) + ); + + const loadDataSpy = jest.spyOn(store, 'loadData'); + const dataResponseSpy = jest.spyOn(store, 'dataResponse'); + + handler({ type: 'LOAD_PLAYLIST_DATA' }); + + expect(store.get('playlistId')).toBe(playlistId); + expect(store.get('author-name')).toBe(mockGetRequestResponse.data.user); + expect(store.get('author-link')).toBe(`/user/${mockGetRequestResponse.data.user}`); + expect(store.get('author-thumb')).toBe(`/${mockGetRequestResponse.data.user_thumbnail_url}`); + + expect(store.get('date-label')).toBe('Created on undefined'); + expect(publishedOnDate).toHaveBeenCalledWith(new Date(mockGetRequestResponse.data.add_date), 3); + + expect(loadDataSpy).toHaveBeenCalledTimes(1); + expect(loadDataSpy).toHaveReturnedWith(undefined); + + expect(dataResponseSpy).toHaveBeenCalledTimes(1); + expect(dataResponseSpy).toHaveBeenCalledWith(mockGetRequestResponse); + + // Verify getRequest was called with correct parameters + expect(getRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + false, + store.dataResponse, + store.dataErrorResponse + ); + + expect(onLoadedPlaylistData).toHaveBeenCalledTimes(1); + expect(onLoadedPlaylistData).toHaveBeenCalledWith(); + + loadDataSpy.mockRestore(); + dataResponseSpy.mockRestore(); + }); + + test('Action type: "LOAD_PLAYLIST_DATA" - completed with error', () => { + const playlistId = 'PLAYLIST_ID_2'; + window.history.pushState({}, '', `/playlists/${playlistId}`); + + // Mock get request + const mockGetRequestResponse = { type: 'private' }; + (getRequest as jest.Mock).mockImplementation((_url, _cache, _successCallback, failCallback) => + failCallback(mockGetRequestResponse) + ); + + const loadDataSpy = jest.spyOn(store, 'loadData'); + const dataErrorResponseSpy = jest.spyOn(store, 'dataErrorResponse'); + + handler({ type: 'LOAD_PLAYLIST_DATA' }); + + expect(store.get('playlistId')).toBe(playlistId); + + expect(loadDataSpy).toHaveBeenCalledTimes(1); + expect(loadDataSpy).toHaveReturnedWith(undefined); + + expect(dataErrorResponseSpy).toHaveBeenCalledTimes(1); + expect(dataErrorResponseSpy).toHaveBeenCalledWith(mockGetRequestResponse); + + // Verify getRequest was called with correct parameters + expect(getRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + false, + store.dataResponse, + store.dataErrorResponse + ); + + expect(onLoadedPlaylistEerror).toHaveBeenCalledTimes(1); + expect(onLoadedPlaylistEerror).toHaveBeenCalledWith(); + + loadDataSpy.mockRestore(); + dataErrorResponseSpy.mockRestore(); + }); + + test('Action type: "TOGGLE_SAVE"', () => { + const initialValue = store.get('saved-playlist'); + + handler({ type: 'TOGGLE_SAVE' }); + + expect(onSavedUpdated).toHaveBeenCalledTimes(1); + expect(onSavedUpdated).toHaveBeenCalledWith(); + + expect(store.get('saved-playlist')).toBe(!initialValue); + }); + + test('Action type: "UPDATE_PLAYLIST" - failed', () => { + // Mock (updated) playlist data + const mockPlaylistData = { title: 'PLAYLIST_TITLE', description: 'PLAYLIST_DESCRIPTION' }; + + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback() + ); + + const initialStoreData = { + title: store.get('title'), + description: store.get('description'), + }; + + expect(store.get('title')).toBe(initialStoreData.title); + expect(store.get('description')).toBe(initialStoreData.description); + + handler({ type: 'UPDATE_PLAYLIST', playlist_data: mockPlaylistData }); + + expect(store.get('title')).toBe(initialStoreData.title); + expect(store.get('description')).toBe(initialStoreData.description); + + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + mockPlaylistData, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.onPlaylistUpdateCompleted, + store.onPlaylistUpdateFailed + ); + + expect(onPlaylistUpdateFailed).toHaveBeenCalledWith(); + }); + + test('Action type: "UPDATE_PLAYLIST" - successful', () => { + // Mock (updated) playlist data + const mockPlaylistData = { title: 'PLAYLIST_TITLE', description: 'PLAYLIST_DESCRIPTION' }; + + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock post request + (postRequest as jest.Mock).mockImplementation( + (_url, _postData, _configData, _sync, successCallback, _failCallback) => + successCallback({ data: mockPlaylistData }) + ); + + const initialStoreData = { + title: store.get('title'), + description: store.get('description'), + }; + + expect(store.get('title')).toBe(initialStoreData.title); + expect(store.get('description')).toBe(initialStoreData.description); + + handler({ type: 'UPDATE_PLAYLIST', playlist_data: mockPlaylistData }); + + expect(store.get('title')).toBe(mockPlaylistData.title); + expect(store.get('description')).toBe(mockPlaylistData.description); + + // Verify postRequest was called with correct parameters + expect(postRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + mockPlaylistData, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.onPlaylistUpdateCompleted, + store.onPlaylistUpdateFailed + ); + + expect(onPlaylistUpdateCompleted).toHaveBeenCalledWith(mockPlaylistData); + }); + + test('Action type: "REMOVE_PLAYLIST" - failed', () => { + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _config, _sync, _successCallback, failCallback) => failCallback() + ); + + handler({ type: 'REMOVE_PLAYLIST' }); + + // Verify deleteRequest was called with correct parameters + expect(deleteRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.onPlaylistRemovalCompleted, + store.onPlaylistRemovalFailed + ); + + expect(onPlaylistRemovalFailed).toHaveBeenCalledWith(); + }); + + test('Action type: "REMOVE_PLAYLIST" - completed successful', () => { + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock delete request + const deleteRequestResponse = { status: 204 }; + (deleteRequest as jest.Mock).mockImplementation( + (_url, _config, _sync, successCallback, _failCallback) => successCallback(deleteRequestResponse) + ); + + handler({ type: 'REMOVE_PLAYLIST' }); + + // Verify deleteRequest was called with correct parameters + expect(deleteRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.onPlaylistRemovalCompleted, + store.onPlaylistRemovalFailed + ); + + expect(onPlaylistRemovalCompleted).toHaveBeenCalledWith(deleteRequestResponse); + }); + + test('Action type: "REMOVE_PLAYLIST" - completed with invalid status code', () => { + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock delete request + const deleteRequestResponse = { status: 403 }; + (deleteRequest as jest.Mock).mockImplementation( + (_url, _config, _sync, successCallback, _failCallback) => successCallback(deleteRequestResponse) + ); + + handler({ type: 'REMOVE_PLAYLIST' }); + + // Verify deleteRequest was called with correct parameters + expect(deleteRequest).toHaveBeenCalledWith( + store.playlistAPIUrl, + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.onPlaylistRemovalCompleted, + store.onPlaylistRemovalFailed + ); + + expect(onPlaylistRemovalFailed).toHaveBeenCalledWith(); + }); + + test('Action type: "PLAYLIST_MEDIA_REORDERED"', () => { + // Mock playlist media data + const mockPlaylistMedia = [ + { thumbnail_url: 'THUMB_URL_1', url: '?id=MEDIA_ID_1' }, + { thumbnail_url: 'THUMB_URL_2', url: '?id=MEDIA_ID_2' }, + ]; + + handler({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: mockPlaylistMedia }); + + expect(onReorderedMediaInPlaylist).toHaveBeenCalledWith(); + + expect(store.get('playlist-media')).toStrictEqual(mockPlaylistMedia); + expect(store.get('thumb')).toBe(mockPlaylistMedia[0].thumbnail_url); + expect(store.get('total-items')).toBe(mockPlaylistMedia.length); + }); + + test('Action type: "MEDIA_REMOVED_FROM_PLAYLIST"', () => { + // Mock playlist media data + const mockPlaylistMedia = [ + { thumbnail_url: 'THUMB_URL_1', url: '?id=MEDIA_ID_1' }, + { thumbnail_url: 'THUMB_URL_2', url: '?id=MEDIA_ID_2' }, + ]; + + handler({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: mockPlaylistMedia }); + + handler({ type: 'MEDIA_REMOVED_FROM_PLAYLIST', media_id: 'MEDIA_ID_2' }); + + expect(store.get('playlist-media')).toStrictEqual([mockPlaylistMedia[0]]); + expect(store.get('thumb')).toBe(mockPlaylistMedia[0].thumbnail_url); + expect(store.get('total-items')).toBe(mockPlaylistMedia.length - 1); + }); + }); + }); +}); diff --git a/frontend/tests/utils/stores/PlaylistViewStore.test.ts b/frontend/tests/utils/stores/PlaylistViewStore.test.ts new file mode 100644 index 00000000..11ece2e3 --- /dev/null +++ b/frontend/tests/utils/stores/PlaylistViewStore.test.ts @@ -0,0 +1,83 @@ +import { BrowserCache } from '../../../src/static/js/utils/classes/'; +import store from '../../../src/static/js/utils/stores/PlaylistViewStore'; + +jest.mock('../../../src/static/js/utils/classes/', () => ({ + BrowserCache: jest.fn().mockImplementation(() => ({ + get: jest.fn(), + set: jest.fn(), + })), +})); + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + BrowserEvents: jest.fn().mockImplementation(() => ({ + doc: jest.fn(), + win: jest.fn(), + })), + exportStore: jest.fn((store) => store), +})); + +describe('utils/store', () => { + describe('PlaylistViewStore', () => { + const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value; + const browserCacheSetSpy = browserCacheInstance.set; + + const handler = store.actions_handler.bind(store); + + const onLoopRepeatUpdated = jest.fn(); + const onShuffleUpdated = jest.fn(); + const onSavedUpdated = jest.fn(); + + store.on('loop-repeat-updated', onLoopRepeatUpdated); + store.on('shuffle-updated', onShuffleUpdated); + store.on('saved-updated', onSavedUpdated); + + test('Validate initial values', () => { + expect(store.get('INVALID_TYPE')).toBe(null); + expect(store.get('logged-in-user-playlist')).toBe(false); + expect(store.get('enabled-loop')).toBe(undefined); + expect(store.get('enabled-shuffle')).toBe(undefined); + expect(store.get('saved-playlist')).toBe(false); + }); + + describe('Trigger and validate actions behavior', () => { + // @todo: Revisit the behavior of this action + test('Action type: "TOGGLE_LOOP"', () => { + handler({ type: 'TOGGLE_LOOP' }); + + expect(onLoopRepeatUpdated).toHaveBeenCalledTimes(1); + expect(onLoopRepeatUpdated).toHaveBeenCalledWith(); + + expect(store.get('enabled-loop')).toBe(undefined); + + expect(browserCacheSetSpy).toHaveBeenCalledWith('loopPlaylist[null]', true); + }); + + // @todo: Revisit the behavior of this action + test('Action type: "TOGGLE_SHUFFLE"', () => { + handler({ type: 'TOGGLE_SHUFFLE' }); + + expect(onShuffleUpdated).toHaveBeenCalledTimes(1); + expect(onShuffleUpdated).toHaveBeenCalledWith(); + + expect(store.get('enabled-shuffle')).toBe(undefined); + + expect(browserCacheSetSpy).toHaveBeenCalledWith('shufflePlaylist[null]', true); + }); + + test('Action type: "TOGGLE_SAVE"', () => { + const initialValue = store.get('saved-playlist'); + + handler({ type: 'TOGGLE_SAVE' }); + + expect(onSavedUpdated).toHaveBeenCalledTimes(1); + expect(onSavedUpdated).toHaveBeenCalledWith(); + + expect(store.get('saved-playlist')).toBe(!initialValue); + }); + }); + }); +}); diff --git a/frontend/tests/utils/stores/ProfilePageStore.test.ts b/frontend/tests/utils/stores/ProfilePageStore.test.ts new file mode 100644 index 00000000..e9015480 --- /dev/null +++ b/frontend/tests/utils/stores/ProfilePageStore.test.ts @@ -0,0 +1,168 @@ +import { getRequest, deleteRequest, csrfToken } from '../../../src/static/js/utils/helpers'; +import store from '../../../src/static/js/utils/stores/ProfilePageStore'; + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => ({ + ...jest.requireActual('../../tests-constants').sampleMediaCMSConfig, + api: { ...jest.requireActual('../../tests-constants').sampleMediaCMSConfig.api, users: '' }, + })), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + getRequest: jest.fn(), + deleteRequest: jest.fn(), + csrfToken: jest.fn(), + exportStore: jest.fn((store) => store), +})); + +describe('utils/store', () => { + const mockAuthorData = { username: 'testuser', name: 'Test User' }; + + beforeAll(() => { + (globalThis as any).window.MediaCMS = { profileId: mockAuthorData.username }; + }); + + afterAll(() => { + delete (globalThis as any).window.MediaCMS; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('ProfilePageStore', () => { + const handler = store.actions_handler.bind(store); + + const onProfileDelete = jest.fn(); + const onProfileDeleteFail = jest.fn(); + const onLoadAuthorData = jest.fn(); + + beforeAll(() => { + store.on('profile_delete', onProfileDelete); + store.on('profile_delete_fail', onProfileDeleteFail); + store.on('load-author-data', onLoadAuthorData); + }); + + beforeEach(() => { + // Reset store state + store.authorData = null; + store.removingProfile = false; + store.authorQuery = undefined; + }); + + describe('Trigger and validate actions behavior', () => { + test('Action type: "REMOVE_PROFILE" - successful deletion', async () => { + // Set up author data + store.authorData = mockAuthorData; + + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _config, _sync, successCallback, _failCallback) => successCallback({ status: 204 }) + ); + + handler({ type: 'REMOVE_PROFILE' }); + + // Verify deleteRequest was called with correct parameters + expect(deleteRequest).toHaveBeenCalledWith( + '/testuser', // API URL constructed from config + username + { headers: { 'X-CSRFToken': mockCSRFtoken } }, + false, + store.removeProfileResponse, + store.removeProfileFail + ); + + // Verify event was emitted + expect(onProfileDelete).toHaveBeenCalledWith(mockAuthorData.username); + expect(onProfileDelete).toHaveBeenCalledTimes(1); + }); + + test('Action type: "REMOVE_PROFILE" - deletion failure', async () => { + // Set up author data + store.authorData = mockAuthorData; + + // Mock the CSRF token + const mockCSRFtoken = 'test-csrf-token'; + (csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken); + + // Mock delete request + (deleteRequest as jest.Mock).mockImplementation( + (_url, _config, _sync, _successCallback, failCallback) => failCallback.call(store) + ); + + handler({ type: 'REMOVE_PROFILE' }); + + // Wait for the setTimeout in removeProfileFail + await new Promise((resolve) => setTimeout(resolve, 150)); + + // Verify event was emitted + expect(onProfileDeleteFail).toHaveBeenCalledWith(mockAuthorData.username); + expect(onProfileDeleteFail).toHaveBeenCalledTimes(1); + }); + + test('Action type: "REMOVE_PROFILE" - prevents duplicate calls while removing', () => { + // Set up author data + store.authorData = mockAuthorData; + + handler({ type: 'REMOVE_PROFILE' }); + expect(deleteRequest).toHaveBeenCalledTimes(1); + + store.removingProfile = true; + handler({ type: 'REMOVE_PROFILE' }); + expect(deleteRequest).toHaveBeenCalledTimes(1); + + store.removingProfile = false; + handler({ type: 'REMOVE_PROFILE' }); + expect(deleteRequest).toHaveBeenCalledTimes(2); + }); + + test('Action type: "LOAD_AUTHOR_DATA"', async () => { + (getRequest as jest.Mock).mockImplementation((_url, _cache, successCallback, _failCallback) => + successCallback({ data: mockAuthorData }) + ); + + handler({ type: 'LOAD_AUTHOR_DATA' }); + + // Verify getRequest was called with correct parameters + expect(getRequest).toHaveBeenCalledWith('/testuser', false, store.onDataLoad, store.onDataLoadFail); + + // Verify event was emitted + expect(onLoadAuthorData).toHaveBeenCalledTimes(1); + + // Verify author data was processed correctly + expect(store.get('author-data')).toStrictEqual(mockAuthorData); + }); + }); + + describe('Getter methods', () => { + test('Validate initial values', () => { + expect(store.get('INVALID_TYPE')).toBe(undefined); + expect(store.get('author-data')).toBe(null); + expect(store.get('author-query')).toBe(null); + }); + + test('get("author-data") returns authorData', () => { + store.authorData = mockAuthorData; + expect(store.get('author-data')).toBe(mockAuthorData); + }); + + test('get("author-query") - without "aq" parameter in URL', () => { + window.history.pushState({}, '', '/path'); + expect(store.get('author-query')).toBe(null); + }); + + test('get("author-query") - with "aq" parameter in URL', () => { + window.history.pushState({}, '', '/path?aq=AUTHOR_QUERY'); + expect(store.get('author-query')).toBe('AUTHOR_QUERY'); + }); + + test('get("author-query") - empty search string', () => { + window.history.pushState({}, '', '/path?aq'); + expect(store.get('author-query')).toBe(null); + }); + }); + }); +}); diff --git a/frontend/tests/utils/stores/SearchFieldStore.test.ts b/frontend/tests/utils/stores/SearchFieldStore.test.ts new file mode 100644 index 00000000..88fe12b9 --- /dev/null +++ b/frontend/tests/utils/stores/SearchFieldStore.test.ts @@ -0,0 +1,64 @@ +const urlParams = { q: 'search_query', c: 'category_1', t: 'tag_1' }; +window.history.pushState({}, '', `/?q=${urlParams.q}&c=${urlParams.c}&t=${urlParams.t}`); + +import store from '../../../src/static/js/utils/stores/SearchFieldStore'; +import { getRequest } from '../../../src/static/js/utils/helpers'; + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + exportStore: jest.fn((store) => store), + getRequest: jest.fn(), +})); + +describe('utils/store', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + describe('SearchFieldStore', () => { + const handler = store.actions_handler.bind(store); + + const onLoadPredictions = jest.fn(); + + store.on('load_predictions', onLoadPredictions); + + test('Validate initial values based on URL params', async () => { + expect(store.get('INVALID_TYPE')).toBe(null); + expect(store.get('search-query')).toBe(urlParams.q); + expect(store.get('search-categories')).toBe(urlParams.c); + expect(store.get('search-tags')).toBe(urlParams.t); + }); + + test('Action type: "Action type: "TOGGLE_VIEWER_MODE"', async () => { + const predictionsQuery_1 = 'predictions_query_1'; + const predictionsQuery_2 = 'predictions_query_2'; + + const response_1 = { data: [{ title: 'Prediction 1' }, { title: 'Prediction 2' }] }; + const response_2 = { data: [{ title: 'Prediction 3' }, { title: 'Prediction 4' }] }; + + (getRequest as jest.Mock) + .mockImplementationOnce((_url, _cache, successCallback, _failCallback) => successCallback(response_1)) + .mockImplementationOnce((_url, _cache, successCallback, _failCallback) => successCallback(response_2)); + + handler({ type: 'REQUEST_PREDICTIONS', query: predictionsQuery_1 }); + handler({ type: 'REQUEST_PREDICTIONS', query: predictionsQuery_2 }); + + expect(onLoadPredictions).toHaveBeenCalledTimes(2); + + expect(onLoadPredictions).toHaveBeenNthCalledWith( + 1, + predictionsQuery_1, + response_1.data.map(({ title }) => title) + ); + + expect(onLoadPredictions).toHaveBeenNthCalledWith( + 2, + predictionsQuery_2, + response_2.data.map(({ title }) => title) + ); + }); + }); +}); diff --git a/frontend/tests/utils/stores/VideoViewerStore.test.ts b/frontend/tests/utils/stores/VideoViewerStore.test.ts new file mode 100644 index 00000000..1449c216 --- /dev/null +++ b/frontend/tests/utils/stores/VideoViewerStore.test.ts @@ -0,0 +1,147 @@ +import { BrowserCache } from '../../../src/static/js/utils/classes/'; +import store from '../../../src/static/js/utils/stores/VideoViewerStore'; + +jest.mock('../../../src/static/js/utils/classes/', () => ({ + BrowserCache: jest.fn().mockImplementation(() => ({ + get: (key: string) => { + let result: any = undefined; + switch (key) { + case 'player-volume': + result = 0.6; + break; + case 'player-sound-muted': + result = false; + break; + case 'in-theater-mode': + result = true; + break; + case 'video-quality': + result = 720; + break; + case 'video-playback-speed': + result = 2; + break; + } + return result; + }, + set: jest.fn(), + })), +})); + +jest.mock('../../../src/static/js/utils/settings/config', () => ({ + config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig), +})); + +jest.mock('../../../src/static/js/utils/helpers', () => ({ + BrowserEvents: jest.fn().mockImplementation(() => ({ + doc: jest.fn(), + win: jest.fn(), + })), + exportStore: jest.fn((store) => store), +})); + +describe('utils/store', () => { + describe('VideoViewerStore', () => { + const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value; + const browserCacheSetSpy = browserCacheInstance.set; + + const handler = store.actions_handler.bind(store); + + const onChangedViewerMode = jest.fn(); + const onChangedPlayerVolume = jest.fn(); + const onChangedPlayerSoundMuted = jest.fn(); + const onChangedVideoQuality = jest.fn(); + const onChangedVideoPlaybackSpeed = jest.fn(); + + store.on('changed_viewer_mode', onChangedViewerMode); + store.on('changed_player_volume', onChangedPlayerVolume); + store.on('changed_player_sound_muted', onChangedPlayerSoundMuted); + store.on('changed_video_quality', onChangedVideoQuality); + store.on('changed_video_playback_speed', onChangedVideoPlaybackSpeed); + + test('Validate initial values', () => { + expect(store.get('player-volume')).toBe(0.6); + expect(store.get('player-sound-muted')).toBe(false); + expect(store.get('in-theater-mode')).toBe(true); + expect(store.get('video-data')).toBe(undefined); // @todo: Revisit this behavior + expect(store.get('video-quality')).toBe(720); + expect(store.get('video-playback-speed')).toBe(2); + }); + + describe('Trigger and validate actions behavior', () => { + test('Action type: "TOGGLE_VIEWER_MODE"', () => { + const initialValue = store.get('in-theater-mode'); + + handler({ type: 'TOGGLE_VIEWER_MODE' }); + + expect(onChangedViewerMode).toHaveBeenCalledWith(); + expect(onChangedViewerMode).toHaveBeenCalledTimes(1); + + expect(store.get('in-theater-mode')).toBe(!initialValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('in-theater-mode', !initialValue); + }); + + test('Action type: "SET_VIEWER_MODE"', () => { + const initialValue = store.get('in-theater-mode'); + const newValue = !initialValue; + + handler({ type: 'SET_VIEWER_MODE', inTheaterMode: newValue }); + + expect(onChangedViewerMode).toHaveBeenCalledWith(); + expect(onChangedViewerMode).toHaveBeenCalledTimes(2); // The first time called by 'TOGGLE_VIEWER_MODE' action. + + expect(store.get('in-theater-mode')).toBe(newValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('in-theater-mode', newValue); + }); + + test('Action type: "SET_PLAYER_VOLUME"', () => { + const newValue = 0.3; + + handler({ type: 'SET_PLAYER_VOLUME', playerVolume: newValue }); + + expect(onChangedPlayerVolume).toHaveBeenCalledWith(); + expect(onChangedPlayerVolume).toHaveBeenCalledTimes(1); + + expect(store.get('player-volume')).toBe(newValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('player-volume', newValue); + }); + + test('Action type: "SET_PLAYER_SOUND_MUTED"', () => { + const initialValue = store.get('player-sound-muted'); + const newValue = !initialValue; + + handler({ type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted: newValue }); + + expect(onChangedPlayerSoundMuted).toHaveBeenCalledWith(); + expect(onChangedPlayerSoundMuted).toHaveBeenCalledTimes(1); + + expect(store.get('player-sound-muted')).toBe(newValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('player-sound-muted', newValue); + }); + + test('Action type: "SET_VIDEO_QUALITY"', () => { + const newValue = 1080; + + handler({ type: 'SET_VIDEO_QUALITY', quality: newValue }); + + expect(onChangedVideoQuality).toHaveBeenCalledWith(); + expect(onChangedVideoQuality).toHaveBeenCalledTimes(1); + + expect(store.get('video-quality')).toBe(newValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('video-quality', newValue); + }); + + test('Action type: "SET_VIDEO_PLAYBACK_SPEED"', () => { + const newValue = 1.5; + + handler({ type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: newValue }); + + expect(onChangedVideoPlaybackSpeed).toHaveBeenCalledWith(); + expect(onChangedVideoPlaybackSpeed).toHaveBeenCalledTimes(1); + + expect(store.get('video-playback-speed')).toBe(newValue); + expect(browserCacheSetSpy).toHaveBeenCalledWith('video-playback-speed', newValue); + }); + }); + }); +});