mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-06-07 09:24:20 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b2996b8c8 | |||
| df4b0422d5 | |||
| 0434f24691 | |||
| c2043fafa1 | |||
| 9f9dd699b2 | |||
| e2bc9399b9 | |||
| 45d94069b9 |
+1
-1
@@ -1 +1 @@
|
||||
VERSION = "8.1.2"
|
||||
VERSION = "8.1.3"
|
||||
|
||||
Vendored
+3
-2
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
"editor.formatOnSave": true,
|
||||
"prettier.configPath": "../.prettierrc"
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ module.exports = {
|
||||
'^.+\\.tsx?$': 'ts-jest',
|
||||
'^.+\\.jsx?$': 'babel-jest',
|
||||
},
|
||||
collectCoverageFrom: ['src/**'],
|
||||
collectCoverageFrom: ['src/**', '!src/static/lib/**'],
|
||||
};
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"@babel/core": "^7.26.9",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@types/flux": "^3.1.15",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
|
||||
@@ -900,4 +900,4 @@ class MediaPageStore extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
export default exportStore(new MediaPageStore(), 'actions_handler');
|
||||
export default exportStore(new MediaPageStore(), 'actions_handler');
|
||||
@@ -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');
|
||||
|
||||
@@ -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: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
import * as MediaPageActions from '../../../src/static/js/utils/actions/MediaPageActions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by MediaPageActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('MediaPageActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('loadMediaData', () => {
|
||||
it('Should dispatch LOAD_MEDIA_DATA action', () => {
|
||||
MediaPageActions.loadMediaData();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_MEDIA_DATA' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('likeMedia / dislikeMedia', () => {
|
||||
it('Should dispatch LIKE_MEDIA action', () => {
|
||||
MediaPageActions.likeMedia();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'LIKE_MEDIA' });
|
||||
});
|
||||
|
||||
it('Should dispatch DISLIKE_MEDIA action', () => {
|
||||
MediaPageActions.dislikeMedia();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'DISLIKE_MEDIA' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('reportMedia', () => {
|
||||
it('Should dispatch REPORT_MEDIA with empty string when description is undefined', () => {
|
||||
MediaPageActions.reportMedia();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: '' });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch REPORT_MEDIA with stripped description when provided', () => {
|
||||
MediaPageActions.reportMedia(' some text ');
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: 'sometext' });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should remove all whitespace characters including newlines and tabs', () => {
|
||||
MediaPageActions.reportMedia('\n\t spaced\ntext \t');
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: 'spacedtext' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('copyShareLink / copyEmbedMediaCode', () => {
|
||||
it('Should dispatch COPY_SHARE_LINK carrying the provided input element', () => {
|
||||
const inputElem = document.createElement('input');
|
||||
MediaPageActions.copyShareLink(inputElem);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'COPY_SHARE_LINK', inputElement: inputElem });
|
||||
});
|
||||
|
||||
it('Should dispatch COPY_EMBED_MEDIA_CODE carrying the provided textarea element', () => {
|
||||
const textarea = document.createElement('textarea');
|
||||
MediaPageActions.copyEmbedMediaCode(textarea);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'COPY_EMBED_MEDIA_CODE', inputElement: textarea });
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeMedia', () => {
|
||||
it('Should dispatch REMOVE_MEDIA action', () => {
|
||||
MediaPageActions.removeMedia();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_MEDIA' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('comments', () => {
|
||||
it('Should dispatch SUBMIT_COMMENT with provided text', () => {
|
||||
const commentText = 'Nice one';
|
||||
MediaPageActions.submitComment(commentText);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'SUBMIT_COMMENT', commentText });
|
||||
});
|
||||
|
||||
it('Should dispatch DELETE_COMMENT with provided comment id', () => {
|
||||
const commentId = 'c-123';
|
||||
MediaPageActions.deleteComment(commentId);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'DELETE_COMMENT', commentId });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch DELETE_COMMENT with numeric comment id', () => {
|
||||
const commentId = 42;
|
||||
MediaPageActions.deleteComment(commentId);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'DELETE_COMMENT', commentId });
|
||||
});
|
||||
});
|
||||
|
||||
describe('playlists', () => {
|
||||
it('Should dispatch CREATE_PLAYLIST with provided data', () => {
|
||||
const payload = { title: 'My list', description: 'Desc' };
|
||||
MediaPageActions.createPlaylist(payload);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'CREATE_PLAYLIST', playlist_data: payload });
|
||||
});
|
||||
|
||||
it('Should dispatch ADD_MEDIA_TO_PLAYLIST with ids', () => {
|
||||
const playlist_id = 'pl-1';
|
||||
const media_id = 'm-1';
|
||||
MediaPageActions.addMediaToPlaylist(playlist_id, media_id);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_MEDIA_TO_PLAYLIST', playlist_id, media_id });
|
||||
});
|
||||
|
||||
it('Should dispatch REMOVE_MEDIA_FROM_PLAYLIST with ids', () => {
|
||||
const playlist_id = 'pl-1';
|
||||
const media_id = 'm-1';
|
||||
MediaPageActions.removeMediaFromPlaylist(playlist_id, media_id);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id, media_id });
|
||||
});
|
||||
|
||||
it('Should dispatch APPEND_NEW_PLAYLIST with provided playlist data', () => {
|
||||
const playlist_data = {
|
||||
playlist_id: 'pl-2',
|
||||
add_date: new Date('2020-01-01T00:00:00Z'),
|
||||
description: 'Cool',
|
||||
title: 'T',
|
||||
media_list: ['a', 'b'],
|
||||
};
|
||||
MediaPageActions.addNewPlaylist(playlist_data);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'APPEND_NEW_PLAYLIST', playlist_data });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import * as PageActions from '../../../src/static/js/utils/actions/PageActions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by PageActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('PageActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('initPage', () => {
|
||||
it('Should dispatch INIT_PAGE with provided page string', () => {
|
||||
PageActions.initPage('home');
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'INIT_PAGE', page: 'home' });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch INIT_PAGE with empty string', () => {
|
||||
PageActions.initPage('');
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'INIT_PAGE', page: '' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleMediaAutoPlay', () => {
|
||||
it('Should dispatch TOGGLE_AUTO_PLAY action', () => {
|
||||
PageActions.toggleMediaAutoPlay();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_AUTO_PLAY' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('addNotification', () => {
|
||||
it('Should dispatch ADD_NOTIFICATION with message and id', () => {
|
||||
const notification = 'Saved!';
|
||||
const notificationId = 'notif-1';
|
||||
PageActions.addNotification(notification, notificationId);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_NOTIFICATION', notification, notificationId });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch ADD_NOTIFICATION with empty notification message', () => {
|
||||
const notification = '';
|
||||
const notificationId = 'id-empty';
|
||||
PageActions.addNotification(notification, notificationId);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_NOTIFICATION', notification, notificationId });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import { PlaylistPageActions } from '../../../src/static/js/utils/actions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by PlaylistPageActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('PlaylistPageActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('loadPlaylistData', () => {
|
||||
it('Should dispatch LOAD_PLAYLIST_DATA action', () => {
|
||||
PlaylistPageActions.loadPlaylistData();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_PLAYLIST_DATA' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleSave', () => {
|
||||
it('Should dispatch TOGGLE_SAVE action', () => {
|
||||
PlaylistPageActions.toggleSave();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SAVE' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePlaylist', () => {
|
||||
it('Should dispatch UPDATE_PLAYLIST with provided title and description', () => {
|
||||
const payload = { title: 'My Playlist', description: 'A description' };
|
||||
PlaylistPageActions.updatePlaylist(payload);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_PLAYLIST', playlist_data: payload });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch UPDATE_PLAYLIST with empty strings for title and description', () => {
|
||||
const payload = { title: '', description: '' };
|
||||
PlaylistPageActions.updatePlaylist(payload);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_PLAYLIST', playlist_data: payload });
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePlaylist', () => {
|
||||
it('Should dispatch REMOVE_PLAYLIST action', () => {
|
||||
PlaylistPageActions.removePlaylist();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_PLAYLIST' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('removedMediaFromPlaylist', () => {
|
||||
it('Should dispatch MEDIA_REMOVED_FROM_PLAYLIST with media and playlist ids', () => {
|
||||
PlaylistPageActions.removedMediaFromPlaylist('m1', 'p1');
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
|
||||
media_id: 'm1',
|
||||
playlist_id: 'p1',
|
||||
});
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch MEDIA_REMOVED_FROM_PLAYLIST with empty ids as strings', () => {
|
||||
PlaylistPageActions.removedMediaFromPlaylist('', '');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
|
||||
media_id: '',
|
||||
playlist_id: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reorderedMediaInPlaylist', () => {
|
||||
it('Should dispatch PLAYLIST_MEDIA_REORDERED with provided array', () => {
|
||||
const items = [
|
||||
{ id: '1', url: '/1', thumbnail_url: '/t1' },
|
||||
{ id: '2', url: '/2', thumbnail_url: '/t2' },
|
||||
];
|
||||
PlaylistPageActions.reorderedMediaInPlaylist(items);
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: items });
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
it('Should dispatch PLAYLIST_MEDIA_REORDERED with empty array for playlist media', () => {
|
||||
const items: any[] = [];
|
||||
PlaylistPageActions.reorderedMediaInPlaylist(items);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: items });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { PlaylistViewActions } from '../../../src/static/js/utils/actions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by PlaylistViewActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('PlaylistViewActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('toggleLoop', () => {
|
||||
it('Should dispatch TOGGLE_LOOP action', () => {
|
||||
PlaylistViewActions.toggleLoop();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_LOOP' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleShuffle', () => {
|
||||
it('Should dispatch TOGGLE_SHUFFLE action', () => {
|
||||
PlaylistViewActions.toggleShuffle();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SHUFFLE' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleSave', () => {
|
||||
it('Should dispatch TOGGLE_SAVE action', () => {
|
||||
PlaylistViewActions.toggleSave();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SAVE' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ProfilePageActions } from '../../../src/static/js/utils/actions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by ProfilePageActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('ProfilePageActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('Should dispatch LOAD_AUTHOR_DATA ', () => {
|
||||
ProfilePageActions.load_author_data();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_AUTHOR_DATA' });
|
||||
});
|
||||
|
||||
it('Should dispatch REMOVE_PROFILE ', () => {
|
||||
ProfilePageActions.remove_profile();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_PROFILE' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { SearchFieldActions } from '../../../src/static/js/utils/actions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by SearchFieldActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('SearchFieldActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('requestPredictions', () => {
|
||||
it('Should dispatch REQUEST_PREDICTIONS with provided query strings', () => {
|
||||
SearchFieldActions.requestPredictions('cats');
|
||||
SearchFieldActions.requestPredictions('');
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'REQUEST_PREDICTIONS', query: 'cats' });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'REQUEST_PREDICTIONS', query: '' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { VideoViewerActions } from '../../../src/static/js/utils/actions';
|
||||
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||
|
||||
// Mock the dispatcher module used by VideoViewerActions
|
||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||
|
||||
describe('utils/actions', () => {
|
||||
describe('VideoViewerActions', () => {
|
||||
const dispatch = dispatcher.dispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('set_viewer_mode', () => {
|
||||
it('Should dispatch SET_VIEWER_MODE with "true" and "false" for enabling and disabling theater mode', () => {
|
||||
VideoViewerActions.set_viewer_mode(true);
|
||||
VideoViewerActions.set_viewer_mode(false);
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIEWER_MODE', inTheaterMode: true });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIEWER_MODE', inTheaterMode: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('set_player_volume', () => {
|
||||
it('Should dispatch SET_PLAYER_VOLUME with provided volume numbers', () => {
|
||||
VideoViewerActions.set_player_volume(0);
|
||||
VideoViewerActions.set_player_volume(0.75);
|
||||
VideoViewerActions.set_player_volume(1);
|
||||
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_PLAYER_VOLUME', playerVolume: 0 });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_PLAYER_VOLUME', playerVolume: 0.75 });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(3, { type: 'SET_PLAYER_VOLUME', playerVolume: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('set_player_sound_muted', () => {
|
||||
it('Should dispatch SET_PLAYER_SOUND_MUTED with "true" and "false"', () => {
|
||||
VideoViewerActions.set_player_sound_muted(true);
|
||||
VideoViewerActions.set_player_sound_muted(false);
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted: true });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(2, {
|
||||
type: 'SET_PLAYER_SOUND_MUTED',
|
||||
playerSoundMuted: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set_video_quality', () => {
|
||||
it('Should dispatch SET_VIDEO_QUALITY with "auto" and numeric quality', () => {
|
||||
VideoViewerActions.set_video_quality('auto');
|
||||
VideoViewerActions.set_video_quality(720);
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIDEO_QUALITY', quality: 'auto' });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIDEO_QUALITY', quality: 720 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('set_video_playback_speed', () => {
|
||||
it('Should dispatch SET_VIDEO_PLAYBACK_SPEED with different speeds', () => {
|
||||
VideoViewerActions.set_video_playback_speed(1.5);
|
||||
VideoViewerActions.set_video_playback_speed(0.5);
|
||||
VideoViewerActions.set_video_playback_speed(2);
|
||||
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 1.5 });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 0.5 });
|
||||
expect(dispatch).toHaveBeenNthCalledWith(3, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import { BrowserCache } from '../../../src/static/js/utils/classes/BrowserCache';
|
||||
|
||||
// Mocks for helpers used by BrowserCache
|
||||
jest.mock('../../../src/static/js/utils/helpers/', () => ({
|
||||
logErrorAndReturnError: jest.fn((args: any[]) => ({ error: true, args })),
|
||||
logWarningAndReturnError: jest.fn((args: any[]) => ({ warning: true, args })),
|
||||
}));
|
||||
|
||||
const { logErrorAndReturnError } = jest.requireMock('../../../src/static/js/utils/helpers/');
|
||||
|
||||
describe('utils/classes', () => {
|
||||
describe('BrowserCache', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('Returns error when prefix is missing', () => {
|
||||
const cache = BrowserCache(undefined, 3600);
|
||||
expect(cache).toEqual(expect.objectContaining({ error: true }));
|
||||
expect(logErrorAndReturnError).toHaveBeenCalledWith(['Cache object prefix is required']);
|
||||
});
|
||||
|
||||
test('Set and get returns stored primitive value before expiration', () => {
|
||||
const cache = BrowserCache('prefix', 3600);
|
||||
|
||||
if (cache instanceof Error) {
|
||||
expect(cache instanceof Error).toBe(false);
|
||||
return;
|
||||
}
|
||||
|
||||
expect(cache.set('foo', 'bar')).toBe(true);
|
||||
expect(cache.get('foo')).toBe('bar');
|
||||
|
||||
// Ensure value serialized in localStorage with namespaced key
|
||||
const raw = localStorage.getItem('prefix[foo]') as string;
|
||||
const parsed = JSON.parse(raw);
|
||||
expect(parsed.value).toBe('bar');
|
||||
expect(typeof parsed.expire).toBe('number');
|
||||
expect(parsed.expire).toBeGreaterThan(Date.now());
|
||||
});
|
||||
|
||||
test('Get returns null when expired', () => {
|
||||
const cache = BrowserCache('prefix', 1);
|
||||
|
||||
if (cache instanceof Error) {
|
||||
expect(cache instanceof Error).toBe(false);
|
||||
return;
|
||||
}
|
||||
|
||||
cache.set('exp', { a: 1 });
|
||||
|
||||
jest.useFakeTimers();
|
||||
jest.advanceTimersByTime(1_000);
|
||||
|
||||
expect(cache.get('exp')).toBeNull();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('Clear removes only keys for its prefix', () => {
|
||||
const cacheA = BrowserCache('A', 3600);
|
||||
const cacheB = BrowserCache('B', 3600);
|
||||
|
||||
if (cacheA instanceof Error) {
|
||||
expect(cacheA instanceof Error).toBe(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cacheB instanceof Error) {
|
||||
expect(cacheB instanceof Error).toBe(false);
|
||||
return;
|
||||
}
|
||||
|
||||
cacheA.set('x', 1);
|
||||
cacheB.set('x', 2);
|
||||
|
||||
expect(localStorage.getItem('A[x]')).toBeTruthy();
|
||||
expect(localStorage.getItem('B[x]')).toBeTruthy();
|
||||
|
||||
cacheA.clear();
|
||||
|
||||
expect(localStorage.getItem('A[x]')).toBeNull();
|
||||
expect(localStorage.getItem('B[x]')).toBeTruthy();
|
||||
|
||||
cacheB.clear();
|
||||
|
||||
expect(localStorage.getItem('A[x]')).toBeNull();
|
||||
expect(localStorage.getItem('B[x]')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
import { MediaDurationInfo } from '../../../src/static/js/utils/classes/MediaDurationInfo';
|
||||
|
||||
describe('utils/classes', () => {
|
||||
describe('MediaDurationInfo', () => {
|
||||
test('Initializes via constructor when seconds is a positive integer (<= 59)', () => {
|
||||
const mdi = new MediaDurationInfo(42);
|
||||
expect(mdi.toString()).toBe('0:42');
|
||||
expect(mdi.ariaLabel()).toBe('42 seconds');
|
||||
expect(mdi.ISO8601()).toBe('P0Y0M0DT0H0M42S');
|
||||
});
|
||||
|
||||
test('Formats minutes and zero-pads seconds; no hours prefix under 60 minutes', () => {
|
||||
const mdi = new MediaDurationInfo();
|
||||
mdi.update(5 * 60 + 7);
|
||||
expect(mdi.toString()).toBe('5:07');
|
||||
expect(mdi.ariaLabel()).toBe('5 minutes, 7 seconds');
|
||||
expect(mdi.ISO8601()).toBe('P0Y0M0DT0H5M7S');
|
||||
});
|
||||
|
||||
test('Includes hours when duration >= 1 hour and zero-pads minutes when needed', () => {
|
||||
const mdi = new MediaDurationInfo();
|
||||
mdi.update(1 * 3600 + 2 * 60 + 3);
|
||||
expect(mdi.toString()).toBe('1:02:03');
|
||||
expect(mdi.ariaLabel()).toBe('1 hours, 2 minutes, 3 seconds');
|
||||
expect(mdi.ISO8601()).toBe('P0Y0M0DT1H2M3S');
|
||||
});
|
||||
|
||||
test('Accumulates hours when days are present (e.g., 1 day + 2:03:04 => 26:03:04)', () => {
|
||||
const mdi = new MediaDurationInfo();
|
||||
const seconds = 1 * 86400 + 2 * 3600 + 3 * 60 + 4; // 1d 2:03:04 => 26:03:04
|
||||
mdi.update(seconds);
|
||||
expect(mdi.toString()).toBe('26:03:04');
|
||||
expect(mdi.ariaLabel()).toBe('26 hours, 3 minutes, 4 seconds');
|
||||
expect(mdi.ISO8601()).toBe('P0Y0M0DT26H3M4S');
|
||||
});
|
||||
|
||||
test('Large durations: multiple days correctly mapped into hours', () => {
|
||||
const mdi = new MediaDurationInfo();
|
||||
const seconds = 3 * 86400 + 10 * 3600 + 15 * 60 + 9; // 3d 10:15:09 => 82:15:09
|
||||
mdi.update(seconds);
|
||||
expect(mdi.toString()).toBe('82:15:09');
|
||||
expect(mdi.ariaLabel()).toBe('82 hours, 15 minutes, 9 seconds');
|
||||
expect(mdi.ISO8601()).toBe('P0Y0M0DT82H15M9S');
|
||||
});
|
||||
|
||||
test('Caching: toString and ariaLabel recompute only after update()', () => {
|
||||
const mdi = new MediaDurationInfo(59);
|
||||
const firstToString = mdi.toString();
|
||||
const firstAria = mdi.ariaLabel();
|
||||
expect(firstToString).toBe('0:59');
|
||||
expect(firstAria).toBe('59 seconds');
|
||||
|
||||
// Call again to hit cached path
|
||||
expect(mdi.toString()).toBe(firstToString);
|
||||
expect(mdi.ariaLabel()).toBe(firstAria);
|
||||
|
||||
// Update and ensure cache invalidates
|
||||
mdi.update(60);
|
||||
expect(mdi.toString()).toBe('1:00');
|
||||
expect(mdi.ariaLabel()).toBe('1 minutes');
|
||||
});
|
||||
|
||||
test('Ignores invalid (non-positive integer or zero) updates, retaining previous value', () => {
|
||||
const mdi = new MediaDurationInfo(10);
|
||||
expect(mdi.toString()).toBe('0:10');
|
||||
|
||||
mdi.update(1.23);
|
||||
expect(mdi.toString()).toBe('0:10');
|
||||
|
||||
mdi.update(-5);
|
||||
expect(mdi.toString()).toBe('0:10');
|
||||
|
||||
mdi.update('x');
|
||||
expect(mdi.toString()).toBe('0:10');
|
||||
});
|
||||
|
||||
test('Boundary conditions around a minute and an hour', () => {
|
||||
const mdi = new MediaDurationInfo();
|
||||
|
||||
mdi.update(59);
|
||||
expect(mdi.toString()).toBe('0:59');
|
||||
|
||||
mdi.update(60);
|
||||
expect(mdi.toString()).toBe('1:00');
|
||||
|
||||
mdi.update(3599);
|
||||
expect(mdi.toString()).toBe('59:59');
|
||||
|
||||
mdi.update(3600);
|
||||
expect(mdi.toString()).toBe('1:00:00');
|
||||
});
|
||||
|
||||
// @todo: Revisit this behavior
|
||||
test('Constructs without initial seconds', () => {
|
||||
const mdi = new MediaDurationInfo();
|
||||
expect(typeof mdi.toString()).toBe('function');
|
||||
expect(mdi.ariaLabel()).toBe('');
|
||||
expect(mdi.ISO8601()).toBe('P0Y0M0DTundefinedHundefinedMundefinedS');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
import { UpNextLoaderView } from '../../../src/static/js/utils/classes/UpNextLoaderView';
|
||||
|
||||
// Minimal helpers mocks used by UpNextLoaderView
|
||||
jest.mock('../../../src/static/js/utils/helpers/', () => ({
|
||||
addClassname: jest.fn((el: any, cn: string) => el && el.classList && el.classList.add(cn)),
|
||||
removeClassname: jest.fn((el: any, cn: string) => el && el.classList && el.classList.remove(cn)),
|
||||
translateString: (s: string) => s,
|
||||
}));
|
||||
|
||||
const { addClassname, removeClassname } = jest.requireMock('../../../src/static/js/utils/helpers/');
|
||||
|
||||
const makeNextItem = () => ({
|
||||
url: '/next-url',
|
||||
title: 'Next title',
|
||||
author_name: 'Jane Doe',
|
||||
thumbnail_url: 'https://example.com/thumb.jpg',
|
||||
});
|
||||
|
||||
describe('utils/classes', () => {
|
||||
describe('UpNextLoaderView', () => {
|
||||
test('html() builds structure with expected classes and content', () => {
|
||||
const v = new UpNextLoaderView(makeNextItem());
|
||||
|
||||
const root = v.html();
|
||||
|
||||
expect(root).toBeInstanceOf(HTMLElement);
|
||||
expect(root.querySelector('.up-next-loader-inner')).not.toBeNull();
|
||||
expect(root.querySelector('.up-next-label')!.textContent).toBe('Up Next');
|
||||
expect(root.querySelector('.next-media-title')!.textContent).toBe('Next title');
|
||||
expect(root.querySelector('.next-media-author')!.textContent).toBe('Jane Doe');
|
||||
|
||||
// poster background
|
||||
const poster = root.querySelector('.next-media-poster') as HTMLElement;
|
||||
expect(poster.style.backgroundImage).toContain('thumb.jpg');
|
||||
|
||||
// go-next link points to next url
|
||||
const link = root.querySelector('.go-next a') as HTMLAnchorElement;
|
||||
expect(link.getAttribute('href')).toBe('/next-url');
|
||||
});
|
||||
|
||||
test('setVideoJsPlayerElem marks player with vjs-mediacms-has-up-next-view class', () => {
|
||||
const v = new UpNextLoaderView(makeNextItem());
|
||||
const player = document.createElement('div');
|
||||
|
||||
v.setVideoJsPlayerElem(player);
|
||||
|
||||
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-has-up-next-view');
|
||||
expect(v.vjsPlayerElem).toBe(player);
|
||||
});
|
||||
|
||||
test('startTimer shows view, registers scroll, and navigates after 10s', () => {
|
||||
const next = makeNextItem();
|
||||
const v = new UpNextLoaderView(next);
|
||||
const player = document.createElement('div');
|
||||
|
||||
v.setVideoJsPlayerElem(player);
|
||||
v.startTimer();
|
||||
|
||||
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-up-next-hidden');
|
||||
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||
});
|
||||
|
||||
test('cancelTimer clears timeout, stops scroll, and marks canceled', () => {
|
||||
const v = new UpNextLoaderView(makeNextItem());
|
||||
const player = document.createElement('div');
|
||||
|
||||
v.setVideoJsPlayerElem(player);
|
||||
|
||||
v.startTimer();
|
||||
v.cancelTimer();
|
||||
|
||||
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||
});
|
||||
|
||||
test('Cancel button click hides the view and cancels timer', () => {
|
||||
const v = new UpNextLoaderView(makeNextItem());
|
||||
const player = document.createElement('div');
|
||||
v.setVideoJsPlayerElem(player);
|
||||
|
||||
v.startTimer();
|
||||
const root = v.html();
|
||||
const cancelBtn = root.querySelector('.up-next-cancel button') as HTMLButtonElement;
|
||||
cancelBtn.click();
|
||||
|
||||
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||
});
|
||||
|
||||
test('showTimerView shows or starts timer based on flag', () => {
|
||||
const v = new UpNextLoaderView(makeNextItem());
|
||||
const player = document.createElement('div');
|
||||
v.setVideoJsPlayerElem(player);
|
||||
|
||||
// beginTimer=false -> just show view
|
||||
v.showTimerView(false);
|
||||
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-up-next-hidden');
|
||||
|
||||
// beginTimer=true -> starts timer
|
||||
v.showTimerView(true);
|
||||
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,749 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, act } from '@testing-library/react';
|
||||
import { useBulkActions } from '../../../src/static/js/utils/hooks/useBulkActions';
|
||||
|
||||
// Mock translateString to return the input for easier assertions
|
||||
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||
translateString: (s: string) => s,
|
||||
}));
|
||||
|
||||
// Component that exposes hook state/handlers to DOM for testing
|
||||
function HookConsumer() {
|
||||
const hook = useBulkActions();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="selected-count">{Array.from(hook.selectedMedia).length}</div>
|
||||
<div data-testid="available-count">{hook.availableMediaIds.length}</div>
|
||||
<div data-testid="show-confirm">{String(hook.showConfirmModal)}</div>
|
||||
<div data-testid="confirm-message">{hook.confirmMessage}</div>
|
||||
<div data-testid="list-key">{hook.listKey}</div>
|
||||
<div data-testid="notification-message">{hook.notificationMessage}</div>
|
||||
<div data-testid="show-notification">{String(hook.showNotification)}</div>
|
||||
|
||||
{/* @todo: It doesn't used */}
|
||||
{/* <div data-testid="notification-type">{hook.notificationType}</div> */}
|
||||
|
||||
<div data-testid="show-permission">{String(hook.showPermissionModal)}</div>
|
||||
<div data-testid="permission-type">{hook.permissionType || ''}</div>
|
||||
<div data-testid="show-playlist">{String(hook.showPlaylistModal)}</div>
|
||||
<div data-testid="show-change-owner">{String(hook.showChangeOwnerModal)}</div>
|
||||
<div data-testid="show-publish-state">{String(hook.showPublishStateModal)}</div>
|
||||
<div data-testid="show-category">{String(hook.showCategoryModal)}</div>
|
||||
<div data-testid="show-tag">{String(hook.showTagModal)}</div>
|
||||
|
||||
<button data-testid="btn-handle-media-select" onClick={() => hook.handleMediaSelection('m1', true)} />
|
||||
<button data-testid="btn-handle-media-deselect" onClick={() => hook.handleMediaSelection('m1', false)} />
|
||||
<button
|
||||
data-testid="btn-handle-items-update"
|
||||
onClick={() => hook.handleItemsUpdate([{ id: 'a' }, { uid: 'b' }, { friendly_token: 'c' }])}
|
||||
/>
|
||||
<button data-testid="btn-select-all" onClick={() => hook.handleSelectAll()} />
|
||||
<button data-testid="btn-deselect-all" onClick={() => hook.handleDeselectAll()} />
|
||||
<button data-testid="btn-clear-selection" onClick={() => hook.clearSelection()} />
|
||||
<button data-testid="btn-clear-refresh" onClick={() => hook.clearSelectionAndRefresh()} />
|
||||
|
||||
<button data-testid="btn-bulk-delete" onClick={() => hook.handleBulkAction('delete-media')} />
|
||||
<button data-testid="btn-bulk-enable-comments" onClick={() => hook.handleBulkAction('enable-comments')} />
|
||||
<button data-testid="btn-bulk-disable-comments" onClick={() => hook.handleBulkAction('disable-comments')} />
|
||||
<button data-testid="btn-bulk-enable-download" onClick={() => hook.handleBulkAction('enable-download')} />
|
||||
<button data-testid="btn-bulk-disable-download" onClick={() => hook.handleBulkAction('disable-download')} />
|
||||
<button data-testid="btn-bulk-copy" onClick={() => hook.handleBulkAction('copy-media')} />
|
||||
<button data-testid="btn-bulk-perm-viewer" onClick={() => hook.handleBulkAction('add-remove-coviewers')} />
|
||||
<button data-testid="btn-bulk-perm-editor" onClick={() => hook.handleBulkAction('add-remove-coeditors')} />
|
||||
<button data-testid="btn-bulk-perm-owner" onClick={() => hook.handleBulkAction('add-remove-coowners')} />
|
||||
<button data-testid="btn-bulk-playlist" onClick={() => hook.handleBulkAction('add-remove-playlist')} />
|
||||
<button data-testid="btn-bulk-change-owner" onClick={() => hook.handleBulkAction('change-owner')} />
|
||||
<button data-testid="btn-bulk-publish" onClick={() => hook.handleBulkAction('publish-state')} />
|
||||
<button data-testid="btn-bulk-category" onClick={() => hook.handleBulkAction('add-remove-category')} />
|
||||
<button data-testid="btn-bulk-tag" onClick={() => hook.handleBulkAction('add-remove-tags')} />
|
||||
<button data-testid="btn-bulk-unknown" onClick={() => hook.handleBulkAction('unknown-action')} />
|
||||
|
||||
<button data-testid="btn-confirm-proceed" onClick={() => hook.handleConfirmProceed()} />
|
||||
<button data-testid="btn-confirm-cancel" onClick={() => hook.handleConfirmCancel()} />
|
||||
<button data-testid="btn-perm-cancel" onClick={() => hook.handlePermissionModalCancel()} />
|
||||
|
||||
<button data-testid="btn-perm-success" onClick={() => hook.handlePermissionModalSuccess('perm ok')} />
|
||||
<button data-testid="btn-perm-error" onClick={() => hook.handlePermissionModalError('perm err')} />
|
||||
<button data-testid="btn-playlist-cancel" onClick={() => hook.handlePlaylistModalCancel()} />
|
||||
|
||||
<button data-testid="btn-playlist-success" onClick={() => hook.handlePlaylistModalSuccess('pl ok')} />
|
||||
<button data-testid="btn-playlist-error" onClick={() => hook.handlePlaylistModalError('pl err')} />
|
||||
<button data-testid="btn-change-owner-cancel" onClick={() => hook.handleChangeOwnerModalCancel()} />
|
||||
|
||||
<button
|
||||
data-testid="btn-change-owner-success"
|
||||
onClick={() => hook.handleChangeOwnerModalSuccess('owner ok')}
|
||||
/>
|
||||
<button
|
||||
data-testid="btn-change-owner-error"
|
||||
onClick={() => hook.handleChangeOwnerModalError('owner err')}
|
||||
/>
|
||||
<button data-testid="btn-publish-cancel" onClick={() => hook.handlePublishStateModalCancel()} />
|
||||
|
||||
<button data-testid="btn-publish-success" onClick={() => hook.handlePublishStateModalSuccess('pub ok')} />
|
||||
<button data-testid="btn-publish-error" onClick={() => hook.handlePublishStateModalError('pub err')} />
|
||||
<button data-testid="btn-category-cancel" onClick={() => hook.handleCategoryModalCancel()} />
|
||||
|
||||
<button data-testid="btn-category-success" onClick={() => hook.handleCategoryModalSuccess('cat ok')} />
|
||||
<button data-testid="btn-category-error" onClick={() => hook.handleCategoryModalError('cat err')} />
|
||||
<button data-testid="btn-tag-cancel" onClick={() => hook.handleTagModalCancel()} />
|
||||
|
||||
<button data-testid="btn-tag-success" onClick={() => hook.handleTagModalSuccess('tag ok')} />
|
||||
<button data-testid="btn-tag-error" onClick={() => hook.handleTagModalError('tag err')} />
|
||||
|
||||
<div data-testid="csrf">{String(hook.getCsrfToken())}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('useBulkActions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
document.cookie.split(';').forEach((c) => {
|
||||
document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
|
||||
});
|
||||
|
||||
global.fetch = jest.fn();
|
||||
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('Utility Functions', () => {
|
||||
test('getCsrfToken reads csrftoken from cookies', () => {
|
||||
document.cookie = 'csrftoken=abc123';
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
expect(getByTestId('csrf').textContent).toBe('abc123');
|
||||
});
|
||||
|
||||
test('getCsrfToken returns null when csrftoken is not present', () => {
|
||||
// No cookie set, should return null
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
expect(getByTestId('csrf').textContent).toBe('null');
|
||||
});
|
||||
|
||||
test('getCsrfToken returns null when document.cookie is empty', () => {
|
||||
// Even if we try to set empty cookie, it should return null if no csrftoken
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
expect(getByTestId('csrf').textContent).toBe('null');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Selection Management', () => {
|
||||
test('handleMediaSelection toggles selected media', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('1');
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-deselect'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('handleItemsUpdate extracts ids correctly from items with different id types', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-items-update'));
|
||||
expect(getByTestId('available-count').textContent).toBe('3');
|
||||
});
|
||||
|
||||
test('handleSelectAll selects all available items', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-items-update'));
|
||||
fireEvent.click(getByTestId('btn-select-all'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('3');
|
||||
});
|
||||
|
||||
test('handleDeselectAll deselects all items', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-items-update'));
|
||||
fireEvent.click(getByTestId('btn-select-all'));
|
||||
fireEvent.click(getByTestId('btn-deselect-all'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('clearSelection clears all selected media', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('1');
|
||||
|
||||
fireEvent.click(getByTestId('btn-clear-selection'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('clearSelectionAndRefresh clears selection and increments listKey', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-items-update'));
|
||||
fireEvent.click(getByTestId('btn-select-all'));
|
||||
expect(getByTestId('list-key').textContent).toBe('0');
|
||||
|
||||
fireEvent.click(getByTestId('btn-clear-refresh'));
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
expect(getByTestId('list-key').textContent).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bulk Actions - Modal Opening', () => {
|
||||
test('handleBulkAction does nothing when no selection', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
fireEvent.click(getByTestId('btn-bulk-delete'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleBulkAction opens confirm modal for delete, enable/disable comments and download, copy', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-delete'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-comments'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-comments'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-download'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-download'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-copy'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
});
|
||||
|
||||
test('handleBulkAction opens permission modals with correct types', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-perm-viewer'));
|
||||
expect(getByTestId('show-permission').textContent).toBe('true');
|
||||
expect(getByTestId('permission-type').textContent).toBe('viewer');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-perm-editor'));
|
||||
expect(getByTestId('permission-type').textContent).toBe('editor');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-perm-owner'));
|
||||
expect(getByTestId('permission-type').textContent).toBe('owner');
|
||||
});
|
||||
|
||||
test('handleBulkAction opens other modals', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-playlist'));
|
||||
expect(getByTestId('show-playlist').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-change-owner'));
|
||||
expect(getByTestId('show-change-owner').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-publish'));
|
||||
expect(getByTestId('show-publish-state').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-category'));
|
||||
expect(getByTestId('show-category').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-bulk-tag'));
|
||||
expect(getByTestId('show-tag').textContent).toBe('true');
|
||||
});
|
||||
|
||||
test('handleBulkAction with unknown action does nothing', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-unknown'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('false');
|
||||
expect(getByTestId('show-permission').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Confirm Modal Handlers', () => {
|
||||
test('handleConfirmCancel closes confirm modal and resets state', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-delete'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-confirm-cancel'));
|
||||
expect(getByTestId('show-confirm').textContent).toBe('false');
|
||||
expect(getByTestId('confirm-message').textContent).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete Media Execution', () => {
|
||||
test('executeDeleteMedia success with notification', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-delete'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('The media was deleted successfully');
|
||||
expect(getByTestId('show-notification').textContent).toBe('true');
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(5000);
|
||||
});
|
||||
expect(getByTestId('show-notification').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('executeDeleteMedia handles response.ok = false', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-delete'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to delete media');
|
||||
});
|
||||
|
||||
test('executeDeleteMedia handles fetch rejection exception', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-delete'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to delete media');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Comments Management Execution', () => {
|
||||
test('executeEnableComments success', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-comments'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Successfully Enabled comments');
|
||||
});
|
||||
|
||||
test('executeEnableComments handles response.ok = false', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-comments'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to enable comments');
|
||||
});
|
||||
|
||||
test('executeEnableComments handles fetch rejection exception', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-comments'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to enable comments');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('executeDisableComments success', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-comments'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Successfully Disabled comments');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('executeDisableComments handles response.ok = false', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-comments'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to disable comments');
|
||||
});
|
||||
|
||||
test('executeDisableComments handles fetch rejection exception', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-comments'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to disable comments');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Download Management Execution', () => {
|
||||
test('executeEnableDownload success', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-download'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Successfully Enabled Download');
|
||||
});
|
||||
|
||||
test('executeEnableDownload handles response.ok = false', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-download'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to enable download');
|
||||
});
|
||||
|
||||
test('executeEnableDownload handles fetch rejection exception', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-enable-download'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to enable download');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('executeDisableDownload success', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-download'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Successfully Disabled Download');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
test('executeDisableDownload handles response.ok = false', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-download'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to disable download');
|
||||
});
|
||||
|
||||
test('executeDisableDownload handles fetch rejection exception', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-disable-download'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to disable download');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Copy Media Execution', () => {
|
||||
test('executeCopyMedia success', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-copy'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Successfully Copied');
|
||||
});
|
||||
|
||||
test('executeCopyMedia handles response.ok = false', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) });
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-copy'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to copy media');
|
||||
});
|
||||
|
||||
test('executeCopyMedia handles fetch rejection exception', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-copy'));
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByTestId('btn-confirm-proceed'));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(getByTestId('notification-message').textContent).toContain('Failed to copy media');
|
||||
expect(getByTestId('selected-count').textContent).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission Modal Handlers', () => {
|
||||
test('handlePermissionModalCancel closes permission modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-perm-viewer'));
|
||||
expect(getByTestId('show-permission').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-perm-cancel'));
|
||||
expect(getByTestId('show-permission').textContent).toBe('false');
|
||||
expect(getByTestId('permission-type').textContent).toBe('');
|
||||
});
|
||||
|
||||
test('handlePermissionModalSuccess shows notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-perm-success'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('perm ok');
|
||||
expect(getByTestId('show-permission').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handlePermissionModalError shows error notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-perm-error'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('perm err');
|
||||
expect(getByTestId('show-permission').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Playlist Modal Handlers', () => {
|
||||
test('handlePlaylistModalCancel closes playlist modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-playlist'));
|
||||
expect(getByTestId('show-playlist').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-playlist-cancel'));
|
||||
expect(getByTestId('show-playlist').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handlePlaylistModalSuccess shows notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-playlist-success'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('pl ok');
|
||||
expect(getByTestId('show-playlist').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handlePlaylistModalError shows error notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-playlist-error'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('pl err');
|
||||
expect(getByTestId('show-playlist').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Change Owner Modal Handlers', () => {
|
||||
test('handleChangeOwnerModalCancel closes change owner modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-change-owner'));
|
||||
expect(getByTestId('show-change-owner').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-change-owner-cancel'));
|
||||
expect(getByTestId('show-change-owner').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleChangeOwnerModalSuccess shows notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-change-owner-success'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('owner ok');
|
||||
expect(getByTestId('show-change-owner').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleChangeOwnerModalError shows error notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-change-owner-error'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('owner err');
|
||||
expect(getByTestId('show-change-owner').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Publish State Modal Handlers', () => {
|
||||
test('handlePublishStateModalCancel closes publish state modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-publish'));
|
||||
expect(getByTestId('show-publish-state').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-publish-cancel'));
|
||||
expect(getByTestId('show-publish-state').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handlePublishStateModalSuccess shows notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-publish-success'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('pub ok');
|
||||
expect(getByTestId('show-publish-state').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handlePublishStateModalError shows error notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-publish-error'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('pub err');
|
||||
expect(getByTestId('show-publish-state').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Category Modal Handlers', () => {
|
||||
test('handleCategoryModalCancel closes category modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-category'));
|
||||
expect(getByTestId('show-category').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-category-cancel'));
|
||||
expect(getByTestId('show-category').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleCategoryModalSuccess shows notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-category-success'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('cat ok');
|
||||
expect(getByTestId('show-category').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleCategoryModalError shows error notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-category-error'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('cat err');
|
||||
expect(getByTestId('show-category').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tag Modal Handlers', () => {
|
||||
test('handleTagModalCancel closes tag modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-handle-media-select'));
|
||||
fireEvent.click(getByTestId('btn-bulk-tag'));
|
||||
expect(getByTestId('show-tag').textContent).toBe('true');
|
||||
|
||||
fireEvent.click(getByTestId('btn-tag-cancel'));
|
||||
expect(getByTestId('show-tag').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleTagModalSuccess shows notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-tag-success'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('tag ok');
|
||||
expect(getByTestId('show-tag').textContent).toBe('false');
|
||||
});
|
||||
|
||||
test('handleTagModalError shows error notification and closes modal', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
|
||||
fireEvent.click(getByTestId('btn-tag-error'));
|
||||
expect(getByTestId('notification-message').textContent).toBe('tag err');
|
||||
expect(getByTestId('show-tag').textContent).toBe('false');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,380 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useItem } from '../../../src/static/js/utils/hooks/useItem';
|
||||
|
||||
// Mock the item components
|
||||
jest.mock('../../../src/static/js/components/list-item/includes/items', () => ({
|
||||
ItemDescription: ({ description }: { description: string }) => (
|
||||
<div data-testid="item-description">{description}</div>
|
||||
),
|
||||
ItemMain: ({ children }: { children: React.ReactNode }) => <div data-testid="item-main">{children}</div>,
|
||||
ItemMainInLink: ({ children, link, title }: { children: React.ReactNode; link: string; title: string }) => (
|
||||
<div data-testid="item-main-in-link" data-link={link} data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
ItemTitle: ({ title, ariaLabel }: { title: string; ariaLabel: string }) => (
|
||||
<h3 data-testid="item-title" data-aria-label={ariaLabel}>
|
||||
{title}
|
||||
</h3>
|
||||
),
|
||||
ItemTitleLink: ({ title, link, ariaLabel }: { title: string; link: string; ariaLabel: string }) => (
|
||||
<h3 data-testid="item-title-link" data-link={link} data-aria-label={ariaLabel}>
|
||||
{title}
|
||||
</h3>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock PageStore
|
||||
jest.mock('../../../src/static/js/utils/stores/PageStore.js', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
get: (key: string) => (key === 'config-site' ? { url: 'https://example.com' } : null),
|
||||
},
|
||||
}));
|
||||
|
||||
// HookConsumer component to test the hook
|
||||
function HookConsumer(props: any) {
|
||||
const { titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper } = useItem(props);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="title">{titleComponent()}</div>
|
||||
<div data-testid="description">{descriptionComponent()}</div>
|
||||
<div data-testid="thumbnail-url">{thumbnailUrl || 'null'}</div>
|
||||
<div data-testid="wrapper-type">{(UnderThumbWrapper as any).name}</div>
|
||||
<div data-testid="wrapper-component">
|
||||
<div>Wrapper content</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Wrapper consumer to test wrapper selection
|
||||
function WrapperTest(props: any) {
|
||||
const { UnderThumbWrapper } = useItem(props);
|
||||
|
||||
return (
|
||||
<UnderThumbWrapper link={props.link} title={props.title} data-testid="wrapper-test">
|
||||
<span data-testid="wrapper-content">Content</span>
|
||||
</UnderThumbWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useItem', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('titleComponent Rendering', () => {
|
||||
test('Renders ItemTitle when singleLinkContent is true', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
singleLinkContent={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByTestId('title').querySelector('[data-testid="item-title"]')).toBeTruthy();
|
||||
expect(getByTestId('title').querySelector('[data-testid="item-title-link"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Renders ItemTitleLink when singleLinkContent is false', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
singleLinkContent={false}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByTestId('title').querySelector('[data-testid="item-title"]')).toBeFalsy();
|
||||
expect(getByTestId('title').querySelector('[data-testid="item-title-link"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Renders with default link when singleLinkContent is not provided', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer title="Test Title" description="Test Description" link="/media/test" thumbnail="" />
|
||||
);
|
||||
|
||||
// Default is false for singleLinkContent
|
||||
expect(getByTestId('title').querySelector('[data-testid="item-title-link"]')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('descriptionComponent Rendering', () => {
|
||||
test('Renders single ItemDescription when hasMediaViewer is false', () => {
|
||||
const { getByTestId, queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="My Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
hasMediaViewer={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const descriptions = queryAllByTestId('item-description');
|
||||
expect(descriptions.length).toBe(1);
|
||||
expect(descriptions[0].textContent).toBe('My Description');
|
||||
});
|
||||
|
||||
test('Renders single ItemDescription when hasMediaViewerDescr is false', () => {
|
||||
const { getByTestId, queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="My Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
hasMediaViewer={true}
|
||||
hasMediaViewerDescr={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const descriptions = queryAllByTestId('item-description');
|
||||
expect(descriptions.length).toBe(1);
|
||||
expect(descriptions[0].textContent).toBe('My Description');
|
||||
});
|
||||
|
||||
test('Renders two ItemDescriptions when hasMediaViewer and hasMediaViewerDescr are both true', () => {
|
||||
const { queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Main Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
hasMediaViewer={true}
|
||||
hasMediaViewerDescr={true}
|
||||
meta_description="Meta Description"
|
||||
/>
|
||||
);
|
||||
|
||||
const descriptions = queryAllByTestId('item-description');
|
||||
expect(descriptions.length).toBe(2);
|
||||
expect(descriptions[0].textContent).toBe('Meta Description');
|
||||
expect(descriptions[1].textContent).toBe('Main Description');
|
||||
});
|
||||
|
||||
test('Trims description text', () => {
|
||||
const { queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description=" Description with spaces "
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
/>
|
||||
);
|
||||
|
||||
expect(queryAllByTestId('item-description')[0].textContent).toBe('Description with spaces');
|
||||
});
|
||||
|
||||
test('Trims meta_description text', () => {
|
||||
const { queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Main Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
hasMediaViewer={true}
|
||||
hasMediaViewerDescr={true}
|
||||
meta_description=" Meta with spaces "
|
||||
/>
|
||||
);
|
||||
|
||||
expect(queryAllByTestId('item-description')[0].textContent).toBe('Meta with spaces');
|
||||
});
|
||||
});
|
||||
|
||||
describe('thumbnailUrl', () => {
|
||||
test('Returns null when thumbnail is empty string', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByTestId('thumbnail-url').textContent).toBe('null');
|
||||
});
|
||||
|
||||
test('Returns formatted URL when thumbnail has value', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail="/media/thumbnail.jpg"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByTestId('thumbnail-url').textContent).toBe('https://example.com/media/thumbnail.jpg');
|
||||
});
|
||||
|
||||
test('Handles absolute URLs as thumbnails', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail="https://cdn.example.com/image.jpg"
|
||||
/>
|
||||
);
|
||||
|
||||
// formatInnerLink should preserve absolute URLs
|
||||
expect(getByTestId('thumbnail-url').textContent).toBe('https://cdn.example.com/image.jpg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UnderThumbWrapper', () => {
|
||||
test('Uses ItemMainInLink when singleLinkContent is true', () => {
|
||||
const { getByTestId } = render(
|
||||
<WrapperTest
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
singleLinkContent={true}
|
||||
/>
|
||||
);
|
||||
|
||||
// When singleLinkContent is true, UnderThumbWrapper should be ItemMainInLink
|
||||
expect(getByTestId('item-main-in-link')).toBeTruthy();
|
||||
expect(getByTestId('item-main-in-link').getAttribute('data-link')).toBe('https://example.com');
|
||||
expect(getByTestId('item-main-in-link').getAttribute('data-title')).toBe('Test Title');
|
||||
});
|
||||
|
||||
test('Uses ItemMain when singleLinkContent is false', () => {
|
||||
const { getByTestId } = render(
|
||||
<WrapperTest
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
singleLinkContent={false}
|
||||
/>
|
||||
);
|
||||
|
||||
// When singleLinkContent is false, UnderThumbWrapper should be ItemMain
|
||||
expect(getByTestId('item-main')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Uses ItemMain by default when singleLinkContent is not provided', () => {
|
||||
const { getByTestId } = render(
|
||||
<WrapperTest
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
/>
|
||||
);
|
||||
|
||||
// Default is singleLinkContent=false, so ItemMain
|
||||
expect(getByTestId('item-main')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onMount callback', () => {
|
||||
test('Calls onMount callback when component mounts', () => {
|
||||
const onMountCallback = jest.fn();
|
||||
|
||||
render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
onMount={onMountCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onMountCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Calls onMount only once on initial mount', () => {
|
||||
const onMountCallback = jest.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<HookConsumer
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
onMount={onMountCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onMountCallback).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(
|
||||
<HookConsumer
|
||||
title="Updated Title"
|
||||
description="Updated Description"
|
||||
link="https://example.com"
|
||||
thumbnail=""
|
||||
onMount={onMountCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
// Should still be called only once (useEffect with empty dependency array)
|
||||
expect(onMountCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration tests', () => {
|
||||
test('Complete rendering with all props', () => {
|
||||
const onMount = jest.fn();
|
||||
const { getByTestId, queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Complete Test"
|
||||
description="Complete Description"
|
||||
link="/media/complete"
|
||||
thumbnail="/img/thumb.jpg"
|
||||
type="media"
|
||||
hasMediaViewer={true}
|
||||
hasMediaViewerDescr={true}
|
||||
meta_description="Complete Meta"
|
||||
singleLinkContent={false}
|
||||
onMount={onMount}
|
||||
/>
|
||||
);
|
||||
|
||||
const descriptions = queryAllByTestId('item-description');
|
||||
expect(descriptions.length).toBe(2);
|
||||
expect(onMount).toHaveBeenCalledTimes(1);
|
||||
expect(getByTestId('thumbnail-url').textContent).toBe('https://example.com/img/thumb.jpg');
|
||||
});
|
||||
|
||||
test('Minimal props required', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer title="Title" description="Description" link="/link" thumbnail="" />
|
||||
);
|
||||
|
||||
expect(getByTestId('title')).toBeTruthy();
|
||||
expect(getByTestId('description')).toBeTruthy();
|
||||
expect(getByTestId('thumbnail-url').textContent).toBe('null');
|
||||
});
|
||||
|
||||
test('Renders with special characters in title and description', () => {
|
||||
const { queryAllByTestId } = render(
|
||||
<HookConsumer
|
||||
title="Title with & < > special chars"
|
||||
description={`Description with 'quotes' and "double quotes"`}
|
||||
link="/media"
|
||||
thumbnail=""
|
||||
/>
|
||||
);
|
||||
|
||||
const descriptions = queryAllByTestId('item-description');
|
||||
expect(descriptions[0].textContent).toContain('Description with');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import React, { createRef } from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
// Stub style imports used by the hook so Jest doesn't try to parse SCSS
|
||||
jest.mock('../../../src/static/js/components/item-list/ItemList.scss', () => ({}), { virtual: true });
|
||||
|
||||
jest.mock('../../../src/static/js/components/item-list/includes/itemLists/initItemsList', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn((_lists: any[]) => [{ appendItems: jest.fn() }]),
|
||||
}));
|
||||
|
||||
import initItemsList from '../../../src/static/js/components/item-list/includes/itemLists/initItemsList';
|
||||
import { useItemList } from '../../../src/static/js/utils/hooks/useItemList';
|
||||
|
||||
function HookConsumer(props: any) {
|
||||
const listRef = createRef<HTMLDivElement>();
|
||||
const [items, countedItems, listHandler, setListHandler, onItemsLoad, onItemsCount, addListItems] = useItemList(
|
||||
props,
|
||||
listRef
|
||||
) as any[];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={listRef} data-testid="list" className="list">
|
||||
{(items as any[]).map((_, idx) => (
|
||||
<div key={idx} className="item" data-testid={`itm-${idx}`} />
|
||||
))}
|
||||
</div>
|
||||
<div data-testid="counted">{String(countedItems)}</div>
|
||||
<div data-testid="len">{items.length}</div>
|
||||
<button data-testid="load-call" onClick={() => onItemsLoad([1, 2])} />
|
||||
<button data-testid="count-call" onClick={() => onItemsCount(5)} />
|
||||
<button data-testid="add-call" onClick={() => addListItems()} />
|
||||
<button data-testid="set-handler" onClick={() => setListHandler({ foo: 'bar' })} />
|
||||
<div data-testid="has-handler">{listHandler ? 'yes' : 'no'}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useItemList', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('Initial state: empty items and not counted', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
expect(getByTestId('counted').textContent).toBe('false');
|
||||
expect(getByTestId('len').textContent).toBe('0');
|
||||
expect(getByTestId('has-handler').textContent).toBe('no');
|
||||
});
|
||||
|
||||
test('onItemsLoad updates items and renders item nodes', () => {
|
||||
const { getByTestId, getByTestId: $ } = render(<HookConsumer />);
|
||||
(getByTestId('load-call') as HTMLButtonElement).click();
|
||||
expect(getByTestId('len').textContent).toBe('2');
|
||||
expect($('itm-0')).toBeTruthy();
|
||||
expect($('itm-1')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('onItemsCount marks countedItems true and triggers callback if provided', () => {
|
||||
const cb = jest.fn();
|
||||
const { getByTestId } = render(<HookConsumer itemsCountCallback={cb} />);
|
||||
(getByTestId('count-call') as HTMLButtonElement).click();
|
||||
expect(getByTestId('counted').textContent).toBe('true');
|
||||
expect(cb).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
test('addListItems initializes itemsListInstance and appends only new items', () => {
|
||||
const mockInit = initItemsList as jest.Mock;
|
||||
|
||||
const { getByTestId, rerender } = render(<HookConsumer />);
|
||||
|
||||
const itemsLen = getByTestId('len') as HTMLDivElement;
|
||||
const addBtn = getByTestId('add-call') as HTMLButtonElement;
|
||||
const loadBtn = getByTestId('load-call') as HTMLButtonElement;
|
||||
|
||||
expect(itemsLen.textContent).toBe('0');
|
||||
loadBtn.click();
|
||||
expect(itemsLen.textContent).toBe('2');
|
||||
|
||||
expect(mockInit).toHaveBeenCalledTimes(0);
|
||||
addBtn.click();
|
||||
expect(mockInit).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(mockInit.mock.results[0].value[0].appendItems).toHaveBeenCalledTimes(2);
|
||||
|
||||
loadBtn.click();
|
||||
expect(itemsLen.textContent).toBe('2');
|
||||
|
||||
addBtn.click();
|
||||
expect(mockInit).toHaveBeenCalledTimes(2);
|
||||
expect(mockInit.mock.results[1].value[0].appendItems).toHaveBeenCalledTimes(2);
|
||||
|
||||
rerender(<HookConsumer />);
|
||||
|
||||
addBtn.click();
|
||||
expect(mockInit).toHaveBeenCalledTimes(3);
|
||||
expect(mockInit.mock.results[2].value[0].appendItems).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('addListItems does nothing when there are no .item elements in the ref', () => {
|
||||
// Render, do not call onItemsLoad, then call addListItems
|
||||
const mockInit = initItemsList as jest.Mock;
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
(getByTestId('add-call') as HTMLButtonElement).click();
|
||||
expect(mockInit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('itemsLoadCallback is invoked when items change', () => {
|
||||
const itemsLoadCallback = jest.fn();
|
||||
const { getByTestId } = render(<HookConsumer itemsLoadCallback={itemsLoadCallback} />);
|
||||
(getByTestId('load-call') as HTMLButtonElement).click();
|
||||
expect(itemsLoadCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('setListHandler updates listHandler', () => {
|
||||
const { getByTestId } = render(<HookConsumer />);
|
||||
expect(getByTestId('has-handler').textContent).toBe('no');
|
||||
(getByTestId('set-handler') as HTMLButtonElement).click();
|
||||
expect(getByTestId('has-handler').textContent).toBe('yes');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,346 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, act } from '@testing-library/react';
|
||||
|
||||
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/helpers/', () => ({
|
||||
addClassname: jest.fn(),
|
||||
removeClassname: jest.fn(),
|
||||
}));
|
||||
|
||||
let mockListHandler: any;
|
||||
let mockInlineSliderInstance: any;
|
||||
let addListItemsSpy = jest.fn();
|
||||
|
||||
jest.mock('../../../src/static/js/utils/hooks/useItemList', () => ({
|
||||
useItemList: (props: any, _ref: any) => {
|
||||
mockListHandler = {
|
||||
loadItems: jest.fn(),
|
||||
totalPages: jest.fn().mockReturnValue(props.__totalPages ?? 1),
|
||||
loadedAllItems: jest.fn().mockReturnValue(Boolean(props.__loadedAll ?? true)),
|
||||
};
|
||||
return [
|
||||
props.__items ?? [], // items
|
||||
props.__countedItems ?? 0, // countedItems
|
||||
mockListHandler, // listHandler
|
||||
jest.fn(), // setListHandler
|
||||
jest.fn(), // onItemsLoad
|
||||
jest.fn(), // onItemsCount
|
||||
addListItemsSpy, // addListItems
|
||||
];
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/components/item-list/includes/itemLists/ItemsInlineSlider', () =>
|
||||
jest.fn().mockImplementation(() => {
|
||||
mockInlineSliderInstance = {
|
||||
updateDataStateOnResize: jest.fn(),
|
||||
updateDataState: jest.fn(),
|
||||
scrollToCurrentSlide: jest.fn(),
|
||||
nextSlide: jest.fn(),
|
||||
previousSlide: jest.fn(),
|
||||
hasNextSlide: jest.fn().mockReturnValue(true),
|
||||
hasPreviousSlide: jest.fn().mockReturnValue(true),
|
||||
loadItemsToFit: jest.fn().mockReturnValue(false),
|
||||
loadMoreItems: jest.fn().mockReturnValue(false),
|
||||
itemsFit: jest.fn().mockReturnValue(3),
|
||||
};
|
||||
return mockInlineSliderInstance;
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../../src/static/js/components/_shared', () => ({
|
||||
CircleIconButton: ({ children, onClick }: any) => (
|
||||
<button data-testid="circle-icon-button" onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
import { useItemListInlineSlider } from '../../../src/static/js/utils/hooks/useItemListInlineSlider';
|
||||
|
||||
function HookConsumer(props: any) {
|
||||
const tuple = useItemListInlineSlider(props);
|
||||
const [
|
||||
_items,
|
||||
_countedItems,
|
||||
_listHandler,
|
||||
classname,
|
||||
_setListHandler,
|
||||
_onItemsCount,
|
||||
_onItemsLoad,
|
||||
_winResizeListener,
|
||||
_sidebarVisibilityChangeListener,
|
||||
itemsListWrapperRef,
|
||||
_itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = tuple as any;
|
||||
|
||||
return (
|
||||
<div ref={itemsListWrapperRef}>
|
||||
<div data-testid="class-list">{classname.list}</div>
|
||||
<div data-testid="class-outer">{classname.listOuter}</div>
|
||||
<div data-testid="render-before">{renderBeforeListWrap()}</div>
|
||||
<div data-testid="render-after">{renderAfterListWrap()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useItemListInlineSlider', () => {
|
||||
beforeEach(() => {
|
||||
addListItemsSpy = jest.fn();
|
||||
mockInlineSliderInstance = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('Returns correct tuple of values from hook', () => {
|
||||
const TestComponent = (props: any) => {
|
||||
const tuple = useItemListInlineSlider(props);
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="tuple-length">{tuple.length}</div>
|
||||
<div data-testid="has-items">{tuple[0] ? 'yes' : 'no'}</div>
|
||||
<div data-testid="has-classname">{tuple[3] ? 'yes' : 'no'}</div>
|
||||
<div data-testid="has-listeners">{typeof tuple[7] === 'function' ? 'yes' : 'no'}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<TestComponent __items={[1, 2, 3]} />);
|
||||
|
||||
expect(getByTestId('tuple-length').textContent).toBe('13');
|
||||
expect(getByTestId('has-classname').textContent).toBe('yes');
|
||||
expect(getByTestId('has-listeners').textContent).toBe('yes');
|
||||
});
|
||||
|
||||
test('Computes classname.list and classname.listOuter with optional className prop', () => {
|
||||
const { getByTestId, rerender } = render(<HookConsumer className=" extra " />);
|
||||
|
||||
expect(getByTestId('class-outer').textContent).toBe('items-list-outer list-inline list-slider extra ');
|
||||
expect(getByTestId('class-list').textContent).toBe('items-list');
|
||||
|
||||
rerender(<HookConsumer />);
|
||||
|
||||
expect(getByTestId('class-outer').textContent).toBe('items-list-outer list-inline list-slider');
|
||||
expect(getByTestId('class-list').textContent).toBe('items-list');
|
||||
});
|
||||
|
||||
test('Invokes addListItems when items change', () => {
|
||||
const { rerender } = render(<HookConsumer __items={[]} />);
|
||||
expect(addListItemsSpy).toHaveBeenCalledTimes(1);
|
||||
rerender(<HookConsumer __items={[1]} />);
|
||||
expect(addListItemsSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('nextSlide loads more items when loadMoreItems returns true and not all items loaded', () => {
|
||||
const { getByTestId } = render(<HookConsumer __items={[1, 2, 3]} __loadedAll={false} />);
|
||||
|
||||
mockInlineSliderInstance.loadMoreItems.mockReturnValue(true);
|
||||
|
||||
const renderAfter = getByTestId('render-after');
|
||||
const nextButton = renderAfter.querySelector('button[data-testid="circle-icon-button"]');
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.click(nextButton!);
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('nextSlide does not load items when all items already loaded', () => {
|
||||
const { getByTestId } = render(<HookConsumer __items={[1, 2, 3]} __loadedAll={true} />);
|
||||
|
||||
mockInlineSliderInstance.loadMoreItems.mockReturnValue(false);
|
||||
|
||||
const renderAfter = getByTestId('render-after');
|
||||
const nextButton = renderAfter.querySelector('button[data-testid="circle-icon-button"]');
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.click(nextButton!);
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('prevSlide calls inlineSlider.previousSlide and updates button view', () => {
|
||||
const { getByTestId } = render(<HookConsumer __items={[1, 2, 3]} __loadedAll={false} />);
|
||||
|
||||
mockInlineSliderInstance.loadMoreItems.mockReturnValue(true);
|
||||
|
||||
const renderBefore = getByTestId('render-before');
|
||||
const prevButton = renderBefore.querySelector('button[data-testid="circle-icon-button"]');
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.click(prevButton!);
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('prevSlide always scrolls to current slide regardless of item load state', () => {
|
||||
const { getByTestId } = render(<HookConsumer __items={[1, 2, 3]} __loadedAll={true} />);
|
||||
|
||||
mockInlineSliderInstance.loadMoreItems.mockReturnValue(false);
|
||||
|
||||
const renderBefore = getByTestId('render-before');
|
||||
const prevButton = renderBefore.querySelector('button[data-testid="circle-icon-button"]');
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.click(prevButton!);
|
||||
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.loadMoreItems).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.nextSlide).toHaveBeenCalledTimes(0);
|
||||
expect(mockInlineSliderInstance.previousSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.hasNextSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.hasPreviousSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('Button state updates based on hasNextSlide and hasPreviousSlide', () => {
|
||||
const { getByTestId, rerender } = render(<HookConsumer __items={[1, 2, 3]} />);
|
||||
|
||||
const renderBefore = getByTestId('render-before');
|
||||
const renderAfter = getByTestId('render-after');
|
||||
|
||||
// Initially should show buttons (default mock returns true)
|
||||
expect(renderBefore.querySelector('button')).toBeTruthy();
|
||||
expect(renderAfter.querySelector('button')).toBeTruthy();
|
||||
|
||||
// Now set hasNextSlide and hasPreviousSlide to false
|
||||
mockInlineSliderInstance.hasNextSlide.mockReturnValue(false);
|
||||
mockInlineSliderInstance.hasPreviousSlide.mockReturnValue(false);
|
||||
|
||||
// Trigger re-render by changing items
|
||||
rerender(<HookConsumer __items={[1, 2, 3, 4]} />);
|
||||
|
||||
// The next and previous buttons should not be rendered now
|
||||
const newRenderAfter = getByTestId('render-after');
|
||||
const newRenderBefore = getByTestId('render-before');
|
||||
expect(newRenderAfter.querySelector('button')).toBeNull();
|
||||
expect(newRenderBefore.querySelector('button')).toBeNull();
|
||||
});
|
||||
|
||||
test('winResizeListener and sidebarVisibilityChangeListener are returned as callable functions', () => {
|
||||
const TestComponentWithListeners = (props: any) => {
|
||||
const tuple = useItemListInlineSlider(props);
|
||||
|
||||
const winResizeListener = tuple[7]; // winResizeListener
|
||||
const sidebarListener = tuple[8]; // sidebarVisibilityChangeListener
|
||||
const wrapperRef = tuple[9]; // itemsListWrapperRef
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef as any} data-testid="wrapper">
|
||||
<button data-testid="trigger-resize" onClick={winResizeListener as any}>
|
||||
Trigger Resize
|
||||
</button>
|
||||
<button data-testid="trigger-sidebar" onClick={sidebarListener as any}>
|
||||
Trigger Sidebars
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<TestComponentWithListeners __items={[1, 2, 3]} />);
|
||||
|
||||
// Should not throw when called
|
||||
const resizeButton = getByTestId('trigger-resize');
|
||||
const sidebarButton = getByTestId('trigger-sidebar');
|
||||
|
||||
expect(() => fireEvent.click(resizeButton)).not.toThrow();
|
||||
expect(() => fireEvent.click(sidebarButton)).not.toThrow();
|
||||
});
|
||||
|
||||
test('winResizeListener updates resizeDate state triggering resize effect', () => {
|
||||
const TestComponent = (props: any) => {
|
||||
const tuple = useItemListInlineSlider(props) as any;
|
||||
const winResizeListener = tuple[7];
|
||||
const wrapperRef = tuple[9];
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} data-testid="wrapper">
|
||||
<button data-testid="trigger-resize" onClick={winResizeListener}>
|
||||
Trigger Resize
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<TestComponent __items={[1, 2, 3]} />);
|
||||
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(1);
|
||||
expect(mockInlineSliderInstance.updateDataStateOnResize).toHaveBeenCalledTimes(0);
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
fireEvent.click(getByTestId('trigger-resize'));
|
||||
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(2);
|
||||
expect(mockInlineSliderInstance.updateDataStateOnResize).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
|
||||
expect(mockInlineSliderInstance.scrollToCurrentSlide).toHaveBeenCalledTimes(3);
|
||||
expect(mockInlineSliderInstance.updateDataStateOnResize).toHaveBeenCalledTimes(2);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,190 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||
|
||||
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
let mockListHandler: any;
|
||||
let addListItemsSpy = jest.fn();
|
||||
const mockRemoveListener = jest.fn();
|
||||
|
||||
jest.mock('../../../src/static/js/utils/hooks/useItemList', () => ({
|
||||
useItemList: (props: any, _ref: any) => {
|
||||
mockListHandler = {
|
||||
loadItems: jest.fn(),
|
||||
totalPages: jest.fn().mockReturnValue(props.__totalPages ?? 1),
|
||||
loadedAllItems: jest.fn().mockReturnValue(Boolean(props.__loadedAll ?? true)),
|
||||
};
|
||||
return [
|
||||
props.__items ?? [], // items
|
||||
props.__countedItems ?? 0, // countedItems
|
||||
mockListHandler, // listHandler
|
||||
jest.fn(), // setListHandler
|
||||
jest.fn(), // onItemsLoad
|
||||
jest.fn(), // onItemsCount
|
||||
addListItemsSpy, // addListItems
|
||||
];
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/stores/', () => ({
|
||||
PageStore: {
|
||||
removeListener: mockRemoveListener,
|
||||
},
|
||||
}));
|
||||
|
||||
import { useItemListLazyLoad } from '../../../src/static/js/utils/hooks/useItemListLazyLoad';
|
||||
|
||||
function HookConsumer(props: any) {
|
||||
const tuple = useItemListLazyLoad(props);
|
||||
|
||||
const [
|
||||
_items,
|
||||
_countedItems,
|
||||
_listHandler,
|
||||
_setListHandler,
|
||||
classname,
|
||||
_onItemsCount,
|
||||
_onItemsLoad,
|
||||
_onWindowScroll,
|
||||
_onDocumentVisibilityChange,
|
||||
_itemsListWrapperRef,
|
||||
_itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = tuple as any;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="class-list">{classname.list}</div>
|
||||
<div data-testid="class-outer">{classname.listOuter}</div>
|
||||
<div data-testid="render-before">{renderBeforeListWrap()}</div>
|
||||
<div data-testid="render-after">{renderAfterListWrap()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HookConsumerWithRefs(props: any) {
|
||||
const tuple = useItemListLazyLoad(props);
|
||||
const [
|
||||
_items,
|
||||
_countedItems,
|
||||
_listHandler,
|
||||
_setListHandler,
|
||||
classname,
|
||||
_onItemsCount,
|
||||
_onItemsLoad,
|
||||
onWindowScroll,
|
||||
onDocumentVisibilityChange,
|
||||
itemsListWrapperRef,
|
||||
itemsListRef,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = tuple as any;
|
||||
|
||||
return (
|
||||
<div ref={itemsListWrapperRef}>
|
||||
<div data-testid="class-list">{classname.list}</div>
|
||||
<div data-testid="class-outer">{classname.listOuter}</div>
|
||||
<div ref={itemsListRef} data-testid="list-ref-node" />
|
||||
<div data-testid="render-before">{renderBeforeListWrap()}</div>
|
||||
<div data-testid="render-after">{renderAfterListWrap()}</div>
|
||||
<button data-testid="trigger-visibility" onClick={onDocumentVisibilityChange} type="button">
|
||||
visibility
|
||||
</button>
|
||||
<button data-testid="trigger-scroll" onClick={onWindowScroll} type="button">
|
||||
scroll
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useItemListLazyLoad', () => {
|
||||
beforeEach(() => {
|
||||
addListItemsSpy = jest.fn();
|
||||
mockRemoveListener.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('Computes classname.list and classname.listOuter with optional className prop', () => {
|
||||
const { getByTestId, rerender } = render(<HookConsumer className=" extra " />);
|
||||
expect(getByTestId('class-outer').textContent).toBe('items-list-outer extra');
|
||||
expect(getByTestId('class-list').textContent).toBe('items-list');
|
||||
rerender(<HookConsumer />);
|
||||
expect(getByTestId('class-outer').textContent).toBe('items-list-outer');
|
||||
expect(getByTestId('class-list').textContent).toBe('items-list');
|
||||
});
|
||||
|
||||
test('Invokes addListItems when items change', () => {
|
||||
const { rerender } = render(<HookConsumer __items={[]} />);
|
||||
expect(addListItemsSpy).toHaveBeenCalledTimes(1);
|
||||
rerender(<HookConsumer __items={[1]} />);
|
||||
expect(addListItemsSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('Renders nothing in renderBeforeListWrap and renderAfterListWrap', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer __items={[1]} __countedItems={1} __totalPages={3} __loadedAll={false} />
|
||||
);
|
||||
expect(getByTestId('render-before').textContent).toBe('');
|
||||
expect(getByTestId('render-after').textContent).toBe('');
|
||||
});
|
||||
|
||||
test('Does not call listHandler.loadItems when refs are not attached', () => {
|
||||
render(<HookConsumer __items={[1]} />);
|
||||
expect(mockListHandler.loadItems).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Calls listHandler.loadItems when refs are set and scroll threshold is reached', async () => {
|
||||
render(<HookConsumerWithRefs __items={[1]} __loadedAll={false} />);
|
||||
await waitFor(() => {
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('Calls PageStore.removeListener when refs are set and loadedAllItems is true', () => {
|
||||
render(<HookConsumerWithRefs __items={[1]} __loadedAll={true} />);
|
||||
expect(mockRemoveListener).toHaveBeenCalledWith('window_scroll', expect.any(Function));
|
||||
});
|
||||
|
||||
test('onDocumentVisibilityChange schedules onWindowScroll when document is visible', () => {
|
||||
jest.useFakeTimers();
|
||||
const setTimeoutSpy = jest.spyOn(globalThis, 'setTimeout');
|
||||
Object.defineProperty(document, 'hidden', { value: false, configurable: true });
|
||||
|
||||
const { getByTestId } = render(<HookConsumerWithRefs __items={[1]} />);
|
||||
fireEvent.click(getByTestId('trigger-visibility'));
|
||||
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 10);
|
||||
|
||||
setTimeoutSpy.mockRestore();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('onDocumentVisibilityChange does nothing when document is hidden', () => {
|
||||
jest.useFakeTimers();
|
||||
const setTimeoutSpy = jest.spyOn(globalThis, 'setTimeout');
|
||||
Object.defineProperty(document, 'hidden', { value: true, configurable: true });
|
||||
|
||||
const { getByTestId } = render(<HookConsumerWithRefs __items={[1]} />);
|
||||
fireEvent.click(getByTestId('trigger-visibility'));
|
||||
|
||||
expect(setTimeoutSpy).toHaveBeenCalledTimes(0);
|
||||
|
||||
setTimeoutSpy.mockRestore();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
|
||||
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/helpers/', () => ({
|
||||
translateString: (s: string) => s,
|
||||
}));
|
||||
|
||||
let mockListHandler: any;
|
||||
let mockOnItemsLoad = jest.fn();
|
||||
let mockOnItemsCount = jest.fn();
|
||||
let addListItemsSpy = jest.fn();
|
||||
|
||||
// Mock useItemList to control items, counts, and listHandler
|
||||
jest.mock('../../../src/static/js/utils/hooks/useItemList', () => ({
|
||||
useItemList: (props: any, _ref: any) => {
|
||||
mockListHandler = {
|
||||
loadItems: jest.fn(),
|
||||
totalPages: jest.fn().mockReturnValue(props.__totalPages ?? 1),
|
||||
loadedAllItems: jest.fn().mockReturnValue(Boolean(props.__loadedAll ?? true)),
|
||||
};
|
||||
return [
|
||||
props.__items ?? [], // items
|
||||
props.__countedItems ?? 0, // countedItems
|
||||
mockListHandler, // listHandler
|
||||
jest.fn(), // setListHandler
|
||||
mockOnItemsLoad, // onItemsLoad
|
||||
mockOnItemsCount, // onItemsCount
|
||||
addListItemsSpy, // addListItems
|
||||
];
|
||||
},
|
||||
}));
|
||||
|
||||
import { useItemListSync } from '../../../src/static/js/utils/hooks/useItemListSync';
|
||||
|
||||
function HookConsumer(props: any) {
|
||||
const tuple = useItemListSync(props);
|
||||
|
||||
const [
|
||||
_countedItems,
|
||||
_items,
|
||||
_listHandler,
|
||||
_setListHandler,
|
||||
classname,
|
||||
_itemsListWrapperRef,
|
||||
_itemsListRef,
|
||||
_onItemsCount,
|
||||
_onItemsLoad,
|
||||
renderBeforeListWrap,
|
||||
renderAfterListWrap,
|
||||
] = tuple as any;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <div data-testid="counted">{String(countedItems)}</div> */}
|
||||
{/* <div data-testid="items">{Array.isArray(items) ? items.length : 0}</div> */}
|
||||
<div data-testid="class-list">{classname.list}</div>
|
||||
<div data-testid="class-outer">{classname.listOuter}</div>
|
||||
{/* <div data-testid="has-handler">{listHandler ? 'yes' : 'no'}</div> */}
|
||||
{/* <div data-testid="wrapper-ref">{itemsListWrapperRef.current ? 'set' : 'unset'}</div> */}
|
||||
{/* <div data-testid="list-ref">{itemsListRef.current ? 'set' : 'unset'}</div> */}
|
||||
<div data-testid="render-before">{renderBeforeListWrap()}</div>
|
||||
<div data-testid="render-after">{renderAfterListWrap()}</div>
|
||||
{/* <button data-testid="call-on-load" onClick={() => onItemsLoad([])} /> */}
|
||||
{/* <button data-testid="call-on-count" onClick={() => onItemsCount(0)} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useItemListSync', () => {
|
||||
beforeEach(() => {
|
||||
mockOnItemsLoad = jest.fn();
|
||||
mockOnItemsCount = jest.fn();
|
||||
addListItemsSpy = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Classname Management', () => {
|
||||
test('Computes classname.listOuter with optional className prop', () => {
|
||||
const { getByTestId, rerender } = render(<HookConsumer className=" extra " />);
|
||||
expect(getByTestId('class-outer').textContent).toBe('items-list-outer extra');
|
||||
expect(getByTestId('class-list').textContent).toBe('items-list');
|
||||
rerender(<HookConsumer />);
|
||||
expect(getByTestId('class-outer').textContent).toBe('items-list-outer');
|
||||
expect(getByTestId('class-list').textContent).toBe('items-list');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Items Management', () => {
|
||||
test('Invokes addListItems and afterItemsLoad when items change', () => {
|
||||
const { rerender } = render(<HookConsumer __items={[]} />);
|
||||
expect(addListItemsSpy).toHaveBeenCalledTimes(1);
|
||||
rerender(<HookConsumer __items={[1]} />);
|
||||
// useEffect runs again due to items change
|
||||
expect(addListItemsSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Load More Button Rendering', () => {
|
||||
test('Renders SHOW MORE button when more pages exist and not loaded all', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer __items={[1]} __countedItems={1} __totalPages={3} __loadedAll={false} />
|
||||
);
|
||||
const btn = getByTestId('render-after').querySelector('button.load-more') as HTMLButtonElement;
|
||||
expect(btn).toBeTruthy();
|
||||
expect(btn.textContent).toBe('SHOW MORE');
|
||||
fireEvent.click(btn);
|
||||
expect(mockListHandler.loadItems).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Hides SHOW MORE when totalPages <= 1', () => {
|
||||
const { getByTestId } = render(
|
||||
// With totalPages=1 the hook should not render the button regardless of loadedAll
|
||||
<HookConsumer __items={[1, 2]} __countedItems={2} __totalPages={1} __loadedAll={true} />
|
||||
);
|
||||
expect(getByTestId('render-after').textContent).toBe('');
|
||||
});
|
||||
|
||||
test('Hides SHOW MORE when loadedAllItems is true', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer __items={[1, 2, 3]} __countedItems={3} __totalPages={5} __loadedAll={true} />
|
||||
);
|
||||
expect(getByTestId('render-after').textContent).toBe('');
|
||||
});
|
||||
|
||||
test('Shows SHOW MORE when loadedAllItems is false even with totalPages > 1', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer __items={[1, 2]} __countedItems={2} __totalPages={2} __loadedAll={false} />
|
||||
);
|
||||
const btn = getByTestId('render-after').querySelector('button.load-more');
|
||||
expect(btn).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Returns null from renderBeforeListWrap', () => {
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer __items={[1]} __countedItems={1} __totalPages={3} __loadedAll={false} />
|
||||
);
|
||||
expect(getByTestId('render-before').textContent).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { act, render } from '@testing-library/react';
|
||||
|
||||
import { useLayout } from '../../../src/static/js/utils/hooks/useLayout';
|
||||
|
||||
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||
get: (key: string) => {
|
||||
let result: any = undefined;
|
||||
switch (key) {
|
||||
case 'visible-sidebar':
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
set: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/dispatcher.js', () => ({
|
||||
register: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||
}));
|
||||
|
||||
import { LayoutProvider } from '../../../src/static/js/utils/contexts';
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useLayout', () => {
|
||||
test('Returns default value', () => {
|
||||
let received: ReturnType<typeof useLayout> | undefined;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
received = useLayout();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<LayoutProvider>
|
||||
<Comp />
|
||||
</LayoutProvider>
|
||||
);
|
||||
|
||||
expect(received).toStrictEqual({
|
||||
enabledSidebar: false,
|
||||
visibleSidebar: true,
|
||||
visibleMobileSearch: false,
|
||||
setVisibleSidebar: expect.any(Function),
|
||||
toggleMobileSearch: expect.any(Function),
|
||||
toggleSidebar: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
test('Returns undefined value when used without a Provider', () => {
|
||||
let received: any = 'init';
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
received = useLayout();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(<Comp />);
|
||||
|
||||
expect(received).toBe(undefined);
|
||||
});
|
||||
|
||||
test('Toggle sidebar', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
let received: ReturnType<typeof useLayout> | undefined;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
received = useLayout();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<LayoutProvider>
|
||||
<Comp />
|
||||
</LayoutProvider>
|
||||
);
|
||||
|
||||
act(() => received?.toggleSidebar());
|
||||
jest.advanceTimersByTime(241);
|
||||
expect(received?.visibleSidebar).toBe(false);
|
||||
|
||||
act(() => received?.toggleSidebar());
|
||||
jest.advanceTimersByTime(241);
|
||||
expect(received?.visibleSidebar).toBe(true);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('Toggle mobile search', () => {
|
||||
let received: ReturnType<typeof useLayout> | undefined;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
received = useLayout();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<LayoutProvider>
|
||||
<Comp />
|
||||
</LayoutProvider>
|
||||
);
|
||||
|
||||
act(() => received?.toggleMobileSearch());
|
||||
expect(received?.visibleMobileSearch).toBe(true);
|
||||
|
||||
act(() => received?.toggleMobileSearch());
|
||||
expect(received?.visibleMobileSearch).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { useManagementTableHeader } from '../../../src/static/js/utils/hooks/useManagementTableHeader';
|
||||
|
||||
function HookConsumer(props: {
|
||||
order: 'asc' | 'desc';
|
||||
selected: boolean;
|
||||
sort: string;
|
||||
type: 'comments' | 'media' | 'users';
|
||||
onCheckAllRows?: (newSort: string, newOrder: 'asc' | 'desc') => void;
|
||||
onClickColumnSort?: (newSelected: boolean, newType: 'comments' | 'media' | 'users') => void;
|
||||
}) {
|
||||
const tuple = useManagementTableHeader(props) as [
|
||||
string,
|
||||
'asc' | 'desc',
|
||||
boolean,
|
||||
React.MouseEventHandler,
|
||||
() => void,
|
||||
];
|
||||
|
||||
const [sort, order, isSelected, sortByColumn, checkAll] = tuple;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="sort">{sort}</div>
|
||||
<div data-testid="order">{order}</div>
|
||||
<div data-testid="selected">{String(isSelected)}</div>
|
||||
<button id="title" data-testid="col-title" onClick={sortByColumn} />
|
||||
<button id="views" data-testid="col-views" onClick={sortByColumn} />
|
||||
<button data-testid="check-all" onClick={checkAll} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useManagementTableHeader', () => {
|
||||
test('Returns a 5-tuple in expected order and reflects initial props', () => {
|
||||
let tuple: any;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
tuple = useManagementTableHeader({ sort: 'title', order: 'asc', selected: false });
|
||||
return null;
|
||||
};
|
||||
|
||||
render(<Comp />);
|
||||
|
||||
expect(Array.isArray(tuple)).toBe(true);
|
||||
expect(tuple).toHaveLength(5);
|
||||
|
||||
const [sort, order, isSelected] = tuple;
|
||||
|
||||
expect(sort).toBe('title');
|
||||
expect(order).toBe('asc');
|
||||
expect(isSelected).toBe(false);
|
||||
});
|
||||
|
||||
test('sortByColumn toggles order when clicking same column and updates sort when clicking different column', () => {
|
||||
const onClickColumnSort = jest.fn();
|
||||
|
||||
const { getByTestId, rerender } = render(
|
||||
<HookConsumer
|
||||
sort="title"
|
||||
order="desc"
|
||||
type="media"
|
||||
selected={false}
|
||||
onClickColumnSort={onClickColumnSort}
|
||||
/>
|
||||
);
|
||||
|
||||
// Initial state
|
||||
expect(getByTestId('sort').textContent).toBe('title');
|
||||
expect(getByTestId('order').textContent).toBe('desc');
|
||||
|
||||
// Click same column -> toggle order to asc
|
||||
fireEvent.click(getByTestId('col-title'));
|
||||
expect(onClickColumnSort).toHaveBeenLastCalledWith('title', 'asc');
|
||||
|
||||
// Rerender to ensure state settled in testing DOM
|
||||
rerender(
|
||||
<HookConsumer
|
||||
sort="title"
|
||||
order="asc"
|
||||
type="media"
|
||||
selected={false}
|
||||
onClickColumnSort={onClickColumnSort}
|
||||
/>
|
||||
);
|
||||
|
||||
// Click same column -> toggle order to desc
|
||||
fireEvent.click(getByTestId('col-title'));
|
||||
expect(onClickColumnSort).toHaveBeenLastCalledWith('title', 'desc');
|
||||
|
||||
// Click different column -> set sort to that column and default order desc
|
||||
fireEvent.click(getByTestId('col-views'));
|
||||
expect(onClickColumnSort).toHaveBeenLastCalledWith('views', 'desc');
|
||||
});
|
||||
|
||||
test('checkAll inverts current selection and invokes callback with newSelected and type', () => {
|
||||
const onCheckAllRows = jest.fn();
|
||||
|
||||
const { getByTestId } = render(
|
||||
<HookConsumer sort="title" order="asc" selected={false} type="media" onCheckAllRows={onCheckAllRows} />
|
||||
);
|
||||
|
||||
expect(getByTestId('selected').textContent).toBe('false');
|
||||
fireEvent.click(getByTestId('check-all'));
|
||||
|
||||
// newSelected computed as !isSelected -> true
|
||||
expect(onCheckAllRows).toHaveBeenCalledWith(true, 'media');
|
||||
});
|
||||
|
||||
test('Effects update internal state when props change', () => {
|
||||
const { getByTestId, rerender } = render(
|
||||
<HookConsumer sort="title" order="asc" type="media" selected={false} />
|
||||
);
|
||||
|
||||
expect(getByTestId('sort').textContent).toBe('title');
|
||||
expect(getByTestId('order').textContent).toBe('asc');
|
||||
expect(getByTestId('selected').textContent).toBe('false');
|
||||
|
||||
rerender(<HookConsumer sort="views" order="desc" type="media" selected={true} />);
|
||||
|
||||
expect(getByTestId('sort').textContent).toBe('views');
|
||||
expect(getByTestId('order').textContent).toBe('desc');
|
||||
expect(getByTestId('selected').textContent).toBe('true');
|
||||
});
|
||||
|
||||
test('Does not throw when optional callbacks are not provided', () => {
|
||||
const { getByTestId } = render(<HookConsumer sort="x" order="desc" type="media" selected={false} />);
|
||||
expect(() => fireEvent.click(getByTestId('col-title'))).not.toThrow();
|
||||
expect(() => fireEvent.click(getByTestId('check-all'))).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useMediaFilter } from '../../../src/static/js/utils/hooks/useMediaFilter';
|
||||
|
||||
jest.mock('../../../src/static/js/components/_shared/popup/PopupContent', () => ({
|
||||
PopupContent: (props: any) => React.createElement('div', props, props.children),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/components/_shared/popup/PopupTrigger', () => ({
|
||||
PopupTrigger: (props: any) => React.createElement('div', props, props.children),
|
||||
}));
|
||||
|
||||
function HookConsumer({ initial }: { initial: string }) {
|
||||
const tuple = useMediaFilter(initial) as [
|
||||
React.RefObject<any>,
|
||||
string,
|
||||
React.Dispatch<React.SetStateAction<string>>,
|
||||
React.RefObject<any>,
|
||||
React.ReactNode,
|
||||
React.ReactNode,
|
||||
];
|
||||
|
||||
const [containerRef, value, setValue, popupContentRef, PopupContent, PopupTrigger] = tuple;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="container-ref">{containerRef && typeof containerRef === 'object' ? 'ok' : 'bad'}</div>
|
||||
<div data-testid="value">{value}</div>
|
||||
<button data-testid="set" onClick={() => setValue('updated')} />
|
||||
<div data-testid="popup-ref">{popupContentRef && typeof popupContentRef === 'object' ? 'ok' : 'bad'}</div>
|
||||
{typeof PopupContent === 'function' ? React.createElement(PopupContent, null, 'c') : null}
|
||||
{typeof PopupTrigger === 'function' ? React.createElement(PopupTrigger, null, 't') : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useMediaFilter', () => {
|
||||
test('Returns a 6-tuple in expected order', () => {
|
||||
let tuple: any;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
tuple = useMediaFilter('init');
|
||||
return null;
|
||||
};
|
||||
|
||||
render(<Comp />);
|
||||
|
||||
expect(Array.isArray(tuple)).toBe(true);
|
||||
expect(tuple).toHaveLength(6);
|
||||
|
||||
const [containerRef, value, setValue, popupContentRef, PopupContent, PopupTrigger] = tuple;
|
||||
|
||||
expect(containerRef).toBeDefined();
|
||||
expect(containerRef.current).toBe(null);
|
||||
expect(value).toBe('init');
|
||||
expect(typeof setValue).toBe('function');
|
||||
expect(popupContentRef).toBeDefined();
|
||||
expect(typeof PopupContent).toBe('function');
|
||||
expect(typeof PopupTrigger).toBe('function');
|
||||
});
|
||||
|
||||
test('Initial value is respected and can be updated via setter', () => {
|
||||
const { getByTestId } = render(<HookConsumer initial="first" />);
|
||||
expect(getByTestId('value').textContent).toBe('first');
|
||||
getByTestId('set').click();
|
||||
expect(getByTestId('value').textContent).toBe('updated');
|
||||
});
|
||||
|
||||
test('containerRef and popupContentRef are mutable ref objects', () => {
|
||||
let data: any;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
data = useMediaFilter('x');
|
||||
return null;
|
||||
};
|
||||
|
||||
render(<Comp />);
|
||||
|
||||
const [containerRef, _value, _setValue, popupContentRef] = data;
|
||||
|
||||
expect(containerRef.current).toBe(null);
|
||||
expect(popupContentRef.current).toBe(null);
|
||||
});
|
||||
|
||||
test('PopupContent and PopupTrigger are stable functions', () => {
|
||||
let first: any;
|
||||
let second: any;
|
||||
|
||||
const First: React.FC = () => {
|
||||
first = useMediaFilter('a');
|
||||
return null;
|
||||
};
|
||||
|
||||
const Second: React.FC = () => {
|
||||
second = useMediaFilter('b');
|
||||
return null;
|
||||
};
|
||||
|
||||
const Parent: React.FC = () => (
|
||||
<>
|
||||
<First />
|
||||
<Second />
|
||||
</>
|
||||
);
|
||||
|
||||
render(<Parent />);
|
||||
|
||||
const [, , , , PopupContent1, PopupTrigger1] = first;
|
||||
const [, , , , PopupContent2, PopupTrigger2] = second;
|
||||
|
||||
expect(typeof PopupContent1).toBe('function');
|
||||
expect(typeof PopupTrigger1).toBe('function');
|
||||
|
||||
expect(PopupContent1).toBe(PopupContent2);
|
||||
expect(PopupTrigger1).toBe(PopupTrigger2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,289 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useMediaItem, itemClassname } from '../../../src/static/js/utils/hooks/useMediaItem';
|
||||
|
||||
// Mock dependencies used by useMediaItem
|
||||
|
||||
// @todo: Revisit this
|
||||
jest.mock('../../../src/static/js/utils/stores/', () => ({
|
||||
PageStore: { get: (_: string) => ({ url: 'https://example.com' }) },
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/components/list-item/includes/items', () => ({
|
||||
MediaItemAuthor: ({ name }: any) => <div data-testid="author" data-name={name} />,
|
||||
MediaItemAuthorLink: ({ name, link }: any) => (
|
||||
<a data-testid="author-link" data-name={name} href={link || undefined} />
|
||||
),
|
||||
MediaItemMetaViews: ({ views }: any) => <span data-testid="views" data-views={views} />,
|
||||
MediaItemMetaDate: ({ time, dateTime, text }: any) => (
|
||||
<time data-testid="date" data-time={String(time)} data-datetime={String(dateTime)}>
|
||||
{text}
|
||||
</time>
|
||||
),
|
||||
MediaItemEditLink: ({ link }: any) => <a data-testid="edit" href={link} />,
|
||||
MediaItemViewLink: ({ link }: any) => <a data-testid="view" href={link} />,
|
||||
}));
|
||||
|
||||
// @todo: Revisit this
|
||||
// useItem returns titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper
|
||||
jest.mock('../../../src/static/js/utils/hooks/useItem', () => ({
|
||||
useItem: (props: any) => ({
|
||||
titleComponent: () => <h3 data-testid="title">{props.title || 'title'}</h3>,
|
||||
descriptionComponent: () => <p data-testid="desc">{props.description || 'desc'}</p>,
|
||||
thumbnailUrl: props.thumb || 'thumb.jpg',
|
||||
UnderThumbWrapper: ({ children }: any) => <div data-testid="under-thumb">{children}</div>,
|
||||
}),
|
||||
}));
|
||||
|
||||
function HookConsumer(props: any) {
|
||||
const [TitleComp, DescComp, thumbUrl, UnderThumbComp, EditComp, MetaComp, ViewComp] = useMediaItem(props);
|
||||
// The hook returns functions/components/values. To satisfy TS, render using React.createElement
|
||||
return (
|
||||
<div>
|
||||
{typeof TitleComp === 'function' ? React.createElement(TitleComp) : null}
|
||||
{typeof DescComp === 'function' ? React.createElement(DescComp) : null}
|
||||
<div data-testid="thumb">{typeof thumbUrl === 'string' ? thumbUrl : ''}</div>
|
||||
{typeof UnderThumbComp === 'function'
|
||||
? React.createElement(
|
||||
UnderThumbComp,
|
||||
null,
|
||||
typeof EditComp === 'function' ? React.createElement(EditComp) : null,
|
||||
typeof MetaComp === 'function' ? React.createElement(MetaComp) : null,
|
||||
typeof ViewComp === 'function' ? React.createElement(ViewComp) : null
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('useMediaItem', () => {
|
||||
describe('itemClassname utility function', () => {
|
||||
test('Returns default classname when no modifications', () => {
|
||||
expect(itemClassname('base', '', false)).toBe('base');
|
||||
});
|
||||
|
||||
test('Appends inherited classname when provided', () => {
|
||||
expect(itemClassname('base', 'extra', false)).toBe('base extra');
|
||||
});
|
||||
|
||||
test('Appends pl-active-item when isActiveInPlaylistPlayback is true', () => {
|
||||
expect(itemClassname('base', '', true)).toBe('base pl-active-item');
|
||||
});
|
||||
|
||||
test('Appends both inherited classname and active state', () => {
|
||||
expect(itemClassname('base', 'extra', true)).toBe('base extra pl-active-item');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic Rendering', () => {
|
||||
test('Renders basic components from useItem and edit/view links', () => {
|
||||
// @todo: Revisit this
|
||||
const props = {
|
||||
title: 'My Title',
|
||||
description: 'My Desc',
|
||||
thumbnail: 'thumb.jpg',
|
||||
link: '/watch/1',
|
||||
singleLinkContent: true,
|
||||
// hasMediaViewer:...
|
||||
// hasMediaViewerDescr:...
|
||||
// meta_description:...
|
||||
// onMount:...
|
||||
// type:...
|
||||
// ------------------------------
|
||||
editLink: '/edit/1',
|
||||
showSelection: true,
|
||||
// publishLink: ...
|
||||
// hideAuthor:...
|
||||
author_name: 'Author',
|
||||
author_link: '/u/author',
|
||||
// hideViews:...
|
||||
views: 10,
|
||||
// hideDate:...
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
// hideAllMeta:...
|
||||
};
|
||||
|
||||
const { getByTestId, queryByTestId } = render(<HookConsumer {...props} />);
|
||||
|
||||
expect(getByTestId('title').textContent).toBe(props.title);
|
||||
expect(getByTestId('desc').textContent).toBe(props.description);
|
||||
expect(getByTestId('thumb').textContent).toBe('thumb.jpg');
|
||||
|
||||
expect(getByTestId('edit').getAttribute('href')).toBe(props.editLink);
|
||||
|
||||
expect(getByTestId('views').getAttribute('data-views')).toBe(props.views.toString());
|
||||
expect(getByTestId('date')).toBeTruthy();
|
||||
expect(getByTestId('view').getAttribute('href')).toBe(props.link);
|
||||
expect(queryByTestId('author')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('View Link Selection', () => {
|
||||
test('Uses publishLink when provided and showSelection=true', () => {
|
||||
const props = {
|
||||
editLink: '/edit/2',
|
||||
link: '/watch/2',
|
||||
publishLink: '/publish/2',
|
||||
showSelection: true,
|
||||
singleLinkContent: true,
|
||||
author_name: 'A',
|
||||
author_link: '',
|
||||
views: 0,
|
||||
publish_date: 0,
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<HookConsumer {...props} />);
|
||||
|
||||
expect(getByTestId('view').getAttribute('href')).toBe(props.publishLink);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Visibility Controls', () => {
|
||||
test('Hides author, views, and date based on props', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
hideAuthor: true,
|
||||
hideViews: true,
|
||||
hideDate: true,
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
views: 5,
|
||||
author_name: 'Hidden',
|
||||
author_link: '/u/x',
|
||||
};
|
||||
|
||||
const { queryByTestId } = render(<HookConsumer {...props} />);
|
||||
|
||||
expect(queryByTestId('author')).toBeNull();
|
||||
expect(queryByTestId('views')).toBeNull();
|
||||
expect(queryByTestId('date')).toBeNull();
|
||||
});
|
||||
|
||||
test('Author link resolves using formatInnerLink and PageStore base url when singleLinkContent=false', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
singleLinkContent: false,
|
||||
hideAuthor: false,
|
||||
author_name: 'John',
|
||||
author_link: '/u/john',
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const { container } = render(<HookConsumer {...props} />);
|
||||
|
||||
const a = container.querySelector('[data-testid="author-link"]') as HTMLAnchorElement;
|
||||
|
||||
expect(a).toBeTruthy();
|
||||
expect(a.getAttribute('href')).toBe(`https://example.com${props.author_link}`);
|
||||
expect(a.getAttribute('data-name')).toBe(props.author_name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Meta Visibility', () => {
|
||||
test('Meta wrapper hidden when hideAllMeta=true', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
hideAllMeta: true,
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const { queryByTestId } = render(<HookConsumer {...props} />);
|
||||
|
||||
expect(queryByTestId('author')).toBeNull();
|
||||
expect(queryByTestId('views')).toBeNull();
|
||||
expect(queryByTestId('date')).toBeNull();
|
||||
});
|
||||
|
||||
test('Meta wrapper hidden individually by hideAuthor, hideViews, hideDate', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
hideAuthor: true,
|
||||
hideViews: false,
|
||||
hideDate: false,
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
views: 5,
|
||||
author_name: 'Test',
|
||||
author_link: '/u/test',
|
||||
};
|
||||
|
||||
const { queryByTestId } = render(<HookConsumer {...props} />);
|
||||
|
||||
expect(queryByTestId('author')).toBeNull();
|
||||
expect(queryByTestId('views')).toBeTruthy();
|
||||
expect(queryByTestId('date')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases & Date Handling', () => {
|
||||
test('Handles views when hideViews is false', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
hideViews: false,
|
||||
views: 100,
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
author_name: 'A',
|
||||
author_link: '/u/a',
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<HookConsumer {...props} />);
|
||||
expect(getByTestId('views')).toBeTruthy();
|
||||
expect(getByTestId('views').getAttribute('data-views')).toBe('100');
|
||||
});
|
||||
|
||||
test('Renders without showSelection', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: false,
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
author_name: 'A',
|
||||
author_link: '/u/a',
|
||||
};
|
||||
|
||||
const { queryByTestId } = render(<HookConsumer {...props} />);
|
||||
expect(queryByTestId('view')).toBeNull();
|
||||
});
|
||||
|
||||
test('Handles numeric publish_date correctly', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
publish_date: 1577836800000, // 2020-01-01 as timestamp
|
||||
author_name: 'A',
|
||||
author_link: '/u/a',
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<HookConsumer {...props} />);
|
||||
expect(getByTestId('date')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Handles empty author_link by setting it to null', () => {
|
||||
const props = {
|
||||
editLink: '/e',
|
||||
link: '/l',
|
||||
showSelection: true,
|
||||
singleLinkContent: false,
|
||||
author_name: 'Anonymous',
|
||||
author_link: '', // Empty link
|
||||
publish_date: '2020-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const { container } = render(<HookConsumer {...props} />);
|
||||
const authorLink = container.querySelector('[data-testid="author-link"]') as HTMLAnchorElement;
|
||||
expect(authorLink).toBeTruthy();
|
||||
expect(authorLink.getAttribute('href')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
// Mock popup components to avoid SCSS imports breaking Jest
|
||||
jest.mock('../../../src/static/js/components/_shared/popup/Popup.jsx', () => {
|
||||
const React = require('react');
|
||||
const Popup = React.forwardRef((props: any, _ref: any) => React.createElement('div', props, props.children));
|
||||
return { __esModule: true, default: Popup };
|
||||
});
|
||||
|
||||
jest.mock('../../../src/static/js/components/_shared/popup/PopupContent.jsx', () => ({
|
||||
PopupContent: (props: any) => React.createElement('div', props, props.children),
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/static/js/components/_shared/popup/PopupTrigger.jsx', () => ({
|
||||
PopupTrigger: (props: any) => React.createElement('div', props, props.children),
|
||||
}));
|
||||
|
||||
import { usePopup } from '../../../src/static/js/utils/hooks/usePopup';
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
describe('usePopup', () => {
|
||||
test('Returns a 3-tuple: [ref, PopupContent, PopupTrigger]', () => {
|
||||
let value: any;
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
value = usePopup();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(<Comp />);
|
||||
|
||||
expect(Array.isArray(value)).toBe(true);
|
||||
expect(value).toHaveLength(3);
|
||||
|
||||
const [ref, PopupContent, PopupTrigger] = value;
|
||||
|
||||
expect(ref).toBeDefined();
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
expect(typeof PopupContent).toBe('function');
|
||||
expect(typeof PopupTrigger).toBe('function');
|
||||
});
|
||||
|
||||
test('Tuple components are stable functions and refs are unique per call', () => {
|
||||
let results: any[] = [];
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
results.push(usePopup());
|
||||
results.push(usePopup());
|
||||
return null;
|
||||
};
|
||||
|
||||
render(<Comp />);
|
||||
|
||||
const [ref1, PopupContent1, PopupTrigger1] = results[0];
|
||||
const [ref2, PopupContent2, PopupTrigger2] = results[1];
|
||||
|
||||
expect(typeof PopupContent1).toBe('function');
|
||||
expect(typeof PopupTrigger1).toBe('function');
|
||||
|
||||
expect(PopupContent1).toBe(PopupContent2);
|
||||
expect(PopupTrigger1).toBe(PopupTrigger2);
|
||||
|
||||
expect(ref1).not.toBe(ref2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { act, render } from '@testing-library/react';
|
||||
|
||||
import { useTheme as useThemeHook } from '../../../src/static/js/utils/hooks/useTheme';
|
||||
|
||||
import { 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/dispatcher.js', () => ({
|
||||
register: jest.fn(),
|
||||
}));
|
||||
|
||||
function getRenderers(ThemeProvider: React.FC<{ children: React.ReactNode }>, useTheme: typeof useThemeHook) {
|
||||
const data: { current: any } = { current: undefined };
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
data.current = useTheme();
|
||||
return null;
|
||||
};
|
||||
|
||||
const wrapper: typeof ThemeProvider = ({ children }) => <ThemeProvider>{children}</ThemeProvider>;
|
||||
|
||||
return { Comp, wrapper, data };
|
||||
}
|
||||
|
||||
function getThemeConfig(override?: {
|
||||
logo?: Partial<(typeof sampleMediaCMSConfig.theme)['logo']>;
|
||||
mode?: (typeof sampleMediaCMSConfig.theme)['mode'];
|
||||
switch?: Partial<(typeof sampleMediaCMSConfig.theme)['switch']>;
|
||||
}) {
|
||||
const { logo, mode, switch: sw } = override ?? {};
|
||||
const { lightMode, darkMode } = logo ?? {};
|
||||
|
||||
const config = {
|
||||
logo: {
|
||||
lightMode: { img: lightMode?.img ?? '/img/light.png', svg: lightMode?.svg ?? '/img/light.svg' },
|
||||
darkMode: { img: darkMode?.img ?? '/img/dark.png', svg: darkMode?.svg ?? '/img/dark.svg' },
|
||||
},
|
||||
mode: mode ?? 'dark',
|
||||
switch: { enabled: sw?.enabled ?? true, position: sw?.position ?? 'sidebar' },
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useTheme', () => {
|
||||
const themeConfig = getThemeConfig();
|
||||
const darkThemeConfig = getThemeConfig({ mode: 'dark' });
|
||||
|
||||
// @todo: Revisit this
|
||||
test.each([
|
||||
[
|
||||
darkThemeConfig,
|
||||
{
|
||||
logo: darkThemeConfig.logo.darkMode.svg,
|
||||
currentThemeMode: darkThemeConfig.mode,
|
||||
changeThemeMode: expect.any(Function),
|
||||
themeModeSwitcher: themeConfig.switch,
|
||||
},
|
||||
],
|
||||
])('Validate value', async (theme, expectedResult) => {
|
||||
jest.doMock('../../../src/static/js/utils/settings/config', () => ({
|
||||
config: jest.fn(() => ({ ...jest.requireActual('../../tests-constants').sampleMediaCMSConfig, theme })),
|
||||
}));
|
||||
|
||||
const { ThemeProvider } = await import('../../../src/static/js/utils/contexts/ThemeContext');
|
||||
const { useTheme } = await import('../../../src/static/js/utils/hooks/useTheme');
|
||||
|
||||
const { Comp, wrapper, data } = getRenderers(ThemeProvider, useTheme);
|
||||
|
||||
render(<Comp />, { wrapper });
|
||||
|
||||
expect(data.current).toStrictEqual(expectedResult);
|
||||
|
||||
act(() => data.current.changeThemeMode());
|
||||
|
||||
const newThemeMode = 'light' === expectedResult.currentThemeMode ? 'dark' : 'light';
|
||||
const newThemeLogo =
|
||||
'light' === newThemeMode ? themeConfig.logo.lightMode.svg : themeConfig.logo.darkMode.svg;
|
||||
|
||||
expect(data.current).toStrictEqual({
|
||||
...expectedResult,
|
||||
logo: newThemeLogo,
|
||||
currentThemeMode: newThemeMode,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { UserProvider } from '../../../src/static/js/utils/contexts/UserContext';
|
||||
import { useUser } from '../../../src/static/js/utils/hooks/useUser';
|
||||
import { sampleMediaCMSConfig } from '../../tests-constants';
|
||||
|
||||
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||
}));
|
||||
|
||||
function getRenderers() {
|
||||
const data: { current: any } = { current: undefined };
|
||||
|
||||
const Comp: React.FC = () => {
|
||||
data.current = useUser();
|
||||
return null;
|
||||
};
|
||||
|
||||
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => <UserProvider>{children}</UserProvider>;
|
||||
|
||||
return { Comp, wrapper, data };
|
||||
}
|
||||
|
||||
describe('utils/hooks', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useUser', () => {
|
||||
test('Validate value', () => {
|
||||
const { Comp, wrapper, data } = getRenderers();
|
||||
|
||||
render(<Comp />, { wrapper });
|
||||
|
||||
expect(data.current).toStrictEqual({
|
||||
isAnonymous: sampleMediaCMSConfig.member.is.anonymous,
|
||||
username: sampleMediaCMSConfig.member.username,
|
||||
thumbnail: sampleMediaCMSConfig.member.thumbnail,
|
||||
userCan: sampleMediaCMSConfig.member.can,
|
||||
pages: sampleMediaCMSConfig.member.pages,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -10767,4 +10767,4 @@ yargs@^17.3.1, yargs@^17.5.1:
|
||||
yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user