Skip to content

Image: support ImageSource with headers #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
27 changes: 25 additions & 2 deletions packages/react-native-web-examples/pages/image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ const dataBase64Svg =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMjAwJyBoZWlnaHQ9JzIwMCcgZmlsbD0iIzAwMDAwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGQ9Ik0yNS44NjcsNDguODUzQzMyLjgwNiw1MC4xNzYsNDYuNDYsNTIuNSw2MS4yMTUsNTIuNWgwLjAwNWM5LjcxLDAsMTguNDAxLTEuMDU3LDI1LjkzOC0yLjkxMyAgIGMwLjE1OS0wLjA0NiwwLjM1LTAuMTM1LDAuNTY1LTAuMTg3YzAuMjgyLTAuMDcyLDAuNTY1LTAuMTY0LDAuODQ0LTAuMjM4YzMuMTg0LTAuOTY0LDIuNTc3LTMuMDUxLDIuMTk5LTMuODUyICAgYy00LjE2Ni03LjcxOS0xNS4wODYtMjMuNDE1LTM1LjAyOC0yMy40MTVjLTIyLjE2OSwwLTMwLjI2MiwxMC42MzUtMzMuMTQsMTkuNTg5QzIyLjU0NSw0Mi4zMzMsMjIuNDA3LDQ3LjEzNSwyNS44NjcsNDguODUzeiAgICBNMjguNjc2LDM4LjAzMmMwLjAxMy0wLjAzNiwwLjYxNC0xLjYyNiwxLjkyMy0xLjAwOGMxLjEzMywwLjUzNSwwLjk2MSwxLjU2MywwLjg4NywxLjg1Yy0wLjAwNywwLjAyNC0wLjAxNCwwLjA0OC0wLjAyMSwwLjA3MyAgIGMwLDAuMDAxLTAuMDAxLDAuMDA0LTAuMDAxLDAuMDA0bDAsMGMtMC4yNDksMC45MjktMC40MDQsMi4wODYtMC4wMTcsMi44NmMwLjE2LDAuMzE5LDAuNDkyLDAuNzY4LDEuNTQyLDAuOTg3bDAuMzY2LDAuMDc3ICAgYzIwLjgxNiw0LjM2LDM2LDIuOTMzLDQ1LjY3OCwwLjYyNmwtMC4wMDQsMC4wMDJjMCwwLDAuMDA1LTAuMDAyLDAuMDA3LTAuMDAzYzAuMjEyLTAuMDUsMC40MjEtMC4xMDEsMC42MjgtMC4xNTIgICBjMC41MDktMC4wNSwxLjE3MywwLjA3OCwxLjM5OSwxYzAuMzUxLDEuNDI0LTAuOTczLDEuODk1LTEuMjE3LDEuOTY5Yy01LjMyNSwxLjI3OS0xMi4yNjYsMi4zMDYtMjAuODM1LDIuMzA3ICAgYy03LjUwNSwwLTE2LjI1NS0wLjc4Ny0yNi4yNTctMi44ODJsLTAuMzY0LTAuMDc3Yy0yLjEyLTAuNDQyLTMuMTExLTEuNjMzLTMuNTY5LTIuNTU1QzI3Ljk4NSw0MS40MjEsMjguMjgxLDM5LjQxNiwyOC42NzYsMzguMDMyICAgeiI+PC9wYXRoPjxjaXJjbGUgY3g9IjEwLjQ5MyIgY3k9IjIzLjQ1NSIgcj0iMC42MTkiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0yLjA4LDI4LjMwOGMwLjY3Ni0wLjE3OCwwLjk4My0wLjM1MiwxLjE3NC0wLjVDNC42OSwyNi42OSw2LjUsMjcuNDgzLDcuNSwyOC4zNTd2MC4wMDJjMCwwLDEuNzExLDEuMjM1LDAuNzM3LDIuMjAyICAgYy0wLjk3NCwwLjk2NS0yLjMxOSwwLjAwNi0yLjMxOSwwLjAwNmwwLjAzNSwwLjAxNmMtMC4zMjctMC4yMDMtMC42LTAuNTYxLTAuNzgtMC41ODRjLTAuMzcsMC4yNi0wLjg3NiwwLjUtMS40NzYsMC41SDMuNyAgIGMwLDAtMS4zNDUsMC43MDksMC4xNzgsMS42NTJjMC4wMDEsMC4wMDEsMC4wMDIsMC4wNzIsMC4wMDQsMC4wNzNjMy45MzksMi4zNDIsOC4yNzEsNS43MDEsOC4yNzEsOC44OCAgIGMwLDAuNjkxLDAuMiwxNy4wNDIsMTcuNjI2LDI0LjczOWwwLjk2NywwLjQ0MmwtMC4xLDEuMDU5Yy0wLjQyMSw0LjM5LDEuMTQ1LDEwLjE5MSwxMC45OTMsMTIuODg4bDAuMTEzLDAuMDM4ICAgYzAuMDY3LDAuMDIzLDYuNzMyLDIuNDI5LDEwLjkwNywyLjQyOWMxLjU4NCwwLDIuMTU1LTAuMzUyLDIuMjQzLTAuNTYxYzAuMDg1LTAuMjAyLDAuNjEyLTIuMTY0LTYuMzMyLTkuMzg3bDAuMDAyLTAuMTgzICAgYzAsMC0yLjQ3Ny0zLjA3LDEuNTMzLTMuMDdjMC4wMSwwLDAuMDE5LDAsMC4wMjksMGMxLjI4NSwwLDIuNjA4LDAuMjE1LDMuOTgsMC4xODRjNC43NzEtMC4xMTcsOS4zMTYtMC40MjUsMTMuNTA2LTEuMDk2ICAgbDAuNDc0LTAuMDI4bDAuNjY4LDAuMTU4YzkuNjUxLDQuOTQ4LDE2LjczOCw3LjcxNiwxOS43MzgsNy43MTZ2MC4wMDZjMCwwLDAuMTY0LDAuMDExLDAuMjMsMC4wMDQgICBjLTAuMTg5LTAuNzIzLTIuMjMtMi44LTcuMjMtOS4wNzl2MC4wMjFjMCwwLTEuNTEyLTEuNjU4LDAuNzk3LTIuNjUzYzAuMDYzLTAuMDI2LDAuMDA4LDAuMDIzLDAuMDYtMC4wMDEgICBjOC42MzktMy41MDksMTMuNTAxLTguMjA0LDE1LjQxMS0xMS43NzVjMS4xNDUtMi4xMjksMC4yMDYtMi43ODQtMC42NTktMi45NzZjLTAuMzE3LTAuMDM4LTAuNjM0LTAuMDYyLTAuOTEyLTAuMDYyICAgYy0wLjIwNSwwLTAuMzc5LDAuMDEtMC41MjgsMC4wMjdsLTMuMTQzLDEuMjE0QzgzLjczMiw1My45MjYsNzMuMjE4LDU1LjUsNjEuMjIsNTUuNWMtMC4wMDIsMC0wLjAwNSwwLTAuMDA1LDAgICBjLTE1LjEyOCwwLTI5LjEwMS0yLjQzMi0zNi4wODMtMy43NzFsLTAuMTczLTAuMTExbC0wLjE2LTAuMTI2Yy01Ljg1OC0yLjY4MS01LjEzNy0xMC4yMDItNS4xMDMtMTAuNTE5bDAuMDYtMC4zICAgYzAuODk1LTIuODM4LDIuNDY3LTYuMzUyLDUuMjEzLTkuNzE5Yy0xLjgwOC0xLjM2OS00LjU5LTQuMTg4LTQuNDMtOC40OTRjMC4wNDYtMS4yNDQtMC40ODYtMi41MDgtMS40OTgtMy41NTkgICBjLTEuNDk4LTEuNTU1LTMuNzg1LTIuNDQ2LTYuMjc0LTIuNDQ2Yy0xLjc3LDAtMy41NTMsMC40NDItNS4yOTMsMS4zMTRjLTQuMDYxLDIuMDM1LTQuODU1LDQuNzM2LTUuNjkyLDcuNTk2ICAgYy0wLjEzNiwwLjQ2OC0wLjI4NCwwLjkzOS0wLjQzOCwxLjQxYy0wLjAwNiwwLjAxOS0wLjAyMiwwLjAzNS0wLjAyOCwwLjA1NkMwLjgzMywyOC40MjMsMS42OTEsMjguMzksMi4wOCwyOC4zMDh6IE0xMC40OTMsMTkuOTA4ICAgYzEuOTU2LDAsMy41NDgsMS41OTEsMy41NDgsMy41NDdjMCwxLjk1Ny0xLjU5MiwzLjU0OC0zLjU0OCwzLjU0OGMtMS45NTcsMC0zLjU0OC0xLjU5Mi0zLjU0OC0zLjU0OCAgIEM2Ljk0NCwyMS40OTksOC41MzYsMTkuOTA4LDEwLjQ5MywxOS45MDh6Ij48L3BhdGg+PC9nPjwvc3ZnPg==';
const dataSvg =
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>';
const sourceWithHeaders = {
uri: placeholder,
headers: {
'x-token': '0012345'
}
};
const sourceWithHeadersAndRedirect = {
uri: source,
headers: {
'x-token': '0012345'
}
};

