Skip to content

Commit 93b0aa1

Browse files
🐛 Fixed race condition in Unsplash search that causes mixed results (#24583)
fixes 24584 This PR fixes a race condition in the Unsplash search flow where results from an intermediate search term (e.g., "germ") may appear alongside the final term (e.g., "germany") when users pause briefly while typing. --------- Co-authored-by: Steve Larson <9larsons@gmail.com>
1 parent 56c7e7a commit 93b0aa1

2 files changed

Lines changed: 58 additions & 5 deletions

File tree

ghost/admin/app/services/unsplash.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,16 @@ export default Service.extend({
113113
yield timeout(DEBOUNCE_MS);
114114

115115
let url = `${API_URL}/search/photos?query=${term}&per_page=30`;
116-
yield this._makeRequest(url);
116+
yield this._makeRequest(url, {searchTermAtRequest: term});
117117
}).restartable(),
118118

119-
_addPhotosFromResponse(response) {
119+
_addPhotosFromResponse(response, searchTermAtRequest) {
120+
// If a search term was provided at the time of the request,
121+
// we only want to add photos if the search term matches the current one.
122+
if (searchTermAtRequest && searchTermAtRequest !== this.searchTerm) {
123+
return;
124+
}
125+
120126
let photos = response.results || response;
121127

122128
photos.forEach(photo => this._addPhoto(photo));
@@ -189,9 +195,9 @@ export default Service.extend({
189195

190196
return fetch(url, {headers})
191197
.then(response => this._checkStatus(response))
192-
.then(response => this._extractPagination(response))
198+
.then(response => this._extractPagination(response, options.searchTermAtRequest))
193199
.then(response => response.json())
194-
.then(response => this._addPhotosFromResponse(response))
200+
.then(response => this._addPhotosFromResponse(response, options.searchTermAtRequest))
195201
.catch(() => {
196202
// if the error text isn't already set then we've get a connection error from `fetch`
197203
if (!options.ignoreErrors && !this.error) {
@@ -233,7 +239,11 @@ export default Service.extend({
233239
});
234240
},
235241

236-
_extractPagination(response) {
242+
_extractPagination(response, searchTermAtRequest) {
243+
if (searchTermAtRequest && searchTermAtRequest !== this.searchTerm) {
244+
return response;
245+
}
246+
237247
let pagination = {};
238248
let linkRegex = new RegExp('<(.*)>; rel="(.*)"');
239249
let {link: links} = response.headers.map;

ghost/admin/tests/unit/services/unsplash-test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,49 @@ describe('Unit: Service: unsplash', function () {
2727
it('can load next page of search results');
2828
it('clears photos when starting new search');
2929
it('loads new when query is cleared');
30+
31+
it('discards responses that no longer match the current search term', async function () {
32+
server.get('https://api.unsplash.com/photos', function () {
33+
return [200, {'Content-Type': 'application/json'}, JSON.stringify([])];
34+
});
35+
server.get('https://api.unsplash.com/search/photos', function () {
36+
return [200, {
37+
'Content-Type': 'application/json',
38+
Link: '<https://api.unsplash.com/search/photos?query=cat&page=2>; rel="next"'
39+
}, JSON.stringify({results: [{id: 'cat-photo', width: 100, height: 100}]})];
40+
});
41+
42+
let service = this.owner.lookup('service:unsplash');
43+
await settled();
44+
45+
// response for "cat" arrives after the term has changed to "dog"
46+
service.set('searchTerm', 'dog');
47+
await service._makeRequest('https://api.unsplash.com/search/photos?query=cat', {searchTermAtRequest: 'cat'});
48+
49+
expect(service.photos.length).to.equal(0);
50+
expect(service._pagination.next).to.not.exist;
51+
});
52+
53+
it('adds photos and pagination when the response matches the current search term', async function () {
54+
server.get('https://api.unsplash.com/photos', function () {
55+
return [200, {'Content-Type': 'application/json'}, JSON.stringify([])];
56+
});
57+
server.get('https://api.unsplash.com/search/photos', function () {
58+
return [200, {
59+
'Content-Type': 'application/json',
60+
Link: '<https://api.unsplash.com/search/photos?query=cat&page=2>; rel="next"'
61+
}, JSON.stringify({results: [{id: 'cat-photo', width: 100, height: 100}]})];
62+
});
63+
64+
let service = this.owner.lookup('service:unsplash');
65+
await settled();
66+
67+
service.set('searchTerm', 'cat');
68+
await service._makeRequest('https://api.unsplash.com/search/photos?query=cat', {searchTermAtRequest: 'cat'});
69+
70+
expect(service.photos.length).to.equal(1);
71+
expect(service._pagination.next).to.exist;
72+
});
3073
});
3174

3275
describe('columns', function () {

0 commit comments

Comments
 (0)