diff --git a/__tests__/src/components/OpenSeadragonViewer.test.js b/__tests__/src/components/OpenSeadragonViewer.test.js
index 7dfa0aae873b665d221d505927f71ee30034ec2b..29566d2e677838560fc16a23c950c203d8e4bafa 100644
--- a/__tests__/src/components/OpenSeadragonViewer.test.js
+++ b/__tests__/src/components/OpenSeadragonViewer.test.js
@@ -10,46 +10,52 @@ const canvases = Utils.parseManifest(fixture).getSequences()[0].getCanvases();
 
 jest.mock('openseadragon');
 
+/**
+ * Helper function to create a shallow wrapper around OpenSeadragonViewer
+ */
+function createWrapper(props) {
+  return shallow(
+    <OpenSeadragonViewer
+      classes={{}}
+      infoResponses={[{
+        id: 'a',
+        json: {
+          '@id': 'http://foo',
+          height: 200,
+          width: 100,
+        },
+      }, {
+        id: 'b',
+        json: {
+          '@id': 'http://bar',
+          height: 201,
+          width: 150,
+        },
+      }]}
+      nonTiledImages={[{
+        getProperty: () => {},
+        id: 'http://foo',
+      }]}
+      windowId="base"
+      config={{}}
+      updateViewport={jest.fn()}
+      t={k => k}
+      canvasWorld={new CanvasWorld(canvases)}
+      {...props}
+    >
+      <div className="foo" />
+      <div className="bar" />
+    </OpenSeadragonViewer>,
+  );
+}
+
 describe('OpenSeadragonViewer', () => {
   let wrapper;
   let updateViewport;
   beforeEach(() => {
     OpenSeadragon.mockClear();
-
-    updateViewport = jest.fn();
-
-    wrapper = shallow(
-      <OpenSeadragonViewer
-        classes={{}}
-        infoResponses={[{
-          id: 'a',
-          json: {
-            '@id': 'http://foo',
-            height: 200,
-            width: 100,
-          },
-        }, {
-          id: 'b',
-          json: {
-            '@id': 'http://bar',
-            height: 201,
-            width: 150,
-          },
-        }]}
-        nonTiledImages={[{
-          getProperty: () => {},
-          id: 'http://foo',
-        }]}
-        windowId="base"
-        config={{}}
-        updateViewport={updateViewport}
-        t={k => k}
-        canvasWorld={new CanvasWorld(canvases)}
-      >
-        <div className="foo" />
-        <div className="bar" />
-      </OpenSeadragonViewer>,
-    );
+    wrapper = createWrapper({});
+    updateViewport = wrapper.instance().props.updateViewport;
   });
   it('renders the component', () => {
     expect(wrapper.find('.mirador-osd-container').length).toBe(1);
@@ -70,10 +76,7 @@ describe('OpenSeadragonViewer', () => {
       expect(wrapper.instance().infoResponsesMatch([])).toBe(false);
     });
     it('with an empty array', () => {
-      wrapper.instance().viewer = {
-        close: () => {},
-      };
-      wrapper.setProps({ infoResponses: [] });
+      wrapper = createWrapper({ infoResponses: [] });
       expect(wrapper.instance().infoResponsesMatch([])).toBe(true);
     });
     it('when the @ids do match', () => {
@@ -87,7 +90,7 @@ describe('OpenSeadragonViewer', () => {
       expect(wrapper.instance().infoResponsesMatch([{ id: 'a', json: { '@id': 'http://foo-degraded' } }])).toBe(false);
     });
     it('when the id props match', () => {
-      wrapper.setProps({
+      wrapper = createWrapper({
         infoResponses: [{
           id: 'a',
           json: {
@@ -106,10 +109,7 @@ describe('OpenSeadragonViewer', () => {
       expect(wrapper.instance().nonTiledImagedMatch([])).toBe(false);
     });
     it('with an empty array', () => {
-      wrapper.instance().viewer = {
-        close: () => {},
-      };
-      wrapper.setProps({ nonTiledImages: [] });
+      wrapper = createWrapper({ nonTiledImages: [] });
       expect(wrapper.instance().nonTiledImagedMatch([])).toBe(true);
     });
     it('when the ids do match', () => {
@@ -118,21 +118,17 @@ describe('OpenSeadragonViewer', () => {
   });
 
   describe('addAllImageSources', () => {
-    it('calls addTileSource for every tileSources and then zoomsToWorld', () => {
-      wrapper.instance().viewer = {
-        close: () => {},
-      };
-      wrapper.setProps({ infoResponses: [1, 2, 3, 4] });
+    it('calls addTileSource for every tileSources and then zoomsToWorld', async () => {
+      wrapper = createWrapper({ infoResponses: [1, 2, 3, 4] });
+      wrapper.setState({ viewer: { viewport: { fitBounds: () => {} }, world: { getItemCount: () => 0 } } });
       const mockAddTileSource = jest.fn();
       wrapper.instance().addTileSource = mockAddTileSource;
-      wrapper.instance().addAllImageSources();
+      await wrapper.instance().addAllImageSources();
       expect(mockAddTileSource).toHaveBeenCalledTimes(4);
     });
-    it('calls addNonTileSource for every nonTiledImage and then zoomsToWorld', () => {
-      wrapper.instance().viewer = {
-        close: () => {},
-      };
-      wrapper.setProps({
+
+    it('calls addNonTileSource for every nonTiledImage and then zoomsToWorld', async () => {
+      wrapper = createWrapper({
         nonTiledImages: [
           { getProperty: () => 'Image' },
           { getProperty: () => 'Image' },
@@ -140,22 +136,18 @@ describe('OpenSeadragonViewer', () => {
           { getProperty: () => 'Image' },
         ],
       });
+      const instance = wrapper.instance();
       const mockAddNonTiledImage = jest.fn();
       wrapper.instance().addNonTiledImage = mockAddNonTiledImage;
-      wrapper.instance().addAllImageSources();
+      await instance.addAllImageSources();
       expect(mockAddNonTiledImage).toHaveBeenCalledTimes(4);
     });
   });
 
   describe('addTileSource', () => {
-    it('calls addTiledImage asynchronously on the OSD viewer', async () => {
-      wrapper.instance().addTileSource({}).then((event) => {
-        expect(event).toBe('event');
-      });
-    });
-    it('when a viewer is not available, returns an unresolved Promise', () => {
-      expect(wrapper.instance().addTileSource({})).toEqual(expect.any(Promise));
-    });
+    it('when a viewer is not available, returns an unresolved Promise', () => (
+      expect(wrapper.instance().addTileSource({})).rejects.toBeUndefined()
+    ));
   });
 
   describe('addNonTiledImage', () => {
@@ -189,17 +181,15 @@ describe('OpenSeadragonViewer', () => {
         layerIndexOfImageResource: i => 1 - i,
         layerOpacityOfImageResource: i => 0.5,
       };
-      wrapper.setProps({ canvasWorld });
+      wrapper = createWrapper({ canvasWorld });
       wrapper.instance().loaded = true;
-      wrapper.setState({
-        viewer: {
-          world: {
-            getItemAt: i => ({ setOpacity, source: { id: i } }),
-            getItemCount: () => 2,
-            setItemIndex,
-          },
+      wrapper.instance().state.viewer = {
+        world: {
+          getItemAt: i => ({ setOpacity, source: { id: i } }),
+          getItemCount: () => 2,
+          setItemIndex,
         },
-      });
+      };
 
       wrapper.instance().refreshTileProperties();
 
diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js
index d6353a624e52c31d715a76b1541b4f43e734c8ce..9fcf686288aa914dd32bec1247819a19c060e86f 100644
--- a/src/components/OpenSeadragonViewer.js
+++ b/src/components/OpenSeadragonViewer.js
@@ -167,10 +167,11 @@ export class OpenSeadragonViewer extends Component {
   /** */
   addAllImageSources(zoomAfterAdd = true) {
     const { nonTiledImages, infoResponses } = this.props;
-    Promise.all(
-      infoResponses.map(infoResponse => this.addTileSource(infoResponse)),
-      nonTiledImages.map(image => this.addNonTiledImage(image)),
-    ).then(() => {
+
+    return Promise.allSettled([
+      ...infoResponses.map(infoResponse => this.addTileSource(infoResponse)),
+      ...nonTiledImages.map(image => this.addNonTiledImage(image)),
+    ]).then(() => {
       if (infoResponses[0] || nonTiledImages[0]) {
         if (zoomAfterAdd) this.zoomToWorld();
         this.refreshTileProperties();
@@ -193,7 +194,7 @@ export class OpenSeadragonViewer extends Component {
         reject();
       }
 
-      viewer.addSimpleImage({
+      resolve(viewer.addSimpleImage({
         error: event => reject(event),
         fitBounds: new OpenSeadragon.Rect(
           ...canvasWorld.contentResourceToWorldCoordinates(contentResource),
@@ -202,7 +203,7 @@ export class OpenSeadragonViewer extends Component {
         opacity: canvasWorld.layerOpacityOfImageResource(contentResource),
         success: event => resolve(event),
         url: contentResource.id,
-      });
+      }));
     });
   }
 
@@ -238,7 +239,11 @@ export class OpenSeadragonViewer extends Component {
   /** */
   refreshTileProperties() {
     const { canvasWorld } = this.props;
-    const { viewer: { world } } = this.state;
+    const { viewer } = this.state;
+
+    if (!viewer) return;
+
+    const { world } = viewer;
 
     const items = [];
     for (let i = 0; i < world.getItemCount(); i += 1) {
@@ -259,7 +264,7 @@ export class OpenSeadragonViewer extends Component {
   fitBounds(x, y, w, h, immediately = true) {
     const { viewer } = this.state;
 
-    viewer.viewport.fitBounds(
+    viewer && viewer.viewport && viewer.viewport.fitBounds(
       new OpenSeadragon.Rect(x, y, w, h),
       immediately,
     );
diff --git a/src/lib/CanvasWorld.js b/src/lib/CanvasWorld.js
index cb1e0078ce34bb39f03cc0ab81e229eb957cbda4..e130f3f3d20c727b27f881793d476998fa451004 100644
--- a/src/lib/CanvasWorld.js
+++ b/src/lib/CanvasWorld.js
@@ -136,7 +136,7 @@ export default class CanvasWorld {
   /** Get the IIIF content resource for an image */
   contentResource(infoResponseId) {
     const miradorCanvas = this.canvases.find(c => c.imageServiceIds.some(id => (
-      normalizeUrl(id, { stripAuthentication: false })
+      id && infoResponseId && normalizeUrl(id, { stripAuthentication: false })
         === normalizeUrl(infoResponseId, { stripAuthentication: false }))));
     if (!miradorCanvas) return undefined;
     return miradorCanvas.imageResources