function Divider() {
return <View style={styles.divider} />;
Expand All @@ -31,8 +43,8 @@ export default function ImagePage() {
onError={() => {
console.log('error');
}}
onLoad={() => {
console.log('load');
onLoad={(result) => {
console.log('load', result);
}}
onLoadEnd={() => {
console.log('load-end');
Expand Down Expand Up @@ -114,6 +126,17 @@ export default function ImagePage() {
/>
</View>
</View>
<Divider />
<View style={styles.row}>
<View style={styles.column}>
<Text style={[styles.text]}>With Headers</Text>
<Image source={sourceWithHeaders} style={styles.image} />
</View>
<View style={styles.column}>
<Text style={[styles.text]}>Headers & Redirect</Text>
<Image source={sourceWithHeadersAndRedirect} style={styles.image} />
</View>
</View>
</Example>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ exports[`components/Image prop "defaultSource" does not override "height" and "w
exports[`components/Image prop "defaultSource" sets "height" and "width" styles if missing 1`] = `
<div
class="css-view-175oi2r r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
style="height: 10px; width: 20px;"
style="width: 20px; height: 10px;"
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
Expand Down Expand Up @@ -329,14 +329,14 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
style="filter: url(#tint-57);"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately these number change any time we add tests or render new Image components, because they are based on ID that is just a counter incremented with every Image component created

const filterRef = React.useRef(_filterId++);

style="filter: url(#tint-97);"
/>
<svg
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
>
<defs>
<filter
id="tint-57"
id="tint-97"
>
<feflood
flood-color="blue"
Expand Down Expand Up @@ -378,7 +378,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-95);"
/>
<img
alt=""
Expand All @@ -391,7 +391,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
>
<defs>
<filter
id="tint-56"
id="tint-95"
>
<feflood
flood-color="red"
Expand Down
195 changes: 176 additions & 19 deletions packages/react-native-web/src/exports/Image/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ describe('components/Image', () => {
beforeEach(() => {
ImageUriCache._entries = {};
window.Image = jest.fn(() => ({}));
ImageLoader.load = jest
.fn()
.mockImplementation((source, onLoad, onError) => {
onLoad({ source });
});
});

afterEach(() => {
Expand Down Expand Up @@ -107,9 +112,6 @@ describe('components/Image', () => {
describe('prop "onLoad"', () => {
test('is called after image is loaded from network', () => {
jest.useFakeTimers();
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
onLoad();
});
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
Expand All @@ -127,9 +129,6 @@ describe('components/Image', () => {

test('is called after image is loaded from cache', () => {
jest.useFakeTimers();
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
onLoad();
});
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
Expand Down Expand Up @@ -174,6 +173,35 @@ describe('components/Image', () => {
expect(onLoadEndStub.mock.calls.length).toBe(2);
});

test('is called on update if "headers" are modified', () => {
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
const { rerender } = render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
source={{
uri: 'https://test.com/img.jpg',
headers: { 'x-custom-header': 'abc123' }
}}
/>
);
act(() => {
rerender(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
source={{
uri: 'https://test.com/img.jpg',
headers: { 'x-custom-header': '123abc' }
}}
/>
);
});
expect(onLoadStub.mock.calls.length).toBe(2);
expect(onLoadEndStub.mock.calls.length).toBe(2);
});

test('is not called on update if "uri" is the same', () => {
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
Expand Down Expand Up @@ -225,6 +253,64 @@ describe('components/Image', () => {
expect(onLoadStub.mock.calls.length).toBe(1);
expect(onLoadEndStub.mock.calls.length).toBe(1);
});

// This test verifies that when source is declared in-line and the parent component
// re-renders we aren't restarting the load process because the source is structurally equal
test('is not called on update when "headers" and "uri" are not modified', () => {
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
const { rerender } = render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
source={{
uri: 'https://test.com/img.jpg',
width: 1,
height: 1,
headers: { 'x-custom-header': 'abc123' }
}}
/>
);
act(() => {
rerender(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
source={{
uri: 'https://test.com/img.jpg',
width: 1,
height: 1,
headers: { 'x-custom-header': 'abc123' }
}}
/>
);
});
expect(onLoadStub.mock.calls.length).toBe(1);
expect(onLoadEndStub.mock.calls.length).toBe(1);
});

test('is not called for default source', () => {
jest.useFakeTimers();
const onLoadStub = jest.fn();
render(
<Image
defaultSource="https://test.com/img-2.jpg"
onLoad={onLoadStub}
source="https://test.com/img.jpg"
/>
);
jest.runOnlyPendingTimers();
expect(onLoadStub).toHaveBeenCalledTimes(1);
expect(onLoadStub).toHaveBeenCalledWith(
expect.objectContaining({
nativeEvent: {
source: {
uri: 'https://test.com/img.jpg'
}
}
})
);
});
});

describe('prop "resizeMode"', () => {
Expand All @@ -245,7 +331,8 @@ describe('components/Image', () => {
'',
{},
{ uri: '' },
{ uri: 'https://google.com' }
{ uri: 'https://google.com' },
{ uri: 'https://google.com', headers: { 'x-custom-header': 'abc123' } }
];
sources.forEach((source) => {
expect(() => render(<Image source={source} />)).not.toThrow();
Expand All @@ -261,11 +348,6 @@ describe('components/Image', () => {

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 { container } = render(<Image source={source} />, {
Expand Down Expand Up @@ -308,19 +390,33 @@ describe('components/Image', () => {
test('is correctly updated only when loaded if defaultSource provided', () => {
const defaultUri = 'https://testing.com/preview.jpg';
const uri = 'https://testing.com/fullSize.jpg';
let loadCallback;
ImageLoader.load = jest
.fn()
.mockImplementationOnce((_, onLoad, onError) => {
loadCallback = onLoad;
});
const calls = [];

// Capture calls and resolve them after render
ImageLoader.load = jest.fn().mockImplementation((source, onLoad) => {
calls.push({ source, onLoad });
});

const { container } = render(
<Image defaultSource={{ uri: defaultUri }} source={{ uri }} />
);

// Both defaultSource and source are loaded at the same time
// Since `defaultSource` is meant to be displayed until `source` loads
// we resolve it first (otherwise it won't be displayed at all)
act(() => {
const call = calls.find(({ source }) => source.uri === defaultUri);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the previous test had a snapshot before anything was resolved. Do we still need that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the previous test had a snapshot before anything was resolved. Do we still need that?

The loading behavior changed and so the test changed

Previously the default source was not loaded using the ImageLoader
Now it is (so it can also use headers if necessary)
So we first need to resolve it by resolving the ImageLoader.load mock

call.onLoad({ source: call.source });
});

expect(container.firstChild).toMatchSnapshot();

// After a while the main source loads as well
act(() => {
loadCallback();
const call = calls.find(({ source }) => source.uri === uri);
call.onLoad({ source: call.source });
});

expect(container.firstChild).toMatchSnapshot();
});

Expand All @@ -346,6 +442,67 @@ describe('components/Image', () => {
'http://localhost/static/[email protected]'
);
});

test('it correctly passes headers to ImageLoader', () => {
const uri = 'https://google.com/favicon.ico';
const headers = { 'x-custom-header': 'abc123' };
const source = { uri, headers };
render(<Image source={source} />);

expect(ImageLoader.load).toHaveBeenCalledWith(
expect.objectContaining({ headers }),
expect.any(Function),
expect.any(Function)
);
});

test('it correctly passes uri to ImageLoader', () => {
const uri = 'https://google.com/favicon.ico';
const source = { uri };
render(<Image source={source} />);

expect(ImageLoader.load).toHaveBeenCalledWith(
expect.objectContaining({ uri }),
expect.any(Function),
expect.any(Function)
);
});

// A common case is `source` declared as an inline object, which creates a
// new object (with the same content) each time parent component renders
test('it still loads the image if source object is changed', () => {
ImageLoader.load.mockImplementation(() => {});

const releaseSpy = jest.spyOn(ImageLoader, 'abort');

const uri = 'https://google.com/favicon.ico';
const { rerender } = render(<Image source={{ uri }} />);
rerender(<Image source={{ uri }} />);

// when the underlying source didn't change we expect the initial request is not cancelled due to re-render
expect(releaseSpy).not.toHaveBeenCalled();
});

test('falls back to default source when source or source.uri is removed', () => {
const source = { uri: 'https://google.com/favicon.ico' };
const defaultSource = { uri: 'http://localhost/static/[email protected]' };

const { container, rerender } = render(
<Image defaultSource={defaultSource} source={source} />
);

rerender(<Image defaultSource={defaultSource} source={{ uri: '' }} />);
expect(container.querySelector('img').src).toBe(defaultSource.uri);
});

test('removes image if source or source.uri is removed and there is no default source', () => {
const source = { uri: 'https://google.com/favicon.ico' };

const { container, rerender } = render(<Image source={source} />);

rerender(<Image source={{ uri: '' }} />);
expect(container.querySelector('img')).toBe(null);
});
});

describe('prop "style"', () => {
Expand Down
Loading