PluginProbe
BeyondWords – AI audio for publishers / 7.2.0
BeyondWords – AI audio for publishers v7.2.0
7.2.0 7.1.0 trunk 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.3.0 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 All 44 releases
speechkit / src / settings / store / index.test.js

index.test.js in BeyondWords – AI audio for publishers 7.2.0, at src/settings/store/index.test.js

171 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global jest, describe, it, expect, beforeEach */
2
3 /**
4 * WordPress dependencies
5 */
6 import apiFetch from '@wordpress/api-fetch';
7 import { createRegistry } from '@wordpress/data';
8
9 /**
10 * Internal dependencies
11 */
12 import store from './index';
13
14 jest.mock( '@wordpress/api-fetch' );
15
16 const STORE = 'beyondwords/settings';
17
18 // Distinct data per resolver argument, so a value leaking from one argument's
19 // fetch into another's slot is observable in the assertions below.
20 const VOICES = {
21 en_US: [ { id: 1, name: 'Amy' } ],
22 fr_FR: [ { id: 2, name: 'Céline' } ],
23 };
24
25 const PROJECT = {
26 11111: { id: 11111, language: 'en_US' },
27 22222: { id: 22222, language: 'fr_FR' },
28 };
29
30 const VIDEO_SIZES = {
31 11111: [ { name: 'square', enabled: true } ],
32 22222: [ { name: 'portrait', enabled: true } ],
33 };
34
35 describe( 'beyondwords/settings store', () => {
36 let registry;
37
38 beforeEach( () => {
39 apiFetch.mockReset();
40 apiFetch.mockImplementation( ( { path } ) => {
41 let m = path.match( /languages\/([^/]+)\/voices$/ );
42 if ( m ) {
43 return Promise.resolve( VOICES[ m[ 1 ] ] ?? [] );
44 }
45 m = path.match( /projects\/([^/]+)\/video-settings$/ );
46 if ( m ) {
47 return Promise.resolve( {
48 sizes: VIDEO_SIZES[ m[ 1 ] ] ?? [],
49 } );
50 }
51 m = path.match( /projects\/([^/]+)$/ );
52 if ( m ) {
53 return Promise.resolve( PROJECT[ m[ 1 ] ] ?? {} );
54 }
55 return Promise.resolve( [] );
56 } );
57
58 registry = createRegistry();
59 registry.register( store );
60 } );
61
62 describe( 'getVoices', () => {
63 it( 'keeps each language in its own slot after another is fetched', async () => {
64 const resolve = registry.resolveSelect( STORE );
65
66 // Fetch A, then B. With the old single shared `voices` slot, B's
67 // response overwrote the value A's already-finished resolution read.
68 expect( await resolve.getVoices( 'en_US' ) ).toEqual(
69 VOICES.en_US
70 );
71 expect( await resolve.getVoices( 'fr_FR' ) ).toEqual(
72 VOICES.fr_FR
73 );
74
75 // No refetch on re-read — resolution for [ 'en_US' ] is finished.
76 const select = registry.select( STORE );
77 expect( select.getVoices( 'en_US' ) ).toEqual( VOICES.en_US );
78 expect( select.getVoices( 'fr_FR' ) ).toEqual( VOICES.fr_FR );
79 } );
80
81 it( 'resolves each language once and reuses the cached result', async () => {
82 const resolve = registry.resolveSelect( STORE );
83 await resolve.getVoices( 'en_US' );
84 await resolve.getVoices( 'en_US' );
85 await resolve.getVoices( 'fr_FR' );
86
87 // en_US fetched once (second call is a cache hit), fr_FR once.
88 expect( apiFetch ).toHaveBeenCalledTimes( 2 );
89 } );
90
91 it( 'returns an empty list for a language that has not been fetched', () => {
92 expect( registry.select( STORE ).getVoices( 'de_DE' ) ).toEqual(
93 []
94 );
95 } );
96 } );
97
98 describe( 'getProject / getVideoSizes', () => {
99 it( 'keeps each project id in its own slot', async () => {
100 const resolve = registry.resolveSelect( STORE );
101 await resolve.getProject( '11111' );
102 await resolve.getProject( '22222' );
103 await resolve.getVideoSizes( '11111' );
104 await resolve.getVideoSizes( '22222' );
105
106 const select = registry.select( STORE );
107 expect( select.getProject( '11111' ) ).toEqual( PROJECT[ 11111 ] );
108 expect( select.getProject( '22222' ) ).toEqual( PROJECT[ 22222 ] );
109 expect( select.getVideoSizes( '11111' ) ).toEqual(
110 VIDEO_SIZES[ 11111 ]
111 );
112 expect( select.getVideoSizes( '22222' ) ).toEqual(
113 VIDEO_SIZES[ 22222 ]
114 );
115 } );
116
117 it( 'defaults to {} / [] and skips the fetch for a falsy id', async () => {
118 const resolve = registry.resolveSelect( STORE );
119 expect( await resolve.getProject( '' ) ).toEqual( {} );
120 expect( await resolve.getVideoSizes( '' ) ).toEqual( [] );
121 expect( apiFetch ).not.toHaveBeenCalled();
122 } );
123 } );
124
125 describe( 'list resolvers', () => {
126 // [ selector, args, wraps the payload as the endpoint does ]
127 const LISTS = [
128 [ 'getLanguages', [], ( v ) => v ],
129 [ 'getVoices', [ 'en_US' ], ( v ) => v ],
130 [ 'getScriptTemplates', [], ( v ) => v ],
131 [ 'getVideoTemplates', [], ( v ) => v ],
132 [ 'getVideoSizes', [ '11111' ], ( v ) => ( { sizes: v } ) ],
133 ];
134
135 const NON_ARRAYS = [
136 [ 'an API error body', { code: 401, message: 'Unauthorized' } ],
137 [ 'false', false ],
138 [ 'null', null ],
139 [ 'a string', 'nope' ],
140 ];
141
142 describe.each( LISTS )( '%s', ( selector, args, wrap ) => {
143 it.each( NON_ARRAYS )(
144 'returns [] when the endpoint returns %s',
145 async ( _label, payload ) => {
146 apiFetch.mockResolvedValue( wrap( payload ) );
147
148 const resolve = registry.resolveSelect( STORE );
149 expect( await resolve[ selector ]( ...args ) ).toEqual(
150 []
151 );
152 }
153 );
154
155 it( 'marks the resolution failed and keeps [] when the fetch rejects', async () => {
156 apiFetch.mockRejectedValue( new Error( 'offline' ) );
157
158 await expect(
159 registry.resolveSelect( STORE )[ selector ]( ...args )
160 ).rejects.toThrow( 'offline' );
161
162 const select = registry.select( STORE );
163 expect( select.hasResolutionFailed( selector, args ) ).toBe(
164 true
165 );
166 expect( select[ selector ]( ...args ) ).toEqual( [] );
167 } );
168 } );
169 } );
170 } );
171