@automattic/newspack-blocks 4.34.2 → 4.35.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +21 -0
- package/dist/editor-rtl.css +17 -17
- package/dist/editor.asset.php +1 -1
- package/dist/editor.css +17 -17
- package/dist/editor.js +22 -22
- package/includes/class-newspack-blocks-api.php +94 -0
- package/includes/class-newspack-blocks-caching.php +1 -1
- package/includes/class-newspack-blocks.php +2 -0
- package/languages/newspack-blocks-de_DE-17dfe823d00514d2459851e9b0f738b1.json +1 -0
- package/languages/newspack-blocks-de_DE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-de_DE.po +15 -10
- package/languages/newspack-blocks-es_ES-17dfe823d00514d2459851e9b0f738b1.json +1 -0
- package/languages/newspack-blocks-es_ES-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-es_ES.po +15 -10
- package/languages/newspack-blocks-fr_BE-17dfe823d00514d2459851e9b0f738b1.json +1 -0
- package/languages/newspack-blocks-fr_BE-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-fr_BE.po +15 -10
- package/languages/newspack-blocks-nb_NO-17dfe823d00514d2459851e9b0f738b1.json +1 -0
- package/languages/newspack-blocks-nb_NO-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-nb_NO.po +15 -10
- package/languages/newspack-blocks-pt_PT-17dfe823d00514d2459851e9b0f738b1.json +1 -0
- package/languages/newspack-blocks-pt_PT-53e2a1d5945b8d2b1c35e81ae1e532f3.json +1 -1
- package/languages/newspack-blocks-pt_PT.po +15 -10
- package/languages/newspack-blocks.pot +17 -11
- package/newspack-blocks.php +2 -2
- package/package.json +6 -6
- package/src/blocks/carousel/edit.js +1 -1
- package/src/blocks/homepage-articles/class-wp-rest-newspack-articles-controller.php +47 -0
- package/src/blocks/homepage-articles/store.js +101 -38
- package/src/blocks/homepage-articles/store.test.js +189 -0
- package/vendor/composer/installed.php +2 -2
|
@@ -11,6 +11,7 @@ import { set } from 'lodash';
|
|
|
11
11
|
*/
|
|
12
12
|
import { register, select } from '@wordpress/data';
|
|
13
13
|
import apiFetch from '@wordpress/api-fetch';
|
|
14
|
+
import { __ } from '@wordpress/i18n';
|
|
14
15
|
import { addQueryArgs } from '@wordpress/url';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -86,28 +87,103 @@ const POSTS_QUERIES_CACHE = {};
|
|
|
86
87
|
const createCacheKey = JSON.stringify;
|
|
87
88
|
|
|
88
89
|
/**
|
|
89
|
-
*
|
|
90
|
+
* The query a block's posts are cached under. A deduplicating block's result depends on
|
|
91
|
+
* the posts shown above it, so the exclusion list is part of its key.
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} block an object with a postsQuery and a deduplicate flag
|
|
94
|
+
* @param {Array} exclude IDs of posts already shown above the block
|
|
95
|
+
* @return {Object} posts query
|
|
96
|
+
*/
|
|
97
|
+
const effectiveQuery = ( block, exclude ) => ( block.deduplicate ? { ...block.postsQuery, exclude } : block.postsQuery );
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Fetch posts for blocks in document order, carrying the exclusion list from each
|
|
101
|
+
* deduplicating block to the next.
|
|
102
|
+
*
|
|
103
|
+
* Blocks answered from the cache are dispatched without a request. From the first block
|
|
104
|
+
* that isn't cached, the rest go to the batch endpoint, which applies the exclusion list
|
|
105
|
+
* server-side, so a page costs one request instead of one per block. When only one block
|
|
106
|
+
* needs posts, it uses the single-block endpoint instead.
|
|
90
107
|
*
|
|
91
108
|
* @yield
|
|
92
|
-
* @param {
|
|
109
|
+
* @param {Array} blockQueries objects with clientId, postsQuery and deduplicate, in document order
|
|
110
|
+
* @param {Array} exclude IDs of posts to exclude from the first deduplicating block
|
|
93
111
|
*/
|
|
94
|
-
function*
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
112
|
+
export function* fetchPostsForBlocks( blockQueries, exclude ) {
|
|
113
|
+
const { posts_rest_url: singleUrl, posts_batch_rest_url: url } = window.newspack_blocks_data;
|
|
114
|
+
// Localized as a string, and the server enforces its own limit either way.
|
|
115
|
+
const maxQueries = Number( window.newspack_blocks_data.posts_batch_max_queries );
|
|
116
|
+
const pending = [ ...blockQueries ];
|
|
117
|
+
|
|
118
|
+
const showPosts = function* ( block, posts ) {
|
|
119
|
+
POSTS_QUERIES_CACHE[ createCacheKey( effectiveQuery( block, exclude ) ) ] = posts;
|
|
120
|
+
yield put( { type: 'UPDATE_BLOCK_POSTS', clientId: block.clientId, posts } );
|
|
121
|
+
if ( block.deduplicate ) {
|
|
122
|
+
exclude = [ ...exclude, ...posts.map( post => post.id ) ];
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
while ( pending.length ) {
|
|
127
|
+
const cached = POSTS_QUERIES_CACHE[ createCacheKey( effectiveQuery( pending[ 0 ], exclude ) ) ];
|
|
128
|
+
if ( cached !== undefined ) {
|
|
129
|
+
yield* showPosts( pending.shift(), cached );
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const batch = pending.splice( 0, maxQueries );
|
|
134
|
+
|
|
135
|
+
if ( batch.length === 1 ) {
|
|
136
|
+
const [ block ] = batch;
|
|
137
|
+
try {
|
|
138
|
+
const posts = yield call( apiFetch, {
|
|
139
|
+
// `context=edit` is needed, so that custom REST fields are returned.
|
|
140
|
+
url: addQueryArgs( singleUrl, { ...effectiveQuery( block, exclude ), context: 'edit' } ),
|
|
141
|
+
} );
|
|
142
|
+
yield* showPosts( block, posts );
|
|
143
|
+
} catch ( e ) {
|
|
144
|
+
// A failed block adds nothing to the exclusion list, so later blocks can still load.
|
|
145
|
+
yield put( { type: 'UPDATE_BLOCK_ERROR', clientId: block.clientId, error: e.message } );
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let results;
|
|
151
|
+
try {
|
|
152
|
+
results = yield call( apiFetch, {
|
|
153
|
+
url,
|
|
154
|
+
method: 'POST',
|
|
155
|
+
data: {
|
|
156
|
+
exclude,
|
|
157
|
+
queries: batch.map( ( { clientId, postsQuery, deduplicate } ) => ( {
|
|
158
|
+
clientId,
|
|
159
|
+
// `context=edit` is needed, so that custom REST fields are returned.
|
|
160
|
+
postsQuery: { ...postsQuery, context: 'edit' },
|
|
161
|
+
deduplicate,
|
|
162
|
+
} ) ),
|
|
163
|
+
},
|
|
164
|
+
} );
|
|
165
|
+
} catch ( e ) {
|
|
166
|
+
// Without this batch's posts the exclusion list for later blocks is unknown.
|
|
167
|
+
for ( const block of [ ...batch, ...pending ] ) {
|
|
168
|
+
yield put( { type: 'UPDATE_BLOCK_ERROR', clientId: block.clientId, error: e.message } );
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
107
172
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
173
|
+
const resultsByClientId = Object.fromEntries( results.map( result => [ result.clientId, result ] ) );
|
|
174
|
+
for ( const block of batch ) {
|
|
175
|
+
const result = resultsByClientId[ block.clientId ];
|
|
176
|
+
if ( result?.posts ) {
|
|
177
|
+
yield* showPosts( block, result.posts );
|
|
178
|
+
} else {
|
|
179
|
+
yield put( {
|
|
180
|
+
type: 'UPDATE_BLOCK_ERROR',
|
|
181
|
+
clientId: block.clientId,
|
|
182
|
+
error: result?.error || __( 'The posts for this block could not be loaded.', 'newspack-blocks' ),
|
|
183
|
+
} );
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
111
187
|
}
|
|
112
188
|
|
|
113
189
|
/**
|
|
@@ -140,33 +216,20 @@ const createFetchPostsSaga = blockNames => {
|
|
|
140
216
|
|
|
141
217
|
const blocks = recursivelyGetBlocks( getBlocks );
|
|
142
218
|
|
|
143
|
-
const blockQueries = getBlockQueries( blocks, blockNames )
|
|
219
|
+
const blockQueries = getBlockQueries( blocks, blockNames ).map( block => ( {
|
|
220
|
+
...block,
|
|
221
|
+
deduplicate: Boolean( shouldDeduplicate( block.clientId ) ),
|
|
222
|
+
} ) );
|
|
144
223
|
|
|
145
224
|
// Use requested specific posts ids as the starting state of exclusion list.
|
|
146
|
-
const specificPostsId = blockQueries.reduce( ( acc, {
|
|
147
|
-
if (
|
|
225
|
+
const specificPostsId = blockQueries.reduce( ( acc, { deduplicate, postsQuery } ) => {
|
|
226
|
+
if ( deduplicate && postsQuery.include ) {
|
|
148
227
|
acc = [ ...acc, ...postsQuery.include ];
|
|
149
228
|
}
|
|
150
229
|
return acc;
|
|
151
230
|
}, [] );
|
|
152
231
|
|
|
153
|
-
|
|
154
|
-
while ( blockQueries.length ) {
|
|
155
|
-
const nextBlock = blockQueries.shift();
|
|
156
|
-
const deduplicate = shouldDeduplicate( nextBlock.clientId );
|
|
157
|
-
if ( deduplicate ) {
|
|
158
|
-
nextBlock.postsQuery.exclude = exclude;
|
|
159
|
-
}
|
|
160
|
-
let fetchedPostIds = [];
|
|
161
|
-
try {
|
|
162
|
-
fetchedPostIds = yield call( getPostsForBlock, nextBlock );
|
|
163
|
-
} catch ( e ) {
|
|
164
|
-
yield put( { type: 'UPDATE_BLOCK_ERROR', clientId: nextBlock.clientId, error: e.message } );
|
|
165
|
-
}
|
|
166
|
-
if ( deduplicate ) {
|
|
167
|
-
exclude = [ ...exclude, ...fetchedPostIds ];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
232
|
+
yield call( fetchPostsForBlocks, blockQueries, sanitizePostList( [ ...specificPostsId, getCurrentPostId() ] ) );
|
|
170
233
|
|
|
171
234
|
yield put( { type: 'ENABLE_UI' } );
|
|
172
235
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { runSaga } from 'redux-saga';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { getQueryArgs } from '@wordpress/url';
|
|
10
|
+
|
|
11
|
+
jest.mock( '@wordpress/api-fetch' );
|
|
12
|
+
|
|
13
|
+
const SINGLE_URL = 'https://example.test/wp-json/newspack-blocks/v1/newspack-blocks-posts';
|
|
14
|
+
const BATCH_URL = 'https://example.test/wp-json/newspack-blocks/v1/newspack-blocks-posts-batch';
|
|
15
|
+
|
|
16
|
+
// Every query draws from posts 1–20, newest first, skipping the posts in its exclusion list.
|
|
17
|
+
const findPosts = ( postsToShow, exclude ) => {
|
|
18
|
+
const ids = [];
|
|
19
|
+
for ( let id = 1; id <= 20 && ids.length < postsToShow; id++ ) {
|
|
20
|
+
if ( ! exclude.includes( id ) ) {
|
|
21
|
+
ids.push( id );
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return ids;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Stands in for both endpoints. The batch endpoint carries the exclusion list from each
|
|
28
|
+
// deduplicating query to the next; the single-block endpoint takes its list from the URL.
|
|
29
|
+
const fakeEndpoints = ( { url, data } ) => {
|
|
30
|
+
if ( ! data ) {
|
|
31
|
+
const { postsToShow, exclude = [] } = getQueryArgs( url );
|
|
32
|
+
return Promise.resolve( findPosts( Number( postsToShow ), exclude.map( Number ) ).map( id => ( { id } ) ) );
|
|
33
|
+
}
|
|
34
|
+
let exclude = [ ...data.exclude ];
|
|
35
|
+
return Promise.resolve(
|
|
36
|
+
data.queries.map( ( { clientId, postsQuery, deduplicate } ) => {
|
|
37
|
+
if ( postsQuery.fail ) {
|
|
38
|
+
return { clientId, error: 'Invalid parameter(s): postsToShow' };
|
|
39
|
+
}
|
|
40
|
+
const ids = findPosts( postsQuery.postsToShow, deduplicate ? exclude : [] );
|
|
41
|
+
if ( deduplicate ) {
|
|
42
|
+
exclude = [ ...exclude, ...ids ];
|
|
43
|
+
}
|
|
44
|
+
return { clientId, posts: ids.map( id => ( { id } ) ) };
|
|
45
|
+
} )
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const block = ( clientId, postsToShow, extra = {} ) => ( {
|
|
50
|
+
clientId,
|
|
51
|
+
postsQuery: { postsToShow, ...extra },
|
|
52
|
+
deduplicate: true,
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
describe( 'fetchPostsForBlocks', () => {
|
|
56
|
+
// The query cache lives at module scope, so each test loads a fresh copy of the store,
|
|
57
|
+
// along with the api-fetch mock that copy calls.
|
|
58
|
+
let fetchPostsForBlocks, apiFetch;
|
|
59
|
+
|
|
60
|
+
const run = async ( blocks, exclude = [] ) => {
|
|
61
|
+
const dispatched = [];
|
|
62
|
+
await runSaga( { dispatch: action => dispatched.push( action ) }, fetchPostsForBlocks, blocks, exclude ).toPromise();
|
|
63
|
+
return {
|
|
64
|
+
postsByBlock: Object.fromEntries(
|
|
65
|
+
dispatched
|
|
66
|
+
.filter( action => action.type === 'UPDATE_BLOCK_POSTS' )
|
|
67
|
+
.map( action => [ action.clientId, action.posts.map( post => post.id ) ] )
|
|
68
|
+
),
|
|
69
|
+
errorsByBlock: Object.fromEntries(
|
|
70
|
+
dispatched.filter( action => action.type === 'UPDATE_BLOCK_ERROR' ).map( action => [ action.clientId, action.error ] )
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
beforeEach( () => {
|
|
76
|
+
// wp_localize_script casts every scalar to a string, so the limit arrives as one.
|
|
77
|
+
window.newspack_blocks_data = { posts_rest_url: SINGLE_URL, posts_batch_rest_url: BATCH_URL, posts_batch_max_queries: '50' };
|
|
78
|
+
jest.isolateModules( () => {
|
|
79
|
+
apiFetch = require( '@wordpress/api-fetch' ).default;
|
|
80
|
+
( { fetchPostsForBlocks } = require( './store' ) );
|
|
81
|
+
} );
|
|
82
|
+
apiFetch.mockImplementation( fakeEndpoints );
|
|
83
|
+
} );
|
|
84
|
+
|
|
85
|
+
it( 'loads every block on the page with one request', async () => {
|
|
86
|
+
const { postsByBlock } = await run( [ block( 'a', 2 ), block( 'b', 2 ), block( 'c', 1 ) ], [ 20 ] );
|
|
87
|
+
|
|
88
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
89
|
+
expect( apiFetch.mock.calls[ 0 ][ 0 ] ).toMatchObject( { url: BATCH_URL, method: 'POST', data: { exclude: [ 20 ] } } );
|
|
90
|
+
expect( postsByBlock ).toEqual( { a: [ 1, 2 ], b: [ 3, 4 ], c: [ 5 ] } );
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
it( 'asks for custom REST fields', async () => {
|
|
94
|
+
await run( [ block( 'a', 1 ), block( 'b', 1 ) ] );
|
|
95
|
+
|
|
96
|
+
expect( apiFetch.mock.calls[ 0 ][ 0 ].data.queries[ 0 ].postsQuery.context ).toBe( 'edit' );
|
|
97
|
+
} );
|
|
98
|
+
|
|
99
|
+
it( 'uses the single-block request when only one block needs posts', async () => {
|
|
100
|
+
const { postsByBlock } = await run( [ block( 'a', 2 ) ], [ 1 ] );
|
|
101
|
+
|
|
102
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
103
|
+
const [ { url, data, method } ] = apiFetch.mock.calls[ 0 ];
|
|
104
|
+
expect( url.startsWith( SINGLE_URL + '?' ) ).toBe( true );
|
|
105
|
+
expect( getQueryArgs( url ) ).toMatchObject( { context: 'edit', exclude: [ '1' ] } );
|
|
106
|
+
expect( data ).toBeUndefined();
|
|
107
|
+
expect( method ).toBeUndefined();
|
|
108
|
+
expect( postsByBlock ).toEqual( { a: [ 2, 3 ] } );
|
|
109
|
+
} );
|
|
110
|
+
|
|
111
|
+
it( 'fetches only the last block, on its own, when it is the one that changed', async () => {
|
|
112
|
+
await run( [ block( 'a', 2 ), block( 'b', 2 ) ] );
|
|
113
|
+
const { postsByBlock } = await run( [ block( 'a', 2 ), block( 'b', 3 ) ] );
|
|
114
|
+
|
|
115
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 2 );
|
|
116
|
+
const [ { url, data } ] = apiFetch.mock.calls[ 1 ];
|
|
117
|
+
expect( data ).toBeUndefined();
|
|
118
|
+
expect( getQueryArgs( url ).exclude ).toEqual( [ '1', '2' ] );
|
|
119
|
+
expect( postsByBlock ).toEqual( { a: [ 1, 2 ], b: [ 3, 4, 5 ] } );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
it( 'reports a failed single-block request on that block only', async () => {
|
|
123
|
+
apiFetch.mockRejectedValueOnce( new Error( 'Service unavailable' ) );
|
|
124
|
+
|
|
125
|
+
const { postsByBlock, errorsByBlock } = await run( [ block( 'a', 1 ) ] );
|
|
126
|
+
|
|
127
|
+
expect( errorsByBlock ).toEqual( { a: 'Service unavailable' } );
|
|
128
|
+
expect( postsByBlock ).toEqual( {} );
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
it( 'reuses cached posts when nothing on the page changed', async () => {
|
|
132
|
+
const blocks = [ block( 'a', 2 ), block( 'b', 2 ) ];
|
|
133
|
+
await run( blocks );
|
|
134
|
+
const { postsByBlock } = await run( blocks );
|
|
135
|
+
|
|
136
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
137
|
+
expect( postsByBlock ).toEqual( { a: [ 1, 2 ], b: [ 3, 4 ] } );
|
|
138
|
+
} );
|
|
139
|
+
|
|
140
|
+
// Blocks above an edited block keep their cached posts. The edited block and everything
|
|
141
|
+
// below it are re-fetched, starting from the posts the blocks above show.
|
|
142
|
+
it( 'only re-fetches from the first block that changed', async () => {
|
|
143
|
+
await run( [ block( 'a', 2 ), block( 'b', 2 ), block( 'c', 2 ) ] );
|
|
144
|
+
const { postsByBlock } = await run( [ block( 'a', 2 ), block( 'b', 3 ), block( 'c', 2 ) ] );
|
|
145
|
+
|
|
146
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 2 );
|
|
147
|
+
const { data } = apiFetch.mock.calls[ 1 ][ 0 ];
|
|
148
|
+
expect( data.queries.map( query => query.clientId ) ).toEqual( [ 'b', 'c' ] );
|
|
149
|
+
expect( data.exclude ).toEqual( [ 1, 2 ] );
|
|
150
|
+
expect( postsByBlock ).toEqual( { a: [ 1, 2 ], b: [ 3, 4, 5 ], c: [ 6, 7 ] } );
|
|
151
|
+
} );
|
|
152
|
+
|
|
153
|
+
it( 'keeps the posts of a block without deduplication available to the blocks below it', async () => {
|
|
154
|
+
const { postsByBlock } = await run( [ block( 'a', 2 ), { ...block( 'b', 2 ), deduplicate: false }, block( 'c', 2 ) ] );
|
|
155
|
+
|
|
156
|
+
expect( postsByBlock ).toEqual( { a: [ 1, 2 ], b: [ 1, 2 ], c: [ 3, 4 ] } );
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
it( 'reports a failed query on its own block and still loads the others', async () => {
|
|
160
|
+
const { postsByBlock, errorsByBlock } = await run( [ block( 'a', 1 ), block( 'b', 1, { fail: true } ), block( 'c', 1 ) ] );
|
|
161
|
+
|
|
162
|
+
expect( errorsByBlock ).toEqual( { b: 'Invalid parameter(s): postsToShow' } );
|
|
163
|
+
expect( postsByBlock ).toEqual( { a: [ 1 ], c: [ 2 ] } );
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
// Later blocks' exclusion lists depend on the failed batch, so they can't be loaded either.
|
|
167
|
+
it( 'reports an error on every remaining block when a batch request fails', async () => {
|
|
168
|
+
window.newspack_blocks_data.posts_batch_max_queries = '2';
|
|
169
|
+
apiFetch.mockImplementationOnce( fakeEndpoints ).mockRejectedValueOnce( new Error( 'Service unavailable' ) );
|
|
170
|
+
|
|
171
|
+
const { postsByBlock, errorsByBlock } = await run( [ block( 'a', 1 ), block( 'b', 1 ), block( 'c', 1 ), block( 'd', 1 ), block( 'e', 1 ) ] );
|
|
172
|
+
|
|
173
|
+
expect( postsByBlock ).toEqual( { a: [ 1 ], b: [ 2 ] } );
|
|
174
|
+
expect( errorsByBlock ).toEqual( { c: 'Service unavailable', d: 'Service unavailable', e: 'Service unavailable' } );
|
|
175
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 2 );
|
|
176
|
+
} );
|
|
177
|
+
|
|
178
|
+
it( 'splits a page larger than the batch limit and carries deduplication across batches', async () => {
|
|
179
|
+
window.newspack_blocks_data.posts_batch_max_queries = '2';
|
|
180
|
+
|
|
181
|
+
const { postsByBlock } = await run( [ block( 'a', 1 ), block( 'b', 1 ), block( 'c', 1 ), block( 'd', 1 ), block( 'e', 1 ) ] );
|
|
182
|
+
|
|
183
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 3 );
|
|
184
|
+
expect( apiFetch.mock.calls[ 1 ][ 0 ].data.exclude ).toEqual( [ 1, 2 ] );
|
|
185
|
+
// The last block is left over on its own, so it uses the single-block request.
|
|
186
|
+
expect( getQueryArgs( apiFetch.mock.calls[ 2 ][ 0 ].url ).exclude ).toEqual( [ '1', '2', '3', '4' ] );
|
|
187
|
+
expect( postsByBlock ).toEqual( { a: [ 1 ], b: [ 2 ], c: [ 3 ], d: [ 4 ], e: [ 5 ] } );
|
|
188
|
+
} );
|
|
189
|
+
} );
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'name' => 'automattic/newspack-blocks',
|
|
4
4
|
'pretty_version' => 'dev-main',
|
|
5
5
|
'version' => 'dev-main',
|
|
6
|
-
'reference' => '
|
|
6
|
+
'reference' => 'ce1df4abbe28872d98ac84032eabca65ae1b6abf',
|
|
7
7
|
'type' => 'wordpress-plugin',
|
|
8
8
|
'install_path' => __DIR__ . '/../../',
|
|
9
9
|
'aliases' => array(),
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
'automattic/newspack-blocks' => array(
|
|
14
14
|
'pretty_version' => 'dev-main',
|
|
15
15
|
'version' => 'dev-main',
|
|
16
|
-
'reference' => '
|
|
16
|
+
'reference' => 'ce1df4abbe28872d98ac84032eabca65ae1b6abf',
|
|
17
17
|
'type' => 'wordpress-plugin',
|
|
18
18
|
'install_path' => __DIR__ . '/../../',
|
|
19
19
|
'aliases' => array(),
|