effects.spec.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import {
  2. ActionStateSetRemote,
  3. ActionTypeLocal,
  4. ActionTypeRemote,
  5. masterSet,
  6. playPaused,
  7. queueOrdered,
  8. queuePushed,
  9. queueRemoved,
  10. queueShifted,
  11. seeked,
  12. songInfoFetched,
  13. stateSet,
  14. } from '../actions';
  15. import { GlobalState, initialState } from '../reducer';
  16. import { Song } from '../types';
  17. import { MusicPlayer } from '../types/state';
  18. import { globalEffects } from './effects';
  19. describe(globalEffects.name, () => {
  20. describe(ActionTypeLocal.StateSet, () => {
  21. it('should create a remote state set action', () => {
  22. expect.assertions(1);
  23. const localPlayer: MusicPlayer = {
  24. songId: 123,
  25. playing: false,
  26. currentTime: 83,
  27. seekTime: 87,
  28. master: 'my-client',
  29. queue: [],
  30. };
  31. const prevState: GlobalState = {
  32. ...initialState,
  33. myClientName: 'my-client-name',
  34. };
  35. const action = stateSet(localPlayer);
  36. const result = globalEffects(prevState, action);
  37. expect(result).toStrictEqual<ActionStateSetRemote>({
  38. type: ActionTypeRemote.StateSet,
  39. payload: localPlayer,
  40. });
  41. });
  42. });
  43. describe(ActionTypeLocal.Seeked, () => {
  44. const stateMaster: GlobalState = {
  45. ...initialState,
  46. player: {
  47. songId: 123,
  48. playing: false,
  49. currentTime: 83,
  50. seekTime: 87,
  51. master: 'my-client-name',
  52. queue: [],
  53. },
  54. myClientName: 'my-client-name',
  55. };
  56. const stateSlave: GlobalState = {
  57. ...initialState,
  58. player: { ...initialState.player, master: 'some-master-client' },
  59. myClientName: 'some-slave-client',
  60. };
  61. const action = seeked(776);
  62. describe.each`
  63. clientType | state
  64. ${'master'} | ${stateMaster}
  65. ${'a slave'} | ${stateSlave}
  66. `('when the client is $clientType', ({ state }) => {
  67. it('should create a remote state set action', () => {
  68. expect.assertions(1);
  69. const result = globalEffects(state, action);
  70. expect(result).toStrictEqual<ActionStateSetRemote>({
  71. type: ActionTypeRemote.StateSet,
  72. payload: { ...state.player, seekTime: 776 },
  73. });
  74. });
  75. });
  76. });
  77. describe(ActionTypeLocal.MasterSet, () => {
  78. const stateMasterWentAway: GlobalState = {
  79. ...initialState,
  80. clientList: [{ name: 'my-client-name', lastPing: 0 }],
  81. player: {
  82. songId: 123,
  83. playing: true,
  84. currentTime: 83,
  85. seekTime: 5,
  86. master: 'some-master-went-away',
  87. queue: [],
  88. },
  89. myClientName: 'my-client-name',
  90. };
  91. const action = masterSet();
  92. it('should return a StateSet action informing other clients that we are the new master', () => {
  93. expect.assertions(1);
  94. const result = globalEffects(stateMasterWentAway, action);
  95. expect(result).toStrictEqual<ActionStateSetRemote>({
  96. type: ActionTypeRemote.StateSet,
  97. payload: {
  98. songId: 123,
  99. playing: false,
  100. currentTime: 83,
  101. seekTime: -1,
  102. master: 'my-client-name',
  103. queue: [],
  104. },
  105. });
  106. });
  107. describe('when the action specified a particular client', () => {
  108. it('should return a StateSet action informing the new client to resume playback', () => {
  109. expect.assertions(1);
  110. const result = globalEffects(stateMasterWentAway, masterSet('other-client'));
  111. expect(result).toStrictEqual<ActionStateSetRemote>({
  112. type: ActionTypeRemote.StateSet,
  113. payload: {
  114. songId: 123,
  115. playing: true,
  116. currentTime: 83,
  117. seekTime: 83,
  118. master: 'other-client',
  119. queue: [],
  120. },
  121. });
  122. });
  123. });
  124. });
  125. describe(ActionTypeLocal.PlayPaused, () => {
  126. const statePriorMaster: GlobalState = {
  127. ...initialState,
  128. player: {
  129. songId: 123,
  130. playing: true,
  131. currentTime: 83,
  132. seekTime: 5,
  133. master: 'some-master-client',
  134. queue: [],
  135. },
  136. myClientName: 'some-master-client',
  137. };
  138. const action = playPaused();
  139. describe('when the client is master', () => {
  140. it('should return null', () => {
  141. expect.assertions(1);
  142. expect(globalEffects(statePriorMaster, action)).toBeNull();
  143. });
  144. });
  145. describe('when the client is a slave', () => {
  146. const stateSlave: GlobalState = {
  147. ...statePriorMaster,
  148. myClientName: 'some-slave-client',
  149. };
  150. it('should return a StateSet action informing other clients of the updated playing state', () => {
  151. expect.assertions(1);
  152. const result = globalEffects(stateSlave, action);
  153. expect(result).toStrictEqual<ActionStateSetRemote>({
  154. type: ActionTypeRemote.StateSet,
  155. payload: {
  156. songId: 123,
  157. playing: false,
  158. currentTime: 83,
  159. seekTime: 5,
  160. master: 'some-master-client',
  161. queue: [],
  162. },
  163. });
  164. });
  165. });
  166. });
  167. describe(ActionTypeLocal.SongInfoFetched, () => {
  168. const statePriorMaster: GlobalState = {
  169. ...initialState,
  170. player: {
  171. songId: 123,
  172. playing: true,
  173. currentTime: 83,
  174. seekTime: 5,
  175. master: 'some-master-client',
  176. queue: [],
  177. },
  178. myClientName: 'some-master-client',
  179. };
  180. const action = songInfoFetched({ id: 185 } as Song, true);
  181. describe('when the client is master', () => {
  182. it('should return null', () => {
  183. expect.assertions(1);
  184. expect(globalEffects(statePriorMaster, action)).toBeNull();
  185. });
  186. });
  187. describe('when the client is a slave', () => {
  188. const stateSlave: GlobalState = {
  189. ...statePriorMaster,
  190. myClientName: 'some-slave-client',
  191. };
  192. it('should return a StateSet action informing other clients of the changed song', () => {
  193. expect.assertions(1);
  194. const result = globalEffects(stateSlave, action);
  195. expect(result).toStrictEqual<ActionStateSetRemote>({
  196. type: ActionTypeRemote.StateSet,
  197. payload: {
  198. songId: 185,
  199. playing: true,
  200. currentTime: 0,
  201. seekTime: 0,
  202. master: 'some-master-client',
  203. queue: [],
  204. },
  205. });
  206. });
  207. describe('when the action is not set to replace the current song', () => {
  208. const actionNoReplace = songInfoFetched({ id: 185 } as Song, false);
  209. it('should return null', () => {
  210. expect.assertions(1);
  211. const result = globalEffects(stateSlave, actionNoReplace);
  212. expect(result).toBeNull();
  213. });
  214. });
  215. });
  216. });
  217. describe(ActionTypeLocal.QueuePushed, () => {
  218. const action = queuePushed([184, 79]);
  219. it('should add to the end of the queue', () => {
  220. expect.assertions(1);
  221. const result = globalEffects(
  222. {
  223. ...initialState,
  224. player: { ...initialState.player, master: 'some-master', queue: [23] },
  225. },
  226. action,
  227. );
  228. expect(result).toStrictEqual<ActionStateSetRemote>({
  229. type: ActionTypeRemote.StateSet,
  230. payload: {
  231. ...initialState.player,
  232. master: 'some-master',
  233. queue: [23, 184, 79],
  234. },
  235. });
  236. });
  237. describe('when the songs are already in the queue', () => {
  238. it('should not modify the queue', () => {
  239. expect.assertions(1);
  240. const result = globalEffects(
  241. {
  242. ...initialState,
  243. player: { ...initialState.player, queue: [184, 23, 79] },
  244. },
  245. action,
  246. );
  247. expect(result).toBeNull();
  248. });
  249. });
  250. });
  251. describe(ActionTypeLocal.QueueShifted, () => {
  252. const action = queueShifted();
  253. const stateWithQueue: GlobalState = {
  254. ...initialState,
  255. player: { ...initialState.player, master: 'some-master', queue: [8843, 23] },
  256. };
  257. it('should play the first song on the queue and remove it from the queue', () => {
  258. expect.assertions(1);
  259. const result = globalEffects(stateWithQueue, action);
  260. expect(result).toStrictEqual<ActionStateSetRemote>({
  261. type: ActionTypeRemote.StateSet,
  262. payload: {
  263. ...initialState.player,
  264. master: 'some-master',
  265. playing: true,
  266. songId: 8843,
  267. currentTime: 0,
  268. seekTime: 0,
  269. queue: [23],
  270. },
  271. });
  272. });
  273. });
  274. describe(ActionTypeLocal.QueueRemoved, () => {
  275. const action = queueRemoved(84);
  276. it('should remove the given song ID from the queue', () => {
  277. expect.assertions(1);
  278. const result = globalEffects(
  279. {
  280. ...initialState,
  281. player: { ...initialState.player, master: 'some-master', queue: [17, 84, 23] },
  282. },
  283. action,
  284. );
  285. expect(result).toStrictEqual<ActionStateSetRemote>({
  286. type: ActionTypeRemote.StateSet,
  287. payload: {
  288. ...initialState.player,
  289. master: 'some-master',
  290. queue: [17, 23],
  291. },
  292. });
  293. });
  294. });
  295. describe(ActionTypeLocal.QueueOrdered, () => {
  296. it.each`
  297. direction | delta | expectedResult
  298. ${'forwards'} | ${1} | ${[17, 23, 84]}
  299. ${'backwards'} | ${-1} | ${[84, 17, 23]}
  300. `('should reorder ($direction) the given song ID', ({ delta, expectedResult }) => {
  301. const action = queueOrdered(84, delta);
  302. expect.assertions(1);
  303. const result = globalEffects(
  304. {
  305. ...initialState,
  306. player: { ...initialState.player, master: 'some-master', queue: [17, 84, 23] },
  307. },
  308. action,
  309. );
  310. expect(result).toStrictEqual<ActionStateSetRemote>({
  311. type: ActionTypeRemote.StateSet,
  312. payload: {
  313. ...initialState.player,
  314. master: 'some-master',
  315. queue: expectedResult,
  316. },
  317. });
  318. });
  319. });
  320. });