diff --git a/__tests__/fixtures/version-3/auth2-active.json b/__tests__/fixtures/version-3/auth2-active.json
new file mode 100644
index 0000000000..cb459a6909
--- /dev/null
+++ b/__tests__/fixtures/version-3/auth2-active.json
@@ -0,0 +1,65 @@
+{
+ "@context": "http://iiif.io/api/presentation/3/context.json",
+ "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json",
+ "type": "Manifest",
+ "label": {
+ "en": [
+ "An actively authorized video"
+ ]
+ },
+ "items": [
+ {
+ "id": "https://auth.example.org/my-video1",
+ "type": "Canvas",
+ "label": {
+ "en": [
+ "Canvas with a single IIIF video"
+ ]
+ },
+ "height": 3024,
+ "width": 4032,
+ "items": [
+ {
+ "id": "https://auth.example.org/my-video/page",
+ "type": "AnnotationPage",
+ "items": [
+ {
+ "id": "https://auth.example.org/my-video/annotation",
+ "type": "Annotation",
+ "motivation": "painting",
+ "body": {
+ "id": "https://auth.example.org/my-video.mp4",
+ "type": "Video",
+ "service": [
+ {
+ "id": "https://auth.example.org/probe/my-video",
+ "type": "AuthProbeService2",
+ "service" : [
+ {
+ "id": "https://auth.example.org/login",
+ "type": "AuthAccessService2",
+ "profile": "active",
+ "label": { "en": [ "Login to Example Institution" ] },
+ "service" : [
+ {
+ "id": "https://auth.example.org/token",
+ "type": "AuthAccessTokenService2"
+ },
+ {
+ "id": "https://auth.example.org/logout",
+ "type": "AuthLogoutService2",
+ "label": { "en": [ "Logout from Example Institution" ] }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/__tests__/fixtures/version-3/auth2-kiosk.json b/__tests__/fixtures/version-3/auth2-kiosk.json
new file mode 100644
index 0000000000..9187902447
--- /dev/null
+++ b/__tests__/fixtures/version-3/auth2-kiosk.json
@@ -0,0 +1,69 @@
+{
+ "@context": "http://iiif.io/api/presentation/3/context.json",
+ "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json",
+ "type": "Manifest",
+ "label": {
+ "en": [
+ "A kiosk-authorized image"
+ ]
+ },
+ "items": [
+ {
+ "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1",
+ "type": "Canvas",
+ "label": {
+ "en": [
+ "Canvas with a single IIIF image"
+ ]
+ },
+ "height": 3024,
+ "width": 4032,
+ "items": [
+ {
+ "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/page/p1/1",
+ "type": "AnnotationPage",
+ "items": [
+ {
+ "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/annotation/p0001-image",
+ "type": "Annotation",
+ "motivation": "painting",
+ "body": {
+ "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg",
+ "type": "Image",
+ "format": "image/jpeg",
+ "height": 3024,
+ "width": 4032,
+ "service": [
+ {
+ "id": "https://auth.example.org/probe/918ecd18c2592080851777620de9bcb5-gottingen",
+ "type": "AuthProbeService2",
+ "service" : [
+ {
+ "id": "https://auth.example.org/kiosk",
+ "type": "AuthAccessService2",
+ "profile": "kiosk",
+ "label": { "en": [ "Kiosk Login" ] },
+ "service" : [
+ {
+ "id": "https://auth.example.org/token/918ecd18c2592080851777620de9bcb5-gottingen",
+ "type": "AuthAccessTokenService2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen",
+ "profile": "level1",
+ "type": "ImageService3"
+ }
+ ]
+ },
+ "target": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/__tests__/src/actions/probeResponse.test.js b/__tests__/src/actions/probeResponse.test.js
new file mode 100644
index 0000000000..7c157ab35a
--- /dev/null
+++ b/__tests__/src/actions/probeResponse.test.js
@@ -0,0 +1,52 @@
+import { JSONLDResource } from 'manifesto.js';
+import * as actions from '../../../src/state/actions';
+import ActionTypes from '../../../src/state/actions/action-types';
+
+describe('probeResponse actions', () => {
+ describe('requestProbeResponse', () => {
+ it('requests a probeResponse from given a url', () => {
+ const id = 'abc123';
+ const expectedAction = {
+ probeId: id,
+ type: ActionTypes.REQUEST_PROBE_RESPONSE,
+ };
+ expect(actions.requestProbeResponse(id)).toEqual(expectedAction);
+ });
+ });
+ describe('receiveProbeResponse', () => {
+ it('receives a probeResponse', () => {
+ const id = 'abc123';
+ const json = {
+ content: 'probe service request',
+ id,
+ };
+ const expectedAction = {
+ probeId: id,
+ probeJson: json,
+ type: ActionTypes.RECEIVE_PROBE_RESPONSE,
+ };
+ expect(actions.receiveProbeResponse(id, json)).toEqual(expectedAction);
+ });
+ });
+ describe('fetchProbeResponse', () => {
+ describe('success response', () => {
+ it('dispatches the REQUEST_PROBE_RESPONSE action', () => {
+ const resource = new JSONLDResource({ services: [{ id: 'someUrl', type: 'AuthProbeService2' }] });
+ expect(actions.fetchProbeResponse({ resource })).toEqual({
+ probeId: 'someUrl',
+ resource,
+ type: 'mirador/REQUEST_PROBE_RESPONSE',
+ });
+ });
+ });
+ });
+ describe('removeProbeResponse', () => {
+ it('removes an existing probeResponse', () => {
+ const expectedAction = {
+ probeId: 'foo',
+ type: ActionTypes.REMOVE_PROBE_RESPONSE,
+ };
+ expect(actions.removeProbeResponse('foo')).toEqual(expectedAction);
+ });
+ });
+});
diff --git a/__tests__/src/lib/MiradorCanvas.test.js b/__tests__/src/lib/MiradorCanvas.test.js
index 5502c67746..26f4ce6b00 100644
--- a/__tests__/src/lib/MiradorCanvas.test.js
+++ b/__tests__/src/lib/MiradorCanvas.test.js
@@ -9,6 +9,8 @@ import fragmentFixtureV3 from '../../fixtures/version-3/hamilton.json';
import audioFixture from '../../fixtures/version-3/0002-mvm-audio.json';
import videoFixture from '../../fixtures/version-3/0015-start.json';
import videoWithAnnoCaptions from '../../fixtures/version-3/video_with_annotation_captions.json';
+import auth2WithImage from '../../fixtures/version-3/auth2-kiosk.json';
+import auth2WithVideo from '../../fixtures/version-3/auth2-active.json';
describe('MiradorCanvas', () => {
let instance;
@@ -133,4 +135,26 @@ describe('MiradorCanvas', () => {
expect(instance.v3VttContent.length).toEqual(1);
});
});
+ describe('iiifImageResources', () => {
+ it('returns image resources', () => {
+ instance = new MiradorCanvas(
+ Utils.parseManifest(auth2WithImage).getSequences()[0].getCanvases()[0],
+ );
+ expect(instance.iiifImageResources.length).toEqual(1);
+ });
+ it('returns only image resources', () => {
+ instance = new MiradorCanvas(
+ Utils.parseManifest(auth2WithVideo).getSequences()[0].getCanvases()[0],
+ );
+ expect(instance.iiifImageResources.length).toEqual(0);
+ });
+ });
+ describe('imageServiceIds', () => {
+ it('returns image service IDs', () => {
+ instance = new MiradorCanvas(
+ Utils.parseManifest(auth2WithImage).getSequences()[0].getCanvases()[0],
+ );
+ expect(instance.imageServiceIds[0]).toEqual('https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen');
+ });
+ });
});
diff --git a/__tests__/src/lib/getServices.test.js b/__tests__/src/lib/getServices.test.js
new file mode 100644
index 0000000000..d7f4a15e99
--- /dev/null
+++ b/__tests__/src/lib/getServices.test.js
@@ -0,0 +1,90 @@
+import { v4 as uuid } from 'uuid';
+import {
+ anyAuthServices, getLogoutService, getProbeService, getTokenService,
+} from '../../../src/lib/getServices';
+
+/**
+ */
+function resourceFixtureWithService(props) {
+ return {
+ id: uuid(),
+ services: [
+ { ...props },
+ ],
+ type: 'Dataset',
+ };
+}
+
+/**
+ */
+function actualLogoutServiceId(resource) {
+ const service = getLogoutService(resource);
+ return service
+ && service.id;
+}
+
+/**
+ */
+function actualTokenServiceId(resource) {
+ const service = getTokenService(resource);
+ return service
+ && service.id;
+}
+
+/**
+ */
+function actualProbeServiceId(resource) {
+ const service = getProbeService(resource);
+ return service
+ && service.id;
+}
+
+describe('anyAuthServices', () => {
+ it('returns a filtered list', () => {
+ const serviceId = uuid();
+ const auth0 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/0/anyService' });
+ expect(anyAuthServices(auth0).length).toEqual(1);
+ const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/anyService' });
+ expect(anyAuthServices(auth1).length).toEqual(1);
+ const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthAnyService2' });
+ expect(anyAuthServices(auth2).length).toEqual(1);
+ const notAuthProfile = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/not-auth/1/anyService' });
+ expect(anyAuthServices(notAuthProfile).length).toEqual(0);
+ const notAuthType = resourceFixtureWithService({ id: serviceId, type: 'NotAuthAnyService2' });
+ expect(anyAuthServices(notAuthType).length).toEqual(0);
+ });
+});
+
+describe('getLogoutService', () => {
+ it('returns a Service', () => {
+ const serviceId = uuid();
+ const auth0 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/0/logout' });
+ expect(actualLogoutServiceId(auth0)).toEqual(serviceId);
+ const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/logout' });
+ expect(actualLogoutServiceId(auth1)).toEqual(serviceId);
+ const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthLogoutService2' });
+ expect(actualLogoutServiceId(auth2)).toEqual(serviceId);
+ });
+});
+
+describe('getProbeService', () => {
+ it('returns a Service', () => {
+ const serviceId = uuid();
+ const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/probe' });
+ expect(actualProbeServiceId(auth1)).toEqual(serviceId);
+ const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthProbeService2' });
+ expect(actualProbeServiceId(auth2)).toEqual(serviceId);
+ });
+});
+
+describe('getTokenService', () => {
+ it('returns a Service', () => {
+ const serviceId = uuid();
+ const auth0 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/0/token' });
+ expect(actualTokenServiceId(auth0)).toEqual(serviceId);
+ const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/token' });
+ expect(actualTokenServiceId(auth1)).toEqual(serviceId);
+ const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthAccessTokenService2' });
+ expect(actualTokenServiceId(auth2)).toEqual(serviceId);
+ });
+});
diff --git a/__tests__/src/lib/typeFilters.test.js b/__tests__/src/lib/typeFilters.test.js
new file mode 100644
index 0000000000..854712d785
--- /dev/null
+++ b/__tests__/src/lib/typeFilters.test.js
@@ -0,0 +1,52 @@
+import { JSONLDResource } from 'manifesto.js';
+import { v4 as uuid } from 'uuid';
+import {
+ filterByTypes, audioResourcesFrom, anyImageServices, hasImageService,
+ iiifImageResourcesFrom, textResourcesFrom, videoResourcesFrom,
+} from '../../../src/lib/typeFilters';
+
+/**
+ */
+function resourceFixtureForProps(props) {
+ return new JSONLDResource({
+ id: uuid(),
+ ...props,
+ });
+}
+
+describe('filterByTypes', () => {
+ it('returns a resource of one type', () => {
+ const typeFixture = 'someType';
+ const resourceFixture = resourceFixtureForProps({ '@type': typeFixture });
+ expect(filterByTypes([resourceFixture], typeFixture)).toEqual([resourceFixture]);
+ });
+ it('returns a resource of any given types', () => {
+ const typeFixture = 'someType';
+ const resourceFixture = resourceFixtureForProps({ '@type': typeFixture });
+ expect(filterByTypes([resourceFixture], ['anotherType', typeFixture])).toEqual([resourceFixture]);
+ });
+});
+
+describe('audioResourcesFrom', () => {
+ it('returns a resource of audio type', () => {
+ const typeFixture = 'Audio';
+ const resourceFixture = resourceFixtureForProps({ '@type': typeFixture });
+ expect(audioResourcesFrom([resourceFixture])).toEqual([resourceFixture]);
+ });
+});
+
+describe('videoResourcesFrom', () => {
+ it('returns a resource of audio type', () => {
+ const typeFixture = 'Video';
+ const resourceFixture = resourceFixtureForProps({ '@type': typeFixture });
+ expect(videoResourcesFrom([resourceFixture])).toEqual([resourceFixture]);
+ });
+});
+
+describe('textResourcesFrom', () => {
+ it('returns a resource of audio type', () => {
+ const typeFixture = 'Document';
+ const resourceFixture = resourceFixtureForProps({ '@type': typeFixture });
+ expect(textResourcesFrom([resourceFixture])).toEqual([resourceFixture]);
+ });
+});
diff --git a/__tests__/src/reducers/accessTokens.test.js b/__tests__/src/reducers/accessTokens.test.js
index 020c450f64..03b88c924f 100644
--- a/__tests__/src/reducers/accessTokens.test.js
+++ b/__tests__/src/reducers/accessTokens.test.js
@@ -36,7 +36,7 @@ describe('access tokens response reducer', () => {
},
});
});
- it('should handle RECEIVE_INFO_RESPONSE_FAILURE', () => {
+ it('should handle RECEIVE_ACCESS_TOKEN_FAILURE', () => {
expect(accessTokensReducer(
{
abc123: {
@@ -73,4 +73,49 @@ describe('access tokens response reducer', () => {
})).toEqual({});
});
});
+ it('should handle RECEIVE_INFO_RESPONSE', () => {
+ expect(accessTokensReducer(
+ {
+ efg456: {
+ id: 'efg456',
+ isFetching: true,
+ },
+ },
+ {
+ infoId: 'abc123',
+ infoJson: {
+ '@type': 'sc:Manifest',
+ content: 'lots of canvases and metadata and such',
+ id: 'abc123',
+ },
+ tokenServiceId: 'efg456',
+ type: ActionTypes.RECEIVE_INFO_RESPONSE,
+ },
+ )).toMatchObject({
+ efg456: {
+ id: 'efg456',
+ success: true,
+ },
+ });
+ });
+ it('should handle RECEIVE_PROBE_RESPONSE', () => {
+ expect(accessTokensReducer(
+ {
+ efg456: {
+ id: 'efg456',
+ isFetching: true,
+ },
+ },
+ {
+ infoId: 'abc123',
+ tokenServiceId: 'efg456',
+ type: ActionTypes.RECEIVE_PROBE_RESPONSE,
+ },
+ )).toMatchObject({
+ efg456: {
+ id: 'efg456',
+ success: true,
+ },
+ });
+ });
});
diff --git a/__tests__/src/reducers/probeResponse.test.js b/__tests__/src/reducers/probeResponse.test.js
new file mode 100644
index 0000000000..14dafe6a6b
--- /dev/null
+++ b/__tests__/src/reducers/probeResponse.test.js
@@ -0,0 +1,95 @@
+import { probeResponsesReducer } from '../../../src/state/reducers/probeResponses';
+import ActionTypes from '../../../src/state/actions/action-types';
+
+describe('probe response reducer', () => {
+ it('should handle REQUEST_PROBE_RESPONSE', () => {
+ expect(probeResponsesReducer({}, {
+ probeId: 'abc123',
+ type: ActionTypes.REQUEST_PROBE_RESPONSE,
+ })).toEqual({
+ abc123: {
+ id: 'abc123',
+ isFetching: true,
+ },
+ });
+ });
+ it('should handle RECEIVE_PROBE_RESPONSE', () => {
+ expect(probeResponsesReducer(
+ {
+ abc123: {
+ id: 'abc123',
+ isFetching: true,
+ },
+ },
+ {
+ probeId: 'abc123',
+ probeJson: {
+ '@type': 'sc:Manifest',
+ content: 'lots of canvases and metadata and such',
+ id: 'abc123',
+ },
+ tokenServiceId: 'efg456',
+ type: ActionTypes.RECEIVE_PROBE_RESPONSE,
+ },
+ )).toMatchObject({
+ abc123: {
+ id: 'abc123',
+ isFetching: false,
+ json: {},
+ tokenServiceId: 'efg456',
+ },
+ });
+ });
+ it('should handle RECEIVE_PROBE_RESPONSE_FAILURE', () => {
+ expect(probeResponsesReducer(
+ {
+ abc123: {
+ id: 'abc123',
+ isFetching: true,
+ },
+ },
+ {
+ error: "This institution didn't enable CORS.",
+ probeId: 'abc123',
+ tokenServiceId: 'efg456',
+ type: ActionTypes.RECEIVE_PROBE_RESPONSE_FAILURE,
+ },
+ )).toEqual({
+ abc123: {
+ error: "This institution didn't enable CORS.",
+ id: 'abc123',
+ isFetching: false,
+ tokenServiceId: 'efg456',
+ },
+ });
+ });
+ it('should handle REMOVE_PROBE_RESPONSE', () => {
+ expect(probeResponsesReducer(
+ {
+ abc123: {
+ id: 'abc123',
+ stuff: 'foo',
+ },
+ def456: {
+ id: 'def456',
+ stuff: 'foo',
+ },
+ },
+ {
+ probeId: 'abc123',
+ type: ActionTypes.REMOVE_PROBE_RESPONSE,
+ },
+ )).toEqual({
+ def456: {
+ id: 'def456',
+ stuff: 'foo',
+ },
+ });
+ });
+ it('should handle IMPORT_MIRADOR_STATE setting to clean state', () => {
+ expect(probeResponsesReducer({}, {
+ state: { probeResponses: { new: 'stuff' } },
+ type: ActionTypes.IMPORT_MIRADOR_STATE,
+ })).toEqual({});
+ });
+});
diff --git a/__tests__/src/selectors/auth.test.js b/__tests__/src/selectors/auth.test.js
index 2071e99317..b06c3f740f 100644
--- a/__tests__/src/selectors/auth.test.js
+++ b/__tests__/src/selectors/auth.test.js
@@ -1,5 +1,6 @@
import manifestFixture001 from '../../fixtures/version-2/001.json';
import manifestFixture019 from '../../fixtures/version-2/019.json';
+import manifestFixtureAuth2ActiveVideo from '../../fixtures/version-3/auth2-active.json';
import settings from '../../../src/config/settings';
import {
getAccessTokens,
@@ -61,6 +62,9 @@ describe('selectCurrentAuthServices', () => {
b: {
json: manifestFixture019,
},
+ c: {
+ json: manifestFixtureAuth2ActiveVideo,
+ },
},
windows: {
noCanvas: {
@@ -84,6 +88,12 @@ describe('selectCurrentAuthServices', () => {
'https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1',
],
},
+ z: {
+ manifestId: 'c',
+ visibleCanvases: [
+ 'https://auth.example.org/my-video1',
+ ],
+ },
},
};
@@ -93,6 +103,7 @@ describe('selectCurrentAuthServices', () => {
it('returns the next auth service to try', () => {
expect(selectCurrentAuthServices(state, { windowId: 'w' })[0].id).toEqual('external');
+ expect(selectCurrentAuthServices(state, { windowId: 'z' })[0].id).toEqual('https://auth.example.org/login');
});
it('returns the service if the next auth service is interactive', () => {
diff --git a/src/components/VideoViewer.js b/src/components/VideoViewer.js
index e8a3d617df..bf9ef76779 100644
--- a/src/components/VideoViewer.js
+++ b/src/components/VideoViewer.js
@@ -9,15 +9,16 @@ export class VideoViewer extends Component {
const {
captions, classes, videoOptions, videoResources,
} = this.props;
+
return (