[add] Image.queryCache API

Image.queryCache is a React Native method that allows the user to see
if a given uri is in the cache. It specifies three return options:
disk, memory or both. Choosing both seemed most appropriate since
we don't really know and can't confirm.

The way Image is implemented, if RNW thinks the image might already
be loaded, it displays it immediately. Otherwise there can be a flash
of a frame. In some scenarios, if the user chooses to preload and then
make an Image element, it would still flash. By adding it to the cache,
we can prevent that.

Close #1344
This commit is contained in:
Charlie Croom
2019-05-11 01:06:55 -07:00
committed by Nicolas Gallagher
parent 39d2e18ccf
commit 950bfd039c
2 changed files with 44 additions and 2 deletions
@@ -14,6 +14,7 @@ const findImageSurfaceStyle = wrapper => StyleSheet.flatten(wrapper.childAt(0).p
describe('components/Image', () => {
beforeEach(() => {
ImageUriCache._entries = {};
window.Image = jest.fn(() => ({}));
});
@@ -111,8 +112,8 @@ describe('components/Image', () => {
});
const onLoadStub = jest.fn();
const uri = 'https://test.com/img.jpg';
shallow(<Image onLoad={onLoadStub} source={uri} />);
ImageUriCache.add(uri);
shallow(<Image onLoad={onLoadStub} source={uri} />);
jest.runOnlyPendingTimers();
expect(ImageLoader.load).not.toBeCalled();
expect(onLoadStub).toBeCalled();
@@ -164,6 +165,19 @@ describe('components/Image', () => {
expect(component.find('img')).toBeUndefined;
});
test('is set immediately if the image was preloaded', () => {
const uri = 'https://yahoo.com/favicon.ico';
ImageLoader.load = jest.fn().mockImplementationOnce((_, onLoad, onError) => {
onLoad();
});
return Image.prefetch(uri).then(() => {
const source = { uri };
const component = shallow(<Image source={source} />, { disableLifecycleMethods: true });
expect(component.find('img').prop('src')).toBe(uri);
ImageUriCache.remove(uri);
});
});
test('is set immediately if the image has already been loaded', () => {
const uriOne = 'https://google.com/favicon.ico';
const uriTwo = 'https://twitter.com/favicon.ico';
@@ -247,4 +261,17 @@ describe('components/Image', () => {
const component = shallow(<Image onResponderGrant={fn} />);
expect(component.prop('onResponderGrant')).toBe(fn);
});
test('queryCache', () => {
const uriOne = 'https://google.com/favicon.ico';
const uriTwo = 'https://twitter.com/favicon.ico';
ImageUriCache.add(uriOne);
ImageUriCache.add(uriTwo);
return Image.queryCache([uriOne, uriTwo, 'oops']).then(res => {
expect(res).toEqual({
[uriOne]: 'disk/memory',
[uriTwo]: 'disk/memory'
});
});
});
});
+16 -1
View File
@@ -130,7 +130,22 @@ class Image extends Component<*, State> {
}
static prefetch(uri) {
return ImageLoader.prefetch(uri);
return ImageLoader.prefetch(uri).then(() => {
// Add the uri to the cache so it can be immediately displayed when used
// but also immediately remove it to correctly reflect that it has no active references
ImageUriCache.add(uri);
ImageUriCache.remove(uri);
});
}
static queryCache(uris) {
const result = {};
uris.forEach(u => {
if (ImageUriCache.has(u)) {
result[u] = 'disk/memory';
}
});
return Promise.resolve(result);
}
_filterId = 0